Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

nanocoap_sock: implement separate response #20266

Merged
merged 9 commits into from
May 21, 2024
7 changes: 7 additions & 0 deletions examples/nanocoap_server/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
43 changes: 43 additions & 0 deletions examples/nanocoap_server/coap_handler.c
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,12 @@
#include <stdio.h>
#include <string.h>

#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"

Expand All @@ -37,14 +41,14 @@
(uint8_t *)sub_uri, sub_uri_len);
}

static ssize_t _riot_board_handler(coap_pkt_t *pkt, uint8_t *buf, size_t len, coap_request_ctx_t *context)

Check warning on line 44 in examples/nanocoap_server/coap_handler.c

View workflow job for this annotation

GitHub Actions / static-tests

line is longer than 100 characters
{
(void)context;
return coap_reply_simple(pkt, COAP_CODE_205, buf, len,
COAP_FORMAT_TEXT, (uint8_t*)RIOT_BOARD, strlen(RIOT_BOARD));
}

static ssize_t _riot_block2_handler(coap_pkt_t *pkt, uint8_t *buf, size_t len, coap_request_ctx_t *context)

Check warning on line 51 in examples/nanocoap_server/coap_handler.c

View workflow job for this annotation

GitHub Actions / static-tests

line is longer than 100 characters
{
(void)context;
coap_block_slicer_t slicer;
Expand All @@ -59,7 +63,7 @@

/* Add actual content */
bufpos += coap_blockwise_put_bytes(&slicer, bufpos, block2_intro, sizeof(block2_intro)-1);
bufpos += coap_blockwise_put_bytes(&slicer, bufpos, (uint8_t*)RIOT_VERSION, strlen(RIOT_VERSION));

Check warning on line 66 in examples/nanocoap_server/coap_handler.c

View workflow job for this annotation

GitHub Actions / static-tests

line is longer than 100 characters
bufpos += coap_blockwise_put_char(&slicer, bufpos, ')');
bufpos += coap_blockwise_put_bytes(&slicer, bufpos, block2_board, sizeof(block2_board)-1);
bufpos += coap_blockwise_put_bytes(&slicer, bufpos, (uint8_t*)RIOT_BOARD, strlen(RIOT_BOARD));
Expand All @@ -77,7 +81,7 @@
buf, len, payload_len, &slicer);
}

static ssize_t _riot_value_handler(coap_pkt_t *pkt, uint8_t *buf, size_t len, coap_request_ctx_t *context)

Check warning on line 84 in examples/nanocoap_server/coap_handler.c

View workflow job for this annotation

GitHub Actions / static-tests

line is longer than 100 characters
{
(void) context;

Expand All @@ -88,7 +92,7 @@
/* read coap method type in packet */
unsigned method_flag = coap_method2flag(coap_get_code_detail(pkt));

switch(method_flag) {

Check warning on line 95 in examples/nanocoap_server/coap_handler.c

View workflow job for this annotation

GitHub Actions / static-tests

keyword 'switch' not followed by a single space
case COAP_GET:
/* write the response buffer with the internal value */
p += fmt_u32_dec(rsp, internal_value);
Expand Down Expand Up @@ -173,7 +177,7 @@
.path = "/riot/board", .methods = COAP_GET, .handler = _riot_board_handler
};
NANOCOAP_RESOURCE(value) {
.path = "/riot/value", .methods = COAP_GET | COAP_PUT | COAP_POST, .handler = _riot_value_handler

Check warning on line 180 in examples/nanocoap_server/coap_handler.c

View workflow job for this annotation

GitHub Actions / static-tests

line is longer than 100 characters
};
NANOCOAP_RESOURCE(ver) {
.path = "/riot/ver", .methods = COAP_GET, .handler = _riot_block2_handler
Expand All @@ -182,6 +186,45 @@
.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)

Check warning on line 202 in examples/nanocoap_server/coap_handler.c

View workflow job for this annotation

GitHub Actions / static-tests

line is longer than 100 characters
{
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);
fabian18 marked this conversation as resolved.
Show resolved Hide resolved
}

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"
Expand Down
5 changes: 5 additions & 0 deletions sys/Makefile.dep
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions sys/include/event/timeout.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
21 changes: 20 additions & 1 deletion sys/include/net/nanocoap.h
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,9 @@
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 */
fabian18 marked this conversation as resolved.
Show resolved Hide resolved
#endif
#if defined(MODULE_GCOAP) || DOXYGEN
/**
* @brief transport the packet was received over
Expand Down Expand Up @@ -1856,7 +1859,7 @@
* @param[in] more more flag (1 or 0)
*
* @returns amount of bytes written to @p buf
*/

Check warning on line 1862 in sys/include/net/nanocoap.h

View workflow job for this annotation

GitHub Actions / static-tests

line is longer than 100 characters
static inline size_t coap_put_option_block1(uint8_t *buf, uint16_t lastonum,
unsigned blknum, unsigned szx, int more)
{
Expand Down Expand Up @@ -1922,7 +1925,7 @@
*
* @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);

/**
Expand Down Expand Up @@ -1956,6 +1959,22 @@
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.
fabian18 marked this conversation as resolved.
Show resolved Hide resolved
*
* @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
*
Expand Down
49 changes: 49 additions & 0 deletions sys/include/net/nanocoap_sock.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
17 changes: 13 additions & 4 deletions sys/net/application_layer/nanocoap/nanocoap.c
Original file line number Diff line number Diff line change
Expand Up @@ -626,6 +626,18 @@
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)
{
Expand Down Expand Up @@ -675,15 +687,12 @@

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);
benpicco marked this conversation as resolved.
Show resolved Hide resolved
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));
Expand Down Expand Up @@ -877,7 +886,7 @@
return offset;
}

size_t coap_put_option(uint8_t *buf, uint16_t lastonum, uint16_t onum, const void *odata, size_t olen)

Check warning on line 889 in sys/net/application_layer/nanocoap/nanocoap.c

View workflow job for this annotation

GitHub Actions / static-tests

line is longer than 100 characters
{
assert(lastonum <= onum);

Expand Down
81 changes: 73 additions & 8 deletions sys/net/application_layer/nanocoap/sock.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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;
}
Expand All @@ -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);
Expand All @@ -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
Expand All @@ -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;
Expand Down Expand Up @@ -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());
fabian18 marked this conversation as resolved.
Show resolved Hide resolved
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);
}
1 change: 1 addition & 0 deletions sys/net/gnrc/sock/udp/gnrc_sock_udp.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading