diff --git a/examples/cord_lc/Makefile.ci b/examples/cord_lc/Makefile.ci index 5021256da2..1cf6f33ed8 100644 --- a/examples/cord_lc/Makefile.ci +++ b/examples/cord_lc/Makefile.ci @@ -9,9 +9,9 @@ BOARD_INSUFFICIENT_MEMORY := \ atmega8 \ atxmega-a3bu-xplained \ bluepill-stm32f030c8 \ - chronos \ + derfmega128 \ i-nucleo-lrwan1 \ - im880b \ + microduino-corerf \ msb-430 \ msb-430h \ nucleo-c031c6 \ @@ -26,6 +26,8 @@ BOARD_INSUFFICIENT_MEMORY := \ olimex-msp430-h1611 \ olimex-msp430-h2618 \ samd10-xmini \ + saml10-xpro \ + saml11-xpro \ slstk3400a \ stk3200 \ stm32f030f4-demo \ @@ -37,4 +39,5 @@ BOARD_INSUFFICIENT_MEMORY := \ waspmote-pro \ weact-g030f6 \ z1 \ + zigduino \ # diff --git a/examples/gcoap/Makefile b/examples/gcoap/Makefile index a1d273d700..9ab3fe3317 100644 --- a/examples/gcoap/Makefile +++ b/examples/gcoap/Makefile @@ -54,6 +54,9 @@ USEMODULE += shell_cmds_default USEMODULE += uri_parser USEMODULE += ps +# /rtc resource for regular observe notifications +FEATURES_OPTIONAL += periph_rtc + # Comment this out to disable code in RIOT that does safety checking # which is not needed in a production environment but helps in the # development process: @@ -81,6 +84,13 @@ DOCKER_ENV_VARS += ZEP_PORT_BASE include $(RIOTBASE)/Makefile.include +ifneq (,$(filter periph_rtc,$(USEMODULE))) + USEMODULE += event_periodic + USEMODULE += event_periodic_callback + USEMODULE += event_thread + USEMODULE += ztimer_msec +endif + # For now this goes after the inclusion of Makefile.include so Kconfig symbols # are available. Only set configuration via CFLAGS if Kconfig is not being used # for this module. diff --git a/examples/gcoap/Makefile.ci b/examples/gcoap/Makefile.ci index 30cafac089..6b820df3b0 100644 --- a/examples/gcoap/Makefile.ci +++ b/examples/gcoap/Makefile.ci @@ -8,7 +8,9 @@ BOARD_INSUFFICIENT_MEMORY := \ atmega328p-xplained-mini \ atmega8 \ atxmega-a3bu-xplained \ + blackpill-stm32f103c8 \ bluepill-stm32f030c8 \ + bluepill-stm32f103c8 \ i-nucleo-lrwan1 \ msb-430 \ msb-430h \ @@ -16,6 +18,7 @@ BOARD_INSUFFICIENT_MEMORY := \ nucleo-f030r8 \ nucleo-f031k6 \ nucleo-f042k6 \ + nucleo-f302r8 \ nucleo-f303k8 \ nucleo-f334r8 \ nucleo-l011k4 \ diff --git a/examples/gcoap/client.c b/examples/gcoap/client.c index acf670697a..a2b31ff040 100644 --- a/examples/gcoap/client.c +++ b/examples/gcoap/client.c @@ -28,6 +28,7 @@ #include #include "net/gcoap.h" +#include "net/sock/udp.h" #include "net/sock/util.h" #include "od.h" #include "uri_parser.h" @@ -53,8 +54,8 @@ static char _proxy_uri[CONFIG_URI_MAX]; * completes or times out. */ static char _last_req_uri[CONFIG_URI_MAX]; -/* whether this node is currently observing a resource as a client */ -static bool observing = false; +/* Last remote endpoint where an Observe request has been sent to */ +static sock_udp_ep_t obs_remote; /* the token used for observing a remote resource */ static uint8_t obs_req_token[GCOAP_TOKENLEN_MAX]; @@ -190,6 +191,8 @@ static int _print_usage(char **argv) printf(" %s proxy unset\n", argv[0]); printf("Options\n"); printf(" -c Send confirmably (defaults to non-confirmable)\n"); + printf(" -o include Observe registration option\n"); + printf(" -d include Observe deregistration option\n"); return 1; } @@ -300,17 +303,10 @@ int gcoap_cli_cmd(int argc, char **argv) if (code_pos == COAP_METHOD_GET) { if (argc > apos) { if (strcmp(argv[apos], "-o") == 0) { - if (observing) { - puts("Only one observe supported"); - return 1; - } observe = true; apos++; - } else if (strcmp(argv[apos], "-d") == 0) { - if (!observing) { - puts("Not observing"); - return 1; - } + } + else if (strcmp(argv[apos], "-d") == 0) { observe = true; apos++; obs_value = COAP_OBS_DEREGISTER; @@ -333,21 +329,6 @@ int gcoap_cli_cmd(int argc, char **argv) gcoap_req_init(&pdu, buf, CONFIG_GCOAP_PDU_BUF_SIZE, code_pos, NULL); if (observe) { - uint8_t *token = coap_get_token(&pdu); - if (obs_value == COAP_OBS_REGISTER) { - obs_req_tkl = coap_get_token_len(&pdu); - /* backup the token of the initial observe registration */ - memcpy(obs_req_token, token, obs_req_tkl); - } else { - /* use the token of the registration for deregistration - * (manually replace the token set by gcoap_req_init) */ - memcpy(token, obs_req_token, obs_req_tkl); - if (gcoap_obs_req_forget(&remote, obs_req_token, obs_req_tkl)) { - printf("could not remove observe request\n"); - return 1; - } - } - coap_opt_add_uint(&pdu, COAP_OPT_OBSERVE, obs_value); } @@ -399,9 +380,14 @@ int gcoap_cli_cmd(int argc, char **argv) } else { if (observe) { - /* on successful observe request, store that this node is - * observing / not observing anymore */ - observing = obs_value == COAP_OBS_REGISTER; + /* forget last Observe token, as only one can be stored in this example */ + gcoap_obs_req_forget(&obs_remote, obs_req_token, obs_req_tkl); + if (obs_value == COAP_OBS_REGISTER) { + obs_req_tkl = coap_get_token_len(&pdu); + /* backup the token of the initial observe registration */ + memcpy(obs_req_token, coap_get_token(&pdu), obs_req_tkl); + obs_remote = remote; + } } /* send Observe notification for /cli/stats */ notify_observers(); diff --git a/examples/gcoap/server.c b/examples/gcoap/server.c index bf2315cd01..b91ff5e419 100644 --- a/examples/gcoap/server.c +++ b/examples/gcoap/server.c @@ -23,11 +23,16 @@ #include #include #include +#include +#include "event/periodic_callback.h" +#include "event/thread.h" #include "fmt.h" #include "net/gcoap.h" #include "net/utils.h" #include "od.h" +#include "periph/rtc.h" +#include "time_units.h" #include "gcoap_example.h" @@ -60,11 +65,17 @@ static ssize_t _encode_link(const coap_resource_t *resource, char *buf, size_t maxlen, coap_link_encoder_ctx_t *context); static ssize_t _stats_handler(coap_pkt_t* pdu, uint8_t *buf, size_t len, coap_request_ctx_t *ctx); static ssize_t _riot_board_handler(coap_pkt_t* pdu, uint8_t *buf, size_t len, coap_request_ctx_t *ctx); +#if IS_USED(MODULE_PERIPH_RTC) +static ssize_t _rtc_handler(coap_pkt_t* pdu, uint8_t *buf, size_t len, coap_request_ctx_t *ctx); +#endif /* CoAP resources. Must be sorted by path (ASCII order). */ static const coap_resource_t _resources[] = { { "/cli/stats", COAP_GET | COAP_PUT, _stats_handler, NULL }, { "/riot/board", COAP_GET, _riot_board_handler, NULL }, +#if IS_USED(MODULE_PERIPH_RTC) + { "/rtc", COAP_GET, _rtc_handler, NULL }, +#endif }; static const char *_link_params[] = { @@ -100,6 +111,62 @@ static ssize_t _encode_link(const coap_resource_t *resource, char *buf, return res; } +#if IS_USED(MODULE_PERIPH_RTC) +static void _rtc_notify_observers(void *arg) +{ + (void)arg; + struct tm tm_now; + if (rtc_get_time(&tm_now)) { + DEBUG_PUTS("gcoap_server: RTC error"); + return; + } + size_t len; + char str_time[20] = ""; + uint8_t buf[sizeof(coap_hdr_t) + COAP_TOKEN_LENGTH_MAX + 1 + sizeof(str_time)]; + coap_pkt_t pdu; + const coap_resource_t *rtc_resource = NULL; + const gcoap_listener_t *listener = NULL; + while ((rtc_resource = gcoap_get_resource_by_path_iterator(&listener, rtc_resource, "/rtc"))) { + if (!strcmp(rtc_resource->path, "/rtc")) { + break; /* exact match */ + } + } + if (rtc_resource) { + switch (gcoap_obs_init(&pdu, buf, sizeof(buf), rtc_resource)) { + case GCOAP_OBS_INIT_OK: + len = coap_opt_finish(&pdu, COAP_OPT_FINISH_PAYLOAD); + memcpy(pdu.payload, str_time, strftime(str_time, sizeof(str_time), "%Y-%m-%d %H:%M:%S", &tm_now)); + pdu.payload_len = strlen(str_time); + len += pdu.payload_len; + if (!gcoap_obs_send(buf, len, rtc_resource)) { + DEBUG_PUTS("gcoap_server: cannot send /rtc notification"); + } + break; + case GCOAP_OBS_INIT_UNUSED: + DEBUG_PUTS("gcoap_server: no observer for /rtc"); + break; + case GCOAP_OBS_INIT_ERR: + DEBUG_PUTS("gcoap_server: error initializing /rtc notification"); + break; + } + } +} + +static ssize_t _rtc_handler(coap_pkt_t* pdu, uint8_t *buf, size_t len, coap_request_ctx_t *ctx) +{ + (void)ctx; + struct tm tm_now; + rtc_get_time(&tm_now); + gcoap_resp_init(pdu, buf, len, COAP_CODE_CONTENT); + size_t resp_len = coap_opt_finish(pdu, COAP_OPT_FINISH_PAYLOAD); + char str_time[20] = ""; + memcpy(pdu->payload, str_time, strftime(str_time, sizeof(str_time), "%Y-%m-%d %H:%M:%S", &tm_now)); + pdu->payload_len = strlen(str_time); + resp_len += pdu->payload_len; + return resp_len; +} +#endif + /* * Server callback for /cli/stats. Accepts either a GET or a PUT. * @@ -202,4 +269,9 @@ void server_init(void) #endif gcoap_register_listener(&_listener); +#if IS_USED(MODULE_PERIPH_RTC) + static event_periodic_callback_t _ev_pcb_rtc; + event_periodic_callback_init(&_ev_pcb_rtc, ZTIMER_MSEC, EVENT_PRIO_MEDIUM, _rtc_notify_observers, NULL); + event_periodic_callback_start(&_ev_pcb_rtc, 10 * MS_PER_SEC); +#endif } diff --git a/sys/include/net/gcoap.h b/sys/include/net/gcoap.h index 264da5a188..2dd8de9869 100644 --- a/sys/include/net/gcoap.h +++ b/sys/include/net/gcoap.h @@ -552,14 +552,35 @@ extern "C" { /** * @ingroup net_gcoap_conf * @brief Maximum number of Observe clients + * + * @note As documented in this file, the implementation is limited to one observer per resource. + * Therefore, every stored observer is associated with a different resource. + * If you have only one observable resource, you could set this value to 1. */ #ifndef CONFIG_GCOAP_OBS_CLIENTS_MAX #define CONFIG_GCOAP_OBS_CLIENTS_MAX (2) #endif +/** + * @ingroup net_gcoap_conf + * @brief Maximum number of local notifying endpoint addresses + * + * @note As documented in this file, the implementation is limited to one observer per resource. + * Therefore, every stored local endpoint alias is associated with an observation context + * of a different resource. + * If you have only one observable resource, you could set this value to 1. + */ +#ifndef CONFIG_GCOAP_OBS_NOTIFIERS_MAX +#define CONFIG_GCOAP_OBS_NOTIFIERS_MAX (2) +#endif + /** * @ingroup net_gcoap_conf * @brief Maximum number of registrations for Observable resources + * + * @note As documented in this file, the implementation is limited to one observer per resource. + * Therefore, every stored observation context is associated with a different resource. + * If you have only one observable resource, you could set this value to 1. */ #ifndef CONFIG_GCOAP_OBS_REGISTRATIONS_MAX #define CONFIG_GCOAP_OBS_REGISTRATIONS_MAX (2) @@ -839,6 +860,7 @@ struct gcoap_request_memo { */ typedef struct { sock_udp_ep_t *observer; /**< Client endpoint; unused if null */ + sock_udp_ep_t *notifier; /**< Local endpoint to send notifications */ const coap_resource_t *resource; /**< Entity being observed */ uint8_t token[GCOAP_TOKENLEN_MAX]; /**< Client token for notifications */ uint16_t last_msgid; /**< Message ID of last notification */ @@ -874,6 +896,22 @@ kernel_pid_t gcoap_init(void); */ void gcoap_register_listener(gcoap_listener_t *listener); +/** + * @brief Iterate through all registered listeners and check for a resource, matching by @p uri_path + * + * This functions returns resources matching a subpath @see COAP_MATCH_SUBTREE. + * If an exact match is required, check with `strncmp()`. + * + * @param[in, out] last_listener A pointer to NULL for the first call, otherwise the last returned listener + * @param[in] last_resource NULL for the first call, otherwise the last returned resource + * @param[in] uri_path The URI path to search for + * + * @return The resource that matches the URI path + */ +const coap_resource_t *gcoap_get_resource_by_path_iterator(const gcoap_listener_t **last_listener, + const coap_resource_t *last_resource, + const char *uri_path); + /** * @brief Initializes a CoAP request PDU on a buffer. * @@ -1055,6 +1093,9 @@ static inline ssize_t gcoap_response(coap_pkt_t *pdu, uint8_t *buf, * * First verifies that an observer has been registered for the resource. * + * @post If this function returns @see GCOAP_OBS_INIT_OK you have to call + * @ref gcoap_obs_send() afterwards to release a mutex. + * * @param[out] pdu Notification metadata * @param[out] buf Buffer containing the PDU * @param[in] len Length of the buffer diff --git a/sys/include/net/nanocoap.h b/sys/include/net/nanocoap.h index 044ea13433..d99857e99d 100644 --- a/sys/include/net/nanocoap.h +++ b/sys/include/net/nanocoap.h @@ -2245,7 +2245,7 @@ extern ssize_t coap_well_known_core_default_handler(coap_pkt_t *pkt, \ * @return <0 if the resource path sorts before the URI * @return >0 if the resource path sorts after the URI */ -int coap_match_path(const coap_resource_t *resource, uint8_t *uri); +int coap_match_path(const coap_resource_t *resource, const uint8_t *uri); #if defined(MODULE_GCOAP) || defined(DOXYGEN) /** diff --git a/sys/net/application_layer/gcoap/gcoap.c b/sys/net/application_layer/gcoap/gcoap.c index 62e8545d16..a40cbe83b1 100644 --- a/sys/net/application_layer/gcoap/gcoap.c +++ b/sys/net/application_layer/gcoap/gcoap.c @@ -29,8 +29,11 @@ #include "net/coap.h" #include "net/gcoap.h" #include "net/gcoap/forward_proxy.h" +#include "net/ipv6/addr.h" +#include "net/nanocoap.h" #include "net/nanocoap/cache.h" #include "net/sock/async/event.h" +#include "net/sock/udp.h" #include "net/sock/util.h" #include "mutex.h" #include "random.h" @@ -79,8 +82,10 @@ static int _find_resource(gcoap_socket_type_t tl_type, const coap_resource_t **resource_ptr, gcoap_listener_t **listener_ptr); static int _find_observer(sock_udp_ep_t **observer, sock_udp_ep_t *remote); -static int _find_obs_memo(gcoap_observe_memo_t **memo, sock_udp_ep_t *remote, - coap_pkt_t *pdu); +static int _find_notifier(sock_udp_ep_t **notifier, sock_udp_ep_t *local); +static int _find_obs_memo(gcoap_observe_memo_t **memo, + sock_udp_ep_t *remote, sock_udp_ep_t *local, + coap_pkt_t *pdu); static void _find_obs_memo_resource(gcoap_observe_memo_t **memo, const coap_resource_t *resource); @@ -103,6 +108,8 @@ static void _on_sock_dtls_evt(sock_dtls_t *sock, sock_async_flags_t type, void * static void _dtls_free_up_session(void *arg); #endif +static char _ipv6_addr_str[IPV6_ADDR_MAX_STR_LEN]; + /* Internal variables */ const coap_resource_t _default_resources[] = { { "/.well-known/core", COAP_GET, _well_known_core_handler, NULL }, @@ -129,6 +136,10 @@ typedef struct { sock_udp_ep_t observers[CONFIG_GCOAP_OBS_CLIENTS_MAX]; /* Observe clients; allows reuse for observe memos */ + /** + * @brief Local endpoint aliases to send notifications from + */ + sock_udp_ep_t notifiers[CONFIG_GCOAP_OBS_NOTIFIERS_MAX]; gcoap_observe_memo_t observe_memos[CONFIG_GCOAP_OBS_REGISTRATIONS_MAX]; /* Observed resource registrations */ uint8_t resend_bufs[CONFIG_GCOAP_RESEND_BUFS_MAX][CONFIG_GCOAP_PDU_BUF_SIZE]; @@ -377,6 +388,22 @@ static void _on_sock_udp_evt(sock_udp_t *sock, sock_async_flags_t type, void *ar } } +static void _memo_clear_resend_buffer(gcoap_request_memo_t *memo) +{ + uint8_t hdr[GCOAP_HEADER_MAXLEN]; + if (memo->send_limit >= 0 && memo->msg.data.pdu_buf) { + /* store header from retransmission buffer */ + memcpy(hdr, memo->msg.data.pdu_buf, sizeof(hdr)); + /* mark referenced retransmission buffer as available again */ + *memo->msg.data.pdu_buf = 0; + /* but store the header to keep the token for Observe notifications */ + memcpy(memo->msg.hdr_buf, hdr, sizeof(hdr)); + /* no further retransmissions should be made and + gcoap_request_memo_get_hdr() has to know how to get the header for Observe notifications */ + memo->send_limit = GCOAP_SEND_LIMIT_NON; + } +} + /* Processes and evaluates the coap pdu */ static void _process_coap_pdu(gcoap_socket_t *sock, sock_udp_ep_t *remote, sock_udp_aux_tx_t *aux, uint8_t *buf, size_t len, bool truncated) @@ -518,9 +545,7 @@ static void _process_coap_pdu(gcoap_socket_t *sock, sock_udp_ep_t *remote, sock_ memo->resp_handler(memo, &pdu, remote); } - if (memo->send_limit >= 0) { /* if confirmable */ - *memo->msg.data.pdu_buf = 0; /* clear resend PDU buffer */ - } + _memo_clear_resend_buffer(memo); /* The memo must be kept if the response is an observe notification. * Non-2.xx notifications indicate that the associated observe entry @@ -657,6 +682,7 @@ static size_t _handle_req(gcoap_socket_t *sock, coap_pkt_t *pdu, uint8_t *buf, const coap_resource_t *resource = NULL; gcoap_listener_t *listener = NULL; sock_udp_ep_t *observer = NULL; + sock_udp_ep_t *notifier = NULL; gcoap_observe_memo_t *memo = NULL; gcoap_observe_memo_t *resource_memo = NULL; @@ -677,7 +703,7 @@ static size_t _handle_req(gcoap_socket_t *sock, coap_pkt_t *pdu, uint8_t *buf, if (coap_get_observe(pdu) == COAP_OBS_REGISTER) { /* lookup remote+token */ - int empty_slot = _find_obs_memo(&memo, remote, pdu); + int empty_slot = _find_obs_memo(&memo, remote, NULL, pdu); /* validate re-registration request */ if (resource_memo != NULL) { if (memo != NULL) { @@ -699,18 +725,28 @@ static size_t _handle_req(gcoap_socket_t *sock, coap_pkt_t *pdu, uint8_t *buf, if ((memo == NULL) && coap_has_observe(pdu)) { /* verify resource not already registered (for another endpoint) */ if ((empty_slot >= 0) && (resource_memo == NULL)) { - int obs_slot = _find_observer(&observer, remote); + int slot = _find_observer(&observer, remote); /* cache new observer */ if (observer == NULL) { - if (obs_slot >= 0) { - observer = &_coap_state.observers[obs_slot]; - memcpy(observer, remote, sizeof(sock_udp_ep_t)); + if (slot >= 0) { + observer = &_coap_state.observers[slot]; } else { DEBUG("gcoap: can't register observer\n"); } } - if (observer != NULL) { + slot = _find_notifier(¬ifier, &aux->local); + if (notifier == NULL) { + if (slot >= 0) { + notifier = &_coap_state.notifiers[slot]; + } else { + DEBUG("gcoap: can't allocate notifier\n"); + } + } + if (observer && notifier) { + memcpy(observer, remote, sizeof(*remote)); + memcpy(notifier, &aux->local, sizeof(aux->local)); memo = &_coap_state.observe_memos[empty_slot]; + memo->notifier = notifier; memo->observer = observer; } } @@ -732,19 +768,25 @@ static size_t _handle_req(gcoap_socket_t *sock, coap_pkt_t *pdu, uint8_t *buf, } } else if (coap_get_observe(pdu) == COAP_OBS_DEREGISTER) { - _find_obs_memo(&memo, remote, pdu); + _find_obs_memo(&memo, remote, NULL, pdu); /* clear memo, and clear observer if no other memos */ if (memo != NULL) { DEBUG("gcoap: Deregistering observer for: %s\n", memo->resource->path); memo->observer = NULL; - memo = NULL; - _find_obs_memo(&memo, remote, NULL); - if (memo == NULL) { + gcoap_observe_memo_t *other_memo = NULL; + _find_obs_memo(&other_memo, remote, NULL, NULL); + if (other_memo == NULL) { _find_observer(&observer, remote); if (observer != NULL) { observer->family = AF_UNSPEC; } } + other_memo = NULL; + _find_obs_memo(&other_memo, NULL, memo->notifier, NULL); + if (!other_memo) { + memo->notifier->family = AF_UNSPEC; + } + memo->notifier = NULL; } coap_clear_observe(pdu); @@ -771,6 +813,28 @@ static size_t _handle_req(gcoap_socket_t *sock, coap_pkt_t *pdu, uint8_t *buf, return pdu_len; } +static const coap_resource_t *_match_resource_path_iterator(const gcoap_listener_t *listener, + const coap_resource_t *last, + const uint8_t *uri_path) +{ + size_t i; + if (!last) { + i = 0; + } + else { + assert(listener); + assert(last >= listener->resources); + assert(last < listener->resources + listener->resources_len); + i = (last - listener->resources) + 1; + } + for (; i < listener->resources_len; i++) { + if (!coap_match_path(&listener->resources[i], uri_path)) { + return &listener->resources[i]; + } + } + return NULL; +} + static int _request_matcher_default(gcoap_listener_t *listener, const coap_resource_t **resource, coap_pkt_t *pdu) @@ -788,18 +852,10 @@ static int _request_matcher_default(gcoap_listener_t *listener, coap_method_flags_t method_flag = coap_method2flag( coap_get_code_detail(pdu)); - for (size_t i = 0; i < listener->resources_len; i++) { - *resource = &listener->resources[i]; - - int res = coap_match_path(*resource, uri); - - /* URI mismatch */ - if (res != 0) { - continue; - } - + *resource = NULL; + while ((*resource = _match_resource_path_iterator(listener, *resource, uri))) { /* potential match, check for method */ - if (! ((*resource)->methods & method_flag)) { + if (!((*resource)->methods & method_flag)) { /* record wrong method error for next iteration, in case * another resource with the same URI and correct method * exists */ @@ -837,7 +893,7 @@ static int _find_resource(gcoap_socket_type_t tl_type, gcoap_listener_t *listener = _coap_state.listeners; while (listener) { - const coap_resource_t *resource; + const coap_resource_t *resource = NULL; int res; /* only makes sense to check if non-UDP transports are supported, @@ -887,7 +943,7 @@ static int _find_resource(gcoap_socket_type_t tl_type, * return Registered request memo, or NULL if not found */ static gcoap_request_memo_t* _find_req_memo_by_token(const sock_udp_ep_t *remote, - const uint8_t *token, size_t tkl) + const uint8_t *token, size_t tkl) { /* no need to initialize struct; we only care about buffer contents below */ coap_pkt_t memo_pdu_data; @@ -901,15 +957,36 @@ static gcoap_request_memo_t* _find_req_memo_by_token(const sock_udp_ep_t *remote gcoap_request_memo_t *memo = &_coap_state.open_reqs[i]; memo_pdu->hdr = gcoap_request_memo_get_hdr(memo); - if (coap_get_token_len(memo_pdu) == tkl) { - if ((memcmp(token, coap_get_token(memo_pdu), tkl) == 0) - && (sock_udp_ep_equal(&memo->remote_ep, remote) - /* Multicast addresses are not considered in matching responses */ - || sock_udp_ep_is_multicast(&memo->remote_ep) - )) { - return memo; + /* verbose debug to catch bugs with request/response matching */ + DEBUG("Seeking memo for remote=%s, tkn=0x%02x%02x%02x%02x%02x%02x%02x%02x, tkl=%"PRIuSIZE"\n", + ipv6_addr_to_str(_ipv6_addr_str, (ipv6_addr_t *)&remote->addr.ipv6, + IPV6_ADDR_MAX_STR_LEN), + token[0], token[1], token[2], token[3], token[4], token[5], token[6], token[7], + tkl); + + if (coap_get_token_len(memo_pdu) != tkl) { + DEBUG("Token length mismatch %u\n", coap_get_token_len(memo_pdu)); + continue; + } + const uint8_t *memo_token = coap_get_token(memo_pdu); + if (memcmp(token, memo_token, tkl)) { + DEBUG("Token mismatch 0x%02x%02x%02x%02x%02x%02x%02x%02x\n", + memo_token[0], memo_token[1], memo_token[2], memo_token[3], + memo_token[4], memo_token[5], memo_token[6], memo_token[7]); + continue; + } + if (!sock_udp_ep_equal(&memo->remote_ep, remote)) { + if (sock_udp_ep_is_multicast(&memo->remote_ep)) { + DEBUG("matching multicast response\n"); + } + else { + DEBUG("Remote address mismatch %s\n", + ipv6_addr_to_str(_ipv6_addr_str, (ipv6_addr_t *)&memo->remote_ep.addr.ipv6, + IPV6_ADDR_MAX_STR_LEN)); + continue; } } + return memo; } return NULL; } @@ -972,9 +1049,7 @@ static void _expire_request(gcoap_request_memo_t *memo) req.hdr = gcoap_request_memo_get_hdr(memo); memo->resp_handler(memo, &req, NULL); } - if (memo->send_limit != GCOAP_SEND_LIMIT_NON) { - *memo->msg.data.pdu_buf = 0; /* clear resend buffer */ - } + _memo_clear_resend_buffer(memo); memo->state = GCOAP_MEMO_UNUSED; } else { @@ -1003,57 +1078,78 @@ static ssize_t _well_known_core_handler(coap_pkt_t* pdu, uint8_t *buf, size_t le } /* - * Find registered observer for a remote address and port. + * Find registered observer or notification endpoint for a remote or local address and port. * - * observer[out] -- Registered observer, or NULL if not found - * remote[in] -- Endpoint to match + * out[in,out] -- in: endpoint array to scan, out: found endpoint or NULL if not found + * in[in] -- Endpoint to match * - * return Index of empty slot, suitable for registering new observer; or -1 + * return Index of empty slot, suitable for registering new endpoint; or -1 * if no empty slots. Undefined if observer found. */ -static int _find_observer(sock_udp_ep_t **observer, sock_udp_ep_t *remote) +static int _find_endpoint(sock_udp_ep_t **out, sock_udp_ep_t *in, unsigned max) { int empty_slot = -1; - *observer = NULL; - for (unsigned i = 0; i < CONFIG_GCOAP_OBS_CLIENTS_MAX; i++) { + sock_udp_ep_t *ep_array = *out; + *out = NULL; + for (unsigned i = 0; i < max; i++) { - if (_coap_state.observers[i].family == AF_UNSPEC) { + if (ep_array[i].family == AF_UNSPEC) { empty_slot = i; } - else if (sock_udp_ep_equal(&_coap_state.observers[i], remote)) { - *observer = &_coap_state.observers[i]; + else if (sock_udp_ep_equal(&ep_array[i], in)) { + *out = &ep_array[i]; break; } } return empty_slot; } +static int _find_observer(sock_udp_ep_t **observer, sock_udp_ep_t *remote) +{ + *observer = _coap_state.observers; + return _find_endpoint(observer, remote, CONFIG_GCOAP_OBS_CLIENTS_MAX); +} + +static int _find_notifier(sock_udp_ep_t **notifier, sock_udp_ep_t *local) +{ + *notifier = _coap_state.notifiers; + return _find_endpoint(notifier, local, CONFIG_GCOAP_OBS_NOTIFIERS_MAX); +} + /* * Find registered observe memo for a remote address and token. * * memo[out] -- Registered observe memo, or NULL if not found - * remote[in] -- Endpoint for address to match + * remote[in] -- Remote endpoint for address to match if not NULL + * local[in] -- Local endpoint for address to match if not NULL * pdu[in] -- PDU for token to match, or NULL to match only on remote address * * return Index of empty slot, suitable for registering new memo; or -1 if no * empty slots. Undefined if memo found. */ -static int _find_obs_memo(gcoap_observe_memo_t **memo, sock_udp_ep_t *remote, - coap_pkt_t *pdu) +static int _find_obs_memo(gcoap_observe_memo_t **memo, + sock_udp_ep_t *remote, sock_udp_ep_t *local, + coap_pkt_t *pdu) { int empty_slot = -1; *memo = NULL; sock_udp_ep_t *remote_observer = NULL; - _find_observer(&remote_observer, remote); - + sock_udp_ep_t *local_notifier = NULL; + if (remote) { + _find_observer(&remote_observer, remote); + } + if (local) { + _find_notifier(&local_notifier, local); + } for (unsigned i = 0; i < CONFIG_GCOAP_OBS_REGISTRATIONS_MAX; i++) { if (_coap_state.observe_memos[i].observer == NULL) { empty_slot = i; continue; } - if (_coap_state.observe_memos[i].observer == remote_observer) { + if ((_coap_state.observe_memos[i].observer == remote_observer || !remote_observer) && + (_coap_state.observe_memos[i].notifier == local_notifier || !local_notifier)) { if (pdu == NULL) { *memo = &_coap_state.observe_memos[i]; break; @@ -1105,10 +1201,19 @@ static void _check_and_expire_obs_memo_last_mid(sock_udp_ep_t *remote, if (stale_obs_memo) { stale_obs_memo->observer = NULL; /* clear memo */ + /* check if no other memo is referencing the same local endpoint ... */ + gcoap_observe_memo_t *other_memo = NULL; + _find_obs_memo(&other_memo, NULL, stale_obs_memo->notifier, NULL); + if (!other_memo) { + /* ... if not -> also free the notifier entry */ + stale_obs_memo->notifier->family = AF_UNSPEC; + } + /* then unreference notifier */ + stale_obs_memo->notifier = NULL; /* check if the observer has more observe memos registered... */ stale_obs_memo = NULL; - _find_obs_memo(&stale_obs_memo, observer, NULL); + _find_obs_memo(&stale_obs_memo, observer, NULL, NULL); if (stale_obs_memo == NULL) { /* ... if not -> also free the observer entry */ observer->family = AF_UNSPEC; @@ -1357,9 +1462,7 @@ static void _receive_from_cache_cb(void *ctx) if (_cache_build_response(ce, &pdu, _listen_buf, sizeof(_listen_buf)) >= 0) { memo->state = (ce->truncated) ? GCOAP_MEMO_RESP_TRUNC : GCOAP_MEMO_RESP; memo->resp_handler(memo, &pdu, &memo->remote_ep); - if (memo->send_limit >= 0) { /* if confirmable */ - *memo->msg.data.pdu_buf = 0; /* clear resend PDU buffer */ - } + _memo_clear_resend_buffer(memo); memo->state = GCOAP_MEMO_UNUSED; } } @@ -1553,6 +1656,24 @@ void gcoap_register_listener(gcoap_listener_t *listener) } } +const coap_resource_t *gcoap_get_resource_by_path_iterator(const gcoap_listener_t **last_listener, + const coap_resource_t *last_resource, + const char *uri_path) +{ + const gcoap_listener_t *listener = *last_listener ? *last_listener : _coap_state.listeners; + const coap_resource_t *resource = last_resource; + + while (listener) { + if ((resource = _match_resource_path_iterator(listener, resource, (const uint8_t *)uri_path))) { + *last_listener = listener; + return resource; + } + listener = listener->next; + } + + return NULL; +} + int gcoap_req_init_path_buffer(coap_pkt_t *pdu, uint8_t *buf, size_t len, unsigned code, const char *path, size_t path_len) { @@ -1765,9 +1886,7 @@ ssize_t gcoap_req_send(const uint8_t *buf, size_t len, } if (res <= 0) { if (memo != NULL) { - if (msg_type == COAP_TYPE_CON) { - *memo->msg.data.pdu_buf = 0; /* clear resend buffer */ - } + _memo_clear_resend_buffer(memo); if (timeout > 0) { event_timeout_clear(&memo->resp_evt_tmout); } @@ -1811,9 +1930,11 @@ int gcoap_obs_init(coap_pkt_t *pdu, uint8_t *buf, size_t len, { gcoap_observe_memo_t *memo = NULL; + mutex_lock(&_coap_state.lock); _find_obs_memo_resource(&memo, resource); if (memo == NULL) { /* Unique return value to specify there is not an observer */ + mutex_unlock(&_coap_state.lock); return GCOAP_OBS_INIT_UNUSED; } @@ -1822,36 +1943,40 @@ int gcoap_obs_init(coap_pkt_t *pdu, uint8_t *buf, size_t len, ssize_t hdrlen = coap_build_hdr(pdu->hdr, COAP_TYPE_NON, &memo->token[0], memo->token_len, COAP_CODE_CONTENT, msgid); - if (hdrlen > 0) { - coap_pkt_init(pdu, buf, len, hdrlen); - - _add_generated_observe_option(pdu); - /* Store message ID of the last notification sent. This is needed - * to match a potential RST returned by a client in order to signal - * it does not recognize this notification. */ - memo->last_msgid = msgid; - - return GCOAP_OBS_INIT_OK; - } - else { + if (hdrlen <= 0) { /* reason for negative hdrlen is not defined, so we also are vague */ + mutex_unlock(&_coap_state.lock); return GCOAP_OBS_INIT_ERR; } + + coap_pkt_init(pdu, buf, len, hdrlen); + + _add_generated_observe_option(pdu); + /* Store message ID of the last notification sent. This is needed + * to match a potential RST returned by a client in order to signal + * it does not recognize this notification. */ + memo->last_msgid = msgid; + + return GCOAP_OBS_INIT_OK; } size_t gcoap_obs_send(const uint8_t *buf, size_t len, const coap_resource_t *resource) { + ssize_t ret = 0; gcoap_observe_memo_t *memo = NULL; _find_obs_memo_resource(&memo, resource); if (memo) { - ssize_t bytes = _tl_send(&memo->socket, buf, len, memo->observer, NULL); - return (size_t)((bytes > 0) ? bytes : 0); - } - else { - return 0; + sock_udp_aux_tx_t aux = { 0 }; + if (memo->notifier) { + memcpy(&aux.local, memo->notifier, sizeof(*memo->notifier)); + aux.flags = SOCK_AUX_SET_LOCAL; + } + ret = _tl_send(&memo->socket, buf, len, memo->observer, &aux); } + mutex_unlock(&_coap_state.lock); + return ret <= 0 ? 0 : (size_t)ret; } uint8_t gcoap_op_state(void) diff --git a/sys/net/application_layer/nanocoap/nanocoap.c b/sys/net/application_layer/nanocoap/nanocoap.c index 08af915cd0..e6c3bb3498 100644 --- a/sys/net/application_layer/nanocoap/nanocoap.c +++ b/sys/net/application_layer/nanocoap/nanocoap.c @@ -184,7 +184,7 @@ int coap_parse(coap_pkt_t *pkt, uint8_t *buf, size_t len) return 0; } -int coap_match_path(const coap_resource_t *resource, uint8_t *uri) +int coap_match_path(const coap_resource_t *resource, const uint8_t *uri) { assert(resource && uri); int res;