1 /*
2 * option.c -- helpers for handling options in CoAP PDUs
3 *
4 * Copyright (C) 2010-2013 Olaf Bergmann <bergmann@tzi.org>
5 *
6 * SPDX-License-Identifier: BSD-2-Clause
7 *
8 * This file is part of the CoAP library libcoap. Please see
9 * README for terms of use.
10 */
11
12
13 #include "coap3/coap_internal.h"
14
15 #include <stdio.h>
16 #include <string.h>
17
18 #define ADVANCE_OPT(o,e,step) if ((e) < step) { \
19 coap_log(LOG_DEBUG, "cannot advance opt past end\n"); \
20 return 0; \
21 } else { \
22 (e) -= step; \
23 (o) = ((o)) + step; \
24 }
25
26 /*
27 * Used to prevent access to *opt when pointing to after end of buffer
28 * after doing a ADVANCE_OPT()
29 */
30 #define ADVANCE_OPT_CHECK(o,e,step) do { \
31 ADVANCE_OPT(o,e,step); \
32 if ((e) < 1) \
33 return 0; \
34 } while (0)
35
36 size_t
coap_opt_parse(const coap_opt_t * opt,size_t length,coap_option_t * result)37 coap_opt_parse(const coap_opt_t *opt, size_t length, coap_option_t *result) {
38
39 const coap_opt_t *opt_start = opt; /* store where parsing starts */
40
41 assert(opt); assert(result);
42
43 if (length < 1)
44 return 0;
45
46 result->delta = (*opt & 0xf0) >> 4;
47 result->length = *opt & 0x0f;
48
49 switch(result->delta) {
50 case 15:
51 if (*opt != COAP_PAYLOAD_START) {
52 coap_log(LOG_DEBUG, "ignored reserved option delta 15\n");
53 }
54 return 0;
55 case 14:
56 /* Handle two-byte value: First, the MSB + 269 is stored as delta value.
57 * After that, the option pointer is advanced to the LSB which is handled
58 * just like case delta == 13. */
59 ADVANCE_OPT_CHECK(opt,length,1);
60 result->delta = ((*opt & 0xff) << 8) + 269;
61 if (result->delta < 269) {
62 coap_log(LOG_DEBUG, "delta too large\n");
63 return 0;
64 }
65 /* fall through */
66 case 13:
67 ADVANCE_OPT_CHECK(opt,length,1);
68 result->delta += *opt & 0xff;
69 break;
70
71 default:
72 ;
73 }
74
75 switch(result->length) {
76 case 15:
77 coap_log(LOG_DEBUG, "found reserved option length 15\n");
78 return 0;
79 case 14:
80 /* Handle two-byte value: First, the MSB + 269 is stored as delta value.
81 * After that, the option pointer is advanced to the LSB which is handled
82 * just like case delta == 13. */
83 ADVANCE_OPT_CHECK(opt,length,1);
84 result->length = ((*opt & 0xff) << 8) + 269;
85 /* fall through */
86 case 13:
87 ADVANCE_OPT_CHECK(opt,length,1);
88 result->length += *opt & 0xff;
89 break;
90
91 default:
92 ;
93 }
94
95 /* ADVANCE_OPT() is correct here */
96 ADVANCE_OPT(opt,length,1);
97 /* opt now points to value, if present */
98
99 result->value = opt;
100 if (length < result->length) {
101 coap_log(LOG_DEBUG, "invalid option length\n");
102 return 0;
103 }
104
105 #undef ADVANCE_OPT
106 #undef ADVANCE_OPT_CHECK
107
108 return (opt + result->length) - opt_start;
109 }
110
111 coap_opt_iterator_t *
coap_option_iterator_init(const coap_pdu_t * pdu,coap_opt_iterator_t * oi,const coap_opt_filter_t * filter)112 coap_option_iterator_init(const coap_pdu_t *pdu, coap_opt_iterator_t *oi,
113 const coap_opt_filter_t *filter) {
114 assert(pdu);
115 assert(oi);
116
117 memset(oi, 0, sizeof(coap_opt_iterator_t));
118 if (pdu->token == NULL) {
119 oi->bad = 1;
120 return NULL;
121 }
122
123 oi->next_option = pdu->token + pdu->token_length;
124 if (pdu->token + pdu->used_size <= oi->next_option) {
125 oi->bad = 1;
126 return NULL;
127 }
128
129 oi->length = pdu->used_size - pdu->token_length;
130
131 if (filter) {
132 memcpy(&oi->filter, filter, sizeof(coap_opt_filter_t));
133 oi->filtered = 1;
134 }
135 return oi;
136 }
137
138 COAP_STATIC_INLINE int
opt_finished(coap_opt_iterator_t * oi)139 opt_finished(coap_opt_iterator_t *oi) {
140 assert(oi);
141
142 if (oi->bad || oi->length == 0 ||
143 !oi->next_option || *oi->next_option == COAP_PAYLOAD_START) {
144 oi->bad = 1;
145 }
146
147 return oi->bad;
148 }
149
150 coap_opt_t *
coap_option_next(coap_opt_iterator_t * oi)151 coap_option_next(coap_opt_iterator_t *oi) {
152 coap_option_t option;
153 coap_opt_t *current_opt = NULL;
154 size_t optsize;
155 int b; /* to store result of coap_option_getb() */
156
157 assert(oi);
158
159 if (opt_finished(oi))
160 return NULL;
161
162 while (1) {
163 /* oi->option always points to the next option to deliver; as
164 * opt_finished() filters out any bad conditions, we can assume that
165 * oi->option is valid. */
166 current_opt = oi->next_option;
167
168 /* Advance internal pointer to next option, skipping any option that
169 * is not included in oi->filter. */
170 optsize = coap_opt_parse(oi->next_option, oi->length, &option);
171 if (optsize) {
172 assert(optsize <= oi->length);
173
174 oi->next_option += optsize;
175 oi->length -= optsize;
176
177 oi->number += option.delta;
178 } else { /* current option is malformed */
179 oi->bad = 1;
180 return NULL;
181 }
182
183 /* Exit the while loop when:
184 * - no filtering is done at all
185 * - the filter matches for the current option
186 * - the filter is too small for the current option number
187 */
188 if (!oi->filtered ||
189 (b = coap_option_filter_get(&oi->filter, oi->number)) > 0)
190 break;
191 else if (b < 0) { /* filter too small, cannot proceed */
192 oi->bad = 1;
193 return NULL;
194 }
195 }
196
197 return current_opt;
198 }
199
200 coap_opt_t *
coap_check_option(const coap_pdu_t * pdu,coap_option_num_t number,coap_opt_iterator_t * oi)201 coap_check_option(const coap_pdu_t *pdu, coap_option_num_t number,
202 coap_opt_iterator_t *oi) {
203 coap_opt_filter_t f;
204
205 coap_option_filter_clear(&f);
206 coap_option_filter_set(&f, number);
207
208 coap_option_iterator_init(pdu, oi, &f);
209
210 return coap_option_next(oi);
211 }
212
213 uint32_t
coap_opt_length(const coap_opt_t * opt)214 coap_opt_length(const coap_opt_t *opt) {
215 uint32_t length;
216
217 length = *opt & 0x0f;
218 switch (*opt & 0xf0) {
219 case 0xf0:
220 coap_log(LOG_DEBUG, "illegal option delta\n");
221 return 0;
222 case 0xe0:
223 ++opt;
224 /* fall through */
225 /* to skip another byte */
226 case 0xd0:
227 ++opt;
228 /* fall through */
229 /* to skip another byte */
230 default:
231 ++opt;
232 }
233
234 switch (length) {
235 case 0x0f:
236 coap_log(LOG_DEBUG, "illegal option length\n");
237 return 0;
238 case 0x0e:
239 length = (*opt++ << 8) + 269;
240 /* fall through */
241 case 0x0d:
242 length += *opt++;
243 break;
244 default:
245 ;
246 }
247 return length;
248 }
249
250 const uint8_t *
coap_opt_value(const coap_opt_t * opt)251 coap_opt_value(const coap_opt_t *opt) {
252 size_t ofs = 1;
253
254 switch (*opt & 0xf0) {
255 case 0xf0:
256 coap_log(LOG_DEBUG, "illegal option delta\n");
257 return 0;
258 case 0xe0:
259 ++ofs;
260 /* fall through */
261 case 0xd0:
262 ++ofs;
263 break;
264 default:
265 ;
266 }
267
268 switch (*opt & 0x0f) {
269 case 0x0f:
270 coap_log(LOG_DEBUG, "illegal option length\n");
271 return 0;
272 case 0x0e:
273 ++ofs;
274 /* fall through */
275 case 0x0d:
276 ++ofs;
277 break;
278 default:
279 ;
280 }
281
282 return (const uint8_t *)opt + ofs;
283 }
284
285 size_t
coap_opt_size(const coap_opt_t * opt)286 coap_opt_size(const coap_opt_t *opt) {
287 coap_option_t option;
288
289 /* we must assume that opt is encoded correctly */
290 return coap_opt_parse(opt, (size_t)-1, &option);
291 }
292
293 size_t
coap_opt_setheader(coap_opt_t * opt,size_t maxlen,uint16_t delta,size_t length)294 coap_opt_setheader(coap_opt_t *opt, size_t maxlen,
295 uint16_t delta, size_t length) {
296 size_t skip = 0;
297
298 assert(opt);
299
300 if (maxlen == 0) /* need at least one byte */
301 return 0;
302
303 if (delta < 13) {
304 opt[0] = (coap_opt_t)(delta << 4);
305 } else if (delta < 269) {
306 if (maxlen < 2) {
307 coap_log(LOG_DEBUG, "insufficient space to encode option delta %d\n",
308 delta);
309 return 0;
310 }
311
312 opt[0] = 0xd0;
313 opt[++skip] = (coap_opt_t)(delta - 13);
314 } else {
315 if (maxlen < 3) {
316 coap_log(LOG_DEBUG, "insufficient space to encode option delta %d\n",
317 delta);
318 return 0;
319 }
320
321 opt[0] = 0xe0;
322 opt[++skip] = ((delta - 269) >> 8) & 0xff;
323 opt[++skip] = (delta - 269) & 0xff;
324 }
325
326 if (length < 13) {
327 opt[0] |= length & 0x0f;
328 } else if (length < 269) {
329 if (maxlen < skip + 2) {
330 coap_log(LOG_DEBUG, "insufficient space to encode option length %zu\n",
331 length);
332 return 0;
333 }
334
335 opt[0] |= 0x0d;
336 opt[++skip] = (coap_opt_t)(length - 13);
337 } else {
338 if (maxlen < skip + 3) {
339 coap_log(LOG_DEBUG, "insufficient space to encode option delta %d\n",
340 delta);
341 return 0;
342 }
343
344 opt[0] |= 0x0e;
345 opt[++skip] = ((length - 269) >> 8) & 0xff;
346 opt[++skip] = (length - 269) & 0xff;
347 }
348
349 return skip + 1;
350 }
351
352 size_t
coap_opt_encode_size(uint16_t delta,size_t length)353 coap_opt_encode_size(uint16_t delta, size_t length) {
354 size_t n = 1;
355
356 if (delta >= 13) {
357 if (delta < 269)
358 n += 1;
359 else
360 n += 2;
361 }
362
363 if (length >= 13) {
364 if (length < 269)
365 n += 1;
366 else
367 n += 2;
368 }
369
370 return n + length;
371 }
372
373 size_t
coap_opt_encode(coap_opt_t * opt,size_t maxlen,uint16_t delta,const uint8_t * val,size_t length)374 coap_opt_encode(coap_opt_t *opt, size_t maxlen, uint16_t delta,
375 const uint8_t *val, size_t length) {
376 size_t l = 1;
377
378 l = coap_opt_setheader(opt, maxlen, delta, length);
379 assert(l <= maxlen);
380
381 if (!l) {
382 coap_log(LOG_DEBUG, "coap_opt_encode: cannot set option header\n");
383 return 0;
384 }
385
386 maxlen -= l;
387 opt += l;
388
389 if (maxlen < length) {
390 coap_log(LOG_DEBUG, "coap_opt_encode: option too large for buffer\n");
391 return 0;
392 }
393
394 if (val) /* better be safe here */
395 memcpy(opt, val, length);
396
397 return l + length;
398 }
399
400 #define LONG_MASK ((1 << COAP_OPT_FILTER_LONG) - 1)
401 #define SHORT_MASK \
402 (~LONG_MASK & ((1 << (COAP_OPT_FILTER_LONG + COAP_OPT_FILTER_SHORT)) - 1))
403
404 /** Returns true iff @p number denotes an option number larger than 255. */
405 COAP_STATIC_INLINE int
is_long_option(coap_option_num_t number)406 is_long_option(coap_option_num_t number) { return number > 255; }
407
408 /** Operation specifiers for coap_filter_op(). */
409 enum filter_op_t { FILTER_SET, FILTER_CLEAR, FILTER_GET };
410
411 /**
412 * Applies @p op on @p filter with respect to @p number. The following
413 * operations are defined:
414 *
415 * FILTER_SET: Store @p number into an empty slot in @p filter. Returns
416 * @c 1 on success, or @c 0 if no spare slot was available.
417 *
418 * FILTER_CLEAR: Remove @p number from filter if it exists.
419 *
420 * FILTER_GET: Search for @p number in @p filter. Returns @c 1 if found,
421 * or @c 0 if not found.
422 *
423 * @param filter The filter object.
424 * @param number The option number to set, get or clear in @p filter.
425 * @param op The operation to apply to @p filter and @p number.
426 *
427 * @return 1 on success, and 0 when FILTER_GET yields no
428 * hit or no free slot is available to store @p number with FILTER_SET.
429 */
430 static int
coap_option_filter_op(coap_opt_filter_t * filter,coap_option_num_t number,enum filter_op_t op)431 coap_option_filter_op(coap_opt_filter_t *filter,
432 coap_option_num_t number,
433 enum filter_op_t op) {
434 size_t lindex = 0;
435 coap_opt_filter_t *of = filter;
436 uint16_t nr, mask = 0;
437
438 if (is_long_option(number)) {
439 mask = LONG_MASK;
440
441 for (nr = 1; lindex < COAP_OPT_FILTER_LONG; nr <<= 1, lindex++) {
442
443 if (((of->mask & nr) > 0) && (of->long_opts[lindex] == number)) {
444 if (op == FILTER_CLEAR) {
445 of->mask &= ~nr;
446 }
447
448 return 1;
449 }
450 }
451 } else {
452 mask = SHORT_MASK;
453
454 for (nr = 1 << COAP_OPT_FILTER_LONG; lindex < COAP_OPT_FILTER_SHORT;
455 nr <<= 1, lindex++) {
456
457 if (((of->mask & nr) > 0) && (of->short_opts[lindex] == (number & 0xff))) {
458 if (op == FILTER_CLEAR) {
459 of->mask &= ~nr;
460 }
461
462 return 1;
463 }
464 }
465 }
466
467 /* number was not found, so there is nothing to do if op is CLEAR or GET */
468 if ((op == FILTER_CLEAR) || (op == FILTER_GET)) {
469 return 0;
470 }
471
472 /* handle FILTER_SET: */
473
474 lindex = coap_fls(~of->mask & mask);
475 if (!lindex) {
476 return 0;
477 }
478
479 if (is_long_option(number)) {
480 of->long_opts[lindex - 1] = number;
481 } else {
482 of->short_opts[lindex - COAP_OPT_FILTER_LONG - 1] = (uint8_t)number;
483 }
484
485 of->mask |= 1 << (lindex - 1);
486
487 return 1;
488 }
489
490 void
coap_option_filter_clear(coap_opt_filter_t * filter)491 coap_option_filter_clear(coap_opt_filter_t *filter) {
492 memset(filter, 0, sizeof(coap_opt_filter_t));
493 }
494
495 int
coap_option_filter_set(coap_opt_filter_t * filter,coap_option_num_t option)496 coap_option_filter_set(coap_opt_filter_t *filter, coap_option_num_t option) {
497 return coap_option_filter_op(filter, option, FILTER_SET);
498 }
499
500 int
coap_option_filter_unset(coap_opt_filter_t * filter,coap_option_num_t option)501 coap_option_filter_unset(coap_opt_filter_t *filter, coap_option_num_t option) {
502 return coap_option_filter_op(filter, option, FILTER_CLEAR);
503 }
504
505 int
coap_option_filter_get(coap_opt_filter_t * filter,coap_option_num_t option)506 coap_option_filter_get(coap_opt_filter_t *filter, coap_option_num_t option) {
507 return coap_option_filter_op(filter, option, FILTER_GET);
508 }
509
510 coap_optlist_t *
coap_new_optlist(uint16_t number,size_t length,const uint8_t * data)511 coap_new_optlist(uint16_t number,
512 size_t length,
513 const uint8_t *data
514 ) {
515 coap_optlist_t *node;
516
517 #ifdef WITH_LWIP
518 if (length > MEMP_LEN_COAPOPTLIST) {
519 coap_log(LOG_CRIT,
520 "coap_new_optlist: size too large (%zu > MEMP_LEN_COAPOPTLIST)\n",
521 length);
522 return NULL;
523 }
524 #endif /* WITH_LWIP */
525 node = coap_malloc_type(COAP_OPTLIST, sizeof(coap_optlist_t) + length);
526
527 if (node) {
528 memset(node, 0, (sizeof(coap_optlist_t) + length));
529 node->number = number;
530 node->length = length;
531 node->data = (uint8_t *)&node[1];
532 memcpy(node->data, data, length);
533 } else {
534 coap_log(LOG_WARNING, "coap_new_optlist: malloc failure\n");
535 }
536
537 return node;
538 }
539
540 static int
order_opts(void * a,void * b)541 order_opts(void *a, void *b) {
542 coap_optlist_t *o1 = (coap_optlist_t *)a;
543 coap_optlist_t *o2 = (coap_optlist_t *)b;
544
545 if (!a || !b)
546 return a < b ? -1 : 1;
547
548 return (int)(o1->number - o2->number);
549 }
550
551 int
coap_add_optlist_pdu(coap_pdu_t * pdu,coap_optlist_t ** options)552 coap_add_optlist_pdu(coap_pdu_t *pdu, coap_optlist_t** options) {
553 coap_optlist_t *opt;
554
555 if (options && *options) {
556 /* sort options for delta encoding */
557 LL_SORT((*options), order_opts);
558
559 LL_FOREACH((*options), opt) {
560 coap_add_option(pdu, opt->number, opt->length, opt->data);
561 }
562 return 1;
563 }
564 return 0;
565 }
566
567 int
coap_insert_optlist(coap_optlist_t ** head,coap_optlist_t * node)568 coap_insert_optlist(coap_optlist_t **head, coap_optlist_t *node) {
569 if (!node) {
570 coap_log(LOG_DEBUG, "optlist not provided\n");
571 } else {
572 /* must append at the list end to avoid re-ordering of
573 * options during sort */
574 LL_APPEND((*head), node);
575 }
576
577 return node != NULL;
578 }
579
580 static int
coap_internal_delete(coap_optlist_t * node)581 coap_internal_delete(coap_optlist_t *node) {
582 if (node) {
583 coap_free_type(COAP_OPTLIST, node);
584 }
585 return 1;
586 }
587
588 void
coap_delete_optlist(coap_optlist_t * queue)589 coap_delete_optlist(coap_optlist_t *queue) {
590 coap_optlist_t *elt, *tmp;
591
592 if (!queue)
593 return;
594
595 LL_FOREACH_SAFE(queue, elt, tmp) {
596 coap_internal_delete(elt);
597 }
598 }
599
600