Skip to content

Commit

Permalink
sys/net/nanocoap: implement observe
Browse files Browse the repository at this point in the history
This adds the new `nanocoap_server_observe` module that implements the
server side of the CoAP Observe option. It does require cooperation
from the resource handler to work, though.
  • Loading branch information
maribu committed Jan 20, 2025
1 parent abf56c4 commit 0998daf
Show file tree
Hide file tree
Showing 7 changed files with 290 additions and 9 deletions.
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
91 changes: 88 additions & 3 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 Down Expand Up @@ -196,7 +196,7 @@ static void _send_response(void *ctx)

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
Expand Down Expand Up @@ -234,6 +234,91 @@ NANOCOAP_RESOURCE(separate) {
};
#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 = 2;
bool registered = false;
coap_opt_get_uint(pkt, COAP_OPT_OBSERVE, &obs);

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);
break;
}

const size_t estimated_data_len =
4 /* Max Observe Option size */
+ 1 /* payload marker */
+ 10 /* strlen("4294967295"), 4294967295 == UINT32_MAX */
+1 /* '\n' */;

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

View workflow job for this annotation

GitHub Actions / static-tests

semicolon is isolated from other tokens
ssize_t hdr_len = coap_build_reply(pkt, COAP_CODE_CONTENT, buf, len, estimated_data_len);

if (hdr_len < 0) {
nanocoap_unregister_observer(context);
return len;
}

if (hdr_len == 0) {
return 0;
}

uint8_t *pos = buf + hdr_len - estimated_data_len;

if (registered) {
uint16_t last_opt = 0;
pos += coap_opt_put_uint(pos, last_opt, COAP_OPT_OBSERVE, now & 0xffffff);
}
*pos++ = 0xff; /* 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_uint(pos, last_opt, COAP_OPT_OBSERVE, now & 0xffffff);
*pos++ = 0xff; /* 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_notify_observers(&coap_resource_time, &data);
}

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);
}
#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
58 changes: 58 additions & 0 deletions sys/include/net/nanocoap_sock.h
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,64 @@ ssize_t nanocoap_server_build_separate(const nanocoap_server_response_ctx_t *ctx
int nanocoap_server_sendv_separate(const nanocoap_server_response_ctx_t *ctx,
const iolist_t *reply);

/**
* @brief Register an observer
* @param[in] req_ctx Request context belonging to @p req_pkt
* @param[in,out] req_pkt Request that contained the observe registration request
*
* @warning This depends on module `nanocoap_server_observe`
*

Check failure on line 360 in sys/include/net/nanocoap_sock.h

View workflow job for this annotation

GitHub Actions / static-tests

Found unknown command '@rerval'
* @rerval 0 Success
* @retval -ENOMEM Not enough resources to register another observer
* @retval <0 Negative errno code indicating error
*/
int nanocoap_register_observer(const coap_request_ctx_t *req_ctx, coap_pkt_t *req_pkt);

/**
* @brief Unregister an observer
* @param req_ctx Request context belonging to @p req_pkt
*
* @warning This depends on module `nanocoap_server_observe`
*
* @details It is safe to call this multiple times, e.g. on a GET request
* without an observe option just in case it may be an eager
* unregistration
*/
void nanocoap_unregister_observer(const coap_request_ctx_t *req_ctx);

/**
* @brief Unregister an observer given by its UDP endpoint
* @param[in] ep Endpoint to wipe from the observer list
*/
void nanocoap_unregister_observer_by_udp_ep(const sock_udp_ep_t *ep);

/**
* @brief Notify all currently registered observers of the given resource
*
* @param[in] res Resource to send updates for
* @param[in] iol I/O list containing the CoAP Options, payload marker,
* and payload of the update to send up
*
* @pre @p iol contains everything but the CoAP header needed to send out.
* This will at least be a CoAP observe option, a payload marker,
* and a payload
*
* @post For each registered observer a CoAP packet header is generated and
* the concatenation of that header and the provided list is send
*/
void nanocoap_notify_observers(const coap_resource_t *res, const iolist_t *iol);

/**
* @brief Wrapper for @ref nanocoap_notify_observers that is easier to use
*
* @param[in] res Resource to send updates for
* @param[in] obs 24-bit number to add as observe option
* @param[in] payload Payload to send out
* @param[in] payload_len Length of @p payload in bytes
*/
void nanocoap_notify_observers_simple(const coap_resource_t *res, uint32_t obs,
const void *payload, size_t payload_len);

/**
* @brief Get next consecutive message ID for use when building a new
* CoAP request.
Expand Down
5 changes: 5 additions & 0 deletions sys/net/application_layer/nanocoap/nanocoap.c
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@

#include "bitarithm.h"
#include "net/nanocoap.h"
#include "net/nanocoap_sock.h"

#define ENABLE_DEBUG 0
#include "debug.h"
Expand Down Expand Up @@ -494,6 +495,10 @@ ssize_t coap_handle_req(coap_pkt_t *pkt, uint8_t *resp_buf, unsigned resp_buf_le
{
assert(ctx);

if (IS_USED(MODULE_NANOCOAP_SERVER_OBSERVE) && (coap_get_type(pkt) == COAP_TYPE_RST)) {
nanocoap_unregister_observer_by_udp_ep(coap_request_ctx_get_remote_udp(ctx));
}

if (coap_get_code_class(pkt) != COAP_REQ) {
DEBUG("coap_handle_req(): not a request.\n");
return -EBADMSG;
Expand Down
Loading

0 comments on commit 0998daf

Please sign in to comment.