Coverage Report

Created: 2026-11-12 06:14

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