diff options
author | Sylvain Munaut <tnt@246tNt.com> | 2009-10-26 20:19:59 +0100 |
---|---|---|
committer | Harald Welte <laforge@gnumonks.org> | 2009-10-26 20:19:59 +0100 |
commit | eb429b7b44f1c39548a1995cb93484b9cbe2996e (patch) | |
tree | b08b278eb318e94bafde722cb688337be94ec446 /openbsc/src/tlv_parser.c | |
parent | 288a0cf912d3a3218db3b88567590f396f43787a (diff) |
[TLV] Split the parser into 'parse loop' and 'parse single value'
This is needed when you need to manually parse TLV blocks
that don't follow the logic supported by tlv_parse but you
still want to rely on working code and not fiddle with details.
Diffstat (limited to 'openbsc/src/tlv_parser.c')
-rw-r--r-- | openbsc/src/tlv_parser.c | 173 |
1 files changed, 101 insertions, 72 deletions
diff --git a/openbsc/src/tlv_parser.c b/openbsc/src/tlv_parser.c index 8321b880e..fd0045f97 100644 --- a/openbsc/src/tlv_parser.c +++ b/openbsc/src/tlv_parser.c @@ -16,6 +16,82 @@ int tlv_dump(struct tlv_parsed *dec) return 0; } +/* o_tag: output: tag found + * o_len: output: length of the data + * o_val: output: pointer to the data + * def: input: a structure defining the valid TLV tags / configurations + * buf: input: the input data buffer to be parsed + * buf_len: input: the length of the input data buffer + * + * Also, returns the number of bytes consumed by the TLV entry + */ +int tlv_parse_one(u_int8_t *o_tag, u_int16_t *o_len, const u_int8_t **o_val, + const struct tlv_definition *def, + const u_int8_t *buf, int buf_len) +{ + u_int8_t tag; + int len; + + tag = *buf; + *o_tag = tag; + + /* FIXME: use tables for knwon IEI */ + switch (def->def[tag].type) { + case TLV_TYPE_T: + /* GSM TS 04.07 11.2.4: Type 1 TV or Type 2 T */ + *o_val = buf; + *o_len = 0; + len = 1; + break; + case TLV_TYPE_TV: + *o_val = buf+1; + *o_len = 1; + len = 2; + break; + case TLV_TYPE_FIXED: + *o_val = buf+1; + *o_len = def->def[tag].fixed_len; + len = def->def[tag].fixed_len + 1; + break; + case TLV_TYPE_TLV: + /* GSM TS 04.07 11.2.4: Type 4 TLV */ + if (buf + 1 > buf + buf_len) + return -1; + *o_val = buf+2; + *o_len = *(buf+1); + len = *o_len + 2; + if (len > buf_len) + return -2; + break; + case TLV_TYPE_TvLV: + if (*(buf+1) & 0x80) { + /* like TLV, but without highest bit of len */ + if (buf + 1 > buf + buf_len) + return -1; + *o_val = buf+2; + *o_len = *(buf+1) & 0x7f; + len = *o_len + 2; + if (len > buf_len) + return -2; + break; + } + /* like TL16V, fallthrough */ + case TLV_TYPE_TL16V: + if (2 > buf_len) + return -1; + *o_val = buf+3; + *o_len = *(buf+1) << 8 | *(buf+2); + len = *o_len + 3; + if (len > buf_len) + return -2; + break; + default: + return -3; + } + + return len; +} + /* dec: output: a caller-allocated pointer to a struct tlv_parsed, * def: input: a structure defining the valid TLV tags / configurations * buf: input: the input data buffer to be parsed @@ -27,94 +103,47 @@ int tlv_parse(struct tlv_parsed *dec, const struct tlv_definition *def, const u_int8_t *buf, int buf_len, u_int8_t lv_tag, u_int8_t lv_tag2) { - u_int8_t tag, len = 1; - const u_int8_t *pos = buf; - int num_parsed = 0; + int ofs = 0, num_parsed = 0; + u_int16_t len; memset(dec, 0, sizeof(*dec)); if (lv_tag) { - if (pos > buf + buf_len) + if (ofs > buf_len) return -1; - dec->lv[lv_tag].val = pos+1; - dec->lv[lv_tag].len = *pos; + dec->lv[lv_tag].val = &buf[ofs+1]; + dec->lv[lv_tag].len = buf[ofs]; len = dec->lv[lv_tag].len + 1; - if (pos + len > buf + buf_len) + if (ofs + len > buf_len) return -2; num_parsed++; - pos += len; + ofs += len; } if (lv_tag2) { - if (pos > buf + buf_len) + if (ofs > buf_len) return -1; - dec->lv[lv_tag2].val = pos+1; - dec->lv[lv_tag2].len = *pos; + dec->lv[lv_tag2].val = &buf[ofs+1]; + dec->lv[lv_tag2].len = buf[ofs]; len = dec->lv[lv_tag2].len + 1; - if (pos + len > buf + buf_len) + if (ofs + len > buf_len) return -2; num_parsed++; - pos += len; + ofs += len; } - for (; pos < buf+buf_len; pos += len) { - tag = *pos; - /* FIXME: use tables for knwon IEI */ - switch (def->def[tag].type) { - case TLV_TYPE_T: - /* GSM TS 04.07 11.2.4: Type 1 TV or Type 2 T */ - dec->lv[tag].val = pos; - dec->lv[tag].len = 0; - len = 1; - num_parsed++; - break; - case TLV_TYPE_TV: - dec->lv[tag].val = pos+1; - dec->lv[tag].len = 1; - len = 2; - num_parsed++; - break; - case TLV_TYPE_FIXED: - dec->lv[tag].val = pos+1; - dec->lv[tag].len = def->def[tag].fixed_len; - len = def->def[tag].fixed_len + 1; - num_parsed++; - break; - case TLV_TYPE_TLV: - /* GSM TS 04.07 11.2.4: Type 4 TLV */ - if (pos + 1 > buf + buf_len) - return -1; - dec->lv[tag].val = pos+2; - dec->lv[tag].len = *(pos+1); - len = dec->lv[tag].len + 2; - if (pos + len > buf + buf_len) - return -2; - num_parsed++; - break; - case TLV_TYPE_TvLV: - if (*(pos+1) & 0x80) { - /* like TLV, but without highest bit of len */ - if (pos + 1 > buf + buf_len) - return -1; - dec->lv[tag].val = pos+2; - dec->lv[tag].len = *(pos+1) & 0x7f; - len = dec->lv[tag].len + 2; - if (pos + len > buf + buf_len) - return -2; - num_parsed++; - break; - } - /* like TL16V, fallthrough */ - case TLV_TYPE_TL16V: - if (pos + 2 > buf + buf_len) - return -1; - dec->lv[tag].val = pos+3; - dec->lv[tag].len = *(pos+1) << 8 | *(pos+2); - len = dec->lv[tag].len + 3; - if (pos + len > buf + buf_len) - return -2; - num_parsed++; - break; - } + while (ofs < buf_len) { + int rv; + u_int8_t tag; + const u_int8_t *val; + + rv = tlv_parse_one(&tag, &len, &val, def, + &buf[ofs], buf_len-ofs); + if (rv < 0) + return rv; + dec->lv[tag].val = val; + dec->lv[tag].len = len; + ofs += rv; + num_parsed++; } //tlv_dump(dec); return num_parsed; |