COAP_BLOCK(3)

πŸ’… okay so this is COAP_BLOCK(3) from the libcoap Manual πŸ’…

Bestie, welcome to the block-wise transfer page β€” it's giving "big payload, multiple packets, main character energy." This is part 1, and there are more parts coming, so don't go anywhere. ✨


🌸 NAME 🌸

coap_block, coap_context_set_block_mode, coap_context_set_max_block_size, coap_add_data_large_request, coap_add_data_large_response, coap_get_data_large, coap_block_build_body, coap_q_block_is_supported β€” literally all the functions for working with CoAP Blocks. 🧚


✨ SYNOPSIS ✨

#include <coap3/coap.h>

void coap_context_set_block_mode(coap_context_t *context, uint32_t
block_mode);

int coap_context_set_max_block_size(coap_context_t *context, size_t
max_block_size);

int coap_add_data_large_request(coap_session_t *session, coap_pdu_t
*pdu, size_t length, const uint8_t *data, coap_release_large_data_t
release_func, void *app_ptr);

int coap_add_data_large_response(coap_resource_t *resource,
coap_session_t *session, const coap_pdu_t *request, coap_pdu_t
*response, const coap_string_t *query, uint16_t media_type, int maxage,
uint64_t etag, size_t length, const uint8_t *data,
coap_release_large_data_t release_func, void *app_ptr);

int coap_get_data_large(const coap_pdu_t *pdu, size_t *length, const
uint8_t **_data, size_t *offset, size_t *total);

coap_binary_t *coap_block_build_body(coap_binary_t *body_data, size_t
length, const uint8_t *data, size_t offset, size_t total);

int coap_q_block_is_supported(void);

And for specific (D)TLS library support, you link with one of these besties:

-lcoap-3-notls,
-lcoap-3-gnutls,
-lcoap-3-openssl,
-lcoap-3-mbedtls,
-lcoap-3-wolfssl

or

-lcoap-3-tinydtls

Otherwise, you link with -lcoap-3 to get the default (D)TLS library support. Slay. πŸ’…


πŸ¦‹ DESCRIPTION πŸ¦‹

Okay so regular setting up of a PDU and transmission is covered in coap_pdu_setup(3), where all the payload data can fit in a single packet. Casual. But this man page? This one covers how to work with PDUs where the overall body of information may need to be split across several packets by using CoAP Block-Wise Transfers (RFC7959 and RFC9177). It's giving "the payload is too big for one packet, so we're doing this in chunks." πŸ”₯

The block-wise transfers can be controlled by the application, or libcoap is instructed to do all the requests for the next blocks and only present the final body of the result to the application. In summary, the following three ways handle processing a body of data that has to be split across multiple payloads (blocks).

  1. Application does all the work

It is the responsibility of the application to analyze each block transmission at receipt and then generate the next request as per RFC7959. In this case, coap_context_set_block_mode() function must not be called to maintain backward compatibility with applications that did the block handling within the application.

  1. Application sees individual blocks

By calling coap_context_set_block_mode(context, COAP_BLOCK_USE_LIBCOAP) and using the appropriate functions, the requests for the next block of data is handled automatically by the libcoap layer. Each individual block of data is presented to the application for processing.

By calling coap_get_data_large(), the application can determine if this is the first block or not (using offset value), whether the first block is all the data (offset = 0, length = total) and whether this is the last block (offset + length = total). It is the responsibility of the application to re-assemble the individual blocks into a single body of data.

NOTE: total is only an approximation (it will be > offset + length) until the final block is received.

If this is the request handler in a server, the server still needs to return a COAP_RESPONSE_CODE_CONTINUE 2.31 (Continue) response code if the received data is not for the final block, otherwise a COAP_RESPONSE_CODE_CREATED 2.01 (Created) or COAP_RESPONSE_CODE_CHANGED 2.04 (Changed) should be returned. No cap. πŸ’€

  1. Application only sees all of the body

By calling coap_context_set_block_mode(context, COAP_BLOCK_USE_LIBCOAP|COAP_BLOCK_SINGLE_BODY) and using the appropriate functions, the requests for all the blocks of data is handled automatically by the libcoap layer. Only the complete body of the data is presented to the application, unless there is an error.

coap_get_data_large() will only return the entire body of data (offset always 0, length = total) and there is no need to re-assemble individual blocks into a large body of data.

In RAM constrained environments, option 2 may be the preferred method. Lowkey important. 🫢

This man page focuses on getting libcoap to do all the work, not how to do it all in the application.

However, if the client supplies a Block1 or Block2 Option in a GET request type PDU where the block number is not 0, this is assumed to be a random access request and any other blocks will not be requested by libcoap even if instructed otherwise.

The functions that are named _large are intended as replacements for the equivalent functions as described in coap_pdu_setup(3).


🫢 CALLBACK HANDLER 🫢

Callback Type: coap_release_large_data_t

/**
 * Callback handler for de-allocating the data based on @p app_ptr provided to
 * coap_add_data_large_*() functions following transmission of the supplied
 * data.
 *
 * @param session The session that this data is associated with
 * @param app_ptr The application provided pointer to the
 *                coap_add_data_large_*() functions
 */
typedef void (*coap_release_large_data_t)(coap_session_t *session,
                                          void *app_ptr);

✨ FUNCTIONS ✨

πŸ’… coap_context_set_block_mode()

okay so coap_context_set_block_mode() is the function that sets up the context level block_mode block-handling bits β€” the ones that make RFC7959 block_mode flows actually work. These bits get pushed down to a session when that session is created, and if the peer doesn't support the respective block mode, then the appropriate bit may get disabled in the session block_mode. bestie it literally downgrades itself for you, so polite 🫢

#define COAP_BLOCK_USE_LIBCOAP   0x01 /* Use libcoap to do block requests */
#define COAP_BLOCK_SINGLE_BODY   0x02 /* Deliver the data as a single body */
#define COAP_BLOCK_TRY_Q_BLOCK   0x04 /* Try Q-Block method */
#define COAP_BLOCK_USE_M_Q_BLOCK 0x08 /* (Client) Use M bit when recovering Q-Block2 */
#define COAP_BLOCK_NO_PREEMPTIVE_RTAG 0x10 /* (Client) Don't use pre-emptive Request-Tags */
#define COAP_BLOCK_STLESS_FETCH  0x20 /* (Client) Assume server supports stateless FETCH */
#define COAP_BLOCK_STLESS_BLOCK2 0x40 /* (Server) Server is stateless for Block2 transfers */
#define COAP_BLOCK_NOT_RANDOM_BLOCK1 0x80 /* (Server) Disable server handling random order block1 */

So block_mode is an or'd set of zero or more COAP_BLOCK_* definitions. zero is allowed, we love a low-effort queen πŸ’…

If COAP_BLOCK_USE_LIBCOAP is not set, then everything works as per Option 1 above. no cap.

If COAP_BLOCK_SINGLE_BODY is set, then the entire body of data is presented to the receiving handler β€” the whole thing, one glorious delivery ✨ Otherwise each individual block is presented on arrival, like, one block at a time, very snacky πŸ“

To obtain the data, length and current offset, coap_get_data_large() must be used instead of coap_get_data(). and ngl it may be appropriate not to set COAP_BLOCK_SINGLE_BODY if there are RAM limitations, so if your device is running on vibes and 4KB, keep that in mind 😭

NOTE: it is the responsibility of the receiving application to re-assemble the data as appropriate (e.g., using coap_block_build_body()) if COAP_BLOCK_SINGLE_BODY is not set. the library will not do it for you, she's busy πŸ’€
NOTE: if COAP_BLOCK_SINGLE_BODY is not set, then the CoAP server on receiving request data that is split over multiple data blocks must respond with COAP_RESPONSE_CODE_CONTINUE 2.31 (Continue) response code if the received data is not for the final block β€” otherwise a COAP_RESPONSE_CODE_CREATED 2.01 (Created) or COAP_RESPONSE_CODE_CHANGED 2.04 (Changed) should be returned. like, be honest with the client about where you're at, communication is key 🫢

To indicate support for Q-Block-1 and Q-Block2, COAP_BLOCK_TRY_Q_BLOCK needs to be set on both the client and server β€” both sides, no solo acts πŸŽ€ And COAP_BLOCK_SINGLE_BODY is assumed to be set if using Q-Block, because the data will always be presented as a single body. If COAP_BLOCK_USE_M_Q_BLOCK is defined, then the M bit version of recovery will be used if possible. iconic.

If COAP_BLOCK_USE_LIBCOAP is set, then any PDUs presented to the application handlers will get the tokens set back to the initiating token β€” so requests can be matched with responses even if different tokens had to be used for the series of packet interchanges πŸ‘‘ and furthermore, if COAP_BLOCK_SINGLE_BODY is set, then the PDU that presents the entire body will have any BlockX or Q-BlockX option removed. clean. every time.

NOTE: COAP_BLOCK_USE_LIBCOAP must be set if libcoap is to do all the block tracking and requesting β€” otherwise the application will have to do all of this work (and that's the default if coap_context_set_block_mode() is not called). so like, if you skip this call, you're signing up to do it manually. main character energy but the exhausting kind 😭

If COAP_BLOCK_NO_PREEMPTIVE_RTAG is set, then Request-Tag options are only sent when a large amount of data is being sent to the server using the Block1 option. Otherwise, a Request-Tag option is sent with any request (apart from DELETE) on the off chance that there may be multiple Block2 based responses for multiple requests to the same resource that need to be differentiated between. she's just being prepared okay πŸ™ˆ

If COAP_BLOCK_STLESS_FETCH is set, then it is assumed that the server supports stateless support for FETCH requests β€” in other words, the FETCH request always includes the FETCH data when requesting the next block of data from the server when using Block2. no memory, no problem ✨

If COAP_BLOCK_STLESS_BLOCK2 is set, then the server does not maintain state for any data returned that uses Block2 to split up the data chunks. The application is called for every request to get the complete set of data, which is then split into the separate Block2 responses as appropriate. lowkey the most "we don't keep receipts" setting ever πŸ’…

If COAP_BLOCK_NOT_RANDOM_BLOCK1 is set, then the server does not allow any Block1 packets arriving in the wrong block number order β€” it just returns 4.08 error codes. ngl normally this should not be set, because networks can re-order the arrival of UDP packets β€” in particular this may happen if NON is being used for Block1 by the client. messy but real 😭

NOTE: this is ignored if Q-Block1 is being used. periodt.

πŸ¦‹ coap_context_set_max_block_size()

coap_context_set_max_block_size() sets the max_block_size in the context β€” i.e. the biggest block a server supports when the Block1 or Block2 options are used. and bestie, this must be set before a server session is created. timing is everything, she's not flexible on this πŸŽ€


🌸 coap_add_data_large_request()

coap_add_data_large_request() is basically coap_add_data()'s taller, more capable sister β€” it's similar to coap_add_data(), but supports the transmission of data that has a body size that is potentially larger than can be fitted into a single client request PDU. the specified payload data of length length is associated with the session, with the first block of data added to the PDU pdu, along with the appropriate CoAP options such as (Q-)Block1, Size1 and Request-Tag if the data does not fit in a single PDU. iconic behavior honestly πŸ‘‘

When the block receipt has been acknowledged by the peer, the library will then send the next block of data β€” and it keeps going until all the data has been transmitted. she's consistent, she's committed, she shows up 🌈

The data passed to coap_add_data_large_request() must exist until all blocks have been transmitted. The callback function release_func can be used to release storage that has been dynamically allocated to hold the transmit data. If not NULL, the callback function is called once the final block of data has been transmitted. The user-defined parameter app_ptr is the same value that was passed to coap_add_data_large_request(). And even if there is an error return, release_func (if set) is always called. loyalty fr fr 🫢

NOTE: this function must only be called once per pdu. once. one time. don't be weird about it πŸ’€
NOTE: options cannot be added to the pdu after coap_add_data_large_request() is called. the door closes, that's it πŸšͺ

🧚 coap_add_data_large_response()

coap_add_data_large_response() is responsible for handling the server's large responses to requests. Use it as a direct replacement for coap_add_data() if it is possible that the length of data will not fit in a single server's response pdu. This function adds in the initial part of the payload data of length length to the PDU pdu. slay.

The data passed to coap_add_data_large_response() must exist until all blocks have been transmitted. The callback function release_func can be used to release storage that has been dynamically allocated to hold the transmit data. If not NULL, the callback function is called once the final block of data has been transmitted. The user-defined parameter app_ptr is the same value that was passed to coap_add_data_large_response(). And even if there is an error return, release_func (if set) is always called. same deal as her sister function πŸ’œ

It also adds in the appropriate CoAP options such as Block2, Size2 and ETag to handle block-wise transfer if the data does not fit in a single PDU. the whole toolkit, no notes ✨

resource, query, session, request, and response are the same parameters as in the called resource handler that invokes coap_add_data_large_response(). If etag is 0, then a unique ETag value will be generated β€” otherwise it's the ETag value to use. The media_type is for the format of the data, and maxage defines the lifetime of the response. If maxage is set to -1, then the Max-Age option does not get included (which indicates the default value of 60 seconds according to "RFC7252 5.6.1. Freshness Model"). so -1 means "no opinion, default it", got it πŸ“

The application request handler for the resource is only called once instead of potentially multiple times. one and done, we love efficiency πŸ‘‘

NOTE: this function must only be called once per pdu. same rule, different function πŸ’…
NOTE: options cannot be added to the pdu after coap_add_data_large_request() is called. (yes, it says coap_add_data_large_request() right there in the manpage for this one too β€” quoting it exactly, no cap 😭)

⭐ coap_get_data_large()

coap_get_data_large() abstracts out of the pdu the info about the received data β€” it updates length with the length of data available, data with a pointer to where the data is located, offset with where this block of data starts, and total with the total amount of data. πŸŽ€

offset will always be zero if block_mode includes COAP_BLOCK_SINGLE_BODY. All of the body's data has been received if "offset + length == total". that's the check bestie, write it down πŸ–ŠοΈ

NOTE: total is potentially only an indication of the total size of the body, and is only exact when all of the data has been received. so don't @ her for an estimate πŸ™ˆ

πŸ’– coap_block_build_body()

coap_block_build_body() re-assembles the received data as returned by coap_get_data_large() into a single blob of data. Data from data of length length starting from offset offset is added to body_data. The resultant state of body_data is returned. If body_data is NULL, or total is larger than the current size of body_data, then body_data is re-allocated and returned. If there is an error, body_data gets de-allocated. it giveth and it taketh away 😭

If block_mode (as set by coap_context_set_block_mode()) includes COAP_BLOCK_SINGLE_BODY, then the request/response handler will only get called once with the entire body containing the data from all of the individual blocks. and if there is a change of data during the blocks receipt (e.g. ETag value changes), then the entire set of data is re-requested and the partial body dropped. she noticed, she's starting over, she's not settling πŸ”₯


🌺 coap_q_block_is_supported()

coap_q_block_is_supported() figures out whether libcoap has been build with Q-Block support or not. (yes, "build" β€” that's literally how it's written in the manpage, i'm not touching it πŸ’€) a little vibe check on your own binary, honestly iconic 🌈


πŸ‘‘ RETURN VALUES πŸ‘‘

coap_add_data_large_request(), coap_add_data_large_response(), and coap_get_data_large() return 0 on failure, 1 on success. binary queen πŸ’…

coap_block_build_body() returns the current state of the body's data (which may have some missing gaps) or NULL on error. gaps are possible, she's doing her best 🫢

coap_context_set_max_block_size() returns 0 on failure, 1 on success.

coap_q_block_is_supported() returns 0 on failure, 1 on success.


πŸŽ€ EXAMPLES πŸŽ€

okay bestie, this is the part where we actually look at the code, and honestly? it's giving "learn by osmosis" and I'm obsessed. two full examples, coming right up. πŸ’–

πŸš€ Setup PDU and Transmit πŸš€

this first one is the whole "build a PDU and yeet it out the door" situation. token, Uri-Path, Uri-Query, Observe, options, data β€” all of it. no cap, it's a lot, but it's all here:

#include <coap3/coap.h>

static int
build_send_pdu(coap_context_t *context, coap_session_t *session,
               uint8_t msgtype, uint8_t request_code, const char *path,
               const char *query, unsigned char *data, size_t length,
               int observe) {
  coap_pdu_t *pdu;
  uint8_t buf[8];
  size_t buflen;
  coap_optlist_t *optlist_chain = NULL;
  /* Remove (void) definition if variable is used */
  (void)context;

  /* Create the pdu with the appropriate options */
  pdu = coap_pdu_init(msgtype, request_code, coap_new_message_id(session),
                      coap_session_max_pdu_size(session));
  if (!pdu)
    return 0;

  /*
   * Create unique token for this request for handling unsolicited /
   * delayed responses
   */
  coap_session_new_token(session, &buflen, buf);
  if (!coap_add_token(pdu, buflen, buf)) {
    coap_log_debug("cannot add token to request\n");
    goto error;
  }

  if (path) {
    /* Add in the Uri-Path options */
    if (!coap_path_into_optlist((const uint8_t *)path, strlen(path),
                                COAP_OPTION_URI_PATH, &optlist_chain))
      goto error;
  }

  if (query) {
    /* Add in the Uri-Query options */
    if (!coap_query_into_optlist((const uint8_t *)query, strlen(query),
                                 COAP_OPTION_URI_QUERY, &optlist_chain))
      goto error;
  }

  if (request_code == COAP_REQUEST_GET && observe) {
    /* Indicate that we want to observe this resource */
    if (!coap_insert_optlist(&optlist_chain,
                             coap_new_optlist(COAP_OPTION_OBSERVE,
                                              coap_encode_var_safe(buf, sizeof(buf),
                                                  COAP_OBSERVE_ESTABLISH), buf)
                            ))
      goto error;
  }

  /* ... Other code / options etc. ... */

  /* Add in all the options (after internal sorting) to the pdu */
  if (!coap_add_optlist_pdu(pdu, &optlist_chain))
    goto error;

  if (data && length) {
    /* Add in the specified data */
    if (!coap_add_data_large_request(session, pdu, length, data, NULL, NULL))
      goto error;
  }

  if (coap_send(session, pdu) == COAP_INVALID_MID)
    goto error;
  coap_delete_optlist(optlist_chain);
  return 1;

error:

  if (pdu)
    coap_delete_pdu(pdu);
  coap_delete_optlist(optlist_chain);
  return 0;

}

int
main(int argc, char *argv[]) {
  coap_context_t *context = NULL;
  coap_session_t *session = NULL;
  unsigned char *data = NULL;
  size_t data_length = 0;

  (void)argc;
  (void)argv;

  /* Initialize libcoap library */
  coap_startup();

  /* ... Set up context, session etc. ... */

  /* Set up using libcoap to do the block work */
  coap_context_set_block_mode(context,
                              COAP_BLOCK_USE_LIBCOAP | COAP_BLOCK_SINGLE_BODY);

  /* ... Other code etc. ... */

  /* .. build data and define data_length ... */

  build_send_pdu(context, session, COAP_MESSAGE_CON, COAP_REQUEST_PUT,
                 "/example/uri", NULL, data, data_length, 0);

  /* ... Other code etc. ... */

  coap_cleanup();
  return 0;
}

the way this example just casually sets COAP_BLOCK_USE_LIBCOAP | COAP_BLOCK_SINGLE_BODY with coap_context_set_block_mode()? main character energy. also notice the error: label doing its cleanup moment β€” very "i said what i said" of it. πŸ’…

🌸 Resource Request Handler Response PDU Update 🌸

and here's the server-side glow-up: a time resource, a GET handler, and a response PDU getting updated. the comments are literally telling you what's being added internally, so read them, bestie:

#include <coap3/coap.h>

#include <stdio.h>

static void
hnd_get_time(coap_resource_t *resource, coap_session_t *session,
             const coap_pdu_t *request, const coap_string_t *query,
             coap_pdu_t *response) {
  unsigned char buf[40];
  size_t len;
  time_t now;

  /* ... Additional analysis code for resource, request pdu etc.  ... */

  /* After analysis, generate a failure response and return if needed */

  now = time(NULL);

  if (query != NULL && coap_string_equal(query, coap_make_str_const("secs"))) {
    /* Output secs since Jan 1 1970 */
    len = snprintf((char *)buf, sizeof(buf), "%lu", now);
  } else {
    /* Output human-readable time */
    struct tm *tmp;
    tmp = gmtime(&now);
    if (!tmp) {
      /* If 'now' is not valid */
      coap_pdu_set_code(response, COAP_RESPONSE_CODE_NOT_FOUND);
      return;
    }
    len = strftime((char *)buf, sizeof(buf), "%b %d %H:%M:%S", tmp);
  }
  coap_pdu_set_code(response, COAP_RESPONSE_CODE_CONTENT);
  /*
   * Invoke coap_add_data_large_response() to do all the hard work.
   * [A good practice, even though ins this case, the amount of data is small]
   *
   * Define the format - COAP_MEDIATYPE_TEXT_PLAIN - to add in
   * Define how long this response is valid for (secs) - 1 - to add in.
   *
   * Observe Option added internally if needed within the function
   * Block2 Option added internally if output too large
   * Size2 Option added internally
   * ETag Option added internally
   */
  coap_add_data_large_response(resource, session, request, response,
                               query, COAP_MEDIATYPE_TEXT_PLAIN, 1, 0,
                               len,
                               buf,
                               NULL, NULL);
  /*
   * When request handler returns, the response pdu will get automatically
   * sent, unless the pdu code is not updated and this is a NON or TCP based
   * request.
   */
}

int
main(int argc, char *argv[]) {
  coap_context_t *context = NULL;
  coap_resource_t *r;
  coap_resource_t *time_resource;
  int not_exit = 1;

  /* Initialize libcoap library */
  coap_startup();

  (void)argc;
  (void)argv;

  /* ... Set up context etc. ... */

  /* Set up using libcoap to do the block work */
  coap_context_set_block_mode(context,
                              COAP_BLOCK_USE_LIBCOAP | COAP_BLOCK_SINGLE_BODY);

  /* Create a resource to return time */
  r = coap_resource_init(coap_make_str_const("time"),
                         COAP_RESOURCE_FLAGS_NOTIFY_CON);
  coap_resource_set_get_observable(r, 1);
  coap_register_request_handler(r, COAP_REQUEST_GET, hnd_get_time);

  /* Document resource for 'time' request */
  coap_add_attr(r, coap_make_str_const("ct"), coap_make_str_const("0"), 0);
  coap_add_attr(r, coap_make_str_const("title"),
                coap_make_str_const("\"Internal Clock\""), 0);
  coap_add_attr(r, coap_make_str_const("rt"), coap_make_str_const("\"secs\""),
                0);
  coap_add_attr(r, coap_make_str_const("if"), coap_make_str_const("\"clock\""),
                0);

  coap_add_resource(context, r);
  time_resource = r;

  /* ... Loop waiting for incoming traffic ... */
  while (!not_exit) {
    coap_io_process(context, 1000);

    /* Cause a notification to anyone Observing 'time' */
    coap_resource_notify_observers(time_resource, NULL);
  }

  /* Clean up */

  coap_free_context(context);
  coap_cleanup();

}

the coap_add_data_large_response() call is lowkey doing the most β€” format, lifetime, len, buf β€” and the comments say Observe, Block2, Size2, and ETag can get added internally. iconic behavior, truly. ✨


πŸ‘‘ SEE ALSO πŸ‘‘

more reading, because of course:

πŸ¦‹ FURTHER INFORMATION πŸ¦‹

see these for further information:

  • "RFC7252: The Constrained Application Protocol (CoAP)"
  • "RFC7959: Block-Wise Transfers in the Constrained Application Protocol (CoAP)"

πŸ’€ BUGS πŸ’€

found a bug? don't suffer in silence, bestie. please raise an issue on GitHub at https://github.com/obgm/libcoap/issues to report any bugs.

and if you've got a fix? please raise a Pull Request at https://github.com/obgm/libcoap/pulls for any fixes.

🌈 AUTHORS 🌈

the libcoap project <libcoap-developers@lists.sourceforge.net>


and that's the whole page, bestie! this gem is from coap_block version 4.3.5, dated 08/24/2026, and it's the COAP_BLOCK(3) manual page. periodt. πŸ’–πŸŽ€πŸ«Ά

πŸ“¦ where this page comes from