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