summaryrefslogtreecommitdiffstats
path: root/src/tlv_parser.c
blob: e835f951f4969ca4e4a3265063f46a399713a0bc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
#include <stdio.h>
#include <openbsc/tlv.h>

int tlv_dump(struct tlv_parsed *dec)
{
	int i;

	for (i = 0; i <= 0xff; i++) {
		if (!dec->lv[i].val)
			continue;
		printf("T=%02x L=%d\n", i, dec->lv[i].len);
	}
	return 0;
}

/* 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
 * buf_len: input: the length of the input data buffer
 * lv_tag:  input: an initial LV tag at the start of the buffer
 * lv_tag2: input: a second initial LV tag following lv_tag 
 */
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;

	memset(dec, 0, sizeof(*dec));

	if (lv_tag) {
		if (pos > buf + buf_len)
			return -1;
		dec->lv[lv_tag].val = pos+1;
		dec->lv[lv_tag].len = *pos;
		len = dec->lv[lv_tag].len + 1;
		if (pos + len > buf + buf_len)
			return -2;
		num_parsed++;
		pos += len;
	}
	if (lv_tag2) {
		if (pos > buf + buf_len)
			return -1;
		dec->lv[lv_tag2].val = pos+1;
		dec->lv[lv_tag2].len = *pos;
		len = dec->lv[lv_tag2].len + 1;
		if (pos + len > buf + buf_len)
			return -2;
		num_parsed++;
		pos += 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_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;
		}
	}
	//tlv_dump(dec);
	return num_parsed;
}