[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.
This commit is contained in:
Sylvain Munaut 2009-10-26 20:19:59 +01:00 committed by Harald Welte
parent 288a0cf912
commit eb429b7b44
2 changed files with 104 additions and 72 deletions

View File

@ -207,6 +207,9 @@ struct tlv_parsed {
extern struct tlv_definition tvlv_att_def;
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);
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);

View File

@ -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;