1 /* resource.c -- generic resource handling
2 *
3 * Copyright (C) 2010--2015 Olaf Bergmann <bergmann@tzi.org>
4 *
5 * This file is part of the CoAP library libcoap. Please see
6 * README for terms of use.
7 */
8
9 #include "coap_config.h"
10 #include "coap.h"
11 #include "debug.h"
12 #include "mem.h"
13 #include "net.h"
14 #include "resource.h"
15 #include "subscribe.h"
16 #include "utlist.h"
17
18 #ifdef WITH_LWIP
19 /* mem.h is only needed for the string free calls for
20 * COAP_ATTR_FLAGS_RELEASE_NAME / COAP_ATTR_FLAGS_RELEASE_VALUE /
21 * COAP_RESOURCE_FLAGS_RELEASE_URI. not sure what those lines should actually
22 * do on lwip. */
23
24 #include <lwip/memp.h>
25
26 #define COAP_MALLOC_TYPE(Type) \
27 ((coap_##Type##_t *)memp_malloc(MEMP_COAP_##Type))
28 #define COAP_FREE_TYPE(Type, Object) memp_free(MEMP_COAP_##Type, Object)
29
30 #endif
31
32 #ifdef WITH_POSIX
33
34 #define COAP_MALLOC_TYPE(Type) \
35 ((coap_##Type##_t *)coap_malloc(sizeof(coap_##Type##_t)))
36 #define COAP_FREE_TYPE(Type, Object) coap_free(Object)
37
38 #endif /* WITH_POSIX */
39 #ifdef WITH_CONTIKI
40 #include "memb.h"
41
42 #define COAP_MALLOC_TYPE(Type) \
43 ((coap_##Type##_t *)memb_alloc(&(Type##_storage)))
44 #define COAP_FREE_TYPE(Type, Object) memb_free(&(Type##_storage), (Object))
45
46 MEMB(subscription_storage, coap_subscription_t, COAP_MAX_SUBSCRIBERS);
47
48 void
coap_resources_init()49 coap_resources_init() {
50 memb_init(&subscription_storage);
51 }
52
53 static inline coap_subscription_t *
coap_malloc_subscription()54 coap_malloc_subscription() {
55 return memb_alloc(&subscription_storage);
56 }
57
58 static inline void
coap_free_subscription(coap_subscription_t * subscription)59 coap_free_subscription(coap_subscription_t *subscription) {
60 memb_free(&subscription_storage, subscription);
61 }
62
63 #endif /* WITH_CONTIKI */
64
65 #define min(a,b) ((a) < (b) ? (a) : (b))
66
67 /* Helper functions for conditional output of character sequences into
68 * a given buffer. The first Offset characters are skipped.
69 */
70
71 /**
72 * Adds Char to Buf if Offset is zero. Otherwise, Char is not written
73 * and Offset is decremented.
74 */
75 #define PRINT_WITH_OFFSET(Buf,Offset,Char) \
76 if ((Offset) == 0) { \
77 (*(Buf)++) = (Char); \
78 } else { \
79 (Offset)--; \
80 } \
81
82 /**
83 * Adds Char to Buf if Offset is zero and Buf is less than Bufend.
84 */
85 #define PRINT_COND_WITH_OFFSET(Buf,Bufend,Offset,Char,Result) { \
86 if ((Buf) < (Bufend)) { \
87 PRINT_WITH_OFFSET(Buf,Offset,Char); \
88 } \
89 (Result)++; \
90 }
91
92 /**
93 * Copies at most Length characters of Str to Buf. The first Offset
94 * characters are skipped. Output may be truncated to Bufend - Buf
95 * characters.
96 */
97 #define COPY_COND_WITH_OFFSET(Buf,Bufend,Offset,Str,Length,Result) { \
98 size_t i; \
99 for (i = 0; i < (Length); i++) { \
100 PRINT_COND_WITH_OFFSET((Buf), (Bufend), (Offset), (Str)[i], (Result)); \
101 } \
102 }
103
104 static int
match(const str * text,const str * pattern,int match_prefix,int match_substring)105 match(const str *text, const str *pattern, int match_prefix, int match_substring) {
106 assert(text); assert(pattern);
107
108 if (text->length < pattern->length)
109 return 0;
110
111 if (match_substring) {
112 unsigned char *next_token = text->s;
113 size_t remaining_length = text->length;
114 while (remaining_length) {
115 size_t token_length;
116 unsigned char *token = next_token;
117 next_token = memchr(token, ' ', remaining_length);
118
119 if (next_token) {
120 token_length = next_token - token;
121 remaining_length -= (token_length + 1);
122 next_token++;
123 } else {
124 token_length = remaining_length;
125 remaining_length = 0;
126 }
127
128 if ((match_prefix || pattern->length == token_length) &&
129 memcmp(token, pattern->s, pattern->length) == 0)
130 return 1;
131 }
132 return 0;
133 }
134
135 return (match_prefix || pattern->length == text->length) &&
136 memcmp(text->s, pattern->s, pattern->length) == 0;
137 }
138
139 /**
140 * Prints the names of all known resources to @p buf. This function
141 * sets @p buflen to the number of bytes actually written and returns
142 * @c 1 on succes. On error, the value in @p buflen is undefined and
143 * the return value will be @c 0.
144 *
145 * @param context The context with the resource map.
146 * @param buf The buffer to write the result.
147 * @param buflen Must be initialized to the maximum length of @p buf and will be
148 * set to the length of the well-known response on return.
149 * @param offset The offset in bytes where the output shall start and is
150 * shifted accordingly with the characters that have been
151 * processed. This parameter is used to support the block
152 * option.
153 * @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>
154 *
155 * @return COAP_PRINT_STATUS_ERROR on error. Otherwise, the lower 28 bits are
156 * set to the number of bytes that have actually been written to
157 * @p buf. COAP_PRINT_STATUS_TRUNC is set when the output has been
158 * truncated.
159 */
160 #if defined(__GNUC__) && defined(WITHOUT_QUERY_FILTER)
161 coap_print_status_t
coap_print_wellknown(coap_context_t * context,unsigned char * buf,size_t * buflen,size_t offset,coap_opt_t * query_filter)162 coap_print_wellknown(coap_context_t *context, unsigned char *buf, size_t *buflen,
163 size_t offset,
164 coap_opt_t *query_filter __attribute__ ((unused))) {
165 #else /* not a GCC */
166 coap_print_status_t
167 coap_print_wellknown(coap_context_t *context, unsigned char *buf, size_t *buflen,
168 size_t offset, coap_opt_t *query_filter) {
169 #endif /* GCC */
170 unsigned char *p = buf;
171 const unsigned char *bufend = buf + *buflen;
172 size_t left, written = 0;
173 coap_print_status_t result;
174 const size_t old_offset = offset;
175 int subsequent_resource = 0;
176 #ifndef WITHOUT_QUERY_FILTER
177 str resource_param = { 0, NULL }, query_pattern = { 0, NULL };
178 int flags = 0; /* MATCH_SUBSTRING, MATCH_PREFIX, MATCH_URI */
179 #define MATCH_URI 0x01
180 #define MATCH_PREFIX 0x02
181 #define MATCH_SUBSTRING 0x04
182 static const str _rt_attributes[] = {
183 {2, (unsigned char *)"rt"},
184 {2, (unsigned char *)"if"},
185 {3, (unsigned char *)"rel"},
186 {0, NULL}};
187 #endif /* WITHOUT_QUERY_FILTER */
188
189 #ifndef WITHOUT_QUERY_FILTER
190 /* split query filter, if any */
191 if (query_filter) {
192 resource_param.s = COAP_OPT_VALUE(query_filter);
193 while (resource_param.length < COAP_OPT_LENGTH(query_filter)
194 && resource_param.s[resource_param.length] != '=')
195 resource_param.length++;
196
197 if (resource_param.length < COAP_OPT_LENGTH(query_filter)) {
198 const str *rt_attributes;
199 if (resource_param.length == 4 &&
200 memcmp(resource_param.s, "href", 4) == 0)
201 flags |= MATCH_URI;
202
203 for (rt_attributes = _rt_attributes; rt_attributes->s; rt_attributes++) {
204 if (resource_param.length == rt_attributes->length &&
205 memcmp(resource_param.s, rt_attributes->s, rt_attributes->length) == 0) {
206 flags |= MATCH_SUBSTRING;
207 break;
208 }
209 }
210
211 /* rest is query-pattern */
212 query_pattern.s =
213 COAP_OPT_VALUE(query_filter) + resource_param.length + 1;
214
215 assert((resource_param.length + 1) <= COAP_OPT_LENGTH(query_filter));
216 query_pattern.length =
217 COAP_OPT_LENGTH(query_filter) - (resource_param.length + 1);
218
219 if ((query_pattern.s[0] == '/') && ((flags & MATCH_URI) == MATCH_URI)) {
220 query_pattern.s++;
221 query_pattern.length--;
222 }
223
224 if (query_pattern.length &&
225 query_pattern.s[query_pattern.length-1] == '*') {
226 query_pattern.length--;
227 flags |= MATCH_PREFIX;
228 }
229 }
230 }
231 #endif /* WITHOUT_QUERY_FILTER */
232
233 RESOURCES_ITER(context->resources, r) {
234
235 #ifndef WITHOUT_QUERY_FILTER
236 if (resource_param.length) { /* there is a query filter */
237
238 if (flags & MATCH_URI) { /* match resource URI */
239 if (!match(&r->uri, &query_pattern, (flags & MATCH_PREFIX) != 0, (flags & MATCH_SUBSTRING) != 0))
240 continue;
241 } else { /* match attribute */
242 coap_attr_t *attr;
243 str unquoted_val;
244 attr = coap_find_attr(r, resource_param.s, resource_param.length);
245 if (!attr) continue;
246 if (attr->value.s[0] == '"') { /* if attribute has a quoted value, remove double quotes */
247 unquoted_val.length = attr->value.length - 2;
248 unquoted_val.s = attr->value.s + 1;
249 } else {
250 unquoted_val = attr->value;
251 }
252 if (!(match(&unquoted_val, &query_pattern,
253 (flags & MATCH_PREFIX) != 0,
254 (flags & MATCH_SUBSTRING) != 0)))
255 continue;
256 }
257 }
258 #endif /* WITHOUT_QUERY_FILTER */
259
260 if (!subsequent_resource) { /* this is the first resource */
261 subsequent_resource = 1;
262 } else {
263 PRINT_COND_WITH_OFFSET(p, bufend, offset, ',', written);
264 }
265
266 left = bufend - p; /* calculate available space */
267 result = coap_print_link(r, p, &left, &offset);
268
269 if (result & COAP_PRINT_STATUS_ERROR) {
270 break;
271 }
272
273 /* coap_print_link() returns the number of characters that
274 * where actually written to p. Now advance to its end. */
275 p += COAP_PRINT_OUTPUT_LENGTH(result);
276 written += left;
277 }
278
279 *buflen = written;
280 result = p - buf;
281 if (result + old_offset - offset < *buflen) {
282 result |= COAP_PRINT_STATUS_TRUNC;
283 }
284 return result;
285 }
286
287 coap_resource_t *
288 coap_resource_init(const unsigned char *uri, size_t len, int flags) {
289 coap_resource_t *r;
290
291 #ifdef WITH_LWIP
292 r = (coap_resource_t *)memp_malloc(MEMP_COAP_RESOURCE);
293 #endif
294 #ifndef WITH_LWIP
295 r = (coap_resource_t *)coap_malloc_type(COAP_RESOURCE, sizeof(coap_resource_t));
296 #endif
297 if (r) {
298 memset(r, 0, sizeof(coap_resource_t));
299
300 r->uri.s = (unsigned char *)uri;
301 r->uri.length = len;
302
303 coap_hash_path(r->uri.s, r->uri.length, r->key);
304
305 r->flags = flags;
306 } else {
307 debug("coap_resource_init: no memory left\n");
308 }
309
310 return r;
311 }
312
313 coap_attr_t *
314 coap_add_attr(coap_resource_t *resource,
315 const unsigned char *name, size_t nlen,
316 const unsigned char *val, size_t vlen,
317 int flags) {
318 coap_attr_t *attr;
319
320 if (!resource || !name)
321 return NULL;
322
323 #ifdef WITH_LWIP
324 attr = (coap_attr_t *)memp_malloc(MEMP_COAP_RESOURCEATTR);
325 #endif
326 #ifndef WITH_LWIP
327 attr = (coap_attr_t *)coap_malloc_type(COAP_RESOURCEATTR, sizeof(coap_attr_t));
328 #endif
329
330 if (attr) {
331 attr->name.length = nlen;
332 attr->value.length = val ? vlen : 0;
333
334 attr->name.s = (unsigned char *)name;
335 attr->value.s = (unsigned char *)val;
336
337 attr->flags = flags;
338
339 /* add attribute to resource list */
340 LL_PREPEND(resource->link_attr, attr);
341 } else {
342 debug("coap_add_attr: no memory left\n");
343 }
344
345 return attr;
346 }
347
348 coap_attr_t *
349 coap_find_attr(coap_resource_t *resource,
350 const unsigned char *name, size_t nlen) {
351 coap_attr_t *attr;
352
353 if (!resource || !name)
354 return NULL;
355
356 LL_FOREACH(resource->link_attr, attr) {
357 if (attr->name.length == nlen &&
358 memcmp(attr->name.s, name, nlen) == 0)
359 return attr;
360 }
361
362 return NULL;
363 }
364
365 void
366 coap_delete_attr(coap_attr_t *attr) {
367 if (!attr)
368 return;
369 if (attr->flags & COAP_ATTR_FLAGS_RELEASE_NAME)
370 coap_free(attr->name.s);
371 if (attr->flags & COAP_ATTR_FLAGS_RELEASE_VALUE)
372 coap_free(attr->value.s);
373
374 #ifdef WITH_LWIP
375 memp_free(MEMP_COAP_RESOURCEATTR, attr);
376 #endif
377 #ifndef WITH_LWIP
378 coap_free_type(COAP_RESOURCEATTR, attr);
379 #endif
380 }
381
382 void
383 coap_hash_request_uri(const coap_pdu_t *request, coap_key_t key) {
384 coap_opt_iterator_t opt_iter;
385 coap_opt_filter_t filter;
386 coap_opt_t *option;
387
388 memset(key, 0, sizeof(coap_key_t));
389
390 coap_option_filter_clear(filter);
391 coap_option_setb(filter, COAP_OPTION_URI_PATH);
392
393 coap_option_iterator_init((coap_pdu_t *)request, &opt_iter, filter);
394 while ((option = coap_option_next(&opt_iter)))
395 coap_hash(COAP_OPT_VALUE(option), COAP_OPT_LENGTH(option), key);
396 }
397
398 void
399 coap_add_resource(coap_context_t *context, coap_resource_t *resource) {
400 RESOURCES_ADD(context->resources, resource);
401 }
402
403 static void
404 coap_free_resource(coap_resource_t *resource) {
405 coap_attr_t *attr, *tmp;
406 coap_subscription_t *obs, *otmp;
407
408 assert(resource);
409
410 /* delete registered attributes */
411 LL_FOREACH_SAFE(resource->link_attr, attr, tmp) coap_delete_attr(attr);
412
413 if (resource->flags & COAP_RESOURCE_FLAGS_RELEASE_URI)
414 coap_free(resource->uri.s);
415
416 /* free all elements from resource->subscribers */
417 LL_FOREACH_SAFE(resource->subscribers, obs, otmp) COAP_FREE_TYPE(subscription, obs);
418
419 #ifdef WITH_LWIP
420 memp_free(MEMP_COAP_RESOURCE, resource);
421 #endif
422 #ifndef WITH_LWIP
423 coap_free_type(COAP_RESOURCE, resource);
424 #endif /* WITH_CONTIKI */
425 }
426
427 int
428 coap_delete_resource(coap_context_t *context, coap_key_t key) {
429 coap_resource_t *resource;
430
431 if (!context)
432 return 0;
433
434 resource = coap_get_resource_from_key(context, key);
435
436 if (!resource)
437 return 0;
438
439 /* remove resource from list */
440 RESOURCES_DELETE(context->resources, resource);
441
442 /* and free its allocated memory */
443 coap_free_resource(resource);
444
445 return 1;
446 }
447
448 void
449 coap_delete_all_resources(coap_context_t *context) {
450 coap_resource_t *res;
451 coap_resource_t *rtmp;
452
453 /* Cannot call RESOURCES_ITER because coap_free_resource() releases
454 * the allocated storage. */
455
456 #ifdef COAP_RESOURCES_NOHASH
457 LL_FOREACH_SAFE(context->resources, res, rtmp) {
458 #else
459 HASH_ITER(hh, context->resources, res, rtmp) {
460 #endif
461 coap_free_resource(res);
462 }
463
464 context->resources = NULL;
465 }
466
467 coap_resource_t *
468 coap_get_resource_from_key(coap_context_t *context, coap_key_t key) {
469 coap_resource_t *result;
470
471 RESOURCES_FIND(context->resources, key, result);
472
473 return result;
474 }
475
476 coap_print_status_t
477 coap_print_link(const coap_resource_t *resource,
478 unsigned char *buf, size_t *len, size_t *offset) {
479 unsigned char *p = buf;
480 const unsigned char *bufend = buf + *len;
481 coap_attr_t *attr;
482 coap_print_status_t result = 0;
483 const size_t old_offset = *offset;
484
485 *len = 0;
486 PRINT_COND_WITH_OFFSET(p, bufend, *offset, '<', *len);
487 PRINT_COND_WITH_OFFSET(p, bufend, *offset, '/', *len);
488
489 COPY_COND_WITH_OFFSET(p, bufend, *offset,
490 resource->uri.s, resource->uri.length, *len);
491
492 PRINT_COND_WITH_OFFSET(p, bufend, *offset, '>', *len);
493
494 LL_FOREACH(resource->link_attr, attr) {
495
496 PRINT_COND_WITH_OFFSET(p, bufend, *offset, ';', *len);
497
498 COPY_COND_WITH_OFFSET(p, bufend, *offset,
499 attr->name.s, attr->name.length, *len);
500
501 if (attr->value.s) {
502 PRINT_COND_WITH_OFFSET(p, bufend, *offset, '=', *len);
503
504 COPY_COND_WITH_OFFSET(p, bufend, *offset,
505 attr->value.s, attr->value.length, *len);
506 }
507
508 }
509 if (resource->observable) {
510 COPY_COND_WITH_OFFSET(p, bufend, *offset, ";obs", 4, *len);
511 }
512
513 result = p - buf;
514 if (result + old_offset - *offset < *len) {
515 result |= COAP_PRINT_STATUS_TRUNC;
516 }
517
518 return result;
519 }
520
521 #ifndef WITHOUT_OBSERVE
522 coap_subscription_t *
523 coap_find_observer(coap_resource_t *resource, const coap_address_t *peer,
524 const str *token) {
525 coap_subscription_t *s;
526
527 assert(resource);
528 assert(peer);
529
530 LL_FOREACH(resource->subscribers, s) {
531 if (coap_address_equals(&s->subscriber, peer)
532 && (!token || (token->length == s->token_length
533 && memcmp(token->s, s->token, token->length) == 0)))
534 return s;
535 }
536
537 return NULL;
538 }
539
540 coap_subscription_t *
541 coap_add_observer(coap_resource_t *resource,
542 const coap_endpoint_t *local_interface,
543 const coap_address_t *observer,
544 const str *token) {
545 coap_subscription_t *s;
546
547 assert(observer);
548
549 /* Check if there is already a subscription for this peer. */
550 s = coap_find_observer(resource, observer, token);
551
552 /* We are done if subscription was found. */
553 if (s)
554 return s;
555
556 /* s points to a different subscription, so we have to create
557 * another one. */
558 s = COAP_MALLOC_TYPE(subscription);
559
560 if (!s)
561 return NULL;
562
563 coap_subscription_init(s);
564 s->local_if = *local_interface;
565 memcpy(&s->subscriber, observer, sizeof(coap_address_t));
566
567 if (token && token->length) {
568 s->token_length = token->length;
569 memcpy(s->token, token->s, min(s->token_length, 8));
570 }
571
572 /* add subscriber to resource */
573 LL_PREPEND(resource->subscribers, s);
574
575 return s;
576 }
577
578 void
579 coap_touch_observer(coap_context_t *context, const coap_address_t *observer,
580 const str *token) {
581 coap_subscription_t *s;
582
583 RESOURCES_ITER(context->resources, r) {
584 s = coap_find_observer(r, observer, token);
585 if (s) {
586 s->fail_cnt = 0;
587 }
588 }
589 }
590
591 int
592 coap_delete_observer(coap_resource_t *resource, const coap_address_t *observer,
593 const str *token) {
594 coap_subscription_t *s;
595
596 s = coap_find_observer(resource, observer, token);
597
598 if (resource->subscribers && s) {
599 LL_DELETE(resource->subscribers, s);
600
601 COAP_FREE_TYPE(subscription,s);
602 }
603
604 return s != NULL;
605 }
606
607 static void
608 coap_notify_observers(coap_context_t *context, coap_resource_t *r) {
609 coap_method_handler_t h;
610 coap_subscription_t *obs;
611 str token;
612 coap_pdu_t *response;
613
614 if (r->observable && (r->dirty || r->partiallydirty)) {
615 r->partiallydirty = 0;
616
617 /* retrieve GET handler, prepare response */
618 h = r->handler[COAP_REQUEST_GET - 1];
619 assert(h); /* we do not allow subscriptions if no
620 * GET handler is defined */
621
622 LL_FOREACH(r->subscribers, obs) {
623 if (r->dirty == 0 && obs->dirty == 0)
624 /* running this resource due to partiallydirty, but this observation's notification was already enqueued */
625 continue;
626
627 coap_tid_t tid = COAP_INVALID_TID;
628 obs->dirty = 0;
629 /* initialize response */
630 response = coap_pdu_init(COAP_MESSAGE_CON, 0, 0, COAP_MAX_PDU_SIZE);
631 if (!response) {
632 obs->dirty = 1;
633 r->partiallydirty = 1;
634 debug("coap_check_notify: pdu init failed, resource stays partially dirty\n");
635 continue;
636 }
637
638 if (!coap_add_token(response, obs->token_length, obs->token)) {
639 obs->dirty = 1;
640 r->partiallydirty = 1;
641 debug("coap_check_notify: cannot add token, resource stays partially dirty\n");
642 coap_delete_pdu(response);
643 continue;
644 }
645
646 token.length = obs->token_length;
647 token.s = obs->token;
648
649 response->hdr->id = coap_new_message_id(context);
650 if ((r->flags & COAP_RESOURCE_FLAGS_NOTIFY_CON) == 0
651 && obs->non_cnt < COAP_OBS_MAX_NON) {
652 response->hdr->type = COAP_MESSAGE_NON;
653 } else {
654 response->hdr->type = COAP_MESSAGE_CON;
655 }
656 /* fill with observer-specific data */
657 h(context, r, &obs->local_if, &obs->subscriber, NULL, &token, response);
658
659 /* TODO: do not send response and remove observer when
660 * COAP_RESPONSE_CLASS(response->hdr->code) > 2
661 */
662 if (response->hdr->type == COAP_MESSAGE_CON) {
663 tid = coap_send_confirmed(context, &obs->local_if, &obs->subscriber, response);
664 obs->non_cnt = 0;
665 } else {
666 tid = coap_send(context, &obs->local_if, &obs->subscriber, response);
667 obs->non_cnt++;
668 }
669
670 if (COAP_INVALID_TID == tid || response->hdr->type != COAP_MESSAGE_CON)
671 coap_delete_pdu(response);
672 if (COAP_INVALID_TID == tid)
673 {
674 debug("coap_check_notify: sending failed, resource stays partially dirty\n");
675 obs->dirty = 1;
676 r->partiallydirty = 1;
677 }
678
679 }
680
681 /* Increment value for next Observe use. */
682 context->observe++;
683 }
684 r->dirty = 0;
685 }
686
687 void
688 coap_check_notify(coap_context_t *context) {
689
690 RESOURCES_ITER(context->resources, r) {
691 coap_notify_observers(context, r);
692 }
693 }
694
695 /**
696 * Checks the failure counter for (peer, token) and removes peer from
697 * the list of observers for the given resource when COAP_OBS_MAX_FAIL
698 * is reached.
699 *
700 * @param context The CoAP context to use
701 * @param resource The resource to check for (peer, token)
702 * @param peer The observer's address
703 * @param token The token that has been used for subscription.
704 */
705 static void
706 coap_remove_failed_observers(coap_context_t *context,
707 coap_resource_t *resource,
708 const coap_address_t *peer,
709 const str *token) {
710 coap_subscription_t *obs, *otmp;
711
712 LL_FOREACH_SAFE(resource->subscribers, obs, otmp) {
713 if (coap_address_equals(peer, &obs->subscriber) &&
714 token->length == obs->token_length &&
715 memcmp(token->s, obs->token, token->length) == 0) {
716
717 /* count failed notifies and remove when
718 * COAP_MAX_FAILED_NOTIFY is reached */
719 if (obs->fail_cnt < COAP_OBS_MAX_FAIL)
720 obs->fail_cnt++;
721 else {
722 LL_DELETE(resource->subscribers, obs);
723 obs->fail_cnt = 0;
724
725 #ifndef NDEBUG
726 if (LOG_DEBUG <= coap_get_log_level()) {
727 #ifndef INET6_ADDRSTRLEN
728 #define INET6_ADDRSTRLEN 40
729 #endif
730 unsigned char addr[INET6_ADDRSTRLEN+8];
731
732 if (coap_print_addr(&obs->subscriber, addr, INET6_ADDRSTRLEN+8))
733 debug("** removed observer %s\n", addr);
734 }
735 #endif
736 coap_cancel_all_messages(context, &obs->subscriber,
737 obs->token, obs->token_length);
738
739 COAP_FREE_TYPE(subscription, obs);
740 }
741 }
742 break; /* break loop if observer was found */
743 }
744 }
745
746 void
747 coap_handle_failed_notify(coap_context_t *context,
748 const coap_address_t *peer,
749 const str *token) {
750
751 RESOURCES_ITER(context->resources, r) {
752 coap_remove_failed_observers(context, r, peer, token);
753 }
754 }
755 #endif /* WITHOUT_NOTIFY */
756