xref: /third_party/libcoap/src/coap_resource.c (revision c87c5fba)
1/* coap_resource.c -- generic resource handling
2 *
3 * Copyright (C) 2010--2023 Olaf Bergmann <bergmann@tzi.org>
4 *
5 * SPDX-License-Identifier: BSD-2-Clause
6 *
7 * This file is part of the CoAP library libcoap. Please see
8 * README for terms of use.
9 */
10
11/**
12 * @file coap_resource.c
13 * @brief Server resource handling functions
14 */
15
16#include "coap3/coap_internal.h"
17
18#if COAP_SERVER_SUPPORT
19#include <stdio.h>
20
21#ifdef COAP_EPOLL_SUPPORT
22#include <sys/epoll.h>
23#include <sys/timerfd.h>
24#endif /* COAP_EPOLL_SUPPORT */
25
26#define COAP_PRINT_STATUS_MAX (~COAP_PRINT_STATUS_MASK)
27
28#ifndef min
29#define min(a,b) ((a) < (b) ? (a) : (b))
30#endif
31
32/* Helper functions for conditional output of character sequences into
33 * a given buffer. The first Offset characters are skipped.
34 */
35
36/**
37 * Adds Char to Buf if Offset is zero. Otherwise, Char is not written
38 * and Offset is decremented.
39 */
40#define PRINT_WITH_OFFSET(Buf,Offset,Char)                \
41  if ((Offset) == 0) {                                        \
42    (*(Buf)++) = (Char);                                \
43  } else {                                                \
44    (Offset)--;                                                \
45  }                                                        \
46
47/**
48 * Adds Char to Buf if Offset is zero and Buf is less than Bufend.
49 */
50#define PRINT_COND_WITH_OFFSET(Buf,Bufend,Offset,Char,Result) {                \
51    if ((Buf) < (Bufend)) {                                                \
52      PRINT_WITH_OFFSET(Buf,Offset,Char);                                \
53    }                                                                        \
54    (Result)++;                                                                \
55  }
56
57/**
58 * Copies at most Length characters of Str to Buf. The first Offset
59 * characters are skipped. Output may be truncated to Bufend - Buf
60 * characters.
61 */
62#define COPY_COND_WITH_OFFSET(Buf,Bufend,Offset,Str,Length,Result) {        \
63    size_t i;                                                                \
64    for (i = 0; i < (Length); i++) {                                        \
65      PRINT_COND_WITH_OFFSET((Buf), (Bufend), (Offset), (Str)[i], (Result)); \
66    }                                                                        \
67  }
68
69static int
70match(const coap_str_const_t *text, const coap_str_const_t *pattern, int match_prefix,
71      int match_substring
72     ) {
73  assert(text);
74  assert(pattern);
75
76  if (text->length < pattern->length)
77    return 0;
78
79  if (match_substring) {
80    const uint8_t *next_token = text->s;
81    size_t remaining_length = text->length;
82    while (remaining_length) {
83      size_t token_length;
84      const uint8_t *token = next_token;
85      next_token = (unsigned char *)memchr(token, ' ', remaining_length);
86
87      if (next_token) {
88        token_length = next_token - token;
89        remaining_length -= (token_length + 1);
90        next_token++;
91      } else {
92        token_length = remaining_length;
93        remaining_length = 0;
94      }
95
96      if ((match_prefix || pattern->length == token_length) &&
97          memcmp(token, pattern->s, pattern->length) == 0)
98        return 1;
99    }
100    return 0;
101  }
102
103  return (match_prefix || pattern->length == text->length) &&
104         memcmp(text->s, pattern->s, pattern->length) == 0;
105}
106
107/**
108 * Prints the names of all known resources to @p buf. This function
109 * sets @p buflen to the number of bytes actually written and returns
110 * @c 1 on succes. On error, the value in @p buflen is undefined and
111 * the return value will be @c 0.
112 *
113 * @param context The context with the resource map.
114 * @param buf     The buffer to write the result.
115 * @param buflen  Must be initialized to the maximum length of @p buf and will be
116 *                set to the length of the well-known response on return.
117 * @param offset  The offset in bytes where the output shall start and is
118 *                shifted accordingly with the characters that have been
119 *                processed. This parameter is used to support the block
120 *                option.
121 * @param query_filter A filter query according to <a href="http://tools.ietf.org/html/draft-ietf-core-link-format-11#section-4.1">Link Format</a>
122 *
123 * @return COAP_PRINT_STATUS_ERROR on error. Otherwise, the lower 28 bits are
124 *         set to the number of bytes that have actually been written to
125 *         @p buf. COAP_PRINT_STATUS_TRUNC is set when the output has been
126 *         truncated.
127 */
128#if defined(__GNUC__) && defined(WITHOUT_QUERY_FILTER)
129coap_print_status_t
130coap_print_wellknown(coap_context_t *context, unsigned char *buf, size_t *buflen,
131                     size_t offset,
132                     const coap_string_t *query_filter COAP_UNUSED) {
133#else /* not a GCC */
134coap_print_status_t
135coap_print_wellknown(coap_context_t *context, unsigned char *buf, size_t *buflen,
136                     size_t offset, const coap_string_t *query_filter) {
137#endif /* GCC */
138  size_t output_length = 0;
139  unsigned char *p = buf;
140  const uint8_t *bufend = buf + *buflen;
141  size_t left, written = 0;
142  coap_print_status_t result;
143  const size_t old_offset = offset;
144  int subsequent_resource = 0;
145#ifndef WITHOUT_QUERY_FILTER
146  coap_str_const_t resource_param = { 0, NULL }, query_pattern = { 0, NULL };
147  int flags = 0; /* MATCH_SUBSTRING, MATCH_PREFIX, MATCH_URI */
148#define MATCH_URI       0x01
149#define MATCH_PREFIX    0x02
150#define MATCH_SUBSTRING 0x04
151  static const coap_str_const_t _rt_attributes[] = {
152    {2, (const uint8_t *)"rt"},
153    {2, (const uint8_t *)"if"},
154    {3, (const uint8_t *)"rel"},
155    {0, NULL}
156  };
157#endif /* WITHOUT_QUERY_FILTER */
158
159#ifndef WITHOUT_QUERY_FILTER
160  /* split query filter, if any */
161  if (query_filter) {
162    resource_param.s = query_filter->s;
163    while (resource_param.length < query_filter->length &&
164           resource_param.s[resource_param.length] != '=')
165      resource_param.length++;
166
167    if (resource_param.length < query_filter->length) {
168      const coap_str_const_t *rt_attributes;
169      if (resource_param.length == 4 &&
170          memcmp(resource_param.s, "href", 4) == 0)
171        flags |= MATCH_URI;
172
173      for (rt_attributes = _rt_attributes; rt_attributes->s; rt_attributes++) {
174        if (resource_param.length == rt_attributes->length &&
175            memcmp(resource_param.s, rt_attributes->s, rt_attributes->length) == 0) {
176          flags |= MATCH_SUBSTRING;
177          break;
178        }
179      }
180
181      /* rest is query-pattern */
182      query_pattern.s =
183          query_filter->s + resource_param.length + 1;
184
185      assert((resource_param.length + 1) <= query_filter->length);
186      query_pattern.length =
187          query_filter->length - (resource_param.length + 1);
188
189      if ((query_pattern.s[0] == '/') && ((flags & MATCH_URI) == MATCH_URI)) {
190        query_pattern.s++;
191        query_pattern.length--;
192      }
193
194      if (query_pattern.length &&
195          query_pattern.s[query_pattern.length-1] == '*') {
196        query_pattern.length--;
197        flags |= MATCH_PREFIX;
198      }
199    }
200  }
201#endif /* WITHOUT_QUERY_FILTER */
202
203  RESOURCES_ITER(context->resources, r) {
204
205#ifndef WITHOUT_QUERY_FILTER
206    if (resource_param.length) { /* there is a query filter */
207
208      if (flags & MATCH_URI) {        /* match resource URI */
209        if (!match(r->uri_path, &query_pattern, (flags & MATCH_PREFIX) != 0,
210                   (flags & MATCH_SUBSTRING) != 0))
211          continue;
212      } else {                        /* match attribute */
213        coap_attr_t *attr;
214        coap_str_const_t unquoted_val;
215        attr = coap_find_attr(r, &resource_param);
216        if (!attr || !attr->value)
217          continue;
218        unquoted_val = *attr->value;
219        if (attr->value->s[0] == '"') {          /* if attribute has a quoted value, remove double quotes */
220          unquoted_val.length -= 2;
221          unquoted_val.s += 1;
222        }
223        if (!(match(&unquoted_val, &query_pattern,
224                    (flags & MATCH_PREFIX) != 0,
225                    (flags & MATCH_SUBSTRING) != 0)))
226          continue;
227      }
228    }
229#endif /* WITHOUT_QUERY_FILTER */
230
231    if (!subsequent_resource) {        /* this is the first resource  */
232      subsequent_resource = 1;
233    } else {
234      PRINT_COND_WITH_OFFSET(p, bufend, offset, ',', written);
235    }
236
237    left = bufend - p; /* calculate available space */
238    result = coap_print_link(r, p, &left, &offset);
239
240    if (result & COAP_PRINT_STATUS_ERROR) {
241      break;
242    }
243
244    /* coap_print_link() returns the number of characters that
245     * where actually written to p. Now advance to its end. */
246    p += COAP_PRINT_OUTPUT_LENGTH(result);
247    written += left;
248  }
249
250  *buflen = written;
251  output_length = p - buf;
252
253  if (output_length > COAP_PRINT_STATUS_MAX) {
254    return COAP_PRINT_STATUS_ERROR;
255  }
256
257  result = (coap_print_status_t)output_length;
258
259  if (result + old_offset - offset < *buflen) {
260    result |= COAP_PRINT_STATUS_TRUNC;
261  }
262  return result;
263}
264
265static coap_str_const_t null_path_value = {0, (const uint8_t *)""};
266static coap_str_const_t *null_path = &null_path_value;
267
268coap_resource_t *
269coap_resource_init(coap_str_const_t *uri_path, int flags) {
270  coap_resource_t *r;
271
272  r = (coap_resource_t *)coap_malloc_type(COAP_RESOURCE, sizeof(coap_resource_t));
273  if (r) {
274    memset(r, 0, sizeof(coap_resource_t));
275
276    if (!(flags & COAP_RESOURCE_FLAGS_RELEASE_URI)) {
277      /* Need to take a copy if caller is not providing a release request */
278      if (uri_path)
279        uri_path = coap_new_str_const(uri_path->s, uri_path->length);
280      else
281        uri_path = coap_new_str_const(null_path->s, null_path->length);
282    } else if (!uri_path) {
283      /* Do not expect this, but ... */
284      uri_path = coap_new_str_const(null_path->s, null_path->length);
285    }
286
287    if (uri_path)
288      r->uri_path = uri_path;
289
290    r->flags = flags;
291    r->observe = 2;
292  } else {
293    coap_log_debug("coap_resource_init: no memory left\n");
294  }
295
296  return r;
297}
298
299static const uint8_t coap_unknown_resource_uri[] =
300    "- Unknown -";
301
302coap_resource_t *
303coap_resource_unknown_init2(coap_method_handler_t put_handler, int flags) {
304  coap_resource_t *r;
305
306  r = (coap_resource_t *)coap_malloc_type(COAP_RESOURCE, sizeof(coap_resource_t));
307  if (r) {
308    memset(r, 0, sizeof(coap_resource_t));
309    r->is_unknown = 1;
310    /* Something unlikely to be used, but it shows up in the logs */
311    r->uri_path = coap_new_str_const(coap_unknown_resource_uri, sizeof(coap_unknown_resource_uri)-1);
312    r->flags = flags & COAP_RESOURCE_FLAGS_MCAST_LIST;
313    coap_register_handler(r, COAP_REQUEST_PUT, put_handler);
314  } else {
315    coap_log_debug("coap_resource_unknown_init: no memory left\n");
316  }
317
318  return r;
319}
320
321coap_resource_t *
322coap_resource_unknown_init(coap_method_handler_t put_handler) {
323  return coap_resource_unknown_init2(put_handler, 0);
324}
325
326static const uint8_t coap_proxy_resource_uri[] =
327    "- Proxy URI -";
328
329coap_resource_t *
330coap_resource_proxy_uri_init2(coap_method_handler_t handler,
331                              size_t host_name_count,
332                              const char *host_name_list[], int flags) {
333  coap_resource_t *r;
334
335  if (host_name_count == 0) {
336    coap_log_err("coap_resource_proxy_uri_init: Must have one or more host names defined\n");
337    return NULL;
338  }
339  r = (coap_resource_t *)coap_malloc_type(COAP_RESOURCE, sizeof(coap_resource_t));
340  if (r) {
341    size_t i;
342    memset(r, 0, sizeof(coap_resource_t));
343    r->is_proxy_uri = 1;
344    /* Something unlikely to be used, but it shows up in the logs */
345    r->uri_path = coap_new_str_const(coap_proxy_resource_uri, sizeof(coap_proxy_resource_uri)-1);
346    /* Preset all the handlers */
347    for (i = 0; i < (sizeof(r->handler) / sizeof(r->handler[0])); i++) {
348      r->handler[i] = handler;
349    }
350    if (host_name_count) {
351      r->proxy_name_list = coap_malloc_type(COAP_STRING, host_name_count *
352                                            sizeof(coap_str_const_t *));
353      if (r->proxy_name_list) {
354        for (i = 0; i < host_name_count; i++) {
355          r->proxy_name_list[i] =
356              coap_new_str_const((const uint8_t *)host_name_list[i],
357                                 strlen(host_name_list[i]));
358          if (!r->proxy_name_list[i]) {
359            coap_log_err("coap_resource_proxy_uri_init: unable to add host name\n");
360            if (i == 0) {
361              coap_free_type(COAP_STRING, r->proxy_name_list);
362              r->proxy_name_list = NULL;
363            }
364            break;
365          }
366        }
367        r->proxy_name_count = i;
368      }
369    }
370    r->flags = flags & COAP_RESOURCE_FLAGS_MCAST_LIST;
371  } else {
372    coap_log_debug("coap_resource_proxy_uri_init2: no memory left\n");
373  }
374
375  return r;
376}
377
378coap_resource_t *
379coap_resource_proxy_uri_init(coap_method_handler_t handler,
380                             size_t host_name_count, const char *host_name_list[]) {
381  return coap_resource_proxy_uri_init2(handler, host_name_count,
382                                       host_name_list, 0);
383}
384
385coap_attr_t *
386coap_add_attr(coap_resource_t *resource,
387              coap_str_const_t *name,
388              coap_str_const_t *val,
389              int flags) {
390  coap_attr_t *attr;
391
392  if (!resource || !name)
393    return NULL;
394  attr = (coap_attr_t *)coap_malloc_type(COAP_RESOURCEATTR, sizeof(coap_attr_t));
395
396  if (attr) {
397    if (!(flags & COAP_ATTR_FLAGS_RELEASE_NAME)) {
398      /* Need to take a copy if caller is not providing a release request */
399      name = coap_new_str_const(name->s, name->length);
400    }
401    attr->name = name;
402    if (val) {
403      if (!(flags & COAP_ATTR_FLAGS_RELEASE_VALUE)) {
404        /* Need to take a copy if caller is not providing a release request */
405        val = coap_new_str_const(val->s, val->length);
406      }
407    }
408    attr->value = val;
409
410    attr->flags = flags;
411
412    /* add attribute to resource list */
413    LL_PREPEND(resource->link_attr, attr);
414  } else {
415    coap_log_debug("coap_add_attr: no memory left\n");
416  }
417
418  return attr;
419}
420
421coap_attr_t *
422coap_find_attr(coap_resource_t *resource,
423               coap_str_const_t *name) {
424  coap_attr_t *attr;
425
426  if (!resource || !name)
427    return NULL;
428
429  LL_FOREACH(resource->link_attr, attr) {
430    if (attr->name->length == name->length &&
431        memcmp(attr->name->s, name->s, name->length) == 0)
432      return attr;
433  }
434
435  return NULL;
436}
437
438coap_str_const_t *
439coap_attr_get_value(coap_attr_t *attr) {
440  if (attr)
441    return attr->value;
442  return NULL;
443}
444
445void
446coap_delete_attr(coap_attr_t *attr) {
447  if (!attr)
448    return;
449  coap_delete_str_const(attr->name);
450  if (attr->value) {
451    coap_delete_str_const(attr->value);
452  }
453
454  coap_free_type(COAP_RESOURCEATTR, attr);
455}
456
457typedef enum coap_deleting_resource_t {
458  COAP_DELETING_RESOURCE,
459  COAP_NOT_DELETING_RESOURCE
460} coap_deleting_resource_t;
461
462static void coap_notify_observers(coap_context_t *context, coap_resource_t *r,
463                                  coap_deleting_resource_t deleting);
464
465static void
466coap_free_resource(coap_resource_t *resource) {
467  coap_attr_t *attr, *tmp;
468  coap_subscription_t *obs, *otmp;
469
470  assert(resource);
471
472  if (!resource->context->observe_no_clear) {
473    coap_resource_notify_observers(resource, NULL);
474    coap_notify_observers(resource->context, resource, COAP_DELETING_RESOURCE);
475  }
476
477  if (resource->context->resource_deleted)
478    resource->context->resource_deleted(resource->context, resource->uri_path,
479                                        resource->context->observe_user_data);
480
481  if (resource->context->release_userdata && resource->user_data)
482    resource->context->release_userdata(resource->user_data);
483
484  /* delete registered attributes */
485  LL_FOREACH_SAFE(resource->link_attr, attr, tmp) coap_delete_attr(attr);
486
487  /* Either the application provided or libcoap copied - need to delete it */
488  coap_delete_str_const(resource->uri_path);
489
490  /* free all elements from resource->subscribers */
491  LL_FOREACH_SAFE(resource->subscribers, obs, otmp) {
492    if (resource->context->observe_deleted)
493      resource->context->observe_deleted(obs->session, obs,
494                                         resource->context->observe_user_data);
495    coap_session_release(obs->session);
496    coap_delete_pdu(obs->pdu);
497    coap_delete_cache_key(obs->cache_key);
498    coap_free_type(COAP_SUBSCRIPTION, obs);
499  }
500  if (resource->proxy_name_count && resource->proxy_name_list) {
501    size_t i;
502
503    for (i = 0; i < resource->proxy_name_count; i++) {
504      coap_delete_str_const(resource->proxy_name_list[i]);
505    }
506    coap_free_type(COAP_STRING, resource->proxy_name_list);
507  }
508
509  coap_free_type(COAP_RESOURCE, resource);
510}
511
512void
513coap_add_resource(coap_context_t *context, coap_resource_t *resource) {
514  if (resource->is_unknown) {
515    if (context->unknown_resource)
516      coap_free_resource(context->unknown_resource);
517    context->unknown_resource = resource;
518  } else if (resource->is_proxy_uri) {
519    if (context->proxy_uri_resource)
520      coap_free_resource(context->proxy_uri_resource);
521    context->proxy_uri_resource = resource;
522  } else {
523    coap_resource_t *r = coap_get_resource_from_uri_path(context,
524                                                         resource->uri_path);
525
526    if (r) {
527      coap_log_warn("coap_add_resource: Duplicate uri_path '%*.*s', old resource deleted\n",
528                    (int)resource->uri_path->length, (int)resource->uri_path->length,
529                    resource->uri_path->s);
530      coap_delete_resource(context, r);
531    }
532    RESOURCES_ADD(context->resources, resource);
533#if COAP_WITH_OBSERVE_PERSIST
534    if (context->unknown_pdu && context->dyn_resource_save_file &&
535        context->dyn_resource_added && resource->observable) {
536      coap_bin_const_t raw_packet;
537
538      raw_packet.s = context->unknown_pdu->token -
539                     context->unknown_pdu->hdr_size;
540      raw_packet.length = context->unknown_pdu->used_size +
541                          context->unknown_pdu->hdr_size;
542      context->dyn_resource_added(context->unknown_session, resource->uri_path,
543                                  &raw_packet, context->observe_user_data);
544    }
545#endif /* COAP_WITH_OBSERVE_PERSIST */
546  }
547  assert(resource->context == NULL);
548  resource->context = context;
549}
550
551/*
552 * Input context is ignored, but param left there to keep API consistent
553 */
554int
555coap_delete_resource(coap_context_t *context, coap_resource_t *resource) {
556  if (!resource)
557    return 0;
558
559  context = resource->context;
560
561  if (resource->is_unknown) {
562    if (context && context->unknown_resource == resource) {
563      context->unknown_resource = NULL;
564    }
565  } else if (resource->is_proxy_uri) {
566    if (context && context->proxy_uri_resource == resource) {
567      context->proxy_uri_resource = NULL;
568    }
569  } else if (context) {
570    /* remove resource from list */
571    RESOURCES_DELETE(context->resources, resource);
572  }
573
574  /* and free its allocated memory */
575  coap_free_resource(resource);
576
577  return 1;
578}
579
580void
581coap_delete_all_resources(coap_context_t *context) {
582  coap_resource_t *res;
583  coap_resource_t *rtmp;
584
585  /* Cannot call RESOURCES_ITER because coap_free_resource() releases
586   * the allocated storage. */
587
588  HASH_ITER(hh, context->resources, res, rtmp) {
589    HASH_DELETE(hh, context->resources, res);
590    coap_free_resource(res);
591  }
592
593  context->resources = NULL;
594
595  if (context->unknown_resource) {
596    coap_free_resource(context->unknown_resource);
597    context->unknown_resource = NULL;
598  }
599  if (context->proxy_uri_resource) {
600    coap_free_resource(context->proxy_uri_resource);
601    context->proxy_uri_resource = NULL;
602  }
603}
604
605coap_resource_t *
606coap_get_resource_from_uri_path(coap_context_t *context, coap_str_const_t *uri_path) {
607  coap_resource_t *result;
608
609  RESOURCES_FIND(context->resources, uri_path, result);
610
611  return result;
612}
613
614coap_print_status_t
615coap_print_link(const coap_resource_t *resource,
616                unsigned char *buf, size_t *len, size_t *offset) {
617  unsigned char *p = buf;
618  const uint8_t *bufend = buf + *len;
619  coap_attr_t *attr;
620  coap_print_status_t result = 0;
621  size_t output_length = 0;
622  const size_t old_offset = *offset;
623
624  *len = 0;
625  PRINT_COND_WITH_OFFSET(p, bufend, *offset, '<', *len);
626  PRINT_COND_WITH_OFFSET(p, bufend, *offset, '/', *len);
627
628  COPY_COND_WITH_OFFSET(p, bufend, *offset,
629                        resource->uri_path->s, resource->uri_path->length, *len);
630
631  PRINT_COND_WITH_OFFSET(p, bufend, *offset, '>', *len);
632
633  LL_FOREACH(resource->link_attr, attr) {
634
635    PRINT_COND_WITH_OFFSET(p, bufend, *offset, ';', *len);
636
637    COPY_COND_WITH_OFFSET(p, bufend, *offset,
638                          attr->name->s, attr->name->length, *len);
639
640    if (attr->value && attr->value->s) {
641      PRINT_COND_WITH_OFFSET(p, bufend, *offset, '=', *len);
642
643      COPY_COND_WITH_OFFSET(p, bufend, *offset,
644                            attr->value->s, attr->value->length, *len);
645    }
646
647  }
648  if (resource->observable) {
649    COPY_COND_WITH_OFFSET(p, bufend, *offset, ";obs", 4, *len);
650  }
651
652#if COAP_OSCORE_SUPPORT
653  /* If oscore is enabled */
654  if (resource->flags & COAP_RESOURCE_FLAGS_OSCORE_ONLY)
655    COPY_COND_WITH_OFFSET(p, bufend, *offset, ";osc", 4, *len);
656#endif /* COAP_OSCORE_SUPPORT */
657
658  output_length = p - buf;
659
660  if (output_length > COAP_PRINT_STATUS_MAX) {
661    return COAP_PRINT_STATUS_ERROR;
662  }
663
664  result = (coap_print_status_t)output_length;
665
666  if (result + old_offset - *offset < *len) {
667    result |= COAP_PRINT_STATUS_TRUNC;
668  }
669
670  return result;
671}
672
673void
674coap_register_handler(coap_resource_t *resource,
675                      coap_request_t method,
676                      coap_method_handler_t handler) {
677  coap_register_request_handler(resource, method, handler);
678}
679
680void
681coap_register_request_handler(coap_resource_t *resource,
682                              coap_request_t method,
683                              coap_method_handler_t handler) {
684  assert(resource);
685  assert(method > 0 && (size_t)(method-1) <
686         sizeof(resource->handler)/sizeof(coap_method_handler_t));
687  resource->handler[method-1] = handler;
688}
689
690coap_subscription_t *
691coap_find_observer(coap_resource_t *resource, coap_session_t *session,
692                   const coap_bin_const_t *token) {
693  coap_subscription_t *s;
694
695  assert(resource);
696  assert(session);
697
698  LL_FOREACH(resource->subscribers, s) {
699    if (s->session == session &&
700        (!token || coap_binary_equal(token, &s->pdu->actual_token)))
701      return s;
702  }
703
704  return NULL;
705}
706
707static coap_subscription_t *
708coap_find_observer_cache_key(coap_resource_t *resource, coap_session_t *session,
709                             const coap_cache_key_t *cache_key) {
710  coap_subscription_t *s;
711
712  assert(resource);
713  assert(session);
714
715  LL_FOREACH(resource->subscribers, s) {
716    if (s->session == session
717        && (memcmp(cache_key, s->cache_key, sizeof(coap_cache_key_t)) == 0))
718      return s;
719  }
720
721  return NULL;
722}
723
724coap_subscription_t *
725coap_add_observer(coap_resource_t *resource,
726                  coap_session_t *session,
727                  const coap_bin_const_t *token,
728                  const coap_pdu_t *request) {
729  coap_subscription_t *s;
730  coap_cache_key_t *cache_key = NULL;
731  size_t len;
732  const uint8_t *data;
733  /* https://rfc-editor.org/rfc/rfc7641#section-3.6 */
734  static const uint16_t cache_ignore_options[] = { COAP_OPTION_ETAG,
735                                                   COAP_OPTION_OSCORE
736                                                 };
737
738  assert(session);
739
740  /* Check if there is already a subscription for this peer. */
741  s = coap_find_observer(resource, session, token);
742  if (!s) {
743    /*
744     * Cannot allow a duplicate to be created for the same query as application
745     * may not be cleaning up duplicates.  If duplicate found, then original
746     * observer is deleted and a new one created with the new token
747     */
748    cache_key = coap_cache_derive_key_w_ignore(session, request,
749                                               COAP_CACHE_IS_SESSION_BASED,
750                                               cache_ignore_options,
751                                               sizeof(cache_ignore_options)/sizeof(cache_ignore_options[0]));
752    if (cache_key) {
753      s = coap_find_observer_cache_key(resource, session, cache_key);
754      if (s) {
755        /* Delete old entry with old token */
756        coap_delete_observer(resource, session, &s->pdu->actual_token);
757        s = NULL;
758      }
759    }
760  }
761
762  /* We are done if subscription was found. */
763  if (s) {
764    return s;
765  }
766
767  /* Check if there is already maximum number of subscribers present */
768#if (COAP_RESOURCE_MAX_SUBSCRIBER > 0)
769  uint32_t subscriber_count = 0;
770  LL_COUNT(resource->subscribers, s, subscriber_count);
771  if (subscriber_count >= COAP_RESOURCE_MAX_SUBSCRIBER) {
772    return NULL; /* Signal error */
773  }
774#endif /* COAP_RESOURCE_MAX_SUBSCRIBER */
775
776  /* Create a new subscription */
777  s = coap_malloc_type(COAP_SUBSCRIPTION, sizeof(coap_subscription_t));
778
779  if (!s) {
780    coap_delete_cache_key(cache_key);
781    return NULL;
782  }
783
784  coap_subscription_init(s);
785  s->pdu = coap_pdu_duplicate(request, session, token->length,
786                              token->s, NULL);
787  if (s->pdu == NULL) {
788    coap_delete_cache_key(cache_key);
789    coap_free_type(COAP_SUBSCRIPTION, s);
790    return NULL;
791  }
792  if (coap_get_data(request, &len, &data)) {
793    /* This could be a large bodied FETCH */
794    s->pdu->max_size = 0;
795    coap_add_data(s->pdu, len, data);
796  }
797  if (cache_key == NULL) {
798    cache_key = coap_cache_derive_key_w_ignore(session, request,
799                                               COAP_CACHE_IS_SESSION_BASED,
800                                               cache_ignore_options,
801                                               sizeof(cache_ignore_options)/sizeof(cache_ignore_options[0]));
802    if (cache_key == NULL) {
803      coap_delete_pdu(s->pdu);
804      coap_delete_cache_key(cache_key);
805      coap_free_type(COAP_SUBSCRIPTION, s);
806      return NULL;
807    }
808  }
809  s->cache_key = cache_key;
810  s->session = coap_session_reference(session);
811
812  /* add subscriber to resource */
813  LL_PREPEND(resource->subscribers, s);
814
815  coap_log_debug("create new subscription %p key 0x%02x%02x%02x%02x\n",
816                 (void *)s, s->cache_key->key[0], s->cache_key->key[1],
817                 s->cache_key->key[2], s->cache_key->key[3]);
818
819  if (session->context->observe_added && session->proto == COAP_PROTO_UDP) {
820    coap_bin_const_t raw_packet;
821    coap_bin_const_t *oscore_info = NULL;
822#if COAP_OSCORE_SUPPORT
823    oscore_association_t *association;
824
825    if (session->recipient_ctx && session->recipient_ctx->recipient_id) {
826      /*
827       * Need to track the association used for tracking this observe, done as
828       * a CBOR array. Read in coap_persist_observe_add().
829       *
830       * If an entry is null, then use nil, else a set of bytes
831       *
832       * Currently tracking 5 items
833       *  recipient_id
834       *  id_context
835       *  aad        (from oscore_association_t)
836       *  partial_iv (from oscore_association_t)
837       *  nonce      (from oscore_association_t)
838       */
839      uint8_t info_buffer[60];
840      uint8_t *info_buf = info_buffer;
841      size_t info_len = sizeof(info_buffer);
842      size_t ret = 0;
843      coap_bin_const_t ctoken = { token->length, token->s };
844
845      ret += oscore_cbor_put_array(&info_buf, &info_len, 5);
846      ret += oscore_cbor_put_bytes(&info_buf,
847                                   &info_len,
848                                   session->recipient_ctx->recipient_id->s,
849                                   session->recipient_ctx->recipient_id->length);
850      if (session->recipient_ctx->osc_ctx &&
851          session->recipient_ctx->osc_ctx->id_context) {
852        ret += oscore_cbor_put_bytes(&info_buf,
853                                     &info_len,
854                                     session->recipient_ctx->osc_ctx->id_context->s,
855                                     session->recipient_ctx->osc_ctx->id_context->length);
856      } else {
857        ret += oscore_cbor_put_nil(&info_buf, &info_len);
858      }
859      association = oscore_find_association(session, &ctoken);
860      if (association) {
861        if (association->aad) {
862          ret += oscore_cbor_put_bytes(&info_buf,
863                                       &info_len,
864                                       association->aad->s,
865                                       association->aad->length);
866        } else {
867          ret += oscore_cbor_put_nil(&info_buf, &info_len);
868        }
869        if (association->partial_iv) {
870          ret += oscore_cbor_put_bytes(&info_buf,
871                                       &info_len,
872                                       association->partial_iv->s,
873                                       association->partial_iv->length);
874        } else {
875          ret += oscore_cbor_put_nil(&info_buf, &info_len);
876        }
877        if (association->nonce) {
878          ret += oscore_cbor_put_bytes(&info_buf,
879                                       &info_len,
880                                       association->nonce->s,
881                                       association->nonce->length);
882        } else {
883          ret += oscore_cbor_put_nil(&info_buf, &info_len);
884        }
885      } else {
886        ret += oscore_cbor_put_nil(&info_buf, &info_len);
887        ret += oscore_cbor_put_nil(&info_buf, &info_len);
888      }
889      oscore_info = coap_new_bin_const(info_buffer, ret);
890    }
891#endif /* COAP_OSCORE_SUPPORT */
892
893    /* s->pdu header is not currently encoded */
894    memcpy(s->pdu->token - request->hdr_size,
895           request->token - request->hdr_size, request->hdr_size);
896    raw_packet.s = s->pdu->token - request->hdr_size;
897    raw_packet.length = s->pdu->used_size + request->hdr_size;
898    session->context->observe_added(session, s, session->proto,
899                                    &session->endpoint->bind_addr,
900                                    &session->addr_info,
901                                    &raw_packet,
902                                    oscore_info,
903                                    session->context->observe_user_data);
904#if COAP_OSCORE_SUPPORT
905    coap_delete_bin_const(oscore_info);
906#endif /* COAP_OSCORE_SUPPORT */
907  }
908  if (resource->context->track_observe_value) {
909    /* Track last used observe value (as app handler is called) */
910    resource->context->track_observe_value(resource->context,resource->uri_path,
911                                           resource->observe,
912                                           resource->context->observe_user_data);
913  }
914
915  return s;
916}
917
918void
919coap_touch_observer(coap_context_t *context, coap_session_t *session,
920                    const coap_bin_const_t *token) {
921  coap_subscription_t *s;
922
923  RESOURCES_ITER(context->resources, r) {
924    s = coap_find_observer(r, session, token);
925    if (s) {
926      s->fail_cnt = 0;
927    }
928  }
929}
930
931int
932coap_delete_observer(coap_resource_t *resource, coap_session_t *session,
933                     const coap_bin_const_t *token) {
934  coap_subscription_t *s;
935
936  s = coap_find_observer(resource, session, token);
937
938  if (s && coap_get_log_level() >= COAP_LOG_DEBUG) {
939    char outbuf[2 * 8 + 1] = "";
940    unsigned int i;
941
942    for (i = 0; i < s->pdu->actual_token.length; i++) {
943      size_t size = strlen(outbuf);
944
945      snprintf(&outbuf[size], sizeof(outbuf)-size, "%02x",
946               s->pdu->actual_token.s[i]);
947    }
948    coap_log_debug("removed subscription %p with token '%s' key 0x%02x%02x%02x%02x\n",
949                   (void *)s, outbuf, s->cache_key->key[0], s->cache_key->key[1],
950                   s->cache_key->key[2], s-> cache_key->key[3]);
951  }
952  if (s && session->context->observe_deleted)
953    session->context->observe_deleted(session, s,
954                                      session->context->observe_user_data);
955
956  if (resource->subscribers && s) {
957    LL_DELETE(resource->subscribers, s);
958    coap_session_release(session);
959    coap_delete_pdu(s->pdu);
960    coap_delete_cache_key(s->cache_key);
961    coap_free_type(COAP_SUBSCRIPTION, s);
962  }
963
964  return s != NULL;
965}
966
967void
968coap_delete_observers(coap_context_t *context, coap_session_t *session) {
969  RESOURCES_ITER(context->resources, resource) {
970    coap_subscription_t *s, *tmp;
971    LL_FOREACH_SAFE(resource->subscribers, s, tmp) {
972      if (s->session == session) {
973        if (context->observe_deleted)
974          context->observe_deleted(session, s, context->observe_user_data);
975        LL_DELETE(resource->subscribers, s);
976        coap_session_release(session);
977        coap_delete_pdu(s->pdu);
978        coap_delete_cache_key(s->cache_key);
979        coap_free_type(COAP_SUBSCRIPTION, s);
980      }
981    }
982  }
983}
984
985static void
986coap_notify_observers(coap_context_t *context, coap_resource_t *r,
987                      coap_deleting_resource_t deleting) {
988  coap_method_handler_t h;
989  coap_subscription_t *obs, *otmp;
990  coap_pdu_t *response;
991  uint8_t buf[4];
992  coap_string_t *query;
993  coap_block_b_t block;
994  coap_tick_t now;
995  coap_session_t *obs_session;
996
997  if (r->observable && (r->dirty || r->partiallydirty)) {
998    r->partiallydirty = 0;
999
1000    LL_FOREACH_SAFE(r->subscribers, obs, otmp) {
1001      obs_session = obs->session;
1002      if (r->dirty == 0 && obs->dirty == 0) {
1003        /*
1004         * running this resource due to partiallydirty, but this observation's
1005         * notification was already enqueued
1006         */
1007        context->observe_pending = 1;
1008        continue;
1009      }
1010      if (obs->session->con_active >= COAP_NSTART(obs->session) &&
1011          ((r->flags & COAP_RESOURCE_FLAGS_NOTIFY_CON) ||
1012           (obs->non_cnt >= COAP_OBS_MAX_NON))) {
1013        /* Waiting for the previous unsolicited response to finish */
1014        r->partiallydirty = 1;
1015        obs->dirty = 1;
1016        context->observe_pending = 1;
1017        continue;
1018      }
1019      coap_ticks(&now);
1020      if (obs->session->lg_xmit && obs->session->lg_xmit->last_all_sent == 0 &&
1021          obs->session->lg_xmit->last_obs &&
1022          (obs->session->lg_xmit->last_obs + 2*COAP_TICKS_PER_SECOND) > now) {
1023        /* Waiting for the previous blocked unsolicited response to finish */
1024        r->partiallydirty = 1;
1025        obs->dirty = 1;
1026        context->observe_pending = 1;
1027        continue;
1028      }
1029
1030      coap_mid_t mid = COAP_INVALID_MID;
1031      obs->dirty = 0;
1032      /* initialize response */
1033      response = coap_pdu_init(COAP_MESSAGE_CON, 0, 0, coap_session_max_pdu_size(obs->session));
1034      if (!response) {
1035        obs->dirty = 1;
1036        r->partiallydirty = 1;
1037        context->observe_pending = 1;
1038        coap_log_debug("coap_check_notify: pdu init failed, resource stays "
1039                       "partially dirty\n");
1040        continue;
1041      }
1042
1043      if (!coap_add_token(response, obs->pdu->actual_token.length,
1044                          obs->pdu->actual_token.s)) {
1045        obs->dirty = 1;
1046        r->partiallydirty = 1;
1047        context->observe_pending = 1;
1048        coap_log_debug("coap_check_notify: cannot add token, resource stays "
1049                       "partially dirty\n");
1050        coap_delete_pdu(response);
1051        continue;
1052      }
1053
1054      obs->pdu->mid = response->mid = coap_new_message_id(obs->session);
1055      /* A lot of the reliable code assumes type is CON */
1056      if (COAP_PROTO_NOT_RELIABLE(obs->session->proto) &&
1057          (r->flags & COAP_RESOURCE_FLAGS_NOTIFY_CON) == 0 &&
1058          ((r->flags & COAP_RESOURCE_FLAGS_NOTIFY_NON_ALWAYS) ||
1059           obs->non_cnt < COAP_OBS_MAX_NON)) {
1060        response->type = COAP_MESSAGE_NON;
1061      } else {
1062        response->type = COAP_MESSAGE_CON;
1063      }
1064      switch (deleting) {
1065      case COAP_NOT_DELETING_RESOURCE:
1066        /* fill with observer-specific data */
1067        coap_add_option_internal(response, COAP_OPTION_OBSERVE,
1068                                 coap_encode_var_safe(buf, sizeof(buf),
1069                                                      r->observe),
1070                                 buf);
1071        if (coap_get_block_b(obs->session, obs->pdu, COAP_OPTION_BLOCK2,
1072                             &block)) {
1073          /* Will get updated later (e.g. M bit) if appropriate */
1074          coap_add_option_internal(response, COAP_OPTION_BLOCK2,
1075                                   coap_encode_var_safe(buf, sizeof(buf),
1076                                                        ((0 << 4) |
1077                                                         (0 << 3) |
1078                                                         block.aszx)),
1079                                   buf);
1080        }
1081#if COAP_Q_BLOCK_SUPPORT
1082        else if (coap_get_block_b(obs->session, obs->pdu, COAP_OPTION_Q_BLOCK2,
1083                                  &block)) {
1084          /* Will get updated later (e.g. M bit) if appropriate */
1085          coap_add_option_internal(response, COAP_OPTION_Q_BLOCK2,
1086                                   coap_encode_var_safe(buf, sizeof(buf),
1087                                                        ((0 << 4) |
1088                                                         (0 << 3) |
1089                                                         block.szx)),
1090                                   buf);
1091        }
1092#endif /* COAP_Q_BLOCK_SUPPORT */
1093
1094        h = r->handler[obs->pdu->code - 1];
1095        assert(h);      /* we do not allow subscriptions if no
1096                         * GET/FETCH handler is defined */
1097        query = coap_get_query(obs->pdu);
1098        coap_log_debug("Observe PDU presented to app.\n");
1099        coap_show_pdu(COAP_LOG_DEBUG, obs->pdu);
1100        coap_log_debug("call custom handler for resource '%*.*s' (4)\n",
1101                       (int)r->uri_path->length, (int)r->uri_path->length,
1102                       r->uri_path->s);
1103        h(r, obs->session, obs->pdu, query, response);
1104        /* Check if lg_xmit generated and update PDU code if so */
1105        coap_check_code_lg_xmit(obs->session, obs->pdu, response, r, query);
1106        coap_delete_string(query);
1107        if (COAP_RESPONSE_CLASS(response->code) != 2) {
1108          coap_remove_option(response, COAP_OPTION_OBSERVE);
1109        }
1110        if (COAP_RESPONSE_CLASS(response->code) > 2) {
1111          coap_delete_observer(r, obs->session, &obs->pdu->actual_token);
1112          obs = NULL;
1113        }
1114        break;
1115      case COAP_DELETING_RESOURCE:
1116      default:
1117        /* Don't worry if it does not get there */
1118        response->type = COAP_MESSAGE_NON;
1119        response->code = COAP_RESPONSE_CODE(404);
1120        break;
1121      }
1122
1123      if (obs) {
1124        if (response->type == COAP_MESSAGE_CON ||
1125            (r->flags & COAP_RESOURCE_FLAGS_NOTIFY_NON_ALWAYS)) {
1126          obs->non_cnt = 0;
1127        } else {
1128          obs->non_cnt++;
1129        }
1130
1131#if COAP_Q_BLOCK_SUPPORT
1132        if (response->code == COAP_RESPONSE_CODE(205) &&
1133            coap_get_block_b(obs->session, response, COAP_OPTION_Q_BLOCK2,
1134                             &block) &&
1135            block.m) {
1136          query = coap_get_query(obs->pdu);
1137          mid = coap_send_q_block2(obs->session, r, query, obs->pdu->code,
1138                                   block, response, 1);
1139          coap_delete_string(query);
1140          goto finish;
1141        }
1142#endif /* COAP_Q_BLOCK_SUPPORT */
1143      }
1144      mid = coap_send_internal(obs_session, response);
1145
1146#if COAP_Q_BLOCK_SUPPORT
1147finish:
1148#endif /* COAP_Q_BLOCK_SUPPORT */
1149      if (COAP_INVALID_MID == mid && obs) {
1150        coap_subscription_t *s;
1151        coap_log_debug("coap_check_notify: sending failed, resource stays "
1152                       "partially dirty\n");
1153        LL_FOREACH(r->subscribers, s) {
1154          if (s == obs) {
1155            /* obs not deleted during coap_send_internal() */
1156            obs->dirty = 1;
1157            break;
1158          }
1159        }
1160        r->partiallydirty = 1;
1161        context->observe_pending = 1;
1162      }
1163    }
1164  }
1165  r->dirty = 0;
1166}
1167
1168int
1169coap_resource_set_dirty(coap_resource_t *r, const coap_string_t *query) {
1170  return coap_resource_notify_observers(r, query);
1171}
1172
1173int
1174coap_resource_notify_observers(coap_resource_t *r,
1175                               const coap_string_t *query COAP_UNUSED) {
1176  if (!r->observable)
1177    return 0;
1178  if (!r->subscribers)
1179    return 0;
1180  r->dirty = 1;
1181
1182  /* Increment value for next Observe use. Observe value must be < 2^24 */
1183  r->observe = (r->observe + 1) & 0xFFFFFF;
1184
1185  assert(r->context);
1186
1187  if (r->context->track_observe_value) {
1188    /* Track last used observe value */
1189    if ((r->observe % r->context->observe_save_freq) == 0)
1190      r->context->track_observe_value(r->context, r->uri_path,
1191                                      r->observe,
1192                                      r->context->observe_user_data);
1193  }
1194
1195  r->context->observe_pending = 1;
1196#ifdef COAP_EPOLL_SUPPORT
1197  coap_update_epoll_timer(r->context, 0);
1198#endif /* COAP_EPOLL_SUPPORT */
1199  return 1;
1200}
1201
1202void
1203coap_resource_set_mode(coap_resource_t *resource, int mode) {
1204  resource->flags = (resource->flags &
1205                     ~(COAP_RESOURCE_FLAGS_NOTIFY_CON|COAP_RESOURCE_FLAGS_NOTIFY_NON)) |
1206                    (mode & (COAP_RESOURCE_FLAGS_NOTIFY_CON|COAP_RESOURCE_FLAGS_NOTIFY_NON));
1207}
1208
1209void
1210coap_resource_set_userdata(coap_resource_t *resource, void *data) {
1211  resource->user_data = data;
1212}
1213
1214void *
1215coap_resource_get_userdata(coap_resource_t *resource) {
1216  return resource->user_data;
1217}
1218
1219void
1220coap_resource_release_userdata_handler(coap_context_t *context,
1221                                       coap_resource_release_userdata_handler_t callback) {
1222  context->release_userdata = callback;
1223}
1224
1225void
1226coap_resource_set_get_observable(coap_resource_t *resource, int mode) {
1227  resource->observable = mode ? 1 : 0;
1228}
1229
1230coap_str_const_t *
1231coap_resource_get_uri_path(coap_resource_t *resource) {
1232  if (resource)
1233    return resource->uri_path;
1234  return NULL;
1235}
1236
1237void
1238coap_check_notify(coap_context_t *context) {
1239
1240  if (context->observe_pending) {
1241    context->observe_pending = 0;
1242    RESOURCES_ITER(context->resources, r) {
1243      coap_notify_observers(context, r, COAP_NOT_DELETING_RESOURCE);
1244    }
1245  }
1246}
1247
1248void
1249coap_persist_set_observe_num(coap_resource_t *resource,
1250                             uint32_t start_observe_no) {
1251  if (!resource)
1252    return;
1253
1254  resource->observe = start_observe_no & 0xffffff;
1255}
1256
1257/**
1258 * Checks the failure counter for (peer, token) and removes peer from
1259 * the list of observers for the given resource when COAP_OBS_MAX_FAIL
1260 * is reached.
1261 *
1262 * @param context  The CoAP context to use
1263 * @param resource The resource to check for (peer, token)
1264 * @param session  The observer's session
1265 * @param token    The token that has been used for subscription.
1266 */
1267static void
1268coap_remove_failed_observers(coap_context_t *context,
1269                             coap_resource_t *resource,
1270                             coap_session_t *session,
1271                             const coap_bin_const_t *token) {
1272  coap_subscription_t *obs, *otmp;
1273
1274  LL_FOREACH_SAFE(resource->subscribers, obs, otmp) {
1275    if (obs->session == session &&
1276        coap_binary_equal(token, &obs->pdu->actual_token)) {
1277      /* count failed notifies and remove when
1278       * COAP_OBS_MAX_FAIL is reached */
1279      obs->fail_cnt++;
1280      if (obs->fail_cnt >= COAP_OBS_MAX_FAIL) {
1281        coap_cancel_all_messages(context, obs->session,
1282                                 &obs->pdu->actual_token);
1283        coap_delete_observer(resource, session, token);
1284      }
1285      break;                        /* break loop if observer was found */
1286    }
1287  }
1288}
1289
1290void
1291coap_handle_failed_notify(coap_context_t *context,
1292                          coap_session_t *session,
1293                          const coap_bin_const_t *token) {
1294
1295  RESOURCES_ITER(context->resources, r) {
1296    coap_remove_failed_observers(context, r, session, token);
1297  }
1298}
1299
1300#endif /* ! COAP_SERVER_SUPPORT */
1301