From f2279e43ae1961f859dd2962cea225f1ff307036 Mon Sep 17 00:00:00 2001 From: Benjamin Valentin Date: Sat, 16 Apr 2022 13:36:34 +0200 Subject: [PATCH 1/4] nanocoap_sock: support sending CoAP packet with payload snips --- sys/include/net/nanocoap.h | 6 +++++- sys/net/application_layer/nanocoap/nanocoap.c | 1 + sys/net/application_layer/nanocoap/sock.c | 18 ++++++++++++------ 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/sys/include/net/nanocoap.h b/sys/include/net/nanocoap.h index dc04b35f70ab..46e947dc50d0 100644 --- a/sys/include/net/nanocoap.h +++ b/sys/include/net/nanocoap.h @@ -88,6 +88,7 @@ #ifdef RIOT_VERSION #include "bitfield.h" #include "byteorder.h" +#include "iolist.h" #include "net/coap.h" #else #include "coap.h" @@ -200,6 +201,7 @@ typedef struct { * @deprecated Use coap_get_token(), * Will be removed after 2022.10. */ uint8_t *payload; /**< pointer to payload */ + iolist_t *snips; /**< payload snips (optional)*/ uint16_t payload_len; /**< length of payload */ uint16_t options_len; /**< length of options array */ coap_optpos_t options[CONFIG_NANOCOAP_NOPTS_MAX]; /**< option offset array */ @@ -430,7 +432,9 @@ static inline unsigned coap_get_total_hdr_len(const coap_pkt_t *pkt) } /** - * @brief Get the total length of a CoAP packet + * @brief Get the total length of a CoAP packet in the packet buffer + * + * @note This does not include possible payload snips. * * @param[in] pkt CoAP packet * diff --git a/sys/net/application_layer/nanocoap/nanocoap.c b/sys/net/application_layer/nanocoap/nanocoap.c index 97d6cab9ffa6..651dae479066 100644 --- a/sys/net/application_layer/nanocoap/nanocoap.c +++ b/sys/net/application_layer/nanocoap/nanocoap.c @@ -69,6 +69,7 @@ int coap_parse(coap_pkt_t *pkt, uint8_t *buf, size_t len) pkt->payload = NULL; pkt->payload_len = 0; memset(pkt->opt_crit, 0, sizeof(pkt->opt_crit)); + pkt->snips = NULL; if (len < sizeof(coap_hdr_t)) { DEBUG("msg too short\n"); diff --git a/sys/net/application_layer/nanocoap/sock.c b/sys/net/application_layer/nanocoap/sock.c index 2132fa9e5737..b2d3200d3505 100644 --- a/sys/net/application_layer/nanocoap/sock.c +++ b/sys/net/application_layer/nanocoap/sock.c @@ -121,15 +121,13 @@ ssize_t nanocoap_sock_request_cb(nanocoap_sock_t *sock, coap_pkt_t *pkt, coap_request_cb_t cb, void *arg) { ssize_t tmp, res = 0; - const void *pdu = pkt->hdr; - const size_t pdu_len = coap_get_total_len(pkt); const unsigned id = coap_get_id(pkt); void *payload, *ctx = NULL; const uint8_t *token = coap_get_token(pkt); uint8_t token_len = coap_get_token_len(pkt); + uint8_t state = STATE_SEND_REQUEST; - unsigned state = STATE_SEND_REQUEST; - + /* random timeout, deadline for receive retries */ uint32_t timeout = random_uint32_range(CONFIG_COAP_ACK_TIMEOUT_MS * US_PER_MS, CONFIG_COAP_ACK_TIMEOUT_MS * CONFIG_COAP_RANDOM_FACTOR_1000); uint32_t deadline = _deadline_from_interval(timeout); @@ -143,16 +141,24 @@ ssize_t nanocoap_sock_request_cb(nanocoap_sock_t *sock, coap_pkt_t *pkt, /* try receiving another packet without re-request */ bool retry_rx = false; + iolist_t head = { + .iol_next = pkt->snips, + .iol_base = pkt->hdr, + .iol_len = coap_get_total_len(pkt), + }; + while (1) { switch (state) { case STATE_SEND_REQUEST: - DEBUG("nanocoap: send %u bytes (%u tries left)\n", (unsigned)pdu_len, tries_left); + DEBUG("nanocoap: send %u bytes (%u tries left)\n", + (unsigned)iolist_size(&head), tries_left); + if (--tries_left == 0) { DEBUG("nanocoap: maximum retries reached\n"); return -ETIMEDOUT; } - res = sock_udp_send(sock, pdu, pdu_len, NULL); + res = sock_udp_sendv(sock, &head, NULL); if (res <= 0) { DEBUG("nanocoap: error sending coap request, %d\n", (int)res); return res; From b99d4b58bd227f067770dcc3d8e9e725584f28d5 Mon Sep 17 00:00:00 2001 From: Benjamin Valentin Date: Fri, 15 Apr 2022 23:01:33 +0200 Subject: [PATCH 2/4] nanocoap_sock: add nanocoap_sock_block_request() --- sys/include/net/nanocoap_sock.h | 93 +++++++++++++++++++++++ sys/net/application_layer/nanocoap/sock.c | 46 +++++++++++ 2 files changed, 139 insertions(+) diff --git a/sys/include/net/nanocoap_sock.h b/sys/include/net/nanocoap_sock.h index 88e8935edd7e..e930eac14668 100644 --- a/sys/include/net/nanocoap_sock.h +++ b/sys/include/net/nanocoap_sock.h @@ -134,6 +134,7 @@ #include "net/nanocoap.h" #include "net/sock/udp.h" +#include "net/sock/util.h" #ifdef __cplusplus extern "C" { @@ -145,6 +146,17 @@ extern "C" { */ typedef sock_udp_t nanocoap_sock_t; +/** + * @brief Blockwise request helper struct + */ +typedef struct { + nanocoap_sock_t sock; /**< socket used for the request */ + const char *path; /**< path on the server */ + uint32_t blknum; /**< current block number */ + uint8_t method; /**< request method (GET, POST, PUT) */ + uint8_t blksize; /**< CoAP blocksize exponent */ +} coap_block_request_t; + /** * @brief Start a nanocoap server instance * @@ -332,6 +344,87 @@ ssize_t nanocoap_request(coap_pkt_t *pkt, sock_udp_ep_t *local, ssize_t nanocoap_get(sock_udp_ep_t *remote, const char *path, void *buf, size_t len); +/** + * @brief Initialize block request context + * + * @param[out] ctx The block request context to initialize + * @param[in] remote Server endpoint + * @param[in] path Server path for request + * @param[in] method Request method (`COAP_METHOD_{GET|PUT|POST}`) + * @param[in] blksize Request blocksize exponent + * + * @retval 0 Success + * @retval <0 Error (see @ref nanocoap_sock_connect for details) + */ +static inline int nanocoap_block_request_init(coap_block_request_t *ctx, + sock_udp_ep_t *remote, + const char *path, + uint8_t method, + coap_blksize_t blksize) +{ + ctx->path = path; + ctx->blknum = 0; + ctx->method = method; + ctx->blksize = blksize; + return nanocoap_sock_connect(&ctx->sock, NULL, remote); +} + +/** + * @brief Initialize block request context by URL + * + * @param[out] ctx The block request context to initialize + * @param[in] url The request URL + * @param[in] method Request method (`COAP_METHOD_{GET|PUT|POST}`) + * @param[in] blksize Request blocksize exponent + * + * @retval 0 Success + * @retval <0 Error (see @ref nanocoap_sock_url_connect for details) + */ +static inline int nanocoap_block_request_init_url(coap_block_request_t *ctx, + const char *url, + uint8_t method, + coap_blksize_t blksize) +{ + ctx->path = sock_urlpath(url); + ctx->blknum = 0; + ctx->method = method; + ctx->blksize = blksize; + return nanocoap_sock_url_connect(url, &ctx->sock); +} + +/** + * @brief Free block request context + * + * @param[out] ctx The block request context to finalize + */ +static inline void nanocoap_block_request_done(coap_block_request_t *ctx) +{ + nanocoap_sock_close(&ctx->sock); +} + +/** + * @brief Do a block-wise request, send a single block. + * + * This method is expected to be called in a loop until all + * payload blocks have been transferred. + * + * @pre @p ctx was initialized with @ref nanocoap_block_request_init or + * @ref nanocoap_block_request_init_url + * + * @param[in] ctx blockwise request context + * @param[in] data payload to send + * @param[in] len payload length + * @param[in] more more blocks after this one + * (will be set automatically if @p len > block size) + * @param[in] cb callback for response + * @param[in] arg callback context + * + * @return Number of payload bytes written on success + * Negative error on failure + */ +int nanocoap_sock_block_request(coap_block_request_t *ctx, + const void *data, size_t len, bool more, + coap_request_cb_t cb, void *arg); #ifdef __cplusplus } #endif diff --git a/sys/net/application_layer/nanocoap/sock.c b/sys/net/application_layer/nanocoap/sock.c index b2d3200d3505..af959c9d6d49 100644 --- a/sys/net/application_layer/nanocoap/sock.c +++ b/sys/net/application_layer/nanocoap/sock.c @@ -392,6 +392,52 @@ static int _fetch_block(nanocoap_sock_t *sock, uint8_t *buf, size_t len, return nanocoap_sock_request_cb(sock, &pkt, _block_cb, ctx); } +int nanocoap_sock_block_request(coap_block_request_t *req, + const void *data, size_t len, bool more, + coap_request_cb_t callback, void *arg) +{ + /* clip the payload at the block size */ + if (len > coap_szx2size(req->blksize)) { + len = coap_szx2size(req->blksize); + more = true; + } + + int res; + uint8_t buf[CONFIG_NANOCOAP_BLOCK_HEADER_MAX]; + iolist_t snip = { + .iol_base = (void *)data, + .iol_len = len, + }; + + coap_pkt_t pkt = { + .hdr = (void *)buf, + .snips = &snip, + }; + + uint8_t *pktpos = (void *)pkt.hdr; + uint16_t lastonum = 0; + + pktpos += coap_build_hdr(pkt.hdr, COAP_TYPE_CON, NULL, 0, req->method, _get_id()); + pktpos += coap_opt_put_uri_pathquery(pktpos, &lastonum, req->path); + pktpos += coap_opt_put_uint(pktpos, lastonum, COAP_OPT_BLOCK1, + (req->blknum << 4) | req->blksize | (more ? 0x8 : 0)); + if (len) { + /* set payload marker */ + *pktpos++ = 0xFF; + } + + pkt.payload = pktpos; + pkt.payload_len = 0; + + res = nanocoap_sock_request_cb(&req->sock, &pkt, callback, arg); + if (res < 0) { + return res; + } + + ++req->blknum; + return len; +} + int nanocoap_sock_get_blockwise(nanocoap_sock_t *sock, const char *path, coap_blksize_t blksize, coap_blockwise_cb_t callback, void *arg) From 14b103d66bcad4e044d862a48f0dc4c9efcd3e60 Mon Sep 17 00:00:00 2001 From: Benjamin Valentin Date: Fri, 15 Apr 2022 23:02:24 +0200 Subject: [PATCH 3/4] tests/nanocoap_cli: add blockwise put test --- tests/nanocoap_cli/main.c | 2 ++ tests/nanocoap_cli/nanocli_client.c | 56 +++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/tests/nanocoap_cli/main.c b/tests/nanocoap_cli/main.c index 9e89d46071e7..66cdab023d46 100644 --- a/tests/nanocoap_cli/main.c +++ b/tests/nanocoap_cli/main.c @@ -31,11 +31,13 @@ static msg_t _main_msg_queue[MAIN_QUEUE_SIZE]; extern int nanotest_client_cmd(int argc, char **argv); extern int nanotest_client_url_cmd(int argc, char **argv); extern int nanotest_server_cmd(int argc, char **argv); +extern int nanotest_client_put_cmd(int argc, char **argv); static int _list_all_inet6(int argc, char **argv); static const shell_command_t shell_commands[] = { { "client", "CoAP client", nanotest_client_cmd }, { "url", "CoAP client URL request", nanotest_client_url_cmd }, + { "put", "experimental put", nanotest_client_put_cmd }, { "server", "CoAP server", nanotest_server_cmd }, { "inet6", "IPv6 addresses", _list_all_inet6 }, { NULL, NULL, NULL } diff --git a/tests/nanocoap_cli/nanocli_client.c b/tests/nanocoap_cli/nanocli_client.c index d0ce7ce02468..d8890e4258dd 100644 --- a/tests/nanocoap_cli/nanocli_client.c +++ b/tests/nanocoap_cli/nanocli_client.c @@ -29,6 +29,7 @@ #include "net/nanocoap.h" #include "net/nanocoap_sock.h" #include "net/sock/udp.h" +#include "net/sock/util.h" #include "od.h" @@ -210,3 +211,58 @@ int nanotest_client_url_cmd(int argc, char **argv) printf("usage: %s [data]\n", argv[0]); return -1; } + +static const char song[] = + "Join us now and share the software;\n" + "You'll be free, hackers, you'll be free.\n" + "Join us now and share the software;\n" + "You'll be free, hackers, you'll be free.\n" + "\n" + "Hoarders can get piles of money,\n" + "That is true, hackers, that is true.\n" + "But they cannot help their neighbors;\n" + "That's not good, hackers, that's not good.\n" + "\n" + "When we have enough free software\n" + "At our call, hackers, at our call,\n" + "We'll kick out those dirty licenses\n" + "Ever more, hackers, ever more.\n" + "\n" + "Join us now and share the software;\n" + "You'll be free, hackers, you'll be free.\n" + "Join us now and share the software;\n" + "You'll be free, hackers, you'll be free.\n"; + +int nanotest_client_put_cmd(int argc, char **argv) +{ + int res; + coap_block_request_t ctx; + + if (argc < 2) { + printf("usage: %s \n", argv[0]); + return 1; + } + + res = nanocoap_block_request_init_url(&ctx, argv[1], + COAP_METHOD_PUT, COAP_BLOCKSIZE_32); + if (res < 0) { + printf("error: %d\n", res); + return res; + } + + const char *pos = song; + size_t len = sizeof(song); + + while (len) { + res = nanocoap_sock_block_request(&ctx, pos, len, 0, NULL, NULL); + if (res <= 0) { + puts(strerror(-res)); + break; + } + len -= res; + pos += res; + } + + nanocoap_block_request_done(&ctx); + return res; +} From c20a1d04c8c66c54120cbe513dab4feb001a4add Mon Sep 17 00:00:00 2001 From: Benjamin Valentin Date: Tue, 17 May 2022 16:08:12 +0200 Subject: [PATCH 4/4] net/nanocoap: improve documentation of coap_pkt_t --- sys/include/net/nanocoap.h | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/sys/include/net/nanocoap.h b/sys/include/net/nanocoap.h index 46e947dc50d0..da7f80e37558 100644 --- a/sys/include/net/nanocoap.h +++ b/sys/include/net/nanocoap.h @@ -194,13 +194,27 @@ typedef struct { /** * @brief CoAP PDU parsing context structure + * + * When this struct is used to assemble the header, @p payload is used as the + * write pointer and @p payload_len contains the number of free bytes left in + * then packet buffer pointed to by @ref coap_pkt_t::hdr + * + * When the header was written, @p payload must not be changed, it must remain + * pointing to the end of the header. + * @p payload_len must then be set to the size of the payload that was further + * copied into the packet buffer, after the header. + * + * @ref coap_pkt_t::snips can be used to attach further payload buffers without copying them + * into the CoAP packet buffer. + * If there are any, they will be attached in order after the last payload byte + * (or header byte) in the original CoAP packet buffer. */ typedef struct { coap_hdr_t *hdr; /**< pointer to raw packet */ uint8_t *token; /**< pointer to token * @deprecated Use coap_get_token(), * Will be removed after 2022.10. */ - uint8_t *payload; /**< pointer to payload */ + uint8_t *payload; /**< pointer to end of the header */ iolist_t *snips; /**< payload snips (optional)*/ uint16_t payload_len; /**< length of payload */ uint16_t options_len; /**< length of options array */