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

sys/net/nanocoap: Implement Observe (Server-Side) #21147

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
6 changes: 4 additions & 2 deletions examples/nanocoap_server/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ USEMODULE += gnrc_icmpv6_echo
USEMODULE += nanocoap_sock
USEMODULE += nanocoap_resources

USEMODULE += xtimer
USEMODULE += ztimer_msec

# include this for nicely formatting the returned internal value
USEMODULE += fmt
Expand Down Expand Up @@ -48,10 +48,12 @@ HIGH_MEMORY_BOARDS := native native64 same54-xpro mcb2388

ifneq (,$(filter $(BOARD),$(HIGH_MEMORY_BOARDS)))
# enable separate response
USEMODULE += nanocoap_server_separate
USEMODULE += event_callback
USEMODULE += event_periodic
USEMODULE += event_thread
USEMODULE += event_timeout_ztimer
USEMODULE += nanocoap_server_observe
USEMODULE += nanocoap_server_separate

# enable fileserver
USEMODULE += nanocoap_fileserver
Expand Down
116 changes: 111 additions & 5 deletions examples/nanocoap_server/coap_handler.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@
#include <string.h>

#include "event/callback.h"
#include "event/timeout.h"
#include "event/periodic.h"
#include "event/thread.h"
#include "event/timeout.h"
#include "fmt.h"
#include "net/nanocoap.h"
#include "net/nanocoap_sock.h"
#include "hashes/sha256.h"
#include "kernel_defines.h"

/* internal value that can be read/written via CoAP */
static uint8_t internal_value = 0;
Expand All @@ -41,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,11 +59,11 @@

bufpos += coap_put_option_ct(bufpos, 0, COAP_FORMAT_TEXT);
bufpos += coap_opt_put_block2(bufpos, COAP_OPT_CONTENT_FORMAT, &slicer, 1);
*bufpos++ = 0xff;
*bufpos++ = COAP_PAYLOAD_MARKER;

/* 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 @@ -81,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 @@ -92,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 @@ -177,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 @@ -196,15 +196,20 @@

puts("_separate_handler(): send delayed response");
nanocoap_server_send_separate(ctx, COAP_CODE_CONTENT, COAP_TYPE_NON,
response, sizeof(response));
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) && !sock_udp_ep_equal(context->remote, &_separate_ctx.remote)) {
if (event_timeout_is_pending(&event_timeout)) {
if (nanocoap_server_is_remote_in_response_ctx(&_separate_ctx, context)) {
/* duplicate of the request a separate response is already scheduled
* for --> resending the ACK */
return coap_build_empty_ack(pkt, (void *)buf);
}
puts("_separate_handler(): response already scheduled");
return coap_build_reply(pkt, COAP_CODE_SERVICE_UNAVAILABLE, buf, len, 0);
}
Expand All @@ -229,6 +234,107 @@
};
#endif /* MODULE_EVENT_THREAD */

#ifdef MODULE_NANOCOAP_SERVER_OBSERVE
static ssize_t _time_handler(coap_pkt_t *pkt, uint8_t *buf, size_t len, coap_request_ctx_t *context)
{
uint32_t obs;
bool registered = false;
if (coap_opt_get_uint(pkt, COAP_OPT_OBSERVE, &obs)) {
/* No (valid) observe option present */
obs = UINT32_MAX;
}

uint32_t now = ztimer_now(ZTIMER_MSEC);

switch (obs) {
case 0:
/* register */
if (nanocoap_register_observer(context, pkt) == 0) {
registered = true;
}
break;
case 1:
/* unregister */
nanocoap_unregister_observer(context, pkt);
maribu marked this conversation as resolved.
Show resolved Hide resolved
break;
default:
/* No (valid) observe option present --> ignore observe and handle
* as regular GET */
break;
}

const size_t estimated_data_len =
4 /* Max Observe Option size */
+ 1 /* payload marker */
+ 10 /* strlen("4294967295"), 4294967295 == UINT32_MAX */
+ 1; /* '\n' */
ssize_t hdr_len = coap_build_reply(pkt, COAP_CODE_CONTENT, buf, len, estimated_data_len);
maribu marked this conversation as resolved.
Show resolved Hide resolved

if (hdr_len < 0) {
/* we undo any potential registration if we cannot reply */
nanocoap_unregister_observer(context, pkt);
return len;
}

if (hdr_len == 0) {
/* no response required, probably because of no-response option matching
* the response class */
return 0;
}

/* coap_build_reply() is a bit goofy: It returns the size of the written
* header + `estiamted_data_len`, so we have to subtract it again to obtain
* the size of data written. */
uint8_t *pos = buf + hdr_len - estimated_data_len;
maribu marked this conversation as resolved.
Show resolved Hide resolved

if (registered) {
uint16_t last_opt = 0;
pos += coap_opt_put_observe(pos, last_opt, now);
}
*pos++ = COAP_PAYLOAD_MARKER;
pos += fmt_u32_dec((void *)pos, now);
*pos++ = '\n';

return (uintptr_t)pos - (uintptr_t)buf;
}

NANOCOAP_RESOURCE(time) {
.path = "/time", .methods = COAP_GET, .handler = _time_handler,
};

static void _notify_observer_handler(event_t *ev)
{
(void)ev;
uint32_t now = ztimer_now(ZTIMER_MSEC);
uint8_t buf[32];
uint8_t *pos = buf;
uint16_t last_opt = 0;
pos += coap_opt_put_observe(pos, last_opt, now);
*pos++ = COAP_PAYLOAD_MARKER;
pos += fmt_u32_dec((void *)pos, now);
*pos++ = '\n';
iolist_t data = {
.iol_base = buf,
.iol_len = (uintptr_t)pos - (uintptr_t)buf,
};

/* `NANOCOAP_RESOURCE(time)` expends to XFA magic adding an entry named
* `coap_resource_time`. */
nanocoap_notify_observers(&coap_resource_time, &data);
maribu marked this conversation as resolved.
Show resolved Hide resolved
}

void setup_observe_event(void)
{
static event_t ev = {
.handler = _notify_observer_handler
};
static event_periodic_t pev;

event_periodic_init(&pev, ZTIMER_MSEC, EVENT_PRIO_MEDIUM, &ev);
event_periodic_start(&pev, MS_PER_SEC);
}
maribu marked this conversation as resolved.
Show resolved Hide resolved
#endif /* MODULE_NANOCOAP_SERVER_OBSERVE */

/* we can also include the fileserver module */
#ifdef MODULE_NANOCOAP_FILESERVER
#include "net/nanocoap/fileserver.h"
Expand Down
10 changes: 8 additions & 2 deletions examples/nanocoap_server/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,15 @@
#include <stdio.h>

#include "net/nanocoap_sock.h"
#include "xtimer.h"
#include "ztimer.h"

#define COAP_INBUF_SIZE (256U)

#define MAIN_QUEUE_SIZE (8)
static msg_t _main_msg_queue[MAIN_QUEUE_SIZE];

extern void setup_observe_event(void);

int main(void)
{
puts("RIOT nanocoap example application");
Expand All @@ -35,7 +37,11 @@ int main(void)
msg_init_queue(_main_msg_queue, MAIN_QUEUE_SIZE);

puts("Waiting for address autoconfiguration...");
xtimer_sleep(3);
ztimer_sleep(ZTIMER_MSEC, 3 * MS_PER_SEC);

if (IS_USED(MODULE_NANOCOAP_SERVER_OBSERVE)) {
setup_observe_event();
}

/* print network addresses */
printf("{\"IPv6 addresses\": [\"");
Expand Down
4 changes: 4 additions & 0 deletions sys/Makefile.dep
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,10 @@ ifneq (,$(filter nanocoap_server_auto_init,$(USEMODULE)))
USEMODULE += nanocoap_server
endif

ifneq (,$(filter nanocoap_server_observe,$(USEMODULE)))
USEMODULE += nanocoap_server_separate
endif

ifneq (,$(filter nanocoap_server_separate,$(USEMODULE)))
USEMODULE += nanocoap_server
USEMODULE += sock_aux_local
Expand Down
1 change: 1 addition & 0 deletions sys/include/net/coap.h
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,7 @@
#define COAP_FORMAT_SWID_CBOR (258)
/**
* @brief Content-Type `application/pkixcmp`
* @see [draft-ietf-ace-cmpv2-coap-transport](https://datatracker.ietf.org/doc/draft-ietf-ace-cmpv2-coap-transport/)

Check warning on line 357 in sys/include/net/coap.h

View workflow job for this annotation

GitHub Actions / static-tests

line is longer than 100 characters
* @see [RFC 4210](https://datatracker.ietf.org/doc/html/rfc4210)
*/
#define COAP_FORMAT_PKIXCMP (259)
Expand Down Expand Up @@ -441,12 +441,12 @@
#define COAP_FORMAT_YAML_DATA_CBOR_ID_NAME (341)
/**
* @brief Content-Type `application/td+json`
* @see [Web of Things (WoT) Thing Description 1.1](https://www.w3.org/TR/wot-thing-description11/)

Check warning on line 444 in sys/include/net/coap.h

View workflow job for this annotation

GitHub Actions / static-tests

line is longer than 100 characters
*/
#define COAP_FORMAT_TD_JSON (432)
/**
* @brief Content-Type `application/tm+json`
* @see [Web of Things (WoT) Thing Description 1.1](https://www.w3.org/TR/wot-thing-description11/)

Check warning on line 449 in sys/include/net/coap.h

View workflow job for this annotation

GitHub Actions / static-tests

line is longer than 100 characters
*/
#define COAP_FORMAT_TM_JSON (433)
/**
Expand Down Expand Up @@ -519,6 +519,7 @@
*/
#define COAP_OBS_REGISTER (0)
#define COAP_OBS_DEREGISTER (1)
#define COAP_OBS_MAX_VALUE_MASK (0xffffff) /**< observe value is 24 bits */
/** @} */

/**
Expand Down
16 changes: 16 additions & 0 deletions sys/include/net/nanocoap.h
Original file line number Diff line number Diff line change
Expand Up @@ -1790,6 +1790,22 @@
(block->blknum << 4) | block->szx);
}

/**
* @brief Insert an CoAP Observe Option into the buffer
*
* @param[out] buf Buffer to write to
* @param[in] lastonum last option number (must be < 6)
* @param[in] obs observe number to write
*
* @param[in] lastonum last option number (must be < 27)
*/
static inline size_t coap_opt_put_observe(uint8_t *buf, uint16_t lastonum,

Check failure on line 1802 in sys/include/net/nanocoap.h

View workflow job for this annotation

GitHub Actions / static-tests

argument 'lastonum' from the argument list of coap_opt_put_observe has multiple @param documentation sections
uint32_t obs)
{
obs &= COAP_OBS_MAX_VALUE_MASK; /* trim obs down to 24 bit */
return coap_opt_put_uint(buf, lastonum, COAP_OPT_OBSERVE, obs);
}

/**
* @brief Encode the given string as multi-part option into buffer
*
Expand Down
Loading
Loading