diff --git a/include/osmocom/gsm/tlv.h b/include/osmocom/gsm/tlv.h index 8654893ba..996f6aacd 100644 --- a/include/osmocom/gsm/tlv.h +++ b/include/osmocom/gsm/tlv.h @@ -433,6 +433,9 @@ int tlv_parse_one(uint8_t *o_tag, uint16_t *o_len, const uint8_t **o_val, const uint8_t *buf, int buf_len); int tlv_parse(struct tlv_parsed *dec, const struct tlv_definition *def, const uint8_t *buf, int buf_len, uint8_t lv_tag, uint8_t lv_tag2); +int tlv_parse2(struct tlv_parsed *dec, int dec_multiples, + const struct tlv_definition *def, const uint8_t *buf, int buf_len, + uint8_t lv_tag, uint8_t lv_tag2); /* take a master (src) tlv def and fill up all empty slots in 'dst' */ void tlv_def_patch(struct tlv_definition *dst, const struct tlv_definition *src); diff --git a/src/gsm/libosmogsm.map b/src/gsm/libosmogsm.map index d99121e20..4d009e043 100644 --- a/src/gsm/libosmogsm.map +++ b/src/gsm/libosmogsm.map @@ -406,6 +406,7 @@ rxlev_stat_reset; tlv_def_patch; tlv_dump; tlv_parse; +tlv_parse2; tlv_parse_one; tvlv_att_def; vtvlv_gan_att_def; diff --git a/src/gsm/tlv_parser.c b/src/gsm/tlv_parser.c index f6939713a..6e089f7fd 100644 --- a/src/gsm/tlv_parser.c +++ b/src/gsm/tlv_parser.c @@ -220,7 +220,12 @@ tlv: /* GSM TS 04.07 11.2.4: Type 4 TLV */ return len; } -/*! Parse an entire buffer of TLV encoded Information Elements +/*! Parse an entire buffer of TLV encoded Information Elements. + * In case of multiple occurences of an IE, keep only the first occurence. + * Most GSM related protocols clearly indicate that in case of duplicate + * IEs, only the first occurrence shall be used, while any further occurrences + * shall be ignored. See e.g. 3GPP TS 24.008 Section 8.6.3. + * For multiple occurences, use tlv_parse2(). * \param[out] dec caller-allocated pointer to \ref tlv_parsed * \param[in] def structure defining the valid TLV tags / configurations * \param[in] buf the input data buffer to be parsed @@ -232,39 +237,78 @@ tlv: /* GSM TS 04.07 11.2.4: Type 4 TLV */ int tlv_parse(struct tlv_parsed *dec, const struct tlv_definition *def, const uint8_t *buf, int buf_len, uint8_t lv_tag, uint8_t lv_tag2) +{ + return tlv_parse2(dec, 1, def, buf, buf_len, lv_tag, lv_tag2); +} + +/*! Like tlv_parse(), but capable of decoding multiple occurences of the same IE. + * Parse an entire buffer of TLV encoded Information Elements. + * To decode multiple occurences of IEs, provide in dec an _array_ of tlv_parsed, and + * pass the size of that array in dec_multiples. The first occurence of each IE + * is stored in dec[0], the second in dec[1] and so forth. If there are more + * occurences than the array length given in dec_multiples, the remaining + * occurences are dropped. + * \param[out] dec caller-allocated pointer to \ref tlv_parsed + * \param[in] dec_multiples length of the tlv_parsed[] in \a dec. + * \param[in] def structure defining the valid TLV tags / configurations + * \param[in] buf the input data buffer to be parsed + * \param[in] buf_len length of the input data buffer + * \param[in] lv_tag an initial LV tag at the start of the buffer + * \param[in] lv_tag2 a second initial LV tag following the \a lv_tag + * \returns number of TLV entries parsed; negative in case of error + */ +int tlv_parse2(struct tlv_parsed *dec, int dec_multiples, + const struct tlv_definition *def, const uint8_t *buf, int buf_len, + uint8_t lv_tag, uint8_t lv_tag2) { int ofs = 0, num_parsed = 0; uint16_t len; + int dec_i; - memset(dec, 0, sizeof(*dec)); + for (dec_i = 0; dec_i < dec_multiples; dec_i++) + memset(&dec[dec_i], 0, sizeof(*dec)); if (lv_tag) { + const uint8_t *val; + uint16_t parsed_len; if (ofs > buf_len) return -1; - dec->lv[lv_tag].val = &buf[ofs+1]; - dec->lv[lv_tag].len = buf[ofs]; - len = dec->lv[lv_tag].len + 1; - if (ofs + len > buf_len) { - dec->lv[lv_tag].val = NULL; - dec->lv[lv_tag].len = 0; + val = &buf[ofs+1]; + len = buf[ofs]; + parsed_len = len + 1; + if (ofs + parsed_len > buf_len) return -2; - } num_parsed++; - ofs += len; + ofs += parsed_len; + /* store the resulting val and len */ + for (dec_i = 0; dec_i < dec_multiples; dec_i++) { + if (dec[dec_i].lv[lv_tag].val != NULL) + continue; + dec->lv[lv_tag].val = val; + dec->lv[lv_tag].len = len; + break; + } } if (lv_tag2) { + const uint8_t *val; + uint16_t parsed_len; if (ofs > buf_len) return -1; - dec->lv[lv_tag2].val = &buf[ofs+1]; - dec->lv[lv_tag2].len = buf[ofs]; - len = dec->lv[lv_tag2].len + 1; - if (ofs + len > buf_len) { - dec->lv[lv_tag2].val = NULL; - dec->lv[lv_tag2].len = 0; + val = &buf[ofs+1]; + len = buf[ofs]; + parsed_len = len + 1; + if (ofs + parsed_len > buf_len) return -2; - } num_parsed++; - ofs += len; + ofs += parsed_len; + /* store the resulting val and len */ + for (dec_i = 0; dec_i < dec_multiples; dec_i++) { + if (dec[dec_i].lv[lv_tag2].val != NULL) + continue; + dec->lv[lv_tag2].val = val; + dec->lv[lv_tag2].len = len; + break; + } } while (ofs < buf_len) { @@ -276,12 +320,12 @@ int tlv_parse(struct tlv_parsed *dec, const struct tlv_definition *def, &buf[ofs], buf_len-ofs); if (rv < 0) return rv; - /* Most GSM related protocols clearly indicate that in case of duplicate - * IEs, only the first occurrence shall be used, while any further occurrences - * shall be ignored. See e.g. 3GPP TS 24.008 Section 8.6.3 */ - if (dec->lv[tag].val == NULL) { - dec->lv[tag].val = val; - dec->lv[tag].len = len; + for (dec_i = 0; dec_i < dec_multiples; dec_i++) { + if (dec[dec_i].lv[tag].val != NULL) + continue; + dec[dec_i].lv[tag].val = val; + dec[dec_i].lv[tag].len = len; + break; } ofs += rv; num_parsed++; diff --git a/tests/tlv/tlv_test.c b/tests/tlv/tlv_test.c index 397327567..e2065b00f 100644 --- a/tests/tlv/tlv_test.c +++ b/tests/tlv/tlv_test.c @@ -254,6 +254,7 @@ static void test_tlv_repeated_ie() int i, rc; const uint8_t tag = 0x1a; struct tlv_parsed dec; + struct tlv_parsed dec3[3]; struct tlv_definition def; memset(&def, 0, sizeof(def)); @@ -273,6 +274,16 @@ static void test_tlv_repeated_ie() /* Value pointer should point at first value in test data array. */ OSMO_ASSERT(dec.lv[tag].val == &test_data[2]); OSMO_ASSERT(*dec.lv[tag].val == test_data[2]); + + /* Accept three decodings, pointing at first, second and third val */ + rc = tlv_parse2(dec3, 3, &def, &test_data[1], sizeof(test_data) - 1, tag, 0); + OSMO_ASSERT(rc == i/3); + OSMO_ASSERT(dec3[0].lv[tag].len == 1); + OSMO_ASSERT(dec3[0].lv[tag].val == &test_data[2]); + OSMO_ASSERT(dec3[1].lv[tag].len == 1); + OSMO_ASSERT(dec3[1].lv[tag].val == &test_data[2 + 3]); + OSMO_ASSERT(dec3[2].lv[tag].len == 1); + OSMO_ASSERT(dec3[2].lv[tag].val == &test_data[2 + 3 + 3]); } int main(int argc, char **argv)