From 551b3513db124df0b83f5a04128caed915c9ee41 Mon Sep 17 00:00:00 2001 From: Koen Zandberg Date: Wed, 11 Apr 2018 22:25:01 +0200 Subject: [PATCH 1/2] nanocoap: Add server-side block2 support --- sys/include/net/nanocoap.h | 146 ++++++++++++++++- sys/net/application_layer/nanocoap/nanocoap.c | 153 ++++++++++++++++-- 2 files changed, 286 insertions(+), 13 deletions(-) diff --git a/sys/include/net/nanocoap.h b/sys/include/net/nanocoap.h index 5fadbaed9867..b2ba50e2222c 100644 --- a/sys/include/net/nanocoap.h +++ b/sys/include/net/nanocoap.h @@ -12,6 +12,31 @@ * @ingroup net * @brief Provides CoAP functionality optimized for minimal resource usage * + * # Create a Block-wise Response (Block2) + * + * Block-wise is a CoAP extension (RFC 7959) to divide a large payload across + * multiple physical packets. This section describes how to write a block-wise + * payload for a response, and is known as Block2. (Block1 is for a block-wise + * payload in a request.) See _riot_board_handler() in the nanocoap_server + * example for an example handler implementation. + * + * Start with coap_block2_init() to read the client request and initialize a + * coap_slicer_t struct with the size and location for this slice of the + * overall payload. Then write the block2 option in the response with + * coap_opt_put_block2(). The option includes an indicator ("more") that a + * slice completes the overall payload transfer. You may not know the value for + * _more_ at this point, but you must initialize the space in the packet for + * the option before writing the payload. The option is rewritten later. + * + * Next, use the coap_blockwise_put_xxx() functions to write the payload + * content. These functions use the coap_block_slicer_t to enable or disable + * actually writing the content, depending on the current position within the + * overall payload transfer. + * + * Finally, use the convenience function coap_block2_build_reply(), which + * finalizes the packet and calls coap_block2_finish() internally to update + * the block2 option. + * * @{ * * @file @@ -62,8 +87,10 @@ extern "C" { * @name Nanocoap specific maximum values * @{ */ -#define NANOCOAP_NOPTS_MAX (16) -#define NANOCOAP_URI_MAX (64) +#define NANOCOAP_NOPTS_MAX (16) +#define NANOCOAP_URI_MAX (64) +#define NANOCOAP_BLOCK_SIZE_EXP_MAX (6) /**< Maximum size for a blockwise + * transfer as power of 2 */ /** @} */ #ifdef MODULE_GCOAP @@ -145,6 +172,16 @@ typedef struct { 1 for more blocks coming */ } coap_block1_t; +/** + * @brief Blockwise transfer helper struct + */ +typedef struct { + size_t start; /**< Start offset of the current block */ + size_t end; /**< End offset of the current block */ + size_t cur; /**< Offset of the generated content */ + uint8_t *opt; /**< Pointer to the placed option */ +} coap_block_slicer_t; + /** * @brief Global CoAP resource list */ @@ -411,6 +448,17 @@ int coap_get_blockopt(coap_pkt_t *pkt, uint16_t option, uint32_t *blknum, unsign */ int coap_get_block1(coap_pkt_t *pkt, coap_block1_t *block1); +/** + * @brief Block2 option getter + * + * @param[in] pkt pkt to work on + * @param[out] block2 ptr to preallocated coap_block1_t structure + * + * @returns 0 if block2 option not present + * @returns 1 if structure has been filled + */ +int coap_get_block2(coap_pkt_t *pkt, coap_block1_t *block2); + /** * @brief Insert block1 option into buffer * @@ -490,6 +538,23 @@ ssize_t coap_opt_add_uint(coap_pkt_t *pkt, uint16_t optnum, uint32_t value); */ ssize_t coap_opt_finish(coap_pkt_t *pkt, uint16_t flags); +/** + * @brief Insert block2 option into buffer + * + * When calling this function to initialize a packet with a block2 option, the + * more flag must be set to prevent the creation of an option with a length too + * small to contain the size bit. + * + * @param[out] buf buffer to write to + * @param[in] lastonum number of previous option (for delta calculation), + * must be < 23 + * @param[in] slicer coap blockwise slicer helper struct + * @param[in] more more flag (1 or 0) + * + * @returns amount of bytes written to @p buf + */ +size_t coap_opt_put_block2(uint8_t *buf, uint16_t lastonum, coap_block_slicer_t *slicer, bool more); + /** * @brief Get content type from packet * @@ -604,6 +669,83 @@ static inline ssize_t coap_get_location_query(const coap_pkt_t *pkt, target, max_len, '&'); } +/** + * @brief Initialize a block2 slicer struct for writing the payload + * + * This function determines the size of the response payload based on the + * size requested by the client in @p pkt. + * + * @param[in] pkt packet to work on + * @param[out] slicer Preallocated slicer struct to fill + */ +void coap_block2_init(coap_pkt_t *pkt, coap_block_slicer_t *slicer); + +/** + * @brief Finish a block2 response + * + * This function finalizes the block2 response header + * + * Checks whether the `more` bit should be set in the block2 option and + * sets/clears it if required. Doesn't return the number of bytes as this + * overwrites bytes in the packet, it doesn't add new bytes to the packet. + * + * @param[inout] slicer Preallocated slicer struct to use + */ +void coap_block2_finish(coap_block_slicer_t *slicer); + +/** + * @brief Build reply to CoAP block2 request + * + * This function can be used to create a reply to a CoAP block2 request + * packet. In addition to @ref coap_build_reply, this function checks the + * block2 option and returns an error message to the client if necessary. + * + * @param[in] pkt packet to reply to + * @param[in] code reply code (e.g., COAP_CODE_204) + * @param[out] rbuf buffer to write reply to + * @param[in] rlen size of @p rbuf + * @param[in] payload_len length of payload + * @param[in] slicer slicer to use + * + * @returns size of reply packet on success + * @returns <0 on error + */ +ssize_t coap_block2_build_reply(coap_pkt_t *pkt, unsigned code, + uint8_t *rbuf, unsigned rlen, unsigned payload_len, + coap_block_slicer_t *slicer); + +/** + * @brief Add a single character to a block2 reply. + * + * This function is used to add single characters to a CoAP block2 reply. It + * checks whether the character should be added to the buffer and ignores it + * when the character is outside the current block2 request. + * + * @param[in] slicer slicer to use + * @param[in] bufpos pointer to the current payload buffer position + * @param[in] c character to write + * + * @returns Number of bytes writen to @p bufpos + */ +size_t coap_blockwise_put_char(coap_block_slicer_t *slicer, uint8_t *bufpos, char c); + +/** + * @brief Add a byte array to a block2 reply. + * + * This function is used to add an array of bytes to a CoAP block2 reply. it + * checks which parts of the string should be added to the reply and ignores + * parts that are outside the current block2 request. + * + * @param[in] slicer slicer to use + * @param[in] bufpos pointer to the current payload buffer position + * @param[in] c byte array to copy + * @param[in] len length of the byte array + * + * @returns Number of bytes writen to @p bufpos + */ +size_t coap_blockwise_put_bytes(coap_block_slicer_t *slicer, uint8_t *bufpos, + const uint8_t *c, size_t len); + /** * @brief Helper to decode SZX value to size in bytes * diff --git a/sys/net/application_layer/nanocoap/nanocoap.c b/sys/net/application_layer/nanocoap/nanocoap.c index 73e39ed15d08..0c9ebb25eef8 100644 --- a/sys/net/application_layer/nanocoap/nanocoap.c +++ b/sys/net/application_layer/nanocoap/nanocoap.c @@ -546,11 +546,39 @@ size_t coap_put_option_ct(uint8_t *buf, uint16_t lastonum, uint16_t content_type } } +static unsigned _size2szx(size_t size) +{ + unsigned szx = 0; + assert(size <= 1024); + + while (size) { + size = size >> 1; + szx++; + } + /* Size exponent + 1 */ + assert(szx >= 5); + return szx - 5; +} + +static unsigned _slicer_blknum(coap_block_slicer_t *slicer) +{ + size_t blksize = slicer->end - slicer->start; + size_t start = slicer->start; + unsigned blknum = 0; + + while (start > 0) { + start -= blksize; + blknum++; + } + return blknum; +} + static size_t coap_put_option_block(uint8_t *buf, uint16_t lastonum, unsigned blknum, unsigned szx, int more, uint16_t option) { uint32_t blkopt = (blknum << 4) | szx | (more ? 0x8 : 0); size_t olen = _encode_uint(&blkopt); - return coap_put_option(buf, lastonum, option, (uint8_t*)&blkopt, olen); + + return coap_put_option(buf, lastonum, option, (uint8_t *)&blkopt, olen); } size_t coap_put_option_block1(uint8_t *buf, uint16_t lastonum, unsigned blknum, unsigned szx, int more) @@ -562,6 +590,7 @@ int coap_get_block1(coap_pkt_t *pkt, coap_block1_t *block1) { uint32_t blknum; unsigned szx; + block1->more = coap_get_blockopt(pkt, COAP_OPT_BLOCK1, &blknum, &szx); if (block1->more >= 0) { block1->offset = blknum << (szx + 4); @@ -576,6 +605,13 @@ int coap_get_block1(coap_pkt_t *pkt, coap_block1_t *block1) return (block1->more >= 0); } +int coap_get_block2(coap_pkt_t *pkt, coap_block1_t *block2) +{ + block2->more = coap_get_blockopt(pkt, COAP_OPT_BLOCK2, &block2->blknum, + &block2->szx); + return (block2->more >= 0); +} + size_t coap_put_block1_ok(uint8_t *pkt_pos, coap_block1_t *block1, uint16_t lastonum) { if (block1->more >= 1) { @@ -586,6 +622,15 @@ size_t coap_put_block1_ok(uint8_t *pkt_pos, coap_block1_t *block1, uint16_t last } } +size_t coap_opt_put_block2(uint8_t *buf, uint16_t lastonum, coap_block_slicer_t *slicer, bool more) +{ + unsigned szx = _size2szx(slicer->end - slicer->start); + unsigned blknum = _slicer_blknum(slicer); + + slicer->opt = buf; + return coap_put_option_block(buf, lastonum, blknum, szx, more, COAP_OPT_BLOCK2); +} + size_t coap_opt_put_string(uint8_t *buf, uint16_t lastonum, uint16_t optnum, const char *string, char separator) { @@ -704,32 +749,118 @@ ssize_t coap_opt_finish(coap_pkt_t *pkt, uint16_t flags) return pkt->payload - (uint8_t *)pkt->hdr; } +void coap_block2_init(coap_pkt_t *pkt, coap_block_slicer_t *slicer) +{ + uint32_t blknum; + unsigned szx; + + /* Retrieve the block2 option from the client request */ + if (coap_get_blockopt(pkt, COAP_OPT_BLOCK2, &blknum, &szx) >= 0) { + /* Use the client requested block size if it is smaller than our own + * maximum block size */ + if (NANOCOAP_BLOCK_SIZE_EXP_MAX - 4 < szx) { + szx = NANOCOAP_BLOCK_SIZE_EXP_MAX - 4; + } + } + slicer->start = blknum * coap_szx2size(szx); + slicer->end = slicer->start + coap_szx2size(szx); + slicer->cur = 0; +} + +void coap_block2_finish(coap_block_slicer_t *slicer) +{ + assert(slicer->opt); + + /* The third parameter for _decode_value() points to the end of the header. + * We don't know this position, but we know we can read the option because + * it's already in the buffer. So just point past the option. */ + uint8_t *pos = slicer->opt + 1; + uint16_t delta = _decode_value(*slicer->opt >> 4, &pos, slicer->opt + 3); + int more = (slicer->cur > slicer->end) ? 1 : 0; + + coap_opt_put_block2(slicer->opt, COAP_OPT_BLOCK2 - delta, slicer, more); +} + +ssize_t coap_block2_build_reply(coap_pkt_t *pkt, unsigned code, + uint8_t *rbuf, unsigned rlen, unsigned payload_len, + coap_block_slicer_t *slicer) +{ + /* Check if the generated data filled the requested block */ + if (slicer->cur < slicer->start) { + return coap_build_reply(pkt, COAP_CODE_BAD_OPTION, rbuf, rlen, 0); + } + coap_block2_finish(slicer); + return coap_build_reply(pkt, code, rbuf, rlen, payload_len); +} + +size_t coap_blockwise_put_char(coap_block_slicer_t *slicer, uint8_t *bufpos, char c) +{ + /* Only copy the char if it is within the window */ + if ((slicer->start <= slicer->cur) && (slicer->cur < slicer->end)) { + *bufpos = c; + slicer->cur++; + return 1; + } + slicer->cur++; + return 0; +} + +size_t coap_blockwise_put_bytes(coap_block_slicer_t *slicer, uint8_t *bufpos, + const uint8_t *c, size_t len) +{ + size_t str_len = 0; /* Length of the string to copy */ + + /* Calculate start offset of the supplied string */ + size_t str_offset = (slicer->start > slicer->cur) + ? slicer->start - slicer->cur + : 0; + + /* Check for string before or beyond window */ + if ((slicer->cur >= slicer->end) || (str_offset > len)) { + slicer->cur += len; + return 0; + } + /* Check if string is over the end of the window */ + if ((slicer->cur + len) >= slicer->end) { + str_len = slicer->end - (slicer->cur + str_offset); + } + else { + str_len = len - str_offset; + } + + /* Only copy the relevant part of the string to the buffer */ + memcpy(bufpos, c + str_offset, str_len); + slicer->cur += len; + return str_len; +} + ssize_t coap_well_known_core_default_handler(coap_pkt_t *pkt, uint8_t *buf, \ size_t len, void *context) { (void)context; - + coap_block_slicer_t slicer; + coap_block2_init(pkt, &slicer); uint8_t *payload = buf + coap_get_total_hdr_len(pkt); - uint8_t *bufpos = payload; - bufpos += coap_put_option_ct(bufpos, 0, COAP_CT_LINK_FORMAT); + bufpos += coap_opt_put_block2(bufpos, COAP_OPT_CONTENT_FORMAT, &slicer, 1); + *bufpos++ = 0xff; for (unsigned i = 0; i < coap_resources_numof; i++) { if (i) { - *bufpos++ = ','; + bufpos += coap_blockwise_put_char(&slicer, bufpos, ','); } - *bufpos++ = '<'; + bufpos += coap_blockwise_put_char(&slicer, bufpos, '<'); unsigned url_len = strlen(coap_resources[i].path); - memcpy(bufpos, coap_resources[i].path, url_len); - bufpos += url_len; - *bufpos++ = '>'; + bufpos += coap_blockwise_put_bytes(&slicer, bufpos, + (uint8_t*)coap_resources[i].path, url_len); + bufpos += coap_blockwise_put_char(&slicer, bufpos, '>'); } unsigned payload_len = bufpos - payload; - - return coap_build_reply(pkt, COAP_CODE_205, buf, len, payload_len); + return coap_block2_build_reply(pkt, COAP_CODE_205, buf, len, payload_len, + &slicer); } unsigned coap_get_len(coap_pkt_t *pkt) From f3b4e44a267455b78e6e0d793f4102b6fb73c54f Mon Sep 17 00:00:00 2001 From: Koen Zandberg Date: Wed, 11 Apr 2018 22:25:16 +0200 Subject: [PATCH 2/2] examples/nanocoap: add blockwise block2 example --- examples/nanocoap_server/Makefile | 2 +- examples/nanocoap_server/coap_handler.c | 39 +++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/examples/nanocoap_server/Makefile b/examples/nanocoap_server/Makefile index 7b6e5fa1b9a4..76e1e5190be2 100644 --- a/examples/nanocoap_server/Makefile +++ b/examples/nanocoap_server/Makefile @@ -11,7 +11,7 @@ BOARD_INSUFFICIENT_MEMORY := arduino-duemilanove arduino-mega2560 arduino-uno \ chronos msb-430 msb-430h nucleo-f031k6 \ nucleo-f042k6 nucleo-l031k6 nucleo-f030r8 \ nucleo-f303k8 nucleo-l053r8 stm32f0discovery \ - telosb waspmote-pro z1 + telosb waspmote-pro wsn430-v1_3b wsn430-v1_4 z1 # Include packages that pull up and auto-init the link layer. # NOTE: 6LoWPAN will be included if IEEE802.15.4 devices are present diff --git a/examples/nanocoap_server/coap_handler.c b/examples/nanocoap_server/coap_handler.c index c8457beaf0b2..156747c839bf 100644 --- a/examples/nanocoap_server/coap_handler.c +++ b/examples/nanocoap_server/coap_handler.c @@ -17,6 +17,10 @@ /* internal value that can be read/written via CoAP */ static uint8_t internal_value = 0; +static const uint8_t block2_intro[] = "This is RIOT (Version: "; +static const uint8_t block2_board[] = " running on a "; +static const uint8_t block2_mcu[] = " board with a "; + static ssize_t _riot_board_handler(coap_pkt_t *pkt, uint8_t *buf, size_t len, void *context) { (void)context; @@ -24,6 +28,40 @@ static ssize_t _riot_board_handler(coap_pkt_t *pkt, uint8_t *buf, size_t len, vo 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, void *context) +{ + (void)context; + coap_block_slicer_t slicer; + coap_block2_init(pkt, &slicer); + uint8_t *payload = buf + coap_get_total_hdr_len(pkt); + + uint8_t *bufpos = payload; + + bufpos += coap_put_option_ct(bufpos, 0, COAP_FORMAT_TEXT); + bufpos += coap_opt_put_block2(bufpos, COAP_OPT_CONTENT_FORMAT, &slicer, 1); + *bufpos++ = 0xff; + + /* Add actual content */ + bufpos += coap_blockwise_put_bytes(&slicer, bufpos, block2_intro, sizeof(block2_intro)); + bufpos += coap_blockwise_put_bytes(&slicer, bufpos, (uint8_t*)RIOT_VERSION, sizeof(RIOT_VERSION)); + bufpos += coap_blockwise_put_char(&slicer, bufpos, ')'); + bufpos += coap_blockwise_put_bytes(&slicer, bufpos, block2_board, sizeof(block2_board)); + bufpos += coap_blockwise_put_bytes(&slicer, bufpos, (uint8_t*)RIOT_BOARD, sizeof(RIOT_BOARD)); + bufpos += coap_blockwise_put_bytes(&slicer, bufpos, block2_mcu, sizeof(block2_mcu)); + bufpos += coap_blockwise_put_bytes(&slicer, bufpos, (uint8_t*)RIOT_MCU, sizeof(RIOT_MCU)); + /* To demonstrate individual chars */ + bufpos += coap_blockwise_put_char(&slicer, bufpos, ' '); + bufpos += coap_blockwise_put_char(&slicer, bufpos, 'M'); + bufpos += coap_blockwise_put_char(&slicer, bufpos, 'C'); + bufpos += coap_blockwise_put_char(&slicer, bufpos, 'U'); + bufpos += coap_blockwise_put_char(&slicer, bufpos, '.'); + + + unsigned payload_len = bufpos - payload; + return coap_block2_build_reply(pkt, COAP_CODE_205, + buf, len, payload_len, &slicer); +} + static ssize_t _riot_value_handler(coap_pkt_t *pkt, uint8_t *buf, size_t len, void *context) { (void) context; @@ -109,6 +147,7 @@ const coap_resource_t coap_resources[] = { COAP_WELL_KNOWN_CORE_DEFAULT_HANDLER, { "/riot/board", COAP_GET, _riot_board_handler, NULL }, { "/riot/value", COAP_GET | COAP_PUT | COAP_POST, _riot_value_handler, NULL }, + { "/riot/ver", COAP_GET, _riot_block2_handler, NULL }, { "/sha256", COAP_POST, _sha256_handler, NULL }, };