diff --git a/examples/gcoap/Makefile b/examples/gcoap/Makefile index ef86ffed5d73..d82ef29123d7 100644 --- a/examples/gcoap/Makefile +++ b/examples/gcoap/Makefile @@ -26,6 +26,10 @@ BOARD_INSUFFICIENT_MEMORY := arduino-duemilanove arduino-leonardo \ #GCOAP_TOKENLEN = 2 #CFLAGS += -DGCOAP_TOKENLEN=$(GCOAP_TOKENLEN) +# Increase from default for confirmable block2 follow-on requests +GCOAP_RESEND_BUFS_MAX ?= 2 +CFLAGS += -DGCOAP_RESEND_BUFS_MAX=$(GCOAP_RESEND_BUFS_MAX) + # Include packages that pull up and auto-init the link layer. # NOTE: 6LoWPAN will be included if IEEE802.15.4 devices are present USEMODULE += gnrc_netdev_default diff --git a/examples/gcoap/gcoap_cli.c b/examples/gcoap/gcoap_cli.c index c09b05b1b30a..0f551214c3db 100644 --- a/examples/gcoap/gcoap_cli.c +++ b/examples/gcoap/gcoap_cli.c @@ -54,6 +54,12 @@ static gcoap_listener_t _listener = { NULL }; +/* Retain request path to re-request if response includes block. User must not + * start a new request (with a new path) until any blockwise transfer + * completes or times out. */ +#define _LAST_REQ_PATH_MAX (32) +static char _last_req_path[_LAST_REQ_PATH_MAX]; + /* Counts requests sent by CLI. */ static uint16_t req_count = 0; @@ -92,6 +98,11 @@ static void _resp_handler(unsigned req_state, coap_pkt_t* pdu, return; } + coap_block1_t block; + if (coap_get_block2(pdu, &block) && block.blknum == 0) { + puts("--- blockwise start ---"); + } + char *class_str = (coap_get_code_class(pdu) == COAP_CLASS_SUCCESS) ? "Success" : "Error"; printf("gcoap: response %s, code %1u.%02u", class_str, @@ -115,6 +126,30 @@ static void _resp_handler(unsigned req_state, coap_pkt_t* pdu, else { printf(", empty payload\n"); } + + /* ask for next block if present */ + if (coap_get_block2(pdu, &block)) { + if (block.more) { + unsigned msg_type = coap_get_type(pdu); + if (block.blknum == 0 && !strlen(_last_req_path)) { + puts("Path too long; can't complete blockwise"); + return; + } + + gcoap_req_init(pdu, (uint8_t *)pdu->hdr, GCOAP_PDU_BUF_SIZE, + COAP_METHOD_GET, _last_req_path); + if (msg_type == COAP_TYPE_ACK) { + coap_hdr_set_type(pdu->hdr, COAP_TYPE_CON); + } + block.blknum++; + coap_opt_add_block2_control(pdu, &block); + int len = coap_opt_finish(pdu, COAP_OPT_FINISH_NONE); + gcoap_req_send((uint8_t *)pdu->hdr, len, remote, _resp_handler); + } + else { + puts("--- blockwise complete ---"); + } + } } /* @@ -280,6 +315,11 @@ int gcoap_cli_cmd(int argc, char **argv) gcoap_req_init(&pdu, &buf[0], GCOAP_PDU_BUF_SIZE, code_pos+1, argv[apos+2]); coap_hdr_set_type(pdu.hdr, msg_type); + memset(_last_req_path, 0, _LAST_REQ_PATH_MAX); + if (strlen(argv[apos+2]) < _LAST_REQ_PATH_MAX) { + memcpy(_last_req_path, argv[apos+2], strlen(argv[apos+2])); + } + size_t paylen = (argc == apos + 4) ? strlen(argv[apos+3]) : 0; if (paylen) { coap_opt_add_format(&pdu, COAP_FORMAT_TEXT); diff --git a/sys/include/net/gcoap.h b/sys/include/net/gcoap.h index 4349bbefc0a4..c2bd10c93d1d 100644 --- a/sys/include/net/gcoap.h +++ b/sys/include/net/gcoap.h @@ -24,15 +24,18 @@ * port, which supports RFC 6282 compression. Internally, gcoap depends on the * nanocoap package for base level structs and functionality. * - * gcoap also supports the Observe extension (RFC 7641) for a server. gcoap - * provides functions to generate and send an observe notification that are - * similar to the functions to send a client request. + * gcoap supports the Observe extension (RFC 7641) for a server. gcoap provides + * functions to generate and send an observe notification that are similar to + * the functions to send a client request. gcoap also supports the Block + * extension (RFC 7959) with block-specific option functions as well as some + * helpers. * * *Contents* * * - Server Operation * - Client Operation * - Observe Server Operation + * - Block Operation * - Implementation Notes * - Implementation Status * @@ -193,6 +196,106 @@ * the Observe option value set to 1. The server does not support cancellation * via a reset (RST) response to a non-confirmable notification. * + * ## Block Operation ## + * + * gcoap provides for both server side and client side blockwise messaging for + * requests and responses. This section outlines how to write a message for + * each situation. + * + * ### CoAP server GET handling ### + * + * The server must slice the full response body into smaller payloads, and + * 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), + * which implements the sequence described below. + * + * - Use coap_block2_init() to initialize a _slicer_ struct from the Block2 + * option in the request. The slicer tracks boundaries while writing the + * payload. If no option present in the initial request, the init function + * defaults to a payload size of 16 bytes. + * - Use gcoap_resp_init() to begin the response. + * - Use coap_opt_add_block2() to write the Block2 option from the slicer. Use + * 1 as a default for the _more_ parameter. At this point, we don't know yet + * if this message will be the last in the block exchange. However, we must + * add the block option at this location in the message. + * - Use coap_opt_finish() to add a payload marker. + * - Add the payload using the `coap_blockwise_put_xxx()` functions. The slicer + * knows the current position in the overall body of the response. It writes + * only the portion of the body specified by the block number and block size + * in the slicer. + * - Finally, use coap_block2_finish() to finalize the block option with the + * proper value for the _more_ parameter. + * + * ### CoAP server PUT/POST handling ### + * + * 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), + * which implements the sequence described below. + * + * - Use coap_get_block1() to initialize a block1 struct from the request. + * - Determine the response code. If the block1 _more_ attribute is 1, use + * COAP_CODE_CONTINUE to request more responses. Otherwise, use + * COAP_CODE_CHANGED to indicate a successful transfer. + * - Use gcoap_resp_init() to begin the response, including the response code. + * - Use coap_opt_add_block1_control() to write the Block1 option. + * - Use coap_opt_finish() to determine the length of the PDU. If appropriate, + * use the COAP_OPT_FINISH_PAYLOAD parameter and then write the payload. + * + * ### CoAP client GET request ### + * + * The client requests a specific blockwise payload from the overall body by + * writing a Block2 option in the request. See _resp_handler() in the + * [gcoap](https://github.com/RIOT-OS/RIOT/blob/master/examples/gcoap/gcoap_cli.c) + * example in the RIOT distribution, which implements the sequence described + * below. + * + * - For the first request, use coap_block_object_init() to initialize a new + * block1 struct. For subsequent requests, first use coap_get_block2() to + * read the Block2 option in the response to the previous request. If the + * _more_ attribute indicates no more blocks, you are done. + * - The gcoap example actually does _not_ include a Block2 option in the + * original request, but the server response includes a blockwise response + * with a Block2 option anyway. On the other hand, this example shows how + * blockwise messaging can be supported in a generic way. + * - If more blocks are available, use gcoap_req_init() to create a new + * request. + * - Increment the _blknum_ attribute in the block1 struct from the previous + * response to request the next blockwise payload. + * - Use coap_opt_put_block2_control() to write the Block2 option to the + * request. + * - Use coap_opt_finish() to determine the length of the PDU. + * + * ### CoAP client PUT/POST request ### + * + * 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) + * example, which implements the sequence described below. + * + * - For the first request, use coap_block_slicer_init() to initialize a + * _slicer_ struct with the desired block number and block size. For + * subsequent requests, first read the response from the server to the + * previous request. If the response code is COAP_CODE_CONTINUE, then + * increment the last block number sent when initializing the slicer struct + * for the next request. + * - Use gcoap_req_init() to initialize the request. + * - Use coap_opt_add_block1() to add the Block1 option from the slicer. Use 1 + * as a default for the _more_ parameter. At this point, we don't know yet if + * this message will be the last in the block exchange. However, we must add + * the block option at this location in the message. + * - Use coap_opt_finish() with COAP_OPT_FINISH_PAYLOAD to write the payload + * marker. + * - Add the payload using the `coap_blockwise_put_xxx()` functions. The slicer + * knows the current position in the overall body of the response. It writes + * only the portion of the body specified by the block number and block size + * in the slicer. + * - Finally, use coap_block1_finish() to finalize the block option with the + * proper value for the _more_ parameter. + * * ## Implementation Notes ## * * ### Waiting for a response ### diff --git a/sys/include/net/nanocoap.h b/sys/include/net/nanocoap.h index 0e9637d6f027..6f4daa40f0cc 100644 --- a/sys/include/net/nanocoap.h +++ b/sys/include/net/nanocoap.h @@ -780,6 +780,75 @@ static inline unsigned coap_szx2size(unsigned szx) * * Use a coap_pkt_t struct to manage writing Options to the PDU. */ +/** + * @brief Add block option in descriptive use from a slicer object + * + * When calling this function to initialize a packet with a block option, the + * more flag must be set to prevent the creation of an option with a length too + * small to contain the size bit. + + * @post pkt.payload advanced to first byte after option + * @post pkt.payload_len reduced by option length + * + * @param[in,out] pkt pkt referencing target buffer + * @param[in] slicer coap blockwise slicer helper struct + * @param[in] more more flag (1 or 0) + * @param[in] option option number (block1 or block2) + * + * @return number of bytes written to buffer + * @return <0 on error + * @return -ENOSPC if no available options or insufficient buffer space + */ +ssize_t coap_opt_add_block(coap_pkt_t *pkt, coap_block_slicer_t *slicer, + bool more, uint16_t option); + +/** + * @brief Add block1 option in descriptive use from a slicer object + * + * When calling this function to initialize a packet with a block option, the + * more flag must be set to prevent the creation of an option with a length too + * small to contain the size bit. + + * @post pkt.payload advanced to first byte after option + * @post pkt.payload_len reduced by option length + * + * @param[in,out] pkt pkt referencing target buffer + * @param[in] slicer coap blockwise slicer helper struct + * @param[in] more more flag (1 or 0) + * + * @return number of bytes written to buffer + * @return <0 on error + * @return -ENOSPC if no available options or insufficient buffer space + */ +static inline ssize_t coap_opt_add_block1(coap_pkt_t *pkt, + coap_block_slicer_t *slicer, bool more) +{ + return coap_opt_add_block(pkt, slicer, more, COAP_OPT_BLOCK1); +} + +/** + * @brief Add block2 option in descriptive use from a slicer object + * + * When calling this function to initialize a packet with a block option, the + * more flag must be set to prevent the creation of an option with a length too + * small to contain the size bit. + + * @post pkt.payload advanced to first byte after option + * @post pkt.payload_len reduced by option length + * + * @param[in,out] pkt pkt referencing target buffer + * @param[in] slicer coap blockwise slicer helper struct + * @param[in] more more flag (1 or 0) + * + * @return number of bytes written to buffer + * @return <0 on error + * @return -ENOSPC if no available options or insufficient buffer space + */ +static inline ssize_t coap_opt_add_block2(coap_pkt_t *pkt, + coap_block_slicer_t *slicer, bool more) +{ + return coap_opt_add_block(pkt, slicer, more, COAP_OPT_BLOCK2); +} /** * @brief Encode the given uint option into pkt * @@ -796,6 +865,43 @@ static inline unsigned coap_szx2size(unsigned szx) */ ssize_t coap_opt_add_uint(coap_pkt_t *pkt, uint16_t optnum, uint32_t value); +/** + * @brief Encode the given block1 option in control use + * + * @post pkt.payload advanced to first byte after option + * @post pkt.payload_len reduced by option length + * + * @param[in,out] pkt pkt referencing target buffer + * @param[in] block block to encode + * + * @return number of bytes written to buffer + * @return <0 on error + * @return -ENOSPC if no available options or insufficient buffer space + */ +static inline ssize_t coap_opt_add_block1_control(coap_pkt_t *pkt, coap_block1_t *block) { + return coap_opt_add_uint(pkt, COAP_OPT_BLOCK1, + (block->blknum << 4) | block->szx | (block->more ? 0x8 : 0)); +} + +/** + * @brief Encode the given block2 option in control use + * + * @post pkt.payload advanced to first byte after option + * @post pkt.payload_len reduced by option length + * + * @param[in,out] pkt pkt referencing target buffer + * @param[in] block block to encode + * + * @return number of bytes written to buffer + * @return <0 on error + * @return -ENOSPC if no available options or insufficient buffer space + */ +static inline ssize_t coap_opt_add_block2_control(coap_pkt_t *pkt, coap_block1_t *block) { + /* block.more must be zero, so no need to 'or' it in */ + return coap_opt_add_uint(pkt, COAP_OPT_BLOCK2, + (block->blknum << 4) | block->szx); +} + /** * @brief Append a Content-Format option to the pkt buffer * diff --git a/sys/net/application_layer/nanocoap/nanocoap.c b/sys/net/application_layer/nanocoap/nanocoap.c index cfae4cffeb02..f5090e2a77e8 100644 --- a/sys/net/application_layer/nanocoap/nanocoap.c +++ b/sys/net/application_layer/nanocoap/nanocoap.c @@ -897,6 +897,18 @@ ssize_t coap_opt_add_uint(coap_pkt_t *pkt, uint16_t optnum, uint32_t value) return _add_opt_pkt(pkt, optnum, (uint8_t *)&tmp, tmp_len); } +ssize_t coap_opt_add_block(coap_pkt_t *pkt, coap_block_slicer_t *slicer, + bool more, uint16_t option) +{ + uint32_t blkopt = (_slicer_blknum(slicer) << 4); + blkopt |= _size2szx(slicer->end - slicer->start); + blkopt |= (more ? 0x8 : 0); + + slicer->opt = pkt->payload; + + return coap_opt_add_uint(pkt, option, blkopt); +} + ssize_t coap_opt_finish(coap_pkt_t *pkt, uint16_t flags) { if (flags & COAP_OPT_FINISH_PAYLOAD) {