blob: b1b1034037de2478524d4fa975273014f7015829 [file] [log] [blame]
Harald Welte468b6432014-09-11 13:05:51 +08001/* (C) 2008-2010 by Harald Welte <laforge@gnumonks.org>
2 *
3 * All Rights Reserved
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 *
18 */
19
Harald Welteec8b4502010-02-20 20:34:29 +010020#include <stdio.h>
21#include <stdint.h>
Maxdbd3a922017-01-02 14:10:30 +010022#include <errno.h>
Pablo Neira Ayuso83419342011-03-22 16:36:13 +010023#include <osmocom/core/utils.h>
24#include <osmocom/gsm/tlv.h>
Harald Welteec8b4502010-02-20 20:34:29 +010025
Harald Welte57c7d372011-08-17 17:50:55 +020026/*! \addtogroup tlv
27 * @{
Neels Hofmeyr87e45502017-06-20 00:17:59 +020028 * Osmocom TLV Parser
Harald Welte96e2a002017-06-12 21:44:18 +020029 *
30 * The Osmocom TLV parser is intended to operate as a low-level C
31 * implementation without dynamic memory allocations. Basically, it
32 * iterates over the IE (Information Elements) of the message and fills
33 * an array of pointers, indexed by the IEI (IE Identifier). The
34 * parser output is thus an array of pointers to the start of the
35 * respective IE inside the message.
36 *
37 * The TLV parser is configured by a TLV parser definition, which
38 * determines which if the IEIs for a given protocol are of which
39 * particular type. Types are e.g. TV (Tag + single byte value), Tag +
40 * fixed-length value, TLV with 8bit length, TLV with 16bit length, TLV
41 * with variable-length length field, etc.
Neels Hofmeyr17518fe2017-06-20 04:35:06 +020042 *
43 * \file tlv_parser.c */
Harald Welte57c7d372011-08-17 17:50:55 +020044
Harald Welteec8b4502010-02-20 20:34:29 +010045struct tlv_definition tvlv_att_def;
Harald Welte2fe68472012-07-14 01:50:33 +020046struct tlv_definition vtvlv_gan_att_def;
Harald Welteec8b4502010-02-20 20:34:29 +010047
Neels Hofmeyr87e45502017-06-20 00:17:59 +020048/*! Dump pasred TLV structure to stdout */
Harald Welteec8b4502010-02-20 20:34:29 +010049int tlv_dump(struct tlv_parsed *dec)
50{
51 int i;
52
53 for (i = 0; i <= 0xff; i++) {
54 if (!dec->lv[i].val)
55 continue;
56 printf("T=%02x L=%d\n", i, dec->lv[i].len);
57 }
58 return 0;
59}
60
Neels Hofmeyr87e45502017-06-20 00:17:59 +020061/*! Copy \ref tlv_parsed using given talloc context
Maxdbd3a922017-01-02 14:10:30 +010062 * \param[in] tp_orig Parsed TLV structure
63 * \param[in] ctx Talloc context for allocations
64 * \returns NULL on errors, \ref tlv_parsed pointer otherwise
65 */
66struct tlv_parsed *osmo_tlvp_copy(const struct tlv_parsed *tp_orig, void *ctx)
67{
68 struct tlv_parsed *tp_out;
69 size_t i, len;
70
71 tp_out = talloc_zero(ctx, struct tlv_parsed);
72 if (!tp_out)
73 return NULL;
74
75 /* if the original is NULL, return empty tlvp */
76 if (!tp_orig)
77 return tp_out;
78
79 for (i = 0; i < ARRAY_SIZE(tp_orig->lv); i++) {
80 len = tp_orig->lv[i].len;
81 tp_out->lv[i].len = len;
82 if (len && tp_out->lv[i].val) {
83 tp_out->lv[i].val = talloc_zero_size(tp_out, len);
84 if (!tp_out->lv[i].val) {
85 talloc_free(tp_out);
86 return NULL;
87 }
88 memcpy((uint8_t *)tp_out->lv[i].val, tp_orig->lv[i].val,
89 len);
90 }
91 }
92
93 return tp_out;
94}
95
Neels Hofmeyr87e45502017-06-20 00:17:59 +020096/*! Merge all \ref tlv_parsed attributes of 'src' into 'dst'
Maxdbd3a922017-01-02 14:10:30 +010097 * \param[in] dst Parsed TLV structure to merge into
98 * \param[in] src Parsed TLV structure to merge from
99 * \returns 0 on success, negative on error
100 */
101int osmo_tlvp_merge(struct tlv_parsed *dst, const struct tlv_parsed *src)
102{
103 size_t i, len;
104 for (i = 0; i < ARRAY_SIZE(dst->lv); i++) {
105 len = src->lv[i].len;
106 if (len == 0 || src->lv[i].val == NULL)
107 continue;
108 if (dst->lv[i].val) {
109 talloc_free((uint8_t *) dst->lv[i].val);
110 dst->lv[i].len = 0;
111 }
112 dst->lv[i].val = talloc_zero_size(dst, len);
113 if (!dst->lv[i].val)
114 return -ENOMEM;
115 memcpy((uint8_t *) dst->lv[i].val, src->lv[i].val, len);
116 }
117 return 0;
118}
119
Neels Hofmeyr87e45502017-06-20 00:17:59 +0200120/*! Parse a single TLV encoded IE
Harald Welte57c7d372011-08-17 17:50:55 +0200121 * \param[out] o_tag the tag of the IE that was found
122 * \param[out] o_len length of the IE that was found
123 * \param[out] o_val pointer to the data of the IE that was found
124 * \param[in] def structure defining the valid TLV tags / configurations
125 * \param[in] buf the input data buffer to be parsed
126 * \param[in] buf_len length of the input data buffer
127 * \returns number of bytes consumed by the TLV entry / IE parsed
Harald Welteec8b4502010-02-20 20:34:29 +0100128 */
129int tlv_parse_one(uint8_t *o_tag, uint16_t *o_len, const uint8_t **o_val,
130 const struct tlv_definition *def,
131 const uint8_t *buf, int buf_len)
132{
133 uint8_t tag;
134 int len;
135
136 tag = *buf;
137 *o_tag = tag;
138
Andreas Eversbergcd2a74b2010-07-12 08:55:14 +0200139 /* single octet TV IE */
140 if (def->def[tag & 0xf0].type == TLV_TYPE_SINGLE_TV) {
141 *o_tag = tag & 0xf0;
142 *o_val = buf;
143 *o_len = 1;
144 return 1;
145 }
146
Neels Hofmeyr667e83d2015-11-02 20:18:11 +0100147 /* FIXME: use tables for known IEI */
Harald Welteec8b4502010-02-20 20:34:29 +0100148 switch (def->def[tag].type) {
149 case TLV_TYPE_T:
150 /* GSM TS 04.07 11.2.4: Type 1 TV or Type 2 T */
151 *o_val = buf;
152 *o_len = 0;
153 len = 1;
154 break;
155 case TLV_TYPE_TV:
156 *o_val = buf+1;
157 *o_len = 1;
158 len = 2;
159 break;
160 case TLV_TYPE_FIXED:
161 *o_val = buf+1;
162 *o_len = def->def[tag].fixed_len;
163 len = def->def[tag].fixed_len + 1;
164 break;
165 case TLV_TYPE_TLV:
Harald Welte2fe68472012-07-14 01:50:33 +0200166tlv: /* GSM TS 04.07 11.2.4: Type 4 TLV */
Harald Welteec8b4502010-02-20 20:34:29 +0100167 if (buf + 1 > buf + buf_len)
168 return -1;
169 *o_val = buf+2;
170 *o_len = *(buf+1);
171 len = *o_len + 2;
172 if (len > buf_len)
173 return -2;
174 break;
Harald Welte2fe68472012-07-14 01:50:33 +0200175 case TLV_TYPE_vTvLV_GAN: /* 44.318 / 11.1.4 */
176 /* FIXME: variable-length TAG! */
177 if (*(buf+1) & 0x80) {
178 /* like TL16Vbut without highest bit of len */
179 if (2 > buf_len)
180 return -1;
181 *o_val = buf+3;
182 *o_len = (*(buf+1) & 0x7F) << 8 | *(buf+2);
183 len = *o_len + 3;
184 if (len > buf_len)
185 return -2;
186 } else {
187 /* like TLV */
188 goto tlv;
189 }
190 break;
Harald Welteec8b4502010-02-20 20:34:29 +0100191 case TLV_TYPE_TvLV:
192 if (*(buf+1) & 0x80) {
193 /* like TLV, but without highest bit of len */
194 if (buf + 1 > buf + buf_len)
195 return -1;
196 *o_val = buf+2;
197 *o_len = *(buf+1) & 0x7f;
198 len = *o_len + 2;
199 if (len > buf_len)
200 return -2;
201 break;
202 }
203 /* like TL16V, fallthrough */
204 case TLV_TYPE_TL16V:
205 if (2 > buf_len)
206 return -1;
207 *o_val = buf+3;
208 *o_len = *(buf+1) << 8 | *(buf+2);
209 len = *o_len + 3;
210 if (len > buf_len)
211 return -2;
212 break;
213 default:
214 return -3;
215 }
216
217 return len;
218}
219
Neels Hofmeyr87e45502017-06-20 00:17:59 +0200220/*! Parse an entire buffer of TLV encoded Information Elements
Harald Welte57c7d372011-08-17 17:50:55 +0200221 * \param[out] dec caller-allocated pointer to \ref tlv_parsed
222 * \param[in] def structure defining the valid TLV tags / configurations
223 * \param[in] buf the input data buffer to be parsed
224 * \param[in] buf_len length of the input data buffer
225 * \param[in] lv_tag an initial LV tag at the start of the buffer
226 * \param[in] lv_tag2 a second initial LV tag following the \a lv_tag
227 * \returns number of bytes consumed by the TLV entry / IE parsed
Harald Welteec8b4502010-02-20 20:34:29 +0100228 */
229int tlv_parse(struct tlv_parsed *dec, const struct tlv_definition *def,
230 const uint8_t *buf, int buf_len, uint8_t lv_tag,
231 uint8_t lv_tag2)
232{
233 int ofs = 0, num_parsed = 0;
234 uint16_t len;
235
236 memset(dec, 0, sizeof(*dec));
237
238 if (lv_tag) {
239 if (ofs > buf_len)
240 return -1;
241 dec->lv[lv_tag].val = &buf[ofs+1];
242 dec->lv[lv_tag].len = buf[ofs];
243 len = dec->lv[lv_tag].len + 1;
244 if (ofs + len > buf_len)
245 return -2;
246 num_parsed++;
247 ofs += len;
248 }
249 if (lv_tag2) {
250 if (ofs > buf_len)
251 return -1;
252 dec->lv[lv_tag2].val = &buf[ofs+1];
253 dec->lv[lv_tag2].len = buf[ofs];
254 len = dec->lv[lv_tag2].len + 1;
255 if (ofs + len > buf_len)
256 return -2;
257 num_parsed++;
258 ofs += len;
259 }
260
261 while (ofs < buf_len) {
262 int rv;
263 uint8_t tag;
264 const uint8_t *val;
265
266 rv = tlv_parse_one(&tag, &len, &val, def,
267 &buf[ofs], buf_len-ofs);
268 if (rv < 0)
269 return rv;
270 dec->lv[tag].val = val;
271 dec->lv[tag].len = len;
272 ofs += rv;
273 num_parsed++;
274 }
275 //tlv_dump(dec);
276 return num_parsed;
277}
278
Neels Hofmeyr87e45502017-06-20 00:17:59 +0200279/*! take a master (src) tlvdev and fill up all empty slots in 'dst'
Harald Welte96e2a002017-06-12 21:44:18 +0200280 * \param dst TLV parser definition that is to be patched
281 * \param[in] src TLV parser definition whose content is patched into \a dst */
Harald Welteec8b4502010-02-20 20:34:29 +0100282void tlv_def_patch(struct tlv_definition *dst, const struct tlv_definition *src)
283{
284 int i;
285
286 for (i = 0; i < ARRAY_SIZE(dst->def); i++) {
287 if (src->def[i].type == TLV_TYPE_NONE)
288 continue;
289 if (dst->def[i].type == TLV_TYPE_NONE)
290 dst->def[i] = src->def[i];
291 }
292}
293
294static __attribute__((constructor)) void on_dso_load_tlv(void)
295{
296 int i;
297 for (i = 0; i < ARRAY_SIZE(tvlv_att_def.def); i++)
298 tvlv_att_def.def[i].type = TLV_TYPE_TvLV;
Harald Welte2fe68472012-07-14 01:50:33 +0200299
300 for (i = 0; i < ARRAY_SIZE(vtvlv_gan_att_def.def); i++)
301 vtvlv_gan_att_def.def[i].type = TLV_TYPE_vTvLV_GAN;
Harald Welteec8b4502010-02-20 20:34:29 +0100302}
Harald Welte57c7d372011-08-17 17:50:55 +0200303
Harald Weltefbd02fa2016-04-25 15:19:35 +0200304/*! Advance the data pointer, subtract length and assign value pointer
305 * \param data pointer to the pointer to data
306 * \param data_len pointer to size_t containing \arg data length
307 * \param[in] len the length that we expect the fixed IE to hav
308 * \param[out] value pointer to pointer of value part of IE
309 * \returns length of IE value; negative in case of error
310 */
311int osmo_shift_v_fixed(uint8_t **data, size_t *data_len,
312 size_t len, uint8_t **value)
313{
314 if (len > *data_len)
315 goto fail;
316
317 if (value)
318 *value = *data;
319
320 *data += len;
321 *data_len -= len;
322
323 return len;
324
325fail:
326 *data += *data_len;
327 *data_len = 0;
328 return -1;
329}
330
331/*! Match tag, check length and assign value pointer
332 * \param data pointer to the pointer to data
333 * \param data_len pointer to size_t containing \arg data length
334 * \param[in] tag the tag (IEI) that we expect at \arg data
335 * \param[in] len the length that we expect the fixed IE to have
336 * \param[out] value pointer to pointer of value part of IE
337 * \returns length of IE value; negative in case of error
338 */
339int osmo_match_shift_tv_fixed(uint8_t **data, size_t *data_len,
340 uint8_t tag, size_t len,
341 uint8_t **value)
342{
343 size_t ie_len;
344
345 if (*data_len == 0)
346 goto fail;
347
348 if ((*data)[0] != tag)
349 return 0;
350
351 if (len > *data_len - 1)
352 goto fail;
353
354 if (value)
355 *value = *data + 1;
356
357 ie_len = len + 1;
358 *data += ie_len;
359 *data_len -= ie_len;
360
361 return ie_len;
362
363fail:
364 *data += *data_len;
365 *data_len = 0;
366 return -1;
367}
368
369/*! Verify TLV header and advance data / subtract length
370 * \param data pointer to the pointer to data
371 * \param data_len pointer to size_t containing \arg data length
372 * \param[in] expected_tag the tag (IEI) that we expect at \arg data
373 * \param[out] value pointer to pointer of value part of IE
374 * \param[out] value_len pointer to length of \arg value
375 * \returns length of IE value; negative in case of error
376 */
377int osmo_match_shift_tlv(uint8_t **data, size_t *data_len,
378 uint8_t expected_tag, uint8_t **value,
379 size_t *value_len)
380{
381 int rc;
382 uint8_t tag;
383 uint8_t *old_data = *data;
384 size_t old_data_len = *data_len;
385
386 rc = osmo_shift_tlv(data, data_len, &tag, value, value_len);
387
388 if (rc > 0 && tag != expected_tag) {
389 *data = old_data;
390 *data_len = old_data_len;
391 return 0;
392 }
393
394 return rc;
395}
396
397/*! Extract TLV and advance data pointer + subtract length
398 * \param data pointer to the pointer to data
399 * \param data_len pointer to size_t containing \arg data lengt
400 * \param[out] tag extract the tag (IEI) at start of \arg data
401 * \param[out] value extracted pointer to value part of TLV
402 * \param[out] value_len extracted length of \arg value
403 * \returns number of bytes subtracted
404 */
405int osmo_shift_tlv(uint8_t **data, size_t *data_len,
406 uint8_t *tag, uint8_t **value, size_t *value_len)
407{
408 size_t len;
409 size_t ie_len;
410
411 if (*data_len < 2)
412 goto fail;
413
414 len = (*data)[1];
415 if (len > *data_len - 2)
416 goto fail;
417
418 if (tag)
419 *tag = (*data)[0];
420 if (value)
421 *value = *data + 2;
422 if (value_len)
423 *value_len = len;
424
425 ie_len = len + 2;
426
427 *data += ie_len;
428 *data_len -= ie_len;
429
430 return ie_len;
431
432fail:
433 *data += *data_len;
434 *data_len = 0;
435 return -1;
436}
437
438/*! Extract LV and advance data pointer + subtract length
439 * \param data pointer to the pointer to data
440 * \param data_len pointer to size_t containing \arg data lengt
441 * \param[out] value extracted pointer to value part of TLV
442 * \param[out] value_len extracted length of \arg value
443 * \returns number of bytes subtracted
444 */
445int osmo_shift_lv(uint8_t **data, size_t *data_len,
446 uint8_t **value, size_t *value_len)
447{
448 size_t len;
449 size_t ie_len;
450
451 if (*data_len < 1)
452 goto fail;
453
454 len = (*data)[0];
455 if (len > *data_len - 1)
456 goto fail;
457
458 if (value)
459 *value = *data + 1;
460 if (value_len)
461 *value_len = len;
462
463 ie_len = len + 1;
464 *data += ie_len;
465 *data_len -= ie_len;
466
467 return ie_len;
468
469fail:
470 *data += *data_len;
471 *data_len = 0;
472 return -1;
473}
474
Sylvain Munautdca7d2c2012-04-18 21:53:23 +0200475/*! @} */