Skip to content

Commit

Permalink
Merge pull request RIOT-OS#11056 from kb2ma/coap/pkt_api_block_write
Browse files Browse the repository at this point in the history
net/gcoap: add/use Packet API Block implementation
  • Loading branch information
benpicco authored Sep 24, 2019
2 parents 0049e84 + 68ccf4b commit e942f86
Show file tree
Hide file tree
Showing 5 changed files with 268 additions and 3 deletions.
4 changes: 4 additions & 0 deletions examples/gcoap/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
40 changes: 40 additions & 0 deletions examples/gcoap/gcoap_cli.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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,
Expand All @@ -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 ---");
}
}
}

/*
Expand Down Expand Up @@ -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);
Expand Down
109 changes: 106 additions & 3 deletions sys/include/net/gcoap.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
*
Expand Down Expand Up @@ -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 ###
Expand Down
106 changes: 106 additions & 0 deletions sys/include/net/nanocoap.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
*
Expand All @@ -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
*
Expand Down
12 changes: 12 additions & 0 deletions sys/net/application_layer/nanocoap/nanocoap.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down

0 comments on commit e942f86

Please sign in to comment.