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

gcoap: add some client-side observe handling #20073

Merged
147 changes: 99 additions & 48 deletions examples/gcoap/client.c
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,15 @@ static char proxy_uri[64];
#define _LAST_REQ_PATH_MAX (64)
static char _last_req_path[_LAST_REQ_PATH_MAX];

/* whether this node is currently observing a resource as a client */
static bool observing = false;

/* the token used for observing a remote resource */
static uint8_t obs_req_token[GCOAP_TOKENLEN_MAX];

/* actual length of above token */
static size_t obs_req_tkl = 0;

uint16_t req_count = 0;

/*
Expand Down Expand Up @@ -145,31 +154,9 @@ static void _resp_handler(const gcoap_request_memo_t *memo, coap_pkt_t* pdu,
}
}

static size_t _send(uint8_t *buf, size_t len, char *addr_str)
static size_t _send(uint8_t *buf, size_t len, sock_udp_ep_t *remote)
{
size_t bytes_sent;
sock_udp_ep_t *remote;
sock_udp_ep_t new_remote;

if (_proxied) {
remote = &_proxy_remote;
}
else {
if (sock_udp_name2ep(&new_remote, addr_str) != 0) {
return 0;
}

if (new_remote.port == 0) {
if (IS_USED(MODULE_GCOAP_DTLS)) {
new_remote.port = CONFIG_GCOAPS_PORT;
}
else {
new_remote.port = CONFIG_GCOAP_PORT;
}
}

remote = &new_remote;
}

bytes_sent = gcoap_req_send(buf, len, remote, _resp_handler, NULL);
if (bytes_sent > 0) {
Expand All @@ -180,17 +167,37 @@ static size_t _send(uint8_t *buf, size_t len, char *addr_str)

static int _print_usage(char **argv)
{
printf("usage: %s <get|post|put|ping|proxy|info>\n", argv[0]);
printf("usage: %s <get [-o|-d]|post|put|ping|proxy|info>\n", argv[0]);
Teufelchen1 marked this conversation as resolved.
Show resolved Hide resolved
return 1;
}

static int _addrstr2remote(const char *addr_str, sock_udp_ep_t *remote)
{
if (sock_udp_name2ep(remote, addr_str) != 0) {
return -1;
}

if (remote->port == 0) {
if (IS_USED(MODULE_GCOAP_DTLS)) {
remote->port = CONFIG_GCOAPS_PORT;
}
else {
remote->port = CONFIG_GCOAP_PORT;
}
}
return 0;
}

int gcoap_cli_cmd(int argc, char **argv)
{
/* Ordered like the RFC method code numbers, but off by 1. GET is code 0. */
char *method_codes[] = {"ping", "get", "post", "put"};
uint8_t buf[CONFIG_GCOAP_PDU_BUF_SIZE];
coap_pkt_t pdu;
size_t len;
unsigned observe = false;
uint32_t obs_value = COAP_OBS_REGISTER;
sock_udp_ep_t remote;

if (argc == 1) {
/* show help for main commands */
Expand Down Expand Up @@ -275,6 +282,30 @@ int gcoap_cli_cmd(int argc, char **argv)

/* parse options */
int apos = 2; /* position of address argument */

/* For GET requests additional switches allow for registering and
* deregistering an observe. This example only supports one observe. */
if (code_pos == COAP_METHOD_GET) {
if (argc > apos) {
if (strcmp(argv[apos], "-o") == 0) {
if (observing) {
puts("Only one observe supported");
return 1;
}
observe = true;
apos++;
} else if (strcmp(argv[apos], "-d") == 0) {
if (!observing) {
puts("Not observing");
return 1;
}
observe = true;
apos++;
obs_value = COAP_OBS_DEREGISTER;
}
}
}

/* ping must be confirmable */
unsigned msg_type = (!code_pos ? COAP_TYPE_CON : COAP_TYPE_NON);
if (argc > apos && strcmp(argv[apos], "-c") == 0) {
Expand All @@ -287,6 +318,12 @@ int gcoap_cli_cmd(int argc, char **argv)
((argc == apos + 2 ||
argc == apos + 3) && (code_pos > 1))) { /* post or put */

/* get unproxied endpoint from address string */
if (_addrstr2remote(argv[apos], &remote)) {
printf("'%s' is not a valid address\n", argv[apos]);
return _print_usage(argv);
}

char *uri = NULL;
int uri_len = 0;
if (code_pos) {
Expand All @@ -300,43 +337,52 @@ int gcoap_cli_cmd(int argc, char **argv)
}

if (_proxied) {
sock_udp_ep_t tmp_remote;
if (sock_udp_name2ep(&tmp_remote, argv[apos]) != 0) {
return _print_usage(argv);
}

if (tmp_remote.port == 0) {
if (IS_USED(MODULE_GCOAP_DTLS)) {
tmp_remote.port = CONFIG_GCOAPS_PORT;
}
else {
tmp_remote.port = CONFIG_GCOAP_PORT;
}
}

#ifdef SOCK_HAS_IPV6
char addrstr[IPV6_ADDR_MAX_STR_LEN];
#else
char addrstr[IPV4_ADDR_MAX_STR_LEN];
#endif
inet_ntop(tmp_remote.family, &tmp_remote.addr, addrstr, sizeof(addrstr));
inet_ntop(remote.family, &remote.addr, addrstr, sizeof(addrstr));

if (tmp_remote.family == AF_INET6) {
if (remote.family == AF_INET6) {
uri_len = snprintf(proxy_uri, sizeof(proxy_uri), "coap://[%s]:%d%s",
addrstr, tmp_remote.port, uri);
addrstr, remote.port, uri);
}
else {
uri_len = snprintf(proxy_uri, sizeof(proxy_uri), "coap://%s:%d%s",
addrstr, tmp_remote.port, uri);
addrstr, remote.port, uri);
}

uri = proxy_uri;
}

gcoap_req_init(&pdu, buf, CONFIG_GCOAP_PDU_BUF_SIZE, code_pos, NULL);

if (observe) {
uint8_t *token = coap_get_token(&pdu);
if (obs_value == COAP_OBS_REGISTER) {
obs_req_tkl = coap_get_token_len(&pdu);
/* backup the token of the initial observe registration */
memcpy(obs_req_token, token, obs_req_tkl);
} else {
/* use the token of the registration for deregistration
* (manually replace the token set by gcoap_req_init) */
memcpy(token, obs_req_token, obs_req_tkl);
if (gcoap_obs_req_forget(&remote, obs_req_token, obs_req_tkl)) {
printf("could not remove observe request\n");
return 1;
}
}

gcoap_req_init(&pdu, &buf[0], CONFIG_GCOAP_PDU_BUF_SIZE, code_pos, NULL);
coap_opt_add_uint(&pdu, COAP_OPT_OBSERVE, obs_value);
}
else {
gcoap_req_init(&pdu, &buf[0], CONFIG_GCOAP_PDU_BUF_SIZE, code_pos, uri);

if (!_proxied) {
/* add uri path option separately
* (options must be added in order) */
coap_opt_add_uri_path(&pdu, uri);
}

coap_hdr_set_type(pdu.hdr, msg_type);

memset(_last_req_path, 0, _LAST_REQ_PATH_MAX);
Expand Down Expand Up @@ -369,18 +415,23 @@ int gcoap_cli_cmd(int argc, char **argv)
}

printf("gcoap_cli: sending msg ID %u, %" PRIuSIZE " bytes\n",
coap_get_id(&pdu), len);
if (!_send(&buf[0], len, argv[apos])) {
coap_get_id(&pdu), len);
if (!_send(&buf[0], len, _proxied ? &_proxy_remote : &remote)) {
puts("gcoap_cli: msg send failed");
}
else {
if (observe) {
/* on successful observe request, store that this node is
* observing / not observing anymore */
observing = obs_value == COAP_OBS_REGISTER;
}
/* send Observe notification for /cli/stats */
notify_observers();
}
return 0;
}
else {
printf("usage: %s <get|post|put> [-c] <host>[:port] <path> [data]\n",
printf("usage: %s <get [-o|-d]|post|put> [-c] <host>[:port] <path> [data]\n",
argv[0]);
printf(" %s ping <host>[:port]\n", argv[0]);
printf("Options\n");
Expand Down
35 changes: 34 additions & 1 deletion sys/include/net/gcoap.h
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@
* identify the slice with a Block2 option. This implementation toggles the
* actual writing of data as it passes over the code for the full response
* body. See the _riot_block2_handler() example in
* [gcoap-block-server](https://github.com/kb2ma/riot-apps/blob/kb2ma-master/gcoap-block-server/gcoap_block.c),

Check warning on line 222 in sys/include/net/gcoap.h

View workflow job for this annotation

GitHub Actions / static-tests

line is longer than 100 characters
* which implements the sequence described below.
*
* - Use coap_block2_init() to initialize a _slicer_ struct from the Block2
Expand All @@ -244,7 +244,7 @@
* The server must ack each blockwise portion of the response body received
* from the client by writing a Block1 option in the response. See the
* _sha256_handler() example in
* [gcoap-block-server](https://github.com/kb2ma/riot-apps/blob/kb2ma-master/gcoap-block-server/gcoap_block.c),

Check warning on line 247 in sys/include/net/gcoap.h

View workflow job for this annotation

GitHub Actions / static-tests

line is longer than 100 characters
* which implements the sequence described below.
*
* - Use coap_get_block1() to initialize a block1 struct from the request.
Expand Down Expand Up @@ -284,7 +284,7 @@
*
* The client pushes a specific blockwise payload from the overall body to the
* server by writing a Block1 option in the request. See _do_block_post() in
* the [gcoap-block-client](https://github.com/kb2ma/riot-apps/blob/kb2ma-master/gcoap-block-client/gcoap_block.c)

Check warning on line 287 in sys/include/net/gcoap.h

View workflow job for this annotation

GitHub Actions / static-tests

line is longer than 100 characters
* example, which implements the sequence described below.
*
* - For the first request, use coap_block_slicer_init() to initialize a
Expand Down Expand Up @@ -371,7 +371,8 @@
* - Message Type: Supports non-confirmable (NON) messaging. Additionally
* provides a callback on timeout. Provides piggybacked ACK response to a
* confirmable (CON) request.
* - Observe extension: Provides server-side registration and notifications.
* - Observe extension: Provides server-side registration and notifications
* and client-side observe.
* - Server and Client provide helper functions for writing the
* response/request. See the CoAP topic in the source documentation for
* details. See the gcoap example for sample implementations.
Expand Down Expand Up @@ -837,6 +838,7 @@
sock_udp_ep_t *observer; /**< Client endpoint; unused if null */
const coap_resource_t *resource; /**< Entity being observed */
uint8_t token[GCOAP_TOKENLEN_MAX]; /**< Client token for notifications */
uint16_t last_msgid; /**< Message ID of last notification */
unsigned token_len; /**< Actual length of token attribute */
gcoap_socket_t socket; /**< Transport type to observer */
} gcoap_observe_memo_t;
Expand Down Expand Up @@ -1074,6 +1076,37 @@
size_t gcoap_obs_send(const uint8_t *buf, size_t len,
const coap_resource_t *resource);

/**
* @brief Forgets (invalidates) an existing observe request.
*
* This invalidates the internal (local) observe request state without actually
* sending a deregistration request to the server. Ths mechanism may be referred
* to as passive deregistration, as it does not send a deregistration request.
* This is implemented according to the description in RFC 7641,
* Section 3.6 (Cancellation): 'A client that is no longer interested in
* receiving notifications for a resource can simply "forget" the observation.'
* Successfully invalidating the request by calling this function guarantees
* that the corresponding observe response handler will not be called anymore.
*
* NOTE: There are cases were active deregistration is preferred instead.
* A server may continue sending notifications if it chooses to ignore the RST
* which is meant to indicate the client did not recognize the notification.
* For such server implementations this function must be called *before*
* sending an explicit deregister request (i.e., a GET request with the token
* of the registration and the observe option set to COAP_OBS_DEREGISTER).
* This will instruct the server to stop sending further notifications.
*
* @param[in] remote remote endpoint that hosts the observed resource
* @param[in] token token of the original GET request used for registering
* an observe
* @param[in] tokenlen the length of the token in bytes
*
* @return 0 on success
* @return < 0 on error
*/
int gcoap_obs_req_forget(const sock_udp_ep_t *remote, const uint8_t *token,
size_t tokenlen);

/**
* @brief Provides important operational statistics
*
Expand Down
Loading
Loading