-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Description
PR #13726 added functions to make it easier to write a payload when using the Packet API:
ssize_t coap_payload_put_bytes(coap_pkt_t *pkt, const void *data, size_t len);
ssize_t coap_payload_put_char(coap_pkt_t *pkt, char c);
These functions are similar to the nanocoap API for writing a blockwise payload, for example:
size_t coap_blockwise_put_bytes(coap_block_slicer_t *slicer, uint8_t *bufpos,
const uint8_t *c, size_t len);
This RFC provides an outline for a follow-up to #13726 to use the same functions to write either a full payload or a single block, by leveraging the coap_pkt_t
struct in the Packet API and the existing coap_blockwise...() functions. This reuse provides these advantages:
- Makes it almost transparent for a user application to switch between writing a full payload and blockwise processing
- Makes it safer to write a payload with use of the underlying coap_blockwise...() functions
- Simplifies other gcoap functions that currently don't have the context to know if a blockwise or full payload is being written, like
gcoap_get_resource_list()
andgcoap_encode_link()
coap_pkt_t
The first key to this proposal is addition of a pointer to the coap_block_slicer_t
from the coap_pkt_t
for use by the Packet API, which by convention means use of gcoap.
/* adds pointer to slicer; NULL initially */
typedef struct {
coap_hdr_t *hdr; /**< pointer to raw packet */
uint8_t *token; /**< pointer to token */
uint8_t *payload; /**< pointer to payload */
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 */
#ifdef MODULE_GCOAP
uint32_t observe_value; /**< observe value */
coap_block_slicer_t *slicer; /**< to help write payload */
#endif
} coap_pkt_t;
Since the coap_pkt_t
is part of all functions to write a packet, the use of the slicer can be transparent to the user. The coap_payload...() functions can pass the payload
pointer and payload_len
attributes to a slicer as shown in the next section.
Slicer use
The second key to this proposal is reuse of the the coap_blockwise...() functions within the coap_payload...() functions to write the payload. These functions use a coap_block_slicer_t
struct to manage writing the contents for a block. The diagram below shows an example payload divided into three blocks, including the use of the cur
, start
, and end
attributes -- which are offsets rather than position pointers. In this diagram the blockwise functions are about to write the 2nd block, between start
and end
.
+---------+---------+---------+
| block | block | block |
+---------+---------+---------+
^ ^ ^
cur start end
For each block, the blockwise functions pass over all of the functions to write the full payload. As the functions execute, the cur
offset is increased. The functions only write the block payload when cur
is between start
and end
.
We can leverage these functions when writing a single payload. The diagram below shows we simply set the start
and end
attributes to the offsets for the payload.
+-------------+
| payload |
+-------------+
^ ^
cur end
start
The advantage of this approach is that it automates management of the payload space, and will not write beyond the end of the payload. This approach means the user does not need to setup or check the remaining payload size until all functions for writing the payload have completed!
gcoap_encode_link() example
Below is the current implementation of gcoap_encode_link()
. Notice the first section determines the length to be written. The user must call this function twice -- once to determine the length, and once to write the payload.
ssize_t gcoap_encode_link(const coap_resource_t *resource, char *buf,
size_t maxlen, coap_link_encoder_ctx_t *context)
{
size_t path_len = strlen(resource->path);
/* count target separators and any link separator */
size_t exp_size = path_len + 2
+ ((context->flags & COAP_LINK_FLAG_INIT_RESLIST) ? 0 : 1);
if (buf) {
unsigned pos = 0;
if (exp_size > maxlen) {
return -1;
}
if (!(context->flags & COAP_LINK_FLAG_INIT_RESLIST)) {
buf[pos++] = ',';
}
buf[pos++] = '<';
memcpy(&buf[pos], resource->path, path_len);
buf[pos+path_len] = '>';
}
return exp_size;
}
The code above can be simplified as shown below with the approach in this proposal. This simpler code is safely managing the payload buffer, and will work both when writing a full payload and when writing blockwise.
ssize_t gcoap_encode_link2(coap_pkt_t *pdu, const coap_resource_t *resource,
coap_link_encoder_ctx_t *context)
{
uint8_t *start_pos = pdu->payload;
if (!(context->flags & COAP_LINK_FLAG_INIT_RESLIST)) {
coap_payload_put_char(pdu, ',');
}
coap_payload_put_char(pdu, '<');
coap_payload_put_bytes(pdu, resource->path, strlen(path_len));
ssize_t res = coap_payload_put_char(pdu, '>');
return res < 0 ? res : (ssize_t)(pdu->payload - start_pos);
}
Initialization
Currently for server request handling, a Packet API user first initializes the slicer and then adds the block to the pkt, like below.
coap_block2_init(pkt, &slicer);
...
coap_opt_add_block2(pkt, &slicer, 1);
So for our purposes, the pkt pointer to the slicer can be set transparently to the user within the generic coap_opt_add_block()
which coap_opt_add_block2()
inlines. Similarly the slicer pointer can be initialized for a client writing a blockwise request via coap_opt_add_block1()
.
When the user is writing the full payload rather than a single block, we can initialize the slicer struct in coap_opt_finish()
, which already is used to add the 0xFF payload marker.
Futures
One further point on addition of context attributes to coap_pkt_t
, like the slicer. Recently I have seen other requests for more context when writing a PDU. So we may develop a more generic context pointer attribute for a coap_pkt_t, and the slicer pointer might move to be within it. At any rate the slicer still will be accessible from the pkt. Any required code changes should be transparent at the user application level.