diff --git a/examples/nanocoap_server/Makefile b/examples/nanocoap_server/Makefile index 9f8b91f201b8..a4efa1e1919a 100644 --- a/examples/nanocoap_server/Makefile +++ b/examples/nanocoap_server/Makefile @@ -47,6 +47,13 @@ endif HIGH_MEMORY_BOARDS := native same54-xpro mcb2388 ifneq (,$(filter $(BOARD),$(HIGH_MEMORY_BOARDS))) + # enable separate response + USEMODULE += nanocoap_server_separate + USEMODULE += event_callback + USEMODULE += event_thread + USEMODULE += event_timeout_ztimer + + # enable fileserver USEMODULE += nanocoap_fileserver USEMODULE += vfs_default diff --git a/examples/nanocoap_server/coap_handler.c b/examples/nanocoap_server/coap_handler.c index 2a04452b9fdf..f4c2516c2aa1 100644 --- a/examples/nanocoap_server/coap_handler.c +++ b/examples/nanocoap_server/coap_handler.c @@ -10,8 +10,12 @@ #include #include +#include "event/callback.h" +#include "event/timeout.h" +#include "event/thread.h" #include "fmt.h" #include "net/nanocoap.h" +#include "net/nanocoap_sock.h" #include "hashes/sha256.h" #include "kernel_defines.h" @@ -182,6 +186,45 @@ NANOCOAP_RESOURCE(sha256) { .path = "/sha256", .methods = COAP_POST, .handler = _sha256_handler }; +/* separate response requires an event thread to execute it */ +#ifdef MODULE_EVENT_THREAD +static nanocoap_server_response_ctx_t _separate_ctx; + +static void _send_response(void *ctx) +{ + const char response[] = "This is a delayed response."; + + puts("_separate_handler(): send delayed response"); + nanocoap_server_send_separate(ctx, COAP_CODE_CONTENT, COAP_TYPE_NON, + response, sizeof(response)); +} + +static ssize_t _separate_handler(coap_pkt_t *pkt, uint8_t *buf, size_t len, coap_request_ctx_t *context) +{ + static event_timeout_t event_timeout; + static event_callback_t event_timed = EVENT_CALLBACK_INIT(_send_response, &_separate_ctx); + + if (event_timeout_is_pending(&event_timeout)) { + puts("_separate_handler(): response already scheduled"); + return coap_build_reply(pkt, COAP_CODE_SERVICE_UNAVAILABLE, buf, len, 0); + } + + puts("_separate_handler(): send ACK, schedule response"); + + nanocoap_server_prepare_separate(&_separate_ctx, pkt, context); + + event_timeout_ztimer_init(&event_timeout, ZTIMER_MSEC, EVENT_PRIO_MEDIUM, + &event_timed.super); + event_timeout_set(&event_timeout, 1 * MS_PER_SEC); + + return coap_build_empty_ack(pkt, (void *)buf); +} + +NANOCOAP_RESOURCE(separate) { + .path = "/separate", .methods = COAP_GET, .handler = _separate_handler, +}; +#endif /* MODULE_EVENT_THREAD */ + /* we can also include the fileserver module */ #ifdef MODULE_NANOCOAP_FILESERVER #include "net/nanocoap/fileserver.h" diff --git a/sys/Makefile.dep b/sys/Makefile.dep index 06765ece83b2..f51e39a7c2a6 100644 --- a/sys/Makefile.dep +++ b/sys/Makefile.dep @@ -544,6 +544,11 @@ ifneq (,$(filter nanocoap_server_auto_init,$(USEMODULE))) USEMODULE += nanocoap_server endif +ifneq (,$(filter nanocoap_server_separate,$(USEMODULE))) + USEMODULE += nanocoap_server + USEMODULE += sock_aux_local +endif + ifneq (,$(filter nanocoap_server,$(USEMODULE))) USEMODULE += nanocoap_resources USEMODULE += nanocoap_sock diff --git a/sys/include/event/timeout.h b/sys/include/event/timeout.h index 3f7b9b7f4695..7885bb19b0be 100644 --- a/sys/include/event/timeout.h +++ b/sys/include/event/timeout.h @@ -115,6 +115,11 @@ void event_timeout_clear(event_timeout_t *event_timeout); */ static inline bool event_timeout_is_pending(const event_timeout_t *event_timeout) { + if (event_timeout->clock == NULL || event_timeout->queue == NULL || + event_timeout->event == NULL) { + return false; + } + return ztimer_is_set(event_timeout->clock, &event_timeout->timer) || event_is_queued(event_timeout->queue, event_timeout->event); } diff --git a/sys/include/net/nanocoap.h b/sys/include/net/nanocoap.h index 3655fd7bd2a9..eb899dfd6451 100644 --- a/sys/include/net/nanocoap.h +++ b/sys/include/net/nanocoap.h @@ -338,6 +338,9 @@ void coap_request_ctx_init(coap_request_ctx_t *ctx, sock_udp_ep_t *remote); struct _coap_request_ctx { const coap_resource_t *resource; /**< resource of the request */ sock_udp_ep_t *remote; /**< remote endpoint of the request */ +#if defined(MODULE_SOCK_AUX_LOCAL) || DOXYGEN + sock_udp_ep_t *local; /**< local endpoint of the request */ +#endif #if defined(MODULE_GCOAP) || DOXYGEN /** * @brief transport the packet was received over @@ -1922,7 +1925,7 @@ ssize_t coap_block2_build_reply(coap_pkt_t *pkt, unsigned code, * * @returns length of resulting header */ -ssize_t coap_build_hdr(coap_hdr_t *hdr, unsigned type, uint8_t *token, +ssize_t coap_build_hdr(coap_hdr_t *hdr, unsigned type, const void *token, size_t token_len, unsigned code, uint16_t id); /** @@ -1956,6 +1959,22 @@ ssize_t coap_build_hdr(coap_hdr_t *hdr, unsigned type, uint8_t *token, ssize_t coap_build_reply(coap_pkt_t *pkt, unsigned code, uint8_t *rbuf, unsigned rlen, unsigned payload_len); +/** + * @brief Build empty reply to CoAP request + * + * This function can be used to create an empty ACK so that a later, separate + * response can be sent independently. + * + * If the request was non-confirmable, this will generate nothing. + * + * @param[in] pkt packet to reply to + * @param[out] ack buffer to write reply to + * + * @returns size of reply packet on success + * @returns -ENOSPC if @p rbuf too small + */ +ssize_t coap_build_empty_ack(coap_pkt_t *pkt, coap_hdr_t *ack); + /** * @brief Handle incoming CoAP request * diff --git a/sys/include/net/nanocoap_sock.h b/sys/include/net/nanocoap_sock.h index d1e668cd20c4..5995cc8538f7 100644 --- a/sys/include/net/nanocoap_sock.h +++ b/sys/include/net/nanocoap_sock.h @@ -209,6 +209,55 @@ typedef struct { uint8_t blksize; /**< CoAP blocksize exponent */ } coap_block_request_t; +/** + * @brief Context from CoAP request for separate response + */ +typedef struct { + sock_udp_ep_t remote; /**< remote to send response to */ +#if defined(MODULE_SOCK_AUX_LOCAL) || DOXYGEN + sock_udp_ep_t local; /**< local from which to send response */ +#endif + uint8_t token[COAP_TOKEN_LENGTH_MAX]; /**< request token */ + uint8_t tkl; /**< request token length */ + uint8_t no_response; /**< no-response bitmap */ +} nanocoap_server_response_ctx_t; + +/** + * @brief Prepare the context for a separate response + * + * This function serializes the CoAP request information so that + * a separate response can be generated outside the CoAP handler. + * + * The CoAP handler should then respond with an empty ACK by calling + * @ref coap_build_empty_ack + * + * @param[out] ctx Context information for separate response + * @param[in] pkt CoAP packet to which the response will be generated + * @param[in] req Context of the CoAP request + */ +void nanocoap_server_prepare_separate(nanocoap_server_response_ctx_t *ctx, + coap_pkt_t *pkt, const coap_request_ctx_t *req); + +/** + * @brief Send a separate response to a CoAP request + * + * This sends a response to a CoAP request outside the CoAP handler + * + * @pre @ref nanocoap_server_prepare_separate has been called on @p ctx + * inside the CoAP handler + * + * @param[in] ctx Context information for the CoAP response + * @param[in] code CoAP response code + * @param[in] type Response type, may be `COAP_TYPE_NON` + * @param[in] payload Response payload + * @param[in] len Payload length + * + * @returns 0 on success + * negative error (see @ref sock_udp_sendv_aux) + */ +int nanocoap_server_send_separate(const nanocoap_server_response_ctx_t *ctx, + unsigned code, unsigned type, + const void *payload, size_t len); /** * @brief Get next consecutive message ID for use when building a new * CoAP request. diff --git a/sys/net/application_layer/nanocoap/nanocoap.c b/sys/net/application_layer/nanocoap/nanocoap.c index 86ea74019f8e..85ac77bb5174 100644 --- a/sys/net/application_layer/nanocoap/nanocoap.c +++ b/sys/net/application_layer/nanocoap/nanocoap.c @@ -626,6 +626,18 @@ ssize_t coap_reply_simple(coap_pkt_t *pkt, return header_len + payload_len; } +ssize_t coap_build_empty_ack(coap_pkt_t *pkt, coap_hdr_t *ack) +{ + if (coap_get_type(pkt) != COAP_TYPE_CON) { + return 0; + } + + coap_build_hdr(ack, COAP_TYPE_ACK, NULL, 0, + COAP_CODE_EMPTY, ntohs(pkt->hdr->id)); + + return sizeof(*ack); +} + ssize_t coap_build_reply(coap_pkt_t *pkt, unsigned code, uint8_t *rbuf, unsigned rlen, unsigned payload_len) { @@ -675,15 +687,12 @@ ssize_t coap_build_reply(coap_pkt_t *pkt, unsigned code, coap_build_hdr((coap_hdr_t *)rbuf, type, coap_get_token(pkt), tkl, code, ntohs(pkt->hdr->id)); - coap_hdr_set_type((coap_hdr_t *)rbuf, type); - coap_hdr_set_code((coap_hdr_t *)rbuf, code); - len += payload_len; return len; } -ssize_t coap_build_hdr(coap_hdr_t *hdr, unsigned type, uint8_t *token, +ssize_t coap_build_hdr(coap_hdr_t *hdr, unsigned type, const void *token, size_t token_len, unsigned code, uint16_t id) { assert(!(type & ~0x3)); diff --git a/sys/net/application_layer/nanocoap/sock.c b/sys/net/application_layer/nanocoap/sock.c index 892c977498e6..572839dd0fdb 100644 --- a/sys/net/application_layer/nanocoap/sock.c +++ b/sys/net/application_layer/nanocoap/sock.c @@ -789,7 +789,7 @@ ssize_t nanocoap_get_blockwise_url_to_buf(const char *url, int nanocoap_server(sock_udp_ep_t *local, uint8_t *buf, size_t bufsize) { - nanocoap_sock_t sock; + sock_udp_t sock; sock_udp_ep_t remote; coap_request_ctx_t ctx = { .remote = &remote, @@ -799,7 +799,7 @@ int nanocoap_server(sock_udp_ep_t *local, uint8_t *buf, size_t bufsize) local->port = COAP_PORT; } - ssize_t res = sock_udp_create(&sock.udp, local, NULL, 0); + ssize_t res = sock_udp_create(&sock, local, NULL, 0); if (res != 0) { return -1; } @@ -814,7 +814,7 @@ int nanocoap_server(sock_udp_ep_t *local, uint8_t *buf, size_t bufsize) aux_in_ptr = &aux_in; #endif - res = sock_udp_recv_aux(&sock.udp, buf, bufsize, SOCK_NO_TIMEOUT, + res = sock_udp_recv_aux(&sock, buf, bufsize, SOCK_NO_TIMEOUT, &remote, aux_in_ptr); if (res <= 0) { DEBUG("nanocoap: error receiving UDP packet %" PRIdSIZE "\n", res); @@ -825,10 +825,6 @@ int nanocoap_server(sock_udp_ep_t *local, uint8_t *buf, size_t bufsize) DEBUG("nanocoap: error parsing packet\n"); continue; } - if ((res = coap_handle_req(&pkt, buf, bufsize, &ctx)) <= 0) { - DEBUG("nanocoap: error handling request %" PRIdSIZE "\n", res); - continue; - } sock_udp_aux_tx_t *aux_out_ptr = NULL; #ifdef MODULE_SOCK_AUX_LOCAL @@ -841,8 +837,14 @@ int nanocoap_server(sock_udp_ep_t *local, uint8_t *buf, size_t bufsize) if (!sock_udp_ep_is_multicast(&aux_in.local)) { aux_out_ptr = &aux_out; } + ctx.local = &aux_in.local; #endif - sock_udp_send_aux(&sock.udp, buf, res, &remote, aux_out_ptr); + if ((res = coap_handle_req(&pkt, buf, bufsize, &ctx)) <= 0) { + DEBUG("nanocoap: error handling request %" PRIdSIZE "\n", res); + continue; + } + + sock_udp_send_aux(&sock, buf, res, &remote, aux_out_ptr); } return 0; @@ -880,3 +882,66 @@ void auto_init_nanocoap_server(void) nanocoap_server_start(&local); } + +void nanocoap_server_prepare_separate(nanocoap_server_response_ctx_t *ctx, + coap_pkt_t *pkt, const coap_request_ctx_t *req) +{ + ctx->tkl = coap_get_token_len(pkt); + memcpy(ctx->token, coap_get_token(pkt), ctx->tkl); + memcpy(&ctx->remote, req->remote, sizeof(ctx->remote)); +#ifdef MODULE_SOCK_AUX_LOCAL + assert(req->local); + memcpy(&ctx->local, req->local, sizeof(ctx->local)); +#endif + uint32_t no_response = 0; + coap_opt_get_uint(pkt, COAP_OPT_NO_RESPONSE, &no_response); + ctx->no_response = no_response; +} + +int nanocoap_server_send_separate(const nanocoap_server_response_ctx_t *ctx, + unsigned code, unsigned type, + const void *payload, size_t len) +{ + uint8_t rbuf[sizeof(coap_hdr_t) + COAP_TOKEN_LENGTH_MAX + 1]; + assert(type != COAP_TYPE_ACK); + assert(type != COAP_TYPE_CON); /* TODO: add support */ + + const uint8_t no_response_index = (code >> 5) - 1; + /* If the handler code misbehaved here, we'd face UB otherwise */ + assert(no_response_index < 7); + + const uint8_t mask = 1 << no_response_index; + if (ctx->no_response & mask) { + return 0; + } + + iolist_t data = { + .iol_base = (void *)payload, + .iol_len = len, + }; + + iolist_t head = { + .iol_next = &data, + .iol_base = rbuf, + }; + head.iol_len = coap_build_hdr((coap_hdr_t *)rbuf, type, + ctx->token, ctx->tkl, + code, random_uint32()); + if (len) { + rbuf[head.iol_len++] = 0xFF; + } + + sock_udp_aux_tx_t *aux_out_ptr = NULL; +#ifdef MODULE_SOCK_AUX_LOCAL + /* make sure we reply with the same address that the request was + * destined for -- except in the multicast case */ + sock_udp_aux_tx_t aux_out = { + .flags = SOCK_AUX_SET_LOCAL, + .local = ctx->local, + }; + if (!sock_udp_ep_is_multicast(&ctx->local)) { + aux_out_ptr = &aux_out; + } +#endif + return sock_udp_sendv_aux(NULL, &head, &ctx->remote, aux_out_ptr); +} diff --git a/sys/net/gnrc/sock/udp/gnrc_sock_udp.c b/sys/net/gnrc/sock/udp/gnrc_sock_udp.c index 2d8f4a6abe15..15dd113db8cd 100644 --- a/sys/net/gnrc/sock/udp/gnrc_sock_udp.c +++ b/sys/net/gnrc/sock/udp/gnrc_sock_udp.c @@ -423,6 +423,7 @@ ssize_t sock_udp_sendv_aux(sock_udp_t *sock, if ((aux != NULL) && (aux->flags & SOCK_AUX_SET_LOCAL)) { local.family = aux->local.family; local.netif = aux->local.netif; + src_port = aux->local.port; memcpy(&local.addr, &aux->local.addr, sizeof(local.addr)); aux->flags &= ~SOCK_AUX_SET_LOCAL;