Skip to content

Extending coap_payload_...() functions to write blockwise #13942

@kb2ma

Description

@kb2ma

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() and gcoap_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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Area: CoAPArea: Constrained Application Protocol implementationsDiscussion: RFCThe issue/PR is used as a discussion starting point about the item of the issue/PRType: enhancementThe issue suggests enhanceable parts / The PR enhances parts of the codebase / documentation

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions