libcoap 4.3.2rc1
Loading...
Searching...
No Matches
coap_subscribe.c
Go to the documentation of this file.
1/* coap_subscribe.c -- subscription handling for CoAP
2 * see RFC7641
3 *
4 * Copyright (C) 2010-2019,2022-2023 Olaf Bergmann <bergmann@tzi.org>
5 *
6 * SPDX-License-Identifier: BSD-2-Clause
7 *
8 * This file is part of the CoAP library libcoap. Please see
9 * README for terms of use.
10 */
11
17#include "coap3/coap_internal.h"
18
19#ifndef min
20#define min(a,b) ((a) < (b) ? (a) : (b))
21#endif
22
23#if COAP_SERVER_SUPPORT
24void
26 assert(s);
27 memset(s, 0, sizeof(coap_subscription_t));
28}
29
30void
32 coap_observe_added_t observe_added,
33 coap_observe_deleted_t observe_deleted,
34 coap_track_observe_value_t track_observe_value,
35 coap_dyn_resource_added_t dyn_resource_added,
36 coap_resource_deleted_t resource_deleted,
37 uint32_t save_freq,
38 void *user_data) {
39 context->observe_added = observe_added;
40 context->observe_deleted = observe_deleted;
41 context->observe_user_data = user_data;
42 context->observe_save_freq = save_freq ? save_freq : 1;
43 context->track_observe_value = track_observe_value;
44 context->dyn_resource_added = dyn_resource_added;
45 context->resource_deleted = resource_deleted;
46}
47
50 coap_proto_t e_proto,
51 const coap_address_t *e_listen_addr,
52 const coap_addr_tuple_t *s_addr_info,
53 const coap_bin_const_t *raw_packet,
54 const coap_bin_const_t *oscore_info) {
55 coap_session_t *session = NULL;
56 const uint8_t *data;
57 size_t data_len;
58 coap_pdu_t *pdu = NULL;
59#if COAP_CONSTRAINED_STACK
60 COAP_MUTEX_DEFINE(e_static_mutex);
61 static coap_packet_t e_packet;
62#else /* ! COAP_CONSTRAINED_STACK */
63 coap_packet_t e_packet;
64#endif /* ! COAP_CONSTRAINED_STACK */
65 coap_packet_t *packet = &e_packet;
66 coap_tick_t now;
67 coap_string_t *uri_path = NULL;
68 coap_opt_iterator_t opt_iter;
69 coap_opt_t *observe;
70 int observe_action;
74
75 if (e_listen_addr == NULL || s_addr_info == NULL || packet == NULL)
76 return NULL;
77
78 /* Will be creating a local 'open' session */
79 if (e_proto != COAP_PROTO_UDP)
80 return NULL;
81
82 ep = context->endpoint;
83 while (ep) {
84 if (ep->proto == e_proto &&
85 memcmp(e_listen_addr, &ep->bind_addr, sizeof(ep->bind_addr)) == 0)
86 break;
87 ep = ep->next;
88 }
89
90#if COAP_CONSTRAINED_STACK
91 coap_mutex_lock(&e_static_mutex);
92#endif /* COAP_CONSTRAINED_STACK */
93
94 /* Build up packet */
95 memcpy(&packet->addr_info, s_addr_info, sizeof(packet->addr_info));
96 packet->ifindex = 0;
97
98 data = raw_packet->s;
99 data_len = raw_packet->length;
100 if (data_len < 4)
101 goto malformed;
102
103 /* Get the session */
104
105 coap_ticks(&now);
106 session = coap_endpoint_get_session(ep, packet, now);
107 if (session == NULL)
108 goto fail;
109 /* Need max space incase PDU is updated with updated token, huge size etc. */
110 pdu = coap_pdu_init(0, 0, 0, 0);
111 if (!pdu)
112 goto fail;
113
114 if (!coap_pdu_parse(session->proto, data, data_len, pdu)) {
115 goto malformed;
116 }
117 pdu->max_size = pdu->used_size;
118
119 if (pdu->code != COAP_REQUEST_CODE_GET &&
121 goto malformed;
122
123 observe = coap_check_option(pdu, COAP_OPTION_OBSERVE, &opt_iter);
124 if (observe == NULL)
125 goto malformed;
126 observe_action = coap_decode_var_bytes(coap_opt_value(observe),
127 coap_opt_length(observe));
128 if (observe_action != COAP_OBSERVE_ESTABLISH)
129 goto malformed;
130
131 /* Get the resource */
132
133 uri_path = coap_get_uri_path(pdu);
134 if (!uri_path)
135 goto malformed;
136
138 (coap_str_const_t *)uri_path);
139 if (r == NULL) {
140 coap_log_warn("coap_persist_observe_add: resource '%s' not defined\n",
141 uri_path->s);
142 goto fail;
143 }
144 if (!r->observable) {
145 coap_log_warn("coap_persist_observe_add: resource '%s' not observable\n",
146 uri_path->s);
147 goto fail;
148 }
149 coap_delete_string(uri_path);
150 uri_path = NULL;
151
152 /* Create / update subscription for observing */
153 /* Now set up the subscription */
154 s = coap_add_observer(r, session, &pdu->actual_token, pdu);
155 if (s == NULL)
156 goto fail;
157
158#if COAP_OSCORE_SUPPORT
159 if (oscore_info) {
160 coap_log_debug("persist: OSCORE association being updated\n");
161 /*
162 * Need to track the association used for tracking this observe, done as
163 * a CBOR array. Written in coap_add_observer().
164 *
165 * If an entry is null, then use nil, else a set of bytes
166 *
167 * Currently tracking 5 items
168 * recipient_id
169 * id_context
170 * aad (from oscore_association_t)
171 * partial_iv (from oscore_association_t)
172 * nonce (from oscore_association_t)
173 */
174 oscore_ctx_t *osc_ctx;
175 const uint8_t *info_buf = oscore_info->s;
176 size_t info_buf_len = oscore_info->length;
177 size_t ret = 0;
178 coap_bin_const_t oscore_key_id;
179 coap_bin_const_t partial_iv;
181 coap_bin_const_t id_context;
182 coap_bin_const_t nonce;
183 int have_aad = 0;
184 int have_partial_iv = 0;
185 int have_id_context = 0;
186 int have_nonce = 0;
187
188 ret = oscore_cbor_get_next_element(&info_buf, &info_buf_len);
189 if (ret != CBOR_ARRAY)
190 goto oscore_fail;
191 if (oscore_cbor_get_element_size(&info_buf, &info_buf_len) != 5)
192 goto oscore_fail;
193
194 /* recipient_id */
195 ret = oscore_cbor_get_next_element(&info_buf, &info_buf_len);
196 if (ret != CBOR_BYTE_STRING)
197 goto oscore_fail;
198 oscore_key_id.length = oscore_cbor_get_element_size(&info_buf,
199 &info_buf_len);
200 oscore_key_id.s = info_buf;
201 info_buf += oscore_key_id.length;
202
203 /* id_context */
204 ret = oscore_cbor_get_next_element(&info_buf, &info_buf_len);
205 if (ret == CBOR_BYTE_STRING) {
206 id_context.length = oscore_cbor_get_element_size(&info_buf,
207 &info_buf_len);
208 id_context.s = info_buf;
209 info_buf += id_context.length;
210 have_id_context = 1;
211 } else if (ret == CBOR_SIMPLE_VALUE &&
213 &info_buf_len) == CBOR_NULL) {
214 } else
215 goto oscore_fail;
216
217 /* aad */
218 ret = oscore_cbor_get_next_element(&info_buf, &info_buf_len);
219 if (ret == CBOR_BYTE_STRING) {
220 aad.length = oscore_cbor_get_element_size(&info_buf, &info_buf_len);
221 aad.s = info_buf;
222 info_buf += aad.length;
223 have_aad = 1;
224 } else if (ret == CBOR_SIMPLE_VALUE &&
226 &info_buf_len) == CBOR_NULL) {
227 } else
228 goto oscore_fail;
229
230 /* partial_iv */
231 ret = oscore_cbor_get_next_element(&info_buf, &info_buf_len);
232 if (ret == CBOR_BYTE_STRING) {
233 partial_iv.length = oscore_cbor_get_element_size(&info_buf,
234 &info_buf_len);
235 partial_iv.s = info_buf;
236 info_buf += partial_iv.length;
237 have_partial_iv = 1;
238 } else if (ret == CBOR_SIMPLE_VALUE &&
240 &info_buf_len) == CBOR_NULL) {
241 } else
242 goto oscore_fail;
243
244 /* nonce */
245 ret = oscore_cbor_get_next_element(&info_buf, &info_buf_len);
246 if (ret == CBOR_BYTE_STRING) {
247 nonce.length = oscore_cbor_get_element_size(&info_buf,
248 &info_buf_len);
249 nonce.s = info_buf;
250 info_buf += nonce.length;
251 have_nonce = 1;
252 } else if (ret == CBOR_SIMPLE_VALUE &&
254 &info_buf_len) == CBOR_NULL) {
255 } else
256 goto oscore_fail;
257
258 osc_ctx = oscore_find_context(session->context, oscore_key_id,
259 have_id_context ? &id_context : NULL, NULL,
260 &session->recipient_ctx);
261 if (osc_ctx) {
262 session->oscore_encryption = 1;
263 oscore_new_association(session, pdu, &pdu->actual_token,
264 session->recipient_ctx,
265 have_aad ? &aad : NULL,
266 have_nonce ? &nonce : NULL,
267 have_partial_iv ? &partial_iv : NULL,
268 1);
269 coap_log_debug("persist: OSCORE association added\n");
271 have_partial_iv ? &partial_iv : NULL);
272 }
273 }
274oscore_fail:
275#else /* ! COAP_OSCORE_SUPPORT */
276 (void)oscore_info;
277#endif /* ! COAP_OSCORE_SUPPORT */
278 coap_delete_pdu(pdu);
279#if COAP_CONSTRAINED_STACK
280 coap_mutex_unlock(&e_static_mutex);
281#endif /* COAP_CONSTRAINED_STACK */
282 return s;
283
284malformed:
285 coap_log_warn("coap_persist_observe_add: discard malformed PDU\n");
286fail:
287#if COAP_CONSTRAINED_STACK
288 coap_mutex_unlock(&e_static_mutex);
289#endif /* COAP_CONSTRAINED_STACK */
290 coap_delete_string(uri_path);
291 coap_delete_pdu(pdu);
292 return NULL;
293}
294
295#if COAP_WITH_OBSERVE_PERSIST
296#include <stdio.h>
297
298/*
299 * read in active observe entry.
300 */
301static int
302coap_op_observe_read(FILE *fp, coap_subscription_t **observe_key,
303 coap_proto_t *e_proto, coap_address_t *e_listen_addr,
304 coap_addr_tuple_t *s_addr_info,
305 coap_bin_const_t **raw_packet, coap_bin_const_t **oscore_info) {
306 size_t size;
307 coap_binary_t *scratch;
308
309 *raw_packet = NULL;
310 *oscore_info = NULL;
311
312 if (fread(observe_key, sizeof(*observe_key), 1, fp) == 1) {
313 /* New record 'key proto listen addr_info len raw_packet len oscore' */
314 if (fread(e_proto, sizeof(*e_proto), 1, fp) != 1)
315 goto fail;
316 if (fread(e_listen_addr, sizeof(*e_listen_addr), 1, fp) != 1)
317 goto fail;
318 if (fread(s_addr_info, sizeof(*s_addr_info), 1, fp) != 1)
319 goto fail;
320 if (fread(&size, sizeof(size), 1, fp) != 1)
321 goto fail;
322 scratch = coap_new_binary(size);
323 if ((scratch) == NULL)
324 goto fail;
325 if (fread(scratch->s, scratch->length, 1, fp) != 1)
326 goto fail;
327 *raw_packet = (coap_bin_const_t *)scratch;
328 if (fread(&size, sizeof(size), 1, fp) != 1)
329 goto fail;
330 if ((ssize_t)size == -1)
331 return 1;
332 else {
333 scratch = coap_new_binary(size);
334 if (scratch == NULL)
335 goto fail;
336 if (fread(scratch->s, scratch->length, 1, fp) != 1)
337 goto fail;
338 *oscore_info = (coap_bin_const_t *)scratch;
339 }
340 return 1;
341 }
342fail:
343 return 0;
344}
345
346/*
347 * write out active observe entry.
348 */
349static int
350coap_op_observe_write(FILE *fp, coap_subscription_t *observe_key,
351 coap_proto_t e_proto, coap_address_t e_listen_addr,
352 coap_addr_tuple_t s_addr_info,
353 coap_bin_const_t *raw_packet, coap_bin_const_t *oscore_info) {
354 if (fwrite(&observe_key, sizeof(observe_key), 1, fp) != 1)
355 goto fail;
356 if (fwrite(&e_proto, sizeof(e_proto), 1, fp) != 1)
357 goto fail;
358 if (fwrite(&e_listen_addr, sizeof(e_listen_addr),
359 1, fp) != 1)
360 goto fail;
361 if (fwrite(&s_addr_info, sizeof(s_addr_info), 1, fp) != 1)
362 goto fail;
363 if (fwrite(&raw_packet->length, sizeof(raw_packet->length), 1, fp) != 1)
364 goto fail;
365 if (fwrite(raw_packet->s, raw_packet->length, 1, fp) != 1)
366 goto fail;
367 if (oscore_info) {
368 if (fwrite(&oscore_info->length, sizeof(oscore_info->length), 1, fp) != 1)
369 goto fail;
370 if (fwrite(oscore_info->s, oscore_info->length, 1, fp) != 1)
371 goto fail;
372 } else {
373 ssize_t not_defined = -1;
374
375 if (fwrite(&not_defined, sizeof(not_defined), 1, fp) != 1)
376 goto fail;
377 }
378 return 1;
379fail:
380 return 0;
381}
382
383/*
384 * This should be called before coap_persist_track_funcs() to prevent
385 * coap_op_observe_added() getting unnecessarily called.
386 * It should be called after init_resources() and coap_op_resource_load_disk()
387 * so that all the resources are in place.
388 */
389static void
390coap_op_observe_load_disk(coap_context_t *ctx) {
391 FILE *fp_orig = fopen((const char *)ctx->observe_save_file->s, "r");
392 FILE *fp_new = NULL;
393 coap_subscription_t *observe_key = NULL;
394 coap_proto_t e_proto;
395 coap_address_t e_listen_addr;
396 coap_addr_tuple_t s_addr_info;
397 coap_bin_const_t *raw_packet = NULL;
398 coap_bin_const_t *oscore_info = NULL;
399 char *new = NULL;
400
401 if (fp_orig == NULL)
402 goto fail;
403 new = coap_malloc_type(COAP_STRING, ctx->observe_save_file->length + 5);
404 if (!new)
405 goto fail;
406
407 strcpy(new, (const char *)ctx->observe_save_file->s);
408 strcat(new, ".tmp");
409 fp_new = fopen(new, "w+");
410 if (fp_new == NULL)
411 goto fail;
412
413 /* Go through and load oscore entry, updating key on the way */
414 while (1) {
415 if (!coap_op_observe_read(fp_orig, &observe_key, &e_proto, &e_listen_addr,
416 &s_addr_info, &raw_packet, &oscore_info))
417 break;
418 coap_log_debug("persist: New session/observe being created\n");
419 observe_key = coap_persist_observe_add(ctx, e_proto,
420 &e_listen_addr,
421 &s_addr_info,
422 raw_packet,
423 oscore_info);
424 if (observe_key) {
425 if (!coap_op_observe_write(fp_new, observe_key, e_proto, e_listen_addr,
426 s_addr_info, raw_packet, oscore_info))
427 goto fail;
428 coap_delete_bin_const(raw_packet);
429 raw_packet = NULL;
430 coap_delete_bin_const(oscore_info);
431 oscore_info = NULL;
432 }
433 }
434 coap_delete_bin_const(raw_packet);
435 raw_packet = NULL;
436 coap_delete_bin_const(oscore_info);
437 oscore_info = NULL;
438
439 if (fflush(fp_new) == EOF)
440 goto fail;
441 fclose(fp_new);
442 fclose(fp_orig);
443 /* Either old or new is in place */
444 rename(new, (const char *)ctx->observe_save_file->s);
446 return;
447
448fail:
449 coap_delete_bin_const(raw_packet);
450 coap_delete_bin_const(oscore_info);
451 if (fp_new)
452 fclose(fp_new);
453 if (fp_orig)
454 fclose(fp_orig);
455 remove(new);
457 return;
458}
459
460/*
461 * client has registered a new observe subscription request.
462 */
463static int
464coap_op_observe_added(coap_session_t *session,
465 coap_subscription_t *a_observe_key,
466 coap_proto_t a_e_proto, coap_address_t *a_e_listen_addr,
467 coap_addr_tuple_t *a_s_addr_info,
468 coap_bin_const_t *a_raw_packet,
469 coap_bin_const_t *a_oscore_info, void *user_data) {
470 FILE *fp_orig = fopen((const char *)session->context->observe_save_file->s,
471 "r");
472 FILE *fp_new = NULL;
473 coap_subscription_t *observe_key = NULL;
474 coap_proto_t e_proto;
475 coap_address_t e_listen_addr;
476 coap_addr_tuple_t s_addr_info;
477 coap_bin_const_t *raw_packet = NULL;
478 coap_bin_const_t *oscore_info = NULL;
479 char *new = NULL;
480
481 (void)user_data;
482
484 session->context->observe_save_file->length + 5);
485 if (!new)
486 goto fail;
487
488 strcpy(new, (const char *)session->context->observe_save_file->s);
489 strcat(new, ".tmp");
490 fp_new = fopen(new, "w+");
491 if (fp_new == NULL)
492 goto fail;
493
494 /* Go through and delete observe entry if it exists */
495 while (fp_orig) {
496 if (!coap_op_observe_read(fp_orig, &observe_key, &e_proto, &e_listen_addr,
497 &s_addr_info, &raw_packet, &oscore_info))
498 break;
499 if (observe_key != a_observe_key) {
500 if (!coap_op_observe_write(fp_new, observe_key, e_proto, e_listen_addr,
501 s_addr_info, raw_packet, oscore_info))
502 goto fail;
503 }
504 coap_delete_bin_const(raw_packet);
505 raw_packet = NULL;
506 coap_delete_bin_const(oscore_info);
507 oscore_info = NULL;
508 }
509 coap_delete_bin_const(raw_packet);
510 raw_packet = NULL;
511 coap_delete_bin_const(oscore_info);
512 oscore_info = NULL;
513
514 /* Add in new entry to the end */
515 if (!coap_op_observe_write(fp_new, a_observe_key, a_e_proto, *a_e_listen_addr,
516 *a_s_addr_info, a_raw_packet, a_oscore_info))
517 goto fail;
518
519 if (fflush(fp_new) == EOF)
520 goto fail;
521 fclose(fp_new);
522 if (fp_orig)
523 fclose(fp_orig);
524 /* Either old or new is in place */
525 rename(new, (const char *)session->context->observe_save_file->s);
527 return 1;
528
529fail:
530 coap_delete_bin_const(raw_packet);
531 coap_delete_bin_const(oscore_info);
532 if (fp_new)
533 fclose(fp_new);
534 if (fp_orig)
535 fclose(fp_orig);
536 remove(new);
538 return 0;
539}
540
541/*
542 * client has de-registered a observe subscription request.
543 */
544static int
545coap_op_observe_deleted(coap_session_t *session,
546 coap_subscription_t *d_observe_key,
547 void *user_data) {
548 FILE *fp_orig = fopen((const char *)session->context->observe_save_file->s,
549 "r");
550 FILE *fp_new = NULL;
551 coap_subscription_t *observe_key = NULL;
552 coap_proto_t e_proto;
553 coap_address_t e_listen_addr;
554 coap_addr_tuple_t s_addr_info;
555 coap_bin_const_t *raw_packet = NULL;
556 coap_bin_const_t *oscore_info = NULL;
557 char *new = NULL;
558
559 (void)user_data;
560
561 if (fp_orig == NULL)
562 goto fail;
564 session->context->observe_save_file->length + 5);
565 if (!new)
566 goto fail;
567
568 strcpy(new, (const char *)session->context->observe_save_file->s);
569 strcat(new, ".tmp");
570 fp_new = fopen(new, "w+");
571 if (fp_new == NULL)
572 goto fail;
573
574 /* Go through and locate observe entry to delete and not copy it across */
575 while (1) {
576 if (!coap_op_observe_read(fp_orig, &observe_key, &e_proto, &e_listen_addr,
577 &s_addr_info, &raw_packet, &oscore_info))
578 break;
579 if (observe_key != d_observe_key) {
580 if (!coap_op_observe_write(fp_new, observe_key, e_proto, e_listen_addr,
581 s_addr_info, (coap_bin_const_t *)raw_packet,
582 (coap_bin_const_t *)oscore_info))
583 goto fail;
584 }
585 coap_delete_bin_const(raw_packet);
586 raw_packet = NULL;
587 coap_delete_bin_const(oscore_info);
588 oscore_info = NULL;
589 }
590 coap_delete_bin_const(raw_packet);
591 raw_packet = NULL;
592 coap_delete_bin_const(oscore_info);
593 oscore_info = NULL;
594
595 if (fflush(fp_new) == EOF)
596 goto fail;
597 fclose(fp_new);
598 fclose(fp_orig);
599 /* Either old or new is in place */
600 rename(new, (const char *)session->context->observe_save_file->s);
602 return 1;
603
604fail:
605 coap_delete_bin_const(raw_packet);
606 coap_delete_bin_const(oscore_info);
607 if (fp_new)
608 fclose(fp_new);
609 if (fp_orig)
610 fclose(fp_orig);
611 remove(new);
613 return 0;
614}
615
616/*
617 * This should be called before coap_persist_track_funcs() to prevent
618 * coap_op_obs_cnt_track_observe() getting unnecessarily called.
619 * Should be called after coap_op_dyn_resource_load_disk() to make sure that
620 * all the resources are in the right place.
621 */
622static void
623coap_op_obs_cnt_load_disk(coap_context_t *context) {
624 FILE *fp = fopen((const char *)context->obs_cnt_save_file->s, "r");
625 char buf[1500];
626
627 if (fp == NULL)
628 return;
629
630 while (fgets(buf, sizeof(buf), fp) != NULL) {
631 char *cp = strchr(buf, ' ');
632 coap_str_const_t resource_key;
633 uint32_t observe_num;
635
636 if (!cp)
637 break;
638
639 *cp = '\000';
640 cp++;
641 observe_num = atoi(cp);
642 /*
643 * Need to assume 0 .. (context->observe_save_freq-1) have in addition
644 * been sent so need to round up to latest possible send value
645 */
646 observe_num = ((observe_num + context->observe_save_freq) /
647 context->observe_save_freq) *
648 context->observe_save_freq - 1;
649 resource_key.s = (uint8_t *)buf;
650 resource_key.length = strlen(buf);
651 r = coap_get_resource_from_uri_path(context, &resource_key);
652 if (r) {
653 coap_log_debug("persist: Initial observe number being updated\n");
654 coap_persist_set_observe_num(r, observe_num);
655 }
656 }
657 fclose(fp);
658}
659
660/*
661 * Called when the observe value of a resource has been changed, but limited
662 * to be called every context->context->observe_save_freq to reduce update
663 * overheads.
664 */
665static int
666coap_op_obs_cnt_track_observe(coap_context_t *context,
667 coap_str_const_t *resource_name,
668 uint32_t n_observe_num,
669 void *user_data) {
670 FILE *fp_orig = fopen((const char *)context->obs_cnt_save_file->s, "r");
671 FILE *fp_new = NULL;
672 char buf[1500];
673 char *new = NULL;
674
675 (void)user_data;
676
677 new = coap_malloc_type(COAP_STRING, context->obs_cnt_save_file->length + 5);
678 if (!new)
679 goto fail;
680
681 strcpy(new, (const char *)context->obs_cnt_save_file->s);
682 strcat(new, ".tmp");
683 fp_new = fopen(new, "w+");
684 if (fp_new == NULL)
685 goto fail;
686
687 /* Go through and locate resource entry to update */
688 while (fp_orig && fgets(buf, sizeof(buf), fp_orig) != NULL) {
689 char *cp = strchr(buf, ' ');
690 uint32_t observe_num;
691 coap_bin_const_t resource_key;
692
693 if (!cp)
694 break;
695
696 *cp = '\000';
697 cp++;
698 observe_num = atoi(cp);
699 resource_key.s = (uint8_t *)buf;
700 resource_key.length = strlen(buf);
701 if (!coap_binary_equal(resource_name, &resource_key)) {
702 if (fprintf(fp_new, "%s %u\n", resource_key.s, observe_num) < 0)
703 goto fail;
704 }
705 }
706 if (fprintf(fp_new, "%s %u\n", resource_name->s, n_observe_num) < 0)
707 goto fail;
708 if (fflush(fp_new) == EOF)
709 goto fail;
710 fclose(fp_new);
711 if (fp_orig)
712 fclose(fp_orig);
713 /* Either old or new is in place */
714 rename(new, (const char *)context->obs_cnt_save_file->s);
716 return 1;
717
718fail:
719 if (fp_new)
720 fclose(fp_new);
721 if (fp_orig)
722 fclose(fp_orig);
723 remove(new);
725 return 0;
726}
727
728/*
729 * Called when a resource has been deleted.
730 */
731static int
732coap_op_obs_cnt_deleted(coap_context_t *context,
733 coap_str_const_t *resource_name) {
734 FILE *fp_orig = fopen((const char *)context->obs_cnt_save_file->s, "r");
735 FILE *fp_new = NULL;
736 char buf[1500];
737 char *new = NULL;
738
739 if (fp_orig == NULL)
740 goto fail;
741 new = coap_malloc_type(COAP_STRING, context->obs_cnt_save_file->length + 5);
742 if (!new)
743 goto fail;
744
745 strcpy(new, (const char *)context->obs_cnt_save_file->s);
746 strcat(new, ".tmp");
747 fp_new = fopen(new, "w+");
748 if (fp_new == NULL)
749 goto fail;
750
751 /* Go through and locate resource entry to delete */
752 while (fgets(buf, sizeof(buf), fp_orig) != NULL) {
753 char *cp = strchr(buf, ' ');
754 uint32_t observe_num;
755 coap_bin_const_t resource_key;
756
757 if (!cp)
758 break;
759
760 *cp = '\000';
761 cp++;
762 observe_num = atoi(cp);
763 resource_key.s = (uint8_t *)buf;
764 resource_key.length = strlen(buf);
765 if (!coap_binary_equal(resource_name, &resource_key)) {
766 if (fprintf(fp_new, "%s %u\n", resource_key.s, observe_num) < 0)
767 goto fail;
768 }
769 }
770 if (fflush(fp_new) == EOF)
771 goto fail;
772 fclose(fp_new);
773 fclose(fp_orig);
774 /* Either old or new is in place */
775 rename(new, (const char *)context->obs_cnt_save_file->s);
777 return 1;
778
779fail:
780 if (fp_new)
781 fclose(fp_new);
782 if (fp_orig)
783 fclose(fp_orig);
784 remove(new);
786 return 0;
787}
788
789/*
790 * read in dynamic resource entry, allocating name & raw_packet
791 * which need to be freed off by caller.
792 */
793static int
794coap_op_dyn_resource_read(FILE *fp, coap_proto_t *e_proto,
795 coap_string_t **name,
796 coap_binary_t **raw_packet) {
797 size_t size;
798
799 *name = NULL;
800 *raw_packet = NULL;
801
802 if (fread(e_proto, sizeof(*e_proto), 1, fp) == 1) {
803 /* New record 'proto len resource_name len raw_packet' */
804 if (fread(&size, sizeof(size), 1, fp) != 1)
805 goto fail;
806 *name = coap_new_string(size);
807 if (!(*name))
808 goto fail;
809 if (fread((*name)->s, size, 1, fp) != 1)
810 goto fail;
811 if (fread(&size, sizeof(size), 1, fp) != 1)
812 goto fail;
813 *raw_packet = coap_new_binary(size);
814 if (!(*raw_packet))
815 goto fail;
816 if (fread((*raw_packet)->s, size, 1, fp) != 1)
817 goto fail;
818 return 1;
819 }
820fail:
821 return 0;
822}
823
824/*
825 * write out dynamic resource entry.
826 */
827static int
828coap_op_dyn_resource_write(FILE *fp, coap_proto_t e_proto,
829 coap_str_const_t *name,
830 coap_bin_const_t *raw_packet) {
831 if (fwrite(&e_proto, sizeof(e_proto), 1, fp) != 1)
832 goto fail;
833 if (fwrite(&name->length, sizeof(name->length), 1, fp) != 1)
834 goto fail;
835 if (fwrite(name->s, name->length, 1, fp) != 1)
836 goto fail;
837 if (fwrite(&raw_packet->length, sizeof(raw_packet->length), 1, fp) != 1)
838 goto fail;
839 if (fwrite(raw_packet->s, raw_packet->length, 1, fp) != 1)
840 goto fail;
841 return 1;
842fail:
843 return 0;
844}
845
846/*
847 * This should be called before coap_persist_track_funcs() to prevent
848 * coap_op_dyn_resource_added() getting unnecessarily called.
849 *
850 * Each record 'proto len resource_name len raw_packet'
851 */
852static void
853coap_op_dyn_resource_load_disk(coap_context_t *ctx) {
854 FILE *fp_orig = NULL;
855 coap_proto_t e_proto;
856 coap_string_t *name = NULL;
857 coap_binary_t *raw_packet = NULL;
859 coap_session_t *session = NULL;
860 coap_pdu_t *request = NULL;
861 coap_pdu_t *response = NULL;
862 coap_string_t *query = NULL;
863
864 if (!ctx->unknown_resource)
865 return;
866
867 fp_orig = fopen((const char *)ctx->dyn_resource_save_file->s, "r");
868 if (fp_orig == NULL)
869 return;
871 sizeof(coap_session_t));
872 if (!session)
873 goto fail;
874 memset(session, 0, sizeof(coap_session_t));
875 session->context = ctx;
876
877 /* Go through and create each dynamic resource if it does not exist*/
878 while (1) {
879 if (!coap_op_dyn_resource_read(fp_orig, &e_proto, &name, &raw_packet))
880 break;
882 if (!r) {
883 /* Create the new resource using the application logic */
884
885 coap_log_debug("persist: dynamic resource being re-created\n");
886 /*
887 * Need max space incase PDU is updated with updated token,
888 * huge size etc.
889 * */
890 request = coap_pdu_init(0, 0, 0, 0);
891 if (!request)
892 goto fail;
893
894 session->proto = e_proto;
895 if (!coap_pdu_parse(session->proto, raw_packet->s,
896 raw_packet->length, request)) {
897 goto fail;
898 }
899 if (!ctx->unknown_resource->handler[request->code-1])
900 goto fail;
901 response = coap_pdu_init(0, 0, 0, 0);
902 if (!response)
903 goto fail;
904 query = coap_get_query(request);
905 /* Call the application handler to set up this dynamic resource */
906 ctx->unknown_resource->handler[request->code-1](ctx->unknown_resource,
907 session, request,
908 query, response);
909 coap_delete_string(query);
910 query = NULL;
911 coap_delete_pdu(request);
912 request = NULL;
913 coap_delete_pdu(response);
914 response = NULL;
915 }
916 coap_delete_string(name);
917 coap_delete_binary(raw_packet);
918 }
919fail:
920 coap_delete_string(name);
921 coap_delete_binary(raw_packet);
922 coap_delete_string(query);
923 coap_delete_pdu(request);
924 coap_delete_pdu(response);
925 fclose(fp_orig);
927}
928
929/*
930 * Server has set up a new dynamic resource agains a request for an unknown
931 * resource.
932 */
933static int
934coap_op_dyn_resource_added(coap_session_t *session,
935 coap_str_const_t *resource_name,
936 coap_bin_const_t *packet,
937 void *user_data) {
938 FILE *fp_orig;
939 FILE *fp_new = NULL;
940 char *new = NULL;
941 coap_context_t *context = session->context;
942 coap_string_t *name = NULL;
943 coap_binary_t *raw_packet = NULL;
944 coap_proto_t e_proto;
945
946 (void)user_data;
947
948 fp_orig = fopen((const char *)context->dyn_resource_save_file->s, "a");
949 if (fp_orig == NULL)
950 return 0;
951
953 context->dyn_resource_save_file->length + 5);
954 if (!new)
955 goto fail;
956
957 strcpy(new, (const char *)context->dyn_resource_save_file->s);
958 strcat(new, ".tmp");
959 fp_new = fopen(new, "w+");
960 if (fp_new == NULL)
961 goto fail;
962
963 /* Go through and locate duplicate resource to delete */
964 while (1) {
965 if (!coap_op_dyn_resource_read(fp_orig, &e_proto, &name, &raw_packet))
966 break;
967 if (!coap_string_equal(resource_name, name)) {
968 /* Copy across non-matching entry */
969 if (!coap_op_dyn_resource_write(fp_new, e_proto, (coap_str_const_t *)name,
970 (coap_bin_const_t *)raw_packet))
971 break;
972 }
973 coap_delete_string(name);
974 name = NULL;
975 coap_delete_binary(raw_packet);
976 raw_packet = NULL;
977 }
978 coap_delete_string(name);
979 coap_delete_binary(raw_packet);
980 /* Add new entry to the end */
981 if (!coap_op_dyn_resource_write(fp_new, session->proto,
982 resource_name, packet))
983 goto fail;
984
985 if (fflush(fp_new) == EOF)
986 goto fail;
987 fclose(fp_new);
988 fclose(fp_orig);
989 /* Either old or new is in place */
990 rename(new, (const char *)context->dyn_resource_save_file->s);
992 return 1;
993
994fail:
995 if (fp_new)
996 fclose(fp_new);
997 if (fp_orig)
998 fclose(fp_orig);
999 remove(new);
1001 return 0;
1002}
1003
1004/*
1005 * Server has deleted a resource
1006 */
1007static int
1008coap_op_resource_deleted(coap_context_t *context,
1009 coap_str_const_t *resource_name,
1010 void *user_data) {
1011 FILE *fp_orig = NULL;
1012 FILE *fp_new = NULL;
1013 char *new = NULL;
1014 coap_proto_t e_proto;
1015 coap_string_t *name = NULL;
1016 coap_binary_t *raw_packet = NULL;
1017 (void)user_data;
1018
1019 coap_op_obs_cnt_deleted(context, resource_name);
1020
1021 fp_orig = fopen((const char *)context->dyn_resource_save_file->s, "r");
1022 if (fp_orig == NULL)
1023 return 1;
1024
1026 context->dyn_resource_save_file->length + 5);
1027 if (!new)
1028 goto fail;
1029
1030 strcpy(new, (const char *)context->dyn_resource_save_file->s);
1031 strcat(new, ".tmp");
1032 fp_new = fopen(new, "w+");
1033 if (fp_new == NULL)
1034 goto fail;
1035
1036 /* Go through and locate resource to delete and not copy it across */
1037 while (1) {
1038 if (!coap_op_dyn_resource_read(fp_orig, &e_proto, &name, &raw_packet))
1039 break;
1040 if (!coap_string_equal(resource_name, name)) {
1041 /* Copy across non-matching entry */
1042 if (!coap_op_dyn_resource_write(fp_new, e_proto, (coap_str_const_t *)name,
1043 (coap_bin_const_t *)raw_packet))
1044 break;
1045 }
1046 coap_delete_string(name);
1047 name = NULL;
1048 coap_delete_binary(raw_packet);
1049 raw_packet = NULL;
1050 }
1051 coap_delete_string(name);
1052 coap_delete_binary(raw_packet);
1053
1054 if (fflush(fp_new) == EOF)
1055 goto fail;
1056 fclose(fp_new);
1057 fclose(fp_orig);
1058 /* Either old or new is in place */
1059 rename(new, (const char *)context->dyn_resource_save_file->s);
1061 return 1;
1062
1063fail:
1064 if (fp_new)
1065 fclose(fp_new);
1066 if (fp_orig)
1067 fclose(fp_orig);
1068 remove(new);
1070 return 0;
1071}
1072
1073int
1075 const char *dyn_resource_save_file,
1076 const char *observe_save_file,
1077 const char *obs_cnt_save_file,
1078 uint32_t save_freq) {
1079 if (dyn_resource_save_file) {
1080 context->dyn_resource_save_file =
1081 coap_new_bin_const((const uint8_t *)dyn_resource_save_file,
1082 strlen(dyn_resource_save_file));
1083 if (!context->dyn_resource_save_file)
1084 return 0;
1085 coap_op_dyn_resource_load_disk(context);
1086 context->dyn_resource_added = coap_op_dyn_resource_added;
1087 context->resource_deleted = coap_op_resource_deleted;
1088 }
1089 if (obs_cnt_save_file) {
1090 context->obs_cnt_save_file =
1091 coap_new_bin_const((const uint8_t *)obs_cnt_save_file,
1092 strlen(obs_cnt_save_file));
1093 if (!context->obs_cnt_save_file)
1094 return 0;
1095 context->observe_save_freq = save_freq ? save_freq : 1;
1096 coap_op_obs_cnt_load_disk(context);
1097 context->track_observe_value = coap_op_obs_cnt_track_observe;
1098 context->resource_deleted = coap_op_resource_deleted;
1099 }
1100 if (observe_save_file) {
1101 context->observe_save_file =
1102 coap_new_bin_const((const uint8_t *)observe_save_file,
1103 strlen(observe_save_file));
1104 if (!context->observe_save_file)
1105 return 0;
1106 coap_op_observe_load_disk(context);
1107 context->observe_added = coap_op_observe_added;
1108 context->observe_deleted = coap_op_observe_deleted;
1109 }
1110 return 1;
1111}
1112
1113void
1115 coap_delete_bin_const(context->dyn_resource_save_file);
1116 coap_delete_bin_const(context->obs_cnt_save_file);
1117 coap_delete_bin_const(context->observe_save_file);
1118 context->dyn_resource_save_file = NULL;
1119 context->obs_cnt_save_file = NULL;
1120 context->observe_save_file = NULL;
1121
1122 /* Close down any tracking */
1123 coap_persist_track_funcs(context, NULL, NULL, NULL, NULL,
1124 NULL, 0, NULL);
1125}
1126
1127void
1129 context->observe_no_clear = 1;
1130 coap_persist_cleanup(context);
1131}
1132#else /* ! COAP_WITH_OBSERVE_PERSIST */
1133int
1135 const char *dyn_resource_save_file,
1136 const char *observe_save_file,
1137 const char *obs_cnt_save_file,
1138 uint32_t save_freq) {
1139 (void)context;
1140 (void)dyn_resource_save_file;
1141 (void)observe_save_file;
1142 (void)obs_cnt_save_file;
1143 (void)save_freq;
1144 return 0;
1145}
1146
1147void
1149 context->observe_no_clear = 1;
1150 /* Close down any tracking */
1151 coap_persist_track_funcs(context, NULL, NULL, NULL, NULL,
1152 NULL, 0, NULL);
1153}
1154
1155#endif /* ! COAP_WITH_OBSERVE_PERSIST */
1156
1157#endif /* COAP_SERVER_SUPPORT */
Pulls together all the internal only header files.
@ COAP_SESSION
Definition coap_mem.h:50
@ COAP_STRING
Definition coap_mem.h:38
void * coap_malloc_type(coap_memory_tag_t type, size_t size)
Allocates a chunk of size bytes and returns a pointer to the newly allocated memory.
void coap_free_type(coap_memory_tag_t type, void *p)
Releases the memory that was allocated by coap_malloc_type().
uint8_t coap_opt_t
Use byte-oriented access methods here because sliding a complex struct coap_opt_t over the data buffe...
Definition coap_option.h:26
uint64_t coap_tick_t
This data type represents internal timer ticks with COAP_TICKS_PER_SECOND resolution.
Definition coap_time.h:144
coap_resource_t * coap_get_resource_from_uri_path(coap_context_t *context, coap_str_const_t *uri_path)
Returns the resource identified by the unique string uri_path.
void coap_ticks(coap_tick_t *)
Returns the current value of an internal tick counter.
unsigned int coap_decode_var_bytes(const uint8_t *buf, size_t len)
Decodes multiple-length byte sequences.
Definition coap_encode.c:38
#define coap_log_debug(...)
Definition coap_debug.h:120
#define coap_log_warn(...)
Definition coap_debug.h:102
@ COAP_LOG_OSCORE
Definition coap_debug.h:59
int(* coap_dyn_resource_added_t)(coap_session_t *session, coap_str_const_t *resource_name, coap_bin_const_t *raw_packet, void *user_data)
Callback handler definition called when a dynamic resource is getting created, as defined in coap_per...
void coap_persist_stop(coap_context_t *context)
Stop tracking persist information, leaving the current persist information in the files defined in co...
void coap_persist_set_observe_num(coap_resource_t *resource, uint32_t observe_num)
Sets the current observe number value.
int(* coap_resource_deleted_t)(coap_context_t *context, coap_str_const_t *resource_name, void *user_data)
Callback handler definition called when resource is removed, as defined in coap_persist_track_funcs()...
int(* coap_observe_added_t)(coap_session_t *session, coap_subscription_t *observe_key, coap_proto_t e_proto, coap_address_t *e_listen_addr, coap_addr_tuple_t *s_addr_info, coap_bin_const_t *raw_packet, coap_bin_const_t *oscore_info, void *user_data)
Callback handler definition called when a new observe has been set up, as defined in coap_persist_tra...
#define COAP_OBSERVE_ESTABLISH
The value COAP_OBSERVE_ESTABLISH in a GET/FETCH request option COAP_OPTION_OBSERVE indicates a new ob...
void coap_persist_track_funcs(coap_context_t *context, coap_observe_added_t observe_added, coap_observe_deleted_t observe_deleted, coap_track_observe_value_t track_observe_value, coap_dyn_resource_added_t dyn_resource_added, coap_resource_deleted_t resource_deleted, uint32_t save_freq, void *user_data)
Set up callbacks to handle persist tracking so on a coap-server inadvertent restart,...
int(* coap_track_observe_value_t)(coap_context_t *context, coap_str_const_t *resource_name, uint32_t observe_num, void *user_data)
Callback handler definition called when an observe unsolicited response is being sent,...
int(* coap_observe_deleted_t)(coap_session_t *session, coap_subscription_t *observe_key, void *user_data)
Callback handler definition called when an observe is being removed, as defined in coap_persist_track...
int coap_persist_startup(coap_context_t *context, const char *dyn_resource_save_file, const char *observe_save_file, const char *obs_cnt_save_file, uint32_t save_freq)
Start up persist tracking using the libcoap module.
coap_subscription_t * coap_persist_observe_add(coap_context_t *context, coap_proto_t e_proto, const coap_address_t *e_listen_addr, const coap_addr_tuple_t *s_addr_info, const coap_bin_const_t *raw_packet, const coap_bin_const_t *oscore_info)
Set up an active subscription for an observe that was previously active over a coap-server inadvertan...
uint32_t coap_opt_length(const coap_opt_t *opt)
Returns the length of the given option.
coap_opt_t * coap_check_option(const coap_pdu_t *pdu, coap_option_num_t number, coap_opt_iterator_t *oi)
Retrieves the first option of number number from pdu.
const uint8_t * coap_opt_value(const coap_opt_t *opt)
Returns a pointer to the value of the given option.
#define CBOR_BYTE_STRING
Definition oscore_cbor.h:62
size_t oscore_cbor_get_element_size(const uint8_t **buffer, size_t *buf_len)
#define CBOR_NULL
Definition oscore_cbor.h:72
#define CBOR_SIMPLE_VALUE
Definition oscore_cbor.h:67
uint8_t oscore_cbor_get_next_element(const uint8_t **buffer, size_t *buf_len)
#define CBOR_ARRAY
Definition oscore_cbor.h:64
int oscore_new_association(coap_session_t *session, coap_pdu_t *sent_pdu, coap_bin_const_t *token, oscore_recipient_ctx_t *recipient_ctx, coap_bin_const_t *aad, coap_bin_const_t *nonce, coap_bin_const_t *partial_iv, int is_observe)
void oscore_log_hex_value(coap_log_t level, const char *name, coap_bin_const_t *value)
oscore_ctx_t * oscore_find_context(const coap_context_t *c_context, const coap_bin_const_t rcpkey_id, const coap_bin_const_t *ctxkey_id, uint8_t *oscore_r2, oscore_recipient_ctx_t **recipient_ctx)
oscore_find_context - Locate recipient context (and hence OSCORE context)
void coap_delete_pdu(coap_pdu_t *pdu)
Dispose of an CoAP PDU and frees associated storage.
Definition coap_pdu.c:163
coap_proto_t
CoAP protocol types.
Definition coap_pdu.h:304
int coap_pdu_parse(coap_proto_t proto, const uint8_t *data, size_t length, coap_pdu_t *pdu)
Parses data into the CoAP PDU structure given in result.
Definition coap_pdu.c:1380
coap_pdu_t * coap_pdu_init(coap_pdu_type_t type, coap_pdu_code_t code, coap_mid_t mid, size_t size)
Creates a new CoAP PDU with at least enough storage space for the given size maximum message size.
Definition coap_pdu.c:97
#define COAP_OPTION_OBSERVE
Definition coap_pdu.h:119
@ COAP_PROTO_UDP
Definition coap_pdu.h:306
@ COAP_REQUEST_CODE_GET
Definition coap_pdu.h:321
@ COAP_REQUEST_CODE_FETCH
Definition coap_pdu.h:325
coap_session_t * coap_endpoint_get_session(coap_endpoint_t *endpoint, const coap_packet_t *packet, coap_tick_t now)
Lookup the server session for the packet received on an endpoint, or create a new one.
void coap_delete_bin_const(coap_bin_const_t *s)
Deletes the given const binary data and releases any memory allocated.
Definition coap_str.c:120
coap_binary_t * coap_new_binary(size_t size)
Returns a new binary object with at least size bytes storage allocated.
Definition coap_str.c:77
coap_bin_const_t * coap_new_bin_const(const uint8_t *data, size_t size)
Take the specified byte array (text) and create a coap_bin_const_t * Returns a new const binary objec...
Definition coap_str.c:110
void coap_delete_binary(coap_binary_t *s)
Deletes the given coap_binary_t object and releases any memory allocated.
Definition coap_str.c:105
#define coap_binary_equal(binary1, binary2)
Compares the two binary data for equality.
Definition coap_str.h:203
#define coap_string_equal(string1, string2)
Compares the two strings for equality.
Definition coap_str.h:189
coap_string_t * coap_new_string(size_t size)
Returns a new string object with at least size+1 bytes storage allocated.
Definition coap_str.c:21
void coap_delete_string(coap_string_t *s)
Deletes the given string and releases any memory allocated.
Definition coap_str.c:46
void coap_persist_cleanup(coap_context_t *context)
Close down persist tracking, releasing any memory used.
void coap_subscription_init(coap_subscription_t *)
coap_subscription_t * coap_add_observer(coap_resource_t *resource, coap_session_t *session, const coap_bin_const_t *token, const coap_pdu_t *pdu)
Adds the specified peer as observer for resource.
coap_string_t * coap_get_uri_path(const coap_pdu_t *request)
Extract uri_path string from request PDU.
Definition coap_uri.c:764
coap_string_t * coap_get_query(const coap_pdu_t *request)
Extract query string from request PDU according to escape rules in 6.5.8.
Definition coap_uri.c:713
Multi-purpose address abstraction.
CoAP binary data definition with const data.
Definition coap_str.h:64
size_t length
length of binary data
Definition coap_str.h:65
const uint8_t * s
read-only binary data
Definition coap_str.h:66
CoAP binary data definition.
Definition coap_str.h:56
size_t length
length of binary data
Definition coap_str.h:57
uint8_t * s
binary data
Definition coap_str.h:58
The CoAP stack's global state is stored in a coap_context_t object.
coap_observe_added_t observe_added
Called when there is a new observe subscription request.
coap_dyn_resource_added_t dyn_resource_added
Callback to save dynamic resource when created.
void * observe_user_data
App provided data for use in observe_added or observe_deleted.
coap_track_observe_value_t track_observe_value
Callback to save observe value when updated.
coap_endpoint_t * endpoint
the endpoints used for listening
uint32_t observe_save_freq
How frequently to update observe value.
uint8_t observe_no_clear
Observe 4.04 not to be sent on deleting resource.
coap_observe_deleted_t observe_deleted
Called when there is a observe subscription de-register request.
coap_resource_t * unknown_resource
can be used for handling unknown resources
coap_resource_deleted_t resource_deleted
Invoked when resource is deleted.
Abstraction of virtual endpoint that can be attached to coap_context_t.
struct coap_endpoint_t * next
coap_address_t bind_addr
local interface address
coap_proto_t proto
protocol used on this interface
Iterator to run through PDU options.
coap_addr_tuple_t addr_info
local and remote addresses
int ifindex
the interface index
structure for CoAP PDUs
size_t max_size
maximum size for token, options and payload, or zero for variable size pdu
coap_pdu_code_t code
request method (value 1–31) or response code (value 64-255)
coap_bin_const_t actual_token
Actual token in pdu.
size_t used_size
used bytes of storage for token, options and payload
Abstraction of resource that can be attached to coap_context_t.
coap_method_handler_t handler[7]
Used to store handlers for the seven coap methods GET, POST, PUT, DELETE, FETCH, PATCH and IPATCH.
unsigned int observable
can be observed
Abstraction of virtual session that can be attached to coap_context_t (client) or coap_endpoint_t (se...
coap_proto_t proto
protocol used
coap_context_t * context
session's context
CoAP string data definition with const data.
Definition coap_str.h:46
const uint8_t * s
read-only string data
Definition coap_str.h:48
size_t length
length of string
Definition coap_str.h:47
CoAP string data definition.
Definition coap_str.h:38
uint8_t * s
string data
Definition coap_str.h:40
Number of notifications that may be sent non-confirmable before a confirmable message is sent to dete...