Coverage Report

Created: 2026-08-08 06:43

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