• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * coap_option.c -- helpers for handling options in CoAP PDUs
3  *
4  * Copyright (C) 2010-2013,2022-2023 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  * @file coap_option.c
14  * @brief CoAP option handling functions
15  */
16 
17 #include "coap3/coap_internal.h"
18 
19 #include <stdio.h>
20 #include <string.h>
21 
22 #define ADVANCE_OPT(o,e,step) if ((e) < step) {           \
23     coap_log_debug("cannot advance opt past end\n"); \
24     return 0;                                             \
25   } else {                                                \
26     (e) -= step;                                          \
27     (o) = ((o)) + step;                                   \
28   }
29 
30 /*
31  * Used to prevent access to *opt when pointing to after end of buffer
32  * after doing a ADVANCE_OPT()
33  */
34 #define ADVANCE_OPT_CHECK(o,e,step) do { \
35     ADVANCE_OPT(o,e,step);               \
36     if ((e) < 1)                         \
37       return 0;                          \
38   } while (0)
39 
40 size_t
coap_opt_parse(const coap_opt_t * opt,size_t length,coap_option_t * result)41 coap_opt_parse(const coap_opt_t *opt, size_t length, coap_option_t *result) {
42 
43   const coap_opt_t *opt_start = opt; /* store where parsing starts  */
44 
45   assert(opt);
46   assert(result);
47 
48   if (length < 1)
49     return 0;
50 
51   result->delta = (*opt & 0xf0) >> 4;
52   result->length = *opt & 0x0f;
53 
54   switch (result->delta) {
55   case 15:
56     if (*opt != COAP_PAYLOAD_START) {
57       coap_log_debug("ignored reserved option delta 15\n");
58     }
59     return 0;
60   case 14:
61     /* Handle two-byte value: First, the MSB + 269 is stored as delta value.
62      * After that, the option pointer is advanced to the LSB which is handled
63      * just like case delta == 13. */
64     ADVANCE_OPT_CHECK(opt,length,1);
65     result->delta = ((*opt & 0xff) << 8) + 269;
66     if (result->delta < 269) {
67       coap_log_debug("delta too large\n");
68       return 0;
69     }
70   /* fall through */
71   case 13:
72     ADVANCE_OPT_CHECK(opt,length,1);
73     result->delta += *opt & 0xff;
74     break;
75 
76   default:
77     ;
78   }
79 
80   switch (result->length) {
81   case 15:
82     coap_log_debug("found reserved option length 15\n");
83     return 0;
84   case 14:
85     /* Handle two-byte value: First, the MSB + 269 is stored as delta value.
86      * After that, the option pointer is advanced to the LSB which is handled
87      * just like case delta == 13. */
88     ADVANCE_OPT_CHECK(opt,length,1);
89     result->length = ((*opt & 0xff) << 8) + 269;
90   /* fall through */
91   case 13:
92     ADVANCE_OPT_CHECK(opt,length,1);
93     result->length += *opt & 0xff;
94     break;
95 
96   default:
97     ;
98   }
99 
100   /* ADVANCE_OPT() is correct here */
101   ADVANCE_OPT(opt,length,1);
102   /* opt now points to value, if present */
103 
104   result->value = opt;
105   if (length < result->length) {
106     coap_log_debug("invalid option length\n");
107     return 0;
108   }
109 
110 #undef ADVANCE_OPT
111 #undef ADVANCE_OPT_CHECK
112 
113   return (opt + result->length) - opt_start;
114 }
115 
116 coap_opt_iterator_t *
coap_option_iterator_init(const coap_pdu_t * pdu,coap_opt_iterator_t * oi,const coap_opt_filter_t * filter)117 coap_option_iterator_init(const coap_pdu_t *pdu, coap_opt_iterator_t *oi,
118                           const coap_opt_filter_t *filter) {
119   assert(pdu);
120   assert(pdu->token);
121   assert(oi);
122 
123   memset(oi, 0, sizeof(coap_opt_iterator_t));
124 
125   oi->next_option = pdu->token + pdu->e_token_length;
126   if (pdu->token + pdu->used_size <= oi->next_option) {
127     oi->bad = 1;
128     return NULL;
129   }
130 
131   oi->length = pdu->used_size - pdu->e_token_length;
132 
133   if (filter) {
134     memcpy(&oi->filter, filter, sizeof(coap_opt_filter_t));
135     oi->filtered = 1;
136   }
137   return oi;
138 }
139 
140 COAP_STATIC_INLINE int
opt_finished(coap_opt_iterator_t * oi)141 opt_finished(coap_opt_iterator_t *oi) {
142   assert(oi);
143 
144   if (oi->bad || oi->length == 0 ||
145       !oi->next_option || *oi->next_option == COAP_PAYLOAD_START) {
146     oi->bad = 1;
147   }
148 
149   return oi->bad;
150 }
151 
152 coap_opt_t *
coap_option_next(coap_opt_iterator_t * oi)153 coap_option_next(coap_opt_iterator_t *oi) {
154   coap_option_t option;
155   coap_opt_t *current_opt = NULL;
156   size_t optsize;
157 
158   assert(oi);
159 
160   if (opt_finished(oi))
161     return NULL;
162 
163   while (1) {
164     /* oi->next_option always points to the next option to deliver; as
165      * opt_finished() filters out any bad conditions, we can assume that
166      * oi->next_option is valid. */
167     current_opt = oi->next_option;
168 
169     /*
170      * Advance internal pointer to next option.
171      * optsize will be 0 when no more options
172      */
173     optsize = coap_opt_parse(oi->next_option, oi->length, &option);
174     if (optsize) {
175       assert(optsize <= oi->length);
176 
177       oi->next_option += optsize;
178       oi->length -= optsize;
179 
180       oi->number += option.delta;
181     } else {                        /* current option is malformed */
182       oi->bad = 1;
183       return NULL;
184     }
185 
186     /* Exit the while loop when:
187      *   - no filtering is done at all
188      *   - the filter matches for the current option
189      */
190     if (!oi->filtered ||
191         coap_option_filter_get(&oi->filter, oi->number) > 0)
192       break;
193   }
194 
195   return current_opt;
196 }
197 
198 coap_opt_t *
coap_check_option(const coap_pdu_t * pdu,coap_option_num_t number,coap_opt_iterator_t * oi)199 coap_check_option(const coap_pdu_t *pdu, coap_option_num_t number,
200                   coap_opt_iterator_t *oi) {
201   coap_opt_filter_t f;
202 
203   coap_option_filter_clear(&f);
204   coap_option_filter_set(&f, number);
205 
206   coap_option_iterator_init(pdu, oi, &f);
207 
208   return coap_option_next(oi);
209 }
210 
211 uint32_t
coap_opt_length(const coap_opt_t * opt)212 coap_opt_length(const coap_opt_t *opt) {
213   uint32_t length;
214 
215   length = *opt & 0x0f;
216   switch (*opt & 0xf0) {
217   case 0xf0:
218     coap_log_debug("illegal option delta\n");
219     return 0;
220   case 0xe0:
221     ++opt;
222   /* fall through */
223   /* to skip another byte */
224   case 0xd0:
225     ++opt;
226   /* fall through */
227   /* to skip another byte */
228   default:
229     ++opt;
230   }
231 
232   switch (length) {
233   case 0x0f:
234     coap_log_debug("illegal option length\n");
235     return 0;
236   case 0x0e:
237     length = (*opt++ << 8) + 269;
238   /* fall through */
239   case 0x0d:
240     length += *opt++;
241     break;
242   default:
243     ;
244   }
245   return length;
246 }
247 
248 const uint8_t *
coap_opt_value(const coap_opt_t * opt)249 coap_opt_value(const coap_opt_t *opt) {
250   size_t ofs = 1;
251 
252   switch (*opt & 0xf0) {
253   case 0xf0:
254     coap_log_debug("illegal option delta\n");
255     return 0;
256   case 0xe0:
257     ++ofs;
258   /* fall through */
259   case 0xd0:
260     ++ofs;
261     break;
262   default:
263     ;
264   }
265 
266   switch (*opt & 0x0f) {
267   case 0x0f:
268     coap_log_debug("illegal option length\n");
269     return 0;
270   case 0x0e:
271     ++ofs;
272   /* fall through */
273   case 0x0d:
274     ++ofs;
275     break;
276   default:
277     ;
278   }
279 
280   return (const uint8_t *)opt + ofs;
281 }
282 
283 size_t
coap_opt_size(const coap_opt_t * opt)284 coap_opt_size(const coap_opt_t *opt) {
285   coap_option_t option;
286 
287   /* we must assume that opt is encoded correctly */
288   return coap_opt_parse(opt, (size_t)-1, &option);
289 }
290 
291 size_t
coap_opt_setheader(coap_opt_t * opt,size_t maxlen,uint16_t delta,size_t length)292 coap_opt_setheader(coap_opt_t *opt, size_t maxlen,
293                    uint16_t delta, size_t length) {
294   size_t skip = 0;
295 
296   assert(opt);
297 
298   if (maxlen == 0)                /* need at least one byte */
299     return 0;
300 
301   if (delta < 13) {
302     opt[0] = (coap_opt_t)(delta << 4);
303   } else if (delta < 269) {
304     if (maxlen < 2) {
305       coap_log_debug("insufficient space to encode option delta %d\n",
306                      delta);
307       return 0;
308     }
309 
310     opt[0] = 0xd0;
311     opt[++skip] = (coap_opt_t)(delta - 13);
312   } else {
313     if (maxlen < 3) {
314       coap_log_debug("insufficient space to encode option delta %d\n",
315                      delta);
316       return 0;
317     }
318 
319     opt[0] = 0xe0;
320     opt[++skip] = ((delta - 269) >> 8) & 0xff;
321     opt[++skip] = (delta - 269) & 0xff;
322   }
323 
324   if (length < 13) {
325     opt[0] |= length & 0x0f;
326   } else if (length < 269) {
327     if (maxlen < skip + 2) {
328       coap_log_debug("insufficient space to encode option length %zu\n",
329                      length);
330       return 0;
331     }
332 
333     opt[0] |= 0x0d;
334     opt[++skip] = (coap_opt_t)(length - 13);
335   } else {
336     if (maxlen < skip + 3) {
337       coap_log_debug("insufficient space to encode option delta %d\n",
338                      delta);
339       return 0;
340     }
341 
342     opt[0] |= 0x0e;
343     opt[++skip] = ((length - 269) >> 8) & 0xff;
344     opt[++skip] = (length - 269) & 0xff;
345   }
346 
347   return skip + 1;
348 }
349 
350 size_t
coap_opt_encode_size(uint16_t delta,size_t length)351 coap_opt_encode_size(uint16_t delta, size_t length) {
352   size_t n = 1;
353 
354   if (delta >= 13) {
355     if (delta < 269)
356       n += 1;
357     else
358       n += 2;
359   }
360 
361   if (length >= 13) {
362     if (length < 269)
363       n += 1;
364     else
365       n += 2;
366   }
367 
368   return n + length;
369 }
370 
371 size_t
coap_opt_encode(coap_opt_t * opt,size_t maxlen,uint16_t delta,const uint8_t * val,size_t length)372 coap_opt_encode(coap_opt_t *opt, size_t maxlen, uint16_t delta,
373                 const uint8_t *val, size_t length) {
374   size_t l = 1;
375 
376   l = coap_opt_setheader(opt, maxlen, delta, length);
377   assert(l <= maxlen);
378 
379   if (!l) {
380     coap_log_debug("coap_opt_encode: cannot set option header\n");
381     return 0;
382   }
383 
384   maxlen -= l;
385   opt += l;
386 
387   if (maxlen < length) {
388     coap_log_debug("coap_opt_encode: option too large for buffer\n");
389     return 0;
390   }
391 
392   if (val)                        /* better be safe here */
393     memcpy(opt, val, length);
394 
395   return l + length;
396 }
397 
398 #define LONG_MASK ((1 << COAP_OPT_FILTER_LONG) - 1)
399 #define SHORT_MASK \
400   (~LONG_MASK & ((1 << (COAP_OPT_FILTER_LONG + COAP_OPT_FILTER_SHORT)) - 1))
401 
402 /** Returns true iff @p number denotes an option number larger than 255. */
403 COAP_STATIC_INLINE int
is_long_option(coap_option_num_t number)404 is_long_option(coap_option_num_t number) {
405   return number > 255;
406 }
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_crit("coap_new_optlist: size too large (%zu > MEMP_LEN_COAPOPTLIST)\n",
520                   length);
521     return NULL;
522   }
523 #endif /* WITH_LWIP */
524   node = coap_malloc_type(COAP_OPTLIST, sizeof(coap_optlist_t) + length);
525 
526   if (node) {
527     memset(node, 0, (sizeof(coap_optlist_t) + length));
528     node->number = number;
529     node->length = length;
530     node->data = (uint8_t *)&node[1];
531     memcpy(node->data, data, length);
532   } else {
533     coap_log_warn("coap_new_optlist: malloc failure\n");
534   }
535 
536   return node;
537 }
538 
539 static int
order_opts(void * a,void * b)540 order_opts(void *a, void *b) {
541   coap_optlist_t *o1 = (coap_optlist_t *)a;
542   coap_optlist_t *o2 = (coap_optlist_t *)b;
543 
544   if (!a || !b)
545     return a < b ? -1 : 1;
546 
547   return (int)(o1->number - o2->number);
548 }
549 
550 int
coap_add_optlist_pdu(coap_pdu_t * pdu,coap_optlist_t ** options)551 coap_add_optlist_pdu(coap_pdu_t *pdu, coap_optlist_t **options) {
552   coap_optlist_t *opt;
553 
554   if (options && *options) {
555     if (pdu->data) {
556       coap_log_warn("coap_add_optlist_pdu: PDU already contains data\n");
557       return 0;
558     }
559     /* sort options for delta encoding */
560     LL_SORT((*options), order_opts);
561 
562     LL_FOREACH((*options), opt) {
563       coap_add_option_internal(pdu, opt->number, opt->length, opt->data);
564     }
565     return 1;
566   }
567   return 0;
568 }
569 
570 int
coap_insert_optlist(coap_optlist_t ** head,coap_optlist_t * node)571 coap_insert_optlist(coap_optlist_t **head, coap_optlist_t *node) {
572   if (!node) {
573     coap_log_debug("optlist not provided\n");
574   } else {
575     /* must append at the list end to avoid re-ordering of
576      * options during sort */
577     LL_APPEND((*head), node);
578   }
579 
580   return node != NULL;
581 }
582 
583 static int
coap_internal_delete(coap_optlist_t * node)584 coap_internal_delete(coap_optlist_t *node) {
585   if (node) {
586     coap_free_type(COAP_OPTLIST, node);
587   }
588   return 1;
589 }
590 
591 void
coap_delete_optlist(coap_optlist_t * queue)592 coap_delete_optlist(coap_optlist_t *queue) {
593   coap_optlist_t *elt, *tmp;
594 
595   if (!queue)
596     return;
597 
598   LL_FOREACH_SAFE(queue, elt, tmp) {
599     coap_internal_delete(elt);
600   }
601 }
602