Coverage Report

Created: 2026-08-13 07:06

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
205M
#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
205M
  } else {                                                \
26
205M
    (e) -= step;                                          \
27
205M
    (o) = ((o)) + step;                                   \
28
205M
  }
29
30
/*
31
 * Used to prevent access to *opt when pointing to after end of buffer
32
 * after doing a ADVANCE_OPT()
33
 */
34
310k
#define ADVANCE_OPT_CHECK(o,e,step) do { \
35
310k
    ADVANCE_OPT(o,e,step);               \
36
310k
    if ((e) < 1)                         \
37
310k
      return 0;                          \
38
310k
  } while (0)
39
40
size_t
41
205M
coap_opt_parse(const coap_opt_t *opt, size_t length, coap_option_t *result) {
42
43
205M
  const coap_opt_t *opt_start = opt; /* store where parsing starts  */
44
45
205M
  assert(opt);
46
205M
  assert(result);
47
48
205M
  if (length < 1)
49
35.9k
    return 0;
50
51
205M
  result->delta = (*opt & 0xf0) >> 4;
52
205M
  result->length = *opt & 0x0f;
53
54
205M
  switch (result->delta) {
55
3.63k
  case 15:
56
3.63k
    if (*opt != COAP_PAYLOAD_START) {
57
888
      coap_log_debug("ignored reserved option delta 15\n");
58
888
    }
59
3.63k
    return 0;
60
6.80k
  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
6.80k
    ADVANCE_OPT_CHECK(opt,length,1);
65
6.57k
    result->delta = ((*opt & 0xff) << 8) + 269;
66
6.57k
    if (result->delta < 269) {
67
72
      coap_log_debug("delta too large\n");
68
72
      return 0;
69
72
    }
70
  /* fall through */
71
23.1k
  case 13:
72
23.1k
    ADVANCE_OPT_CHECK(opt,length,1);
73
22.7k
    result->delta += *opt & 0xff;
74
22.7k
    break;
75
76
205M
  default:
77
205M
    ;
78
205M
  }
79
80
205M
  switch (result->length) {
81
531
  case 15:
82
531
    coap_log_debug("found reserved option length 15\n");
83
531
    return 0;
84
19.0k
  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
19.0k
    ADVANCE_OPT_CHECK(opt,length,1);
89
18.8k
    result->length = ((*opt & 0xff) << 8) + 269;
90
  /* fall through */
91
261k
  case 13:
92
261k
    ADVANCE_OPT_CHECK(opt,length,1);
93
261k
    result->length += *opt & 0xff;
94
261k
    break;
95
96
205M
  default:
97
205M
    ;
98
205M
  }
99
100
  /* ADVANCE_OPT() is correct here */
101
205M
  ADVANCE_OPT(opt,length,1);
102
  /* opt now points to value, if present */
103
104
205M
  result->value = opt;
105
205M
  if (length < result->length) {
106
4.65k
    coap_log_debug("invalid option length\n");
107
4.65k
    return 0;
108
4.65k
  }
109
110
205M
#undef ADVANCE_OPT
111
205M
#undef ADVANCE_OPT_CHECK
112
113
205M
  return (opt + result->length) - opt_start;
114
205M
}
115
116
coap_opt_iterator_t *
117
coap_option_iterator_init(const coap_pdu_t *pdu, coap_opt_iterator_t *oi,
118
57.9k
                          const coap_opt_filter_t *filter) {
119
57.9k
  assert(pdu);
120
57.9k
  assert(pdu->token);
121
57.9k
  assert(oi);
122
123
57.9k
  memset(oi, 0, sizeof(coap_opt_iterator_t));
124
125
57.9k
  oi->next_option = pdu->token + pdu->e_token_length;
126
57.9k
  if (pdu->token + pdu->used_size <= oi->next_option) {
127
2.09k
    oi->bad = 1;
128
2.09k
    return NULL;
129
2.09k
  }
130
131
55.8k
  oi->length = pdu->used_size - pdu->e_token_length;
132
133
55.8k
  if (filter) {
134
47.9k
    memcpy(&oi->filter, filter, sizeof(coap_opt_filter_t));
135
47.9k
    oi->filtered = 1;
136
47.9k
  }
137
55.8k
  return oi;
138
57.9k
}
139
140
COAP_STATIC_INLINE int
141
49.8M
opt_finished(coap_opt_iterator_t *oi) {
142
49.8M
  assert(oi);
143
144
49.8M
  if (oi->bad || oi->length == 0 ||
145
49.8M
      !oi->next_option || *oi->next_option == COAP_PAYLOAD_START) {
146
16.4k
    oi->bad = 1;
147
16.4k
  }
148
149
49.8M
  return oi->bad;
150
49.8M
}
151
152
coap_opt_t *
153
49.8M
coap_option_next(coap_opt_iterator_t *oi) {
154
49.8M
  coap_option_t option;
155
49.8M
  coap_opt_t *current_opt = NULL;
156
49.8M
  size_t optsize;
157
158
49.8M
  assert(oi);
159
160
49.8M
  if (opt_finished(oi))
161
16.4k
    return NULL;
162
163
149M
  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
149M
    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
149M
    optsize = coap_opt_parse(oi->next_option, oi->length, &option);
174
149M
    if (optsize) {
175
149M
      assert(optsize <= oi->length);
176
177
149M
      oi->next_option += optsize;
178
149M
      oi->length -= optsize;
179
180
149M
      oi->number += option.delta;
181
149M
    } else {                        /* current option is malformed */
182
38.6k
      oi->bad = 1;
183
38.6k
      return NULL;
184
38.6k
    }
185
186
    /* Exit the while loop when:
187
     *   - no filtering is done at all
188
     *   - the filter matches for the current option
189
     */
190
149M
    if (!oi->filtered ||
191
149M
        coap_option_filter_get(&oi->filter, oi->number) > 0)
192
49.7M
      break;
193
149M
  }
194
195
49.7M
  return current_opt;
196
49.8M
}
197
198
coap_opt_t *
199
coap_check_option(const coap_pdu_t *pdu, coap_option_num_t number,
200
31.8k
                  coap_opt_iterator_t *oi) {
201
31.8k
  coap_opt_filter_t f;
202
203
31.8k
  coap_option_filter_clear(&f);
204
31.8k
  coap_option_filter_set(&f, number);
205
206
31.8k
  coap_option_iterator_init(pdu, oi, &f);
207
208
31.8k
  return coap_option_next(oi);
209
31.8k
}
210
211
uint32_t
212
106M
coap_opt_length(const coap_opt_t *opt) {
213
106M
  uint32_t length;
214
215
106M
  length = *opt & 0x0f;
216
106M
  switch (*opt & 0xf0) {
217
0
  case 0xf0:
218
0
    coap_log_debug("illegal option delta\n");
219
0
    return 0;
220
3.90k
  case 0xe0:
221
3.90k
    ++opt;
222
  /* fall through */
223
  /* to skip another byte */
224
13.6k
  case 0xd0:
225
13.6k
    ++opt;
226
  /* fall through */
227
  /* to skip another byte */
228
106M
  default:
229
106M
    ++opt;
230
106M
  }
231
232
106M
  switch (length) {
233
0
  case 0x0f:
234
0
    coap_log_debug("illegal option length\n");
235
0
    return 0;
236
15.4k
  case 0x0e:
237
15.4k
    length = (*opt++ << 8) + 269;
238
  /* fall through */
239
153k
  case 0x0d:
240
153k
    length += *opt++;
241
153k
    break;
242
105M
  default:
243
105M
    ;
244
106M
  }
245
106M
  return length;
246
106M
}
247
248
const uint8_t *
249
106M
coap_opt_value(const coap_opt_t *opt) {
250
106M
  size_t ofs = 1;
251
252
106M
  switch (*opt & 0xf0) {
253
0
  case 0xf0:
254
0
    coap_log_debug("illegal option delta\n");
255
0
    return 0;
256
3.66k
  case 0xe0:
257
3.66k
    ++ofs;
258
  /* fall through */
259
13.1k
  case 0xd0:
260
13.1k
    ++ofs;
261
13.1k
    break;
262
106M
  default:
263
106M
    ;
264
106M
  }
265
266
106M
  switch (*opt & 0x0f) {
267
0
  case 0x0f:
268
0
    coap_log_debug("illegal option length\n");
269
0
    return 0;
270
15.4k
  case 0x0e:
271
15.4k
    ++ofs;
272
  /* fall through */
273
153k
  case 0x0d:
274
153k
    ++ofs;
275
153k
    break;
276
105M
  default:
277
105M
    ;
278
106M
  }
279
280
106M
  return (const uint8_t *)opt + ofs;
281
106M
}
282
283
size_t
284
29.2k
coap_opt_size(const coap_opt_t *opt) {
285
29.2k
  coap_option_t option;
286
287
  /* we must assume that opt is encoded correctly */
288
29.2k
  return coap_opt_parse(opt, (size_t)-1, &option);
289
29.2k
}
290
291
size_t
292
coap_opt_setheader(coap_opt_t *opt, size_t maxlen,
293
28.6k
                   uint16_t delta, size_t length) {
294
28.6k
  size_t skip = 0;
295
296
28.6k
  assert(opt);
297
298
28.6k
  if (maxlen == 0)                /* need at least one byte */
299
0
    return 0;
300
301
28.6k
  if (delta < 13) {
302
28.3k
    opt[0] = (coap_opt_t)(delta << 4);
303
28.3k
  } else if (delta < 269) {
304
235
    if (maxlen < 2) {
305
0
      coap_log_debug("insufficient space to encode option delta %d\n",
306
0
                     delta);
307
0
      return 0;
308
0
    }
309
310
235
    opt[0] = 0xd0;
311
235
    opt[++skip] = (coap_opt_t)(delta - 13);
312
235
  } else {
313
0
    if (maxlen < 3) {
314
0
      coap_log_debug("insufficient space to encode option delta %d\n",
315
0
                     delta);
316
0
      return 0;
317
0
    }
318
319
0
    opt[0] = 0xe0;
320
0
    opt[++skip] = ((delta - 269) >> 8) & 0xff;
321
0
    opt[++skip] = (delta - 269) & 0xff;
322
0
  }
323
324
28.6k
  if (length < 13) {
325
24.8k
    opt[0] |= length & 0x0f;
326
24.8k
  } else if (length < 269) {
327
3.62k
    if (maxlen < skip + 2) {
328
347
      coap_log_debug("insufficient space to encode option length %" PRIuS "\n",
329
347
                     length);
330
347
      return 0;
331
347
    }
332
333
3.28k
    opt[0] |= 0x0d;
334
3.28k
    opt[++skip] = (coap_opt_t)(length - 13);
335
3.28k
  } else {
336
163
    if (maxlen < skip + 3) {
337
38
      coap_log_debug("insufficient space to encode option delta %d\n",
338
38
                     delta);
339
38
      return 0;
340
38
    }
341
342
125
    opt[0] |= 0x0e;
343
125
    opt[++skip] = ((length - 269) >> 8) & 0xff;
344
125
    opt[++skip] = (length - 269) & 0xff;
345
125
  }
346
347
28.2k
  return skip + 1;
348
28.6k
}
349
350
size_t
351
13.1k
coap_opt_encode_size(uint16_t delta, size_t length) {
352
13.1k
  size_t n = 1;
353
354
13.1k
  if (delta >= 13) {
355
380
    if (delta < 269)
356
380
      n += 1;
357
0
    else
358
0
      n += 2;
359
380
  }
360
361
13.1k
  if (length >= 13) {
362
606
    if (length < 269)
363
606
      n += 1;
364
0
    else
365
0
      n += 2;
366
606
  }
367
368
13.1k
  return n + length;
369
13.1k
}
370
371
size_t
372
coap_opt_encode(coap_opt_t *opt, size_t maxlen, uint16_t delta,
373
13.0k
                const uint8_t *val, size_t length) {
374
13.0k
  size_t l = 1;
375
376
13.0k
  l = coap_opt_setheader(opt, maxlen, delta, length);
377
13.0k
  assert(l <= maxlen);
378
379
13.0k
  if (!l) {
380
0
    coap_log_debug("coap_opt_encode: cannot set option header\n");
381
0
    return 0;
382
0
  }
383
384
13.0k
  maxlen -= l;
385
13.0k
  opt += l;
386
387
13.0k
  if (maxlen < length) {
388
0
    coap_log_debug("coap_opt_encode: option too large for buffer\n");
389
0
    return 0;
390
0
  }
391
392
13.0k
  if (val)                        /* better be safe here */
393
13.0k
    memcpy(opt, val, length);
394
395
13.0k
  return l + length;
396
13.0k
}
397
398
155M
#define LONG_MASK ((1 << COAP_OPT_FILTER_LONG) - 1)
399
#define SHORT_MASK \
400
149M
  (~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
404
155M
is_long_option(coap_option_num_t number) {
405
155M
  return number > 255;
406
155M
}
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
431
coap_option_filter_op(coap_opt_filter_t *filter,
432
                      coap_option_num_t number,
433
155M
                      enum filter_op_t op) {
434
155M
  size_t lindex = 0;
435
155M
  coap_opt_filter_t *of = filter;
436
155M
  uint16_t nr, mask = 0;
437
438
155M
  if (is_long_option(number)) {
439
6.19M
    mask = LONG_MASK;
440
441
17.0M
    for (nr = 1; lindex < COAP_OPT_FILTER_LONG; nr <<= 1, lindex++) {
442
443
11.6M
      if (((of->mask & nr) > 0) && (of->long_opts[lindex] == number)) {
444
814k
        if (op == FILTER_CLEAR) {
445
0
          of->mask &= ~nr;
446
0
        }
447
448
814k
        return 1;
449
814k
      }
450
11.6M
    }
451
149M
  } else {
452
149M
    mask = SHORT_MASK;
453
454
986M
    for (nr = 1 << COAP_OPT_FILTER_LONG; lindex < COAP_OPT_FILTER_SHORT;
455
892M
         nr <<= 1, lindex++) {
456
457
892M
      if (((of->mask & nr) > 0) && (of->short_opts[lindex] == (number & 0xff))) {
458
55.3M
        if (op == FILTER_CLEAR) {
459
0
          of->mask &= ~nr;
460
0
        }
461
462
55.3M
        return 1;
463
55.3M
      }
464
892M
    }
465
149M
  }
466
467
  /* number was not found, so there is nothing to do if op is CLEAR or GET */
468
99.5M
  if ((op == FILTER_CLEAR) || (op == FILTER_GET)) {
469
99.3M
    return 0;
470
99.3M
  }
471
472
  /* handle FILTER_SET: */
473
474
165k
  lindex = coap_fls(~of->mask & mask);
475
165k
  if (!lindex) {
476
109k
    return 0;
477
109k
  }
478
479
55.8k
  if (is_long_option(number)) {
480
5.05k
    of->long_opts[lindex - 1] = number;
481
50.7k
  } else {
482
50.7k
    of->short_opts[lindex - COAP_OPT_FILTER_LONG - 1] = (uint8_t)number;
483
50.7k
  }
484
485
55.8k
  of->mask |= 1 << (lindex - 1);
486
487
55.8k
  return 1;
488
165k
}
489
490
void
491
66.6k
coap_option_filter_clear(coap_opt_filter_t *filter) {
492
66.6k
  memset(filter, 0, sizeof(coap_opt_filter_t));
493
66.6k
}
494
495
int
496
6.57M
coap_option_filter_set(coap_opt_filter_t *filter, coap_option_num_t option) {
497
6.57M
  return coap_option_filter_op(filter, option, FILTER_SET);
498
6.57M
}
499
500
int
501
0
coap_option_filter_unset(coap_opt_filter_t *filter, coap_option_num_t option) {
502
0
  return coap_option_filter_op(filter, option, FILTER_CLEAR);
503
0
}
504
505
int
506
149M
coap_option_filter_get(coap_opt_filter_t *filter, coap_option_num_t option) {
507
149M
  return coap_option_filter_op(filter, option, FILTER_GET);
508
149M
}
509
510
coap_optlist_t *
511
coap_new_optlist(coap_option_num_t number,
512
                 size_t length,
513
31.9k
                 const uint8_t *data) {
514
31.9k
  coap_optlist_t *node;
515
516
31.9k
  if (!coap_pdu_parse_opt_base(number, length, data)) {
517
443
    coap_log_warn("coap_new_optlist: %d: Invalid option length / data\n", number);
518
443
    return NULL;
519
443
  }
520
#if defined(WITH_LWIP) && MEMP_USE_CUSTOM_POOLS
521
  if (length > MEMP_LEN_COAPOPTLIST) {
522
    coap_log_crit("coap_new_optlist: size too large (%" PRIuS " > MEMP_LEN_COAPOPTLIST)\n",
523
                  length);
524
    return NULL;
525
  }
526
#endif /* WITH_LWIP && MEMP_USE_CUSTOM_POOLS */
527
31.4k
  node = coap_malloc_type(COAP_OPTLIST, sizeof(coap_optlist_t) + length);
528
529
31.4k
  if (node) {
530
31.4k
    memset(node, 0, (sizeof(coap_optlist_t) + length));
531
31.4k
    node->number = number;
532
31.4k
    node->length = length;
533
31.4k
    node->data = (uint8_t *)&node[1];
534
31.4k
    memcpy(node->data, data, length);
535
31.4k
  } else {
536
0
    coap_log_warn("coap_new_optlist: malloc failure\n");
537
0
  }
538
539
31.4k
  return node;
540
31.9k
}
541
542
static int
543
175
order_opts(void *a, void *b) {
544
175
  coap_optlist_t *o1 = (coap_optlist_t *)a;
545
175
  coap_optlist_t *o2 = (coap_optlist_t *)b;
546
547
175
  if (!a || !b)
548
0
    return a < b ? -1 : 1;
549
550
175
  return (int)(o1->number - o2->number);
551
175
}
552
553
int
554
11
coap_add_optlist_pdu(coap_pdu_t *pdu, coap_optlist_t **options) {
555
11
  coap_optlist_t *opt;
556
557
11
  if (options && *options) {
558
7
    if (pdu->data) {
559
0
      coap_log_warn("coap_add_optlist_pdu: PDU already contains data\n");
560
0
      return 0;
561
0
    }
562
    /* sort options for delta encoding */
563
7
    LL_SORT((*options), order_opts);
564
565
73
    LL_FOREACH((*options), opt) {
566
73
      if (!coap_add_option_internal(pdu, opt->number, opt->length, opt->data))
567
0
        return 0;
568
73
    }
569
7
  }
570
11
  return 1;
571
11
}
572
573
int
574
0
coap_sort_optlist(coap_optlist_t **options) {
575
0
  if (options && *options) {
576
    /* sort options for delta encoding */
577
0
    LL_SORT((*options), order_opts);
578
0
    return 1;
579
0
  }
580
0
  return 0;
581
0
}
582
583
int
584
31.4k
coap_insert_optlist(coap_optlist_t **head, coap_optlist_t *node) {
585
31.4k
  if (!node) {
586
0
    coap_log_debug("optlist not provided\n");
587
31.4k
  } else {
588
    /* must append at the list end to avoid re-ordering of
589
     * options during sort */
590
31.4k
    LL_APPEND((*head), node);
591
31.4k
  }
592
593
31.4k
  return node != NULL;
594
31.4k
}
595
596
static int
597
31.4k
coap_internal_delete(coap_optlist_t *node) {
598
31.4k
  if (node) {
599
31.4k
    coap_free_type(COAP_OPTLIST, node);
600
31.4k
  }
601
31.4k
  return 1;
602
31.4k
}
603
604
void
605
5.73k
coap_delete_optlist(coap_optlist_t *queue) {
606
5.73k
  coap_optlist_t *elt, *tmp;
607
608
5.73k
  if (!queue)
609
165
    return;
610
611
31.4k
  LL_FOREACH_SAFE(queue, elt, tmp) {
612
31.4k
    coap_internal_delete(elt);
613
31.4k
  }
614
5.57k
}