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 observe (Client-Side) #21160

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions examples/nanocoap_server/coap_handler.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 @@ -63,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 @@ -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 @@ -199,7 +199,7 @@
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);
Expand Down Expand Up @@ -252,6 +252,9 @@
if (nanocoap_register_observer(context, pkt) == 0) {
registered = true;
}
else {
puts("_time_handler: can't register observer");
}
break;
case 1:
/* unregister */
Expand Down
6 changes: 6 additions & 0 deletions sys/Makefile.dep
Original file line number Diff line number Diff line change
Expand Up @@ -564,6 +564,12 @@ ifneq (,$(filter nanocoap_link_format,$(USEMODULE)))
USEMODULE += fmt
endif

ifneq (,$(filter nanocoap_sock_observe,$(USEMODULE)))
USEMODULE += event_thread
USEMODULE += sock_async_event
USEMODULE += nanocoap_sock
endif

ifneq (,$(filter nanocoap_vfs,$(USEMODULE)))
USEMODULE += nanocoap_sock
USEMODULE += vfs
Expand Down
50 changes: 50 additions & 0 deletions sys/include/net/nanocoap_sock.h
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,13 @@
#define CONFIG_NANOCOAP_SOCK_BLOCK_TOKEN (0)
#endif

/**
* @brief Event priority for nanoCoAP sock events (e.g. used by `nanocoap_sock_observe`)
*/
#ifndef CONFIG_NANOCOAP_SOCK_EVENT_PRIO
# define CONFIG_NANOCOAP_SOCK_EVENT_PRIO EVENT_PRIO_MEDIUM
#endif

/**
* @brief NanoCoAP socket types
*/
Expand Down Expand Up @@ -224,6 +231,15 @@
uint8_t blksize; /**< CoAP blocksize exponent */
} coap_block_request_t;

/**
* @brief Observe Client helper struct
*/
typedef struct {
coap_request_cb_t cb; /**< user callback function */
void *arg; /**< callback function argument */
nanocoap_sock_t sock; /**< socket used for the request */
} coap_observe_client_t;

/**
* @brief Context from CoAP request for separate response
*/
Expand Down Expand Up @@ -293,7 +309,7 @@
* @param[in] len Payload length
*
* @retval 0 Success
* @retval -ECANCELED Request contained no-response option that did match the given @p code

Check warning on line 312 in sys/include/net/nanocoap_sock.h

View workflow job for this annotation

GitHub Actions / static-tests

line is longer than 100 characters
* @retval <0 Negative errno code indicating the error
*/
int nanocoap_server_send_separate(const nanocoap_server_response_ctx_t *ctx,
Expand Down Expand Up @@ -322,7 +338,7 @@
* @param[in] msg_id Message ID to send
*
* @return Length of the header build in bytes
* @retval -ECANCELED Request contained no-response option that did match the given @p code

Check warning on line 341 in sys/include/net/nanocoap_sock.h

View workflow job for this annotation

GitHub Actions / static-tests

line is longer than 100 characters
* @retval <0 Negative errno code indicating the error
*/
ssize_t nanocoap_server_build_separate(const nanocoap_server_response_ctx_t *ctx,
Expand Down Expand Up @@ -550,6 +566,40 @@
sock_udp_close(&sock->udp);
}

/**
* @brief Observe a CoAP resource behind a URL (via GET)
*
* @note This requires the `nanocoap_sock_observe` module.
*
* @param[in] url URL to subscribe to
* @param[out] ctx nanoCoAP observe context
* @param[in] cb callback function called for every resource update
* @param[in] arg callback function argument
*
* @returns Number of bytes send on success
* @retval -EPROTONOSUPPORT registration failed
* @retval <0 other error

*/
ssize_t nanocoap_sock_observe_url(const char *url, coap_observe_client_t *ctx,
coap_request_cb_t cb, void *arg);
/**
* @brief Stop observing a CoAP resource
*
* @note This requires the `nanocoap_sock_observe` module.
*
* @param[in] url URL to unsubscribe subscribe from
* @param[out] ctx nanoCoAP observe context that was previously used with
* @see nanocoap_sock_observe_url
*
* @pre @p nanocoap_sock_observe_url has been called on the same arguments
* before and the Observation has not yet been cancelled yet.
*
* @returns >=0 on success
* @returns <0 on error
*/
ssize_t nanocoap_sock_unobserve_url(const char *url, coap_observe_client_t *ctx);

/**
* @brief Simple synchronous CoAP (confirmable) GET
*
Expand Down
118 changes: 118 additions & 0 deletions sys/net/application_layer/nanocoap/sock.c
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,13 @@
#include <stdio.h>

#include "container.h"
#include "event/thread.h"
#include "net/credman.h"
#include "net/nanocoap.h"
#include "net/nanocoap_sock.h"
#ifdef MODULE_SOCK_ASYNC_EVENT
#include "net/sock/async/event.h"
#endif
#include "net/sock/udp.h"
#include "net/sock/util.h"
#include "random.h"
Expand Down Expand Up @@ -463,6 +467,120 @@
return _sock_get(sock, path, COAP_TYPE_CON, buf, len);
}

#ifdef MODULE_NANOCOAP_SOCK_OBSERVE
static void _async_udp_handler(sock_udp_t *sock, sock_async_flags_t type, void *arg)
{
if (!(type & SOCK_ASYNC_MSG_RECV)) {
return;
}

coap_pkt_t pkt;
void *payload, *ctx = NULL;
coap_observe_client_t *obs = arg;
ssize_t res = sock_udp_recv_buf(sock, &payload, &ctx, 0, NULL);
if (res <= 0) {
return;
}

/* parse response */
if (coap_parse(&pkt, payload, res) < 0) {
DEBUG("nanocoap: error parsing packet\n");
goto out;
}

DEBUG("nanocoap: response code=%i\n", coap_get_code_decimal(&pkt));
switch (coap_get_type(&pkt)) {
case COAP_TYPE_CON:
_send_ack(&obs->sock, &pkt);
/* fall-through */
case COAP_TYPE_NON:
obs->cb(obs->arg, &pkt);
break;
default:
DEBUG("nanocoap: ignore observe pkt of invalid type %u\n", coap_get_type(&pkt));
break;
}
out:
/* release data */
sock_udp_recv_buf(sock, &payload, &ctx, 0, NULL);
}

static int _observe_reg_wrapper(void *arg, coap_pkt_t *pkt)
{
coap_observe_client_t *obs = arg;
bool registered = coap_find_option(pkt, COAP_OPT_OBSERVE);
int res = obs->cb(obs->arg, pkt);

return registered ? res : -EPROTONOSUPPORT;
}

static ssize_t _get_observe(coap_observe_client_t *ctx, const char *path,
bool unregister)
{
/* buffer for CoAP header */
uint8_t buffer[CONFIG_NANOCOAP_BLOCK_HEADER_MAX];
uint8_t *pktpos = buffer;

coap_pkt_t pkt = {
.hdr = (void *)pktpos,
};

uint16_t lastonum = 0;

pktpos += coap_build_hdr(pkt.hdr, COAP_TYPE_CON, NULL, 0, COAP_METHOD_GET,
nanocoap_sock_next_msg_id(&ctx->sock));
pktpos += coap_opt_put_observe(pktpos, lastonum, unregister);
lastonum = COAP_OPT_OBSERVE;
pktpos += coap_opt_put_uri_pathquery(pktpos, &lastonum, path);

pkt.payload = pktpos;
pkt.payload_len = 0;

return nanocoap_sock_request_cb(&ctx->sock, &pkt, _observe_reg_wrapper, ctx);
}

ssize_t nanocoap_sock_observe_url(const char *url, coap_observe_client_t *ctx,
coap_request_cb_t cb, void *arg)
{
int res = nanocoap_sock_url_connect(url, &ctx->sock);
if (res) {
return res;
}
ctx->cb = cb;
ctx->arg = arg;

res = _get_observe(ctx, sock_urlpath(url), false);
if (res >= 0) {
sock_udp_event_init(&ctx->sock.udp, CONFIG_NANOCOAP_SOCK_EVENT_PRIO,
_async_udp_handler, ctx);
}
else {
nanocoap_sock_close(&ctx->sock);
ctx->cb = NULL;
}

return res;
}

ssize_t nanocoap_sock_unobserve_url(const char *url, coap_observe_client_t *ctx)
{
if (ctx->cb == NULL) {
return -ENOTCONN;
}

int res = _get_observe(ctx, sock_urlpath(url), true);

/* we expect no observe option in the response */
if (res == -EPROTONOSUPPORT) {
res = 0;
}

nanocoap_sock_close(&ctx->sock);
ctx->cb = NULL;
return res;
}
#endif /* MODULE_NANOCOAP_SOCK_OBSERVE */

ssize_t _sock_put_post(nanocoap_sock_t *sock, const char *path, unsigned code,
uint8_t type, const void *request, size_t len,
void *response, size_t max_len)
Expand Down Expand Up @@ -1284,7 +1402,7 @@
&& (_observer_pool[i].response.tkl == coap_get_token_len(req_pkt))
&& !memcmp(_observer_pool[i].response.token, coap_get_token(req_pkt),
_observer_pool[i].response.tkl)
&& sock_udp_ep_equal(&_observer_pool[i].response.remote, coap_request_ctx_get_remote_udp(req_ctx))) {

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

View workflow job for this annotation

GitHub Actions / static-tests

line is longer than 100 characters
DEBUG("nanocoap: observer at index %" PRIuSIZE " unregistered\n", i);
_observer_pool[i].resource = NULL;
}
Expand Down
1 change: 1 addition & 0 deletions tests/net/nanocoap_cli/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ ifeq (,$(filter $(BOARD),$(LOW_MEMORY_BOARDS)))
USEMODULE += nanocoap_dtls
USEMODULE += prng_sha256prng

USEMODULE += nanocoap_sock_observe
USEMODULE += nanocoap_vfs
USEMODULE += vfs_default
# USEMODULE += vfs_auto_format
Expand Down
47 changes: 47 additions & 0 deletions tests/net/nanocoap_cli/nanocli_client.c
Original file line number Diff line number Diff line change
Expand Up @@ -391,3 +391,50 @@ static int _cmd_get_non(int argc, char **argv)
}

SHELL_COMMAND(get_non, "non-confirmable get", _cmd_get_non);

static int _observe_cb(void *arg, coap_pkt_t *pkt)
{
(void)arg;

if (coap_get_code_class(pkt) != COAP_CLASS_SUCCESS) {
printf("observe: error\n");
}

od_hex_dump(pkt->payload, pkt->payload_len, OD_WIDTH_DEFAULT);

return pkt->payload_len;
}

static int _cmd_observe(int argc, char **argv)
{
static coap_observe_client_t ctx;
bool observe = true;
int res;

if ((argc < 2) || (argc > 3)) {
printf("usage: %s <url> [on|off]\n", argv[0]);
return 1;
}
if (argc > 2 && !strcmp("off", argv[2])) {
observe = false;
}

if (ctx.cb && observe) {
puts("CLI can observe only a single resource at a time");
return -1;
}

if (observe) {
res = nanocoap_sock_observe_url(argv[1], &ctx, _observe_cb, NULL);
}
else {
res = nanocoap_sock_unobserve_url(argv[1], &ctx);
}

if (res < 0) {
printf("error: %d\n", res);
}
return res;
}

SHELL_COMMAND(observe, "observe URL", _cmd_observe);
Loading