Merge pull request #8932 from bergzand/pr/nanocoap/block2

nanocoap: add server-side block2 support
This commit is contained in:
Ken Bannister 2018-10-14 13:03:08 +00:00 committed by GitHub
commit 14c9b3062b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 326 additions and 14 deletions

View File

@ -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

View File

@ -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 },
};

View File

@ -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
@ -64,6 +89,8 @@ extern "C" {
*/
#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
*

View File

@ -546,10 +546,38 @@ 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);
}
@ -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)