A demo of unicoap message APIs.

A demo of unicoap message APIs.

Sample code. This example demonstrates how you can use unicoap message and options APIs and how to parse PDUs. You can find a demo application in the examples/networking/coap/unicoap_message folder.

Bytes to Message (Deserializing)

Parsing a PDU

To start, let us assume pdu is a buffer containing the CoAP PDU.

const uint8_t pdu[] = { /* ... */ };

Next, allocate a result structure.

unicoap_parser_result_t parsed = { 0 };
Helper structure for parsing PDUs manually.
Definition: message.h:1117

Then, call one of the message parsers. CoAP supports different transports which is why the CoAP PDU header varies. In this case, let us assume we received the message over UDP or DTLS. In these cases, we use the RFC 7252 PDU format. Using the result structure frees you of needing to allocate options and a message struct and to wire up options with message struct.

if ((res = unicoap_pdu_parse_rfc7252_result(pdu, sizeof(pdu), &parsed)) < 0) {
puts("Error: parsing failed");
return;
}
unicoap_message_t* message = &parsed.message;
static ssize_t unicoap_pdu_parse_rfc7252_result(uint8_t *pdu, size_t size, unicoap_parser_result_t *parsed)
Helper method for manually parsing a PDU.
Definition: message.h:1272
Generic CoAP message.
Definition: message.h:75
unicoap_message_t message
Message.
Definition: message.h:1119

Because the header varies, transport-dependent details like the RFC 7252 message type and ID are accessible via the unicoap_message_properties_t::rfc7252 member.

printf("CoAP message has token=<%i bytes>\n",
printf("CoAP over UDP/DTLS has id=%i type=%s\n",
#define printf(...)
A wrapper for the printf() function that passes arguments through unmodified, but fails to compile if...
Definition: stdio.h:60
const char * unicoap_string_from_rfc7252_type(unicoap_rfc7252_message_type_t type)
Returns a null-terminated label for the CoAP over UDP/DTLS message type.
uint16_t id
RFC 7252 message ID.
Definition: message.h:327
struct unicoap_message_properties_t::@395 rfc7252
RFC 7252 only properties.
uint8_t token_length
Length of unicoap_message_properties_t::token.
Definition: message.h:305
unicoap_rfc7252_message_type_t type
RFC 7252 message type.
Definition: message.h:324
unicoap_message_properties_t properties
Parsed message properties.
Definition: message.h:1125

Inspecting a Message

You use the unicoap_message_is_request, unicoap_message_is_response, and unicoap_message_is_signal methods to check whether a given message is a request, response, or signaling message.

The corresponding typed view of the code is accessible through unicoap_message_t::method, unicoap_message_t::status, and unicoap_message_t::signal.

You can also obtain a human-readable constant null-terminated C string. There are also versions available for status codes and signal numbers. To get a string description of the CoAP code without checking the message class first, use unicoap_string_from_code.

const char* method_name = unicoap_string_from_method(message->method);
const char * unicoap_string_from_method(unicoap_method_t method)
Obtains label for given request method.
unicoap_method_t method
CoAP request method.
Definition: message.h:144

The payload and payload size in bytes can be retrieved the unicoap_message_t::payload and unicoap_message_t::payload_size members.

Reading Options

First, let us dump all options to the standard output.

void unicoap_options_dump_all(const unicoap_options_t *options)
Iterates and dumps all options using printf
unicoap_options_t * options
Message options.
Definition: message.h:83

To read options like Content-Format which can occur no more than once, you use unicoap_options_t::unicoap_options_get_content_format. Read accessors for non-repeatable options are prefixed with unicoap_options_get.

if (unicoap_options_get_content_format(message->options, &format) < 0) {
puts("Error: could not read Content-Format!");
}
#define assert(cond)
abort the program if assertion is false
Definition: assert.h:135
unicoap_content_format_t
Content-Format option values
Definition: constants.h:713
static ssize_t unicoap_options_get_content_format(const unicoap_options_t *options, unicoap_content_format_t *format)
Retrieves the Content-Format option, if present.
Definition: options.h:1100
@ UNICOAP_FORMAT_JSON
Content type application/json
Definition: constants.h:793

Options like Uri-Query can occur more than once. For these types of options, unicoap defines several convenience accessors. Let us retrieve the first Uri-Query option.

const char* query = NULL;
ssize_t res = unicoap_options_get_first_uri_query(message->options, &query);
if (res < 0) {
if (res == -ENOENT) {
puts("Message has no Uri-Query option");
}
printf("Error: could read first Uri-Query option");
}
#define ENOENT
No such file or directory.
Definition: errno.h:113
static ssize_t unicoap_options_get_first_uri_query(const unicoap_options_t *options, const char **query)
Retrieves the first Uri-Query option, if present.
Definition: options.h:1967

The first getter provides a view into the PDU buffer. The returned string is thus not null-terminated.

printf("First URI query: '%.*s'\n", (int)res, query);

In the case of URI queries, you can also retrieve queries by name (if they obey the name=value format).

res = unicoap_options_get_first_uri_query_by_name(message->options, "color", &query);
if (res < 0) {
/* The getter also fails in cases where no option was found */
if (res == -ENOENT) {
puts("Message has no 'color' query");
}
printf("Error: could read first 'color' query");
}
static ssize_t unicoap_options_get_first_uri_query_by_name(unicoap_options_t *options, const char *name, const char **value)
Retrieves the first Uri-Query option matching the given name, if present.
Definition: options.h:2035

For a number of repeatable options, such as Uri-Path, Location-Path, Uri-Query, and Location-Query, unicoap offers accessors that generate the original, contiguous representation. This means that multiple Uri-Path options are stitched back together, forming the /original/path. These accessores do copy. Now, let us create a query string (?a=1&b=2&c=3).

char query_string[50] = { 0 };
res = unicoap_options_copy_uri_queries(message->options, query_string, sizeof(query_string));
if (res < 0) {
puts("Error: could not generate URI query string");
}
static ssize_t unicoap_options_copy_uri_queries(const unicoap_options_t *options, char *queries, size_t capacity)
Copies URI query string into the given buffer.
Definition: options.h:2060

Alternatively, you can iterate over all query options, avoiding the copy operation and allocation. To do this, you will need to allocate an unicoap_options_iterator_t and initialize it using unicoap_options_iterator_t::unicoap_options_iterator_init. This is the main tool to iterate over options. unicoap exposes multiple methods for getting the next instance of a repeatable option.

while ((res = unicoap_options_get_next_uri_query(&iterator, &query)) >= 0) {
printf("- URI query: '%.*s'\n", (int)res, query);
}
static void unicoap_options_iterator_init(unicoap_options_iterator_t *iterator, unicoap_options_t *options)
Initializes the given iterator structure.
Definition: options.h:601
static ssize_t unicoap_options_get_next_uri_query(unicoap_options_iterator_t *iterator, const char **query)
Gets the next Uri-Query option provided by the specified iterator.
Definition: options.h:1986
The iterator you use to retrieve option values in-order.
Definition: options.h:582

The option iterator can also be used to iterate over all options, regardless of their type.

const uint8_t* value = NULL;
while ((res = unicoap_options_get_next(&iterator, &number, &value)) >= 0) {
const char* name = unicoap_string_from_option_number(number);
printf("- option %s nr=%i contains %" PRIuSIZE " bytes\n", name, number, res);
}
ssize_t unicoap_options_get_next(unicoap_options_iterator_t *iterator, unicoap_option_number_t *number, const uint8_t **value)
Gets the next option provided by the given iterator.
unicoap_option_number_t
CoAP option number.
Definition: constants.h:419
const char * unicoap_string_from_option_number(unicoap_option_number_t number)
Returns label of option corresponding to the given number.
#define PRIuSIZE
Macro holding the format specifier to print an size_t variable in decimal representation.
Definition: architecture.h:178

Message to Bytes (Serializing)

Creating a Message Container

Since we want to add options to the CoAP message, we need to allocate an options buffer first. To avoid the boilerplate necessary for allocating a helper structure and buffer and the initialization work, you just need to call UNICOAP_OPTIONS_ALLOC and provide the desired buffer capacity.

UNICOAP_OPTIONS_ALLOC(options, 100);
#define UNICOAP_OPTIONS_ALLOC(name, capacity)
Allocates options with buffer capacity.
Definition: options.h:200

Now, let us initialize a message. You can either use the designated initializer or initializer function.

unicoap_request_init_string_with_options(&message, UNICOAP_METHOD_POST, "Hello, World!", &options);
static void unicoap_request_init_string_with_options(unicoap_message_t *request, unicoap_method_t method, const char *payload, unicoap_options_t *options)
Initializes request with payload from null-terminated UTF-8 string and options.
Definition: message.h:857
@ UNICOAP_METHOD_POST
POST request (resource processes payload)
Definition: constants.h:146

Customizing Options

To set non-repeatable options like Content-Format, use unicoap_options_set accessors.

if (res < 0) {
puts("Error: could not set Content-Format");
}
static ssize_t unicoap_options_set_content_format(unicoap_options_t *options, unicoap_content_format_t format)
Sets the Content-Format option.
Definition: options.h:1116
@ UNICOAP_FORMAT_TEXT
Content type text/plain; charset=utf-8
Definition: constants.h:720

For repeatable options, unicoap provides two versions. You can either add multiple instances of an option like Uri-Path by providing the original, contiguous representation (e.g., the path).

int res = unicoap_options_add_uri_path_string(&options, "/thermostat/temperature");
if (res < 0) {
if (res == -ENOBUFS) {
puts("Error: options buffer too small");
}
puts("Error: could not add URI path");
}
#define ENOBUFS
No buffer space available.
Definition: errno.h:110
static ssize_t unicoap_options_add_uri_path_string(unicoap_options_t *options, char *path)
Adds multiple Uri-Path options from null-terminated string.
Definition: options.h:1931

Or, you can add components individually as follows.

res = unicoap_options_add_uri_path_component_string(&options, "thermostat");
if (res < 0) {
puts("Error: could not add path component");
}
res = unicoap_options_add_uri_path_component_string(&options, "temperature");
if (res < 0) {
puts("Error: could not add path component");
}
static ssize_t unicoap_options_add_uri_path_component_string(unicoap_options_t *options, char *component)
Adds Uri-Path option from null-terminated string.
Definition: options.h:1885

The same applies to Uri-Query.

res = unicoap_options_add_uri_queries_string(&options, "unit=C&friendly=yes");
if (res < 0) {
puts("Error: could not add URI query");
}
static ssize_t unicoap_options_add_uri_queries_string(unicoap_options_t *options, char *queries)
Adds multiple Uri-Query options from null-terminated string.
Definition: options.h:2148

unicoap offers versions for both null-terminated C strings and strings without a null-terminator that require a length indication instead. Example: unicoap_options_t::unicoap_options_add_uri_queries and unicoap_options_t::unicoap_options_add_uri_queries_string, or unicoap_options_t::unicoap_options_add_uri_query and unicoap_options_t::unicoap_options_add_uri_query_string.

Serializing a Message

First, allocate a buffer with a capacity of your choice.

uint8_t pdu[200];

The header format varies depending on the transport. Let's use CoAP over UDP or CoAP over DTLS, i.e., the RFC 7252 format.

Remarks
In this very simple scenario, we don't use a token. Very constrained nodes are allowed to handle one request at a time and thus don't need a token to differentiate responses to outstanding requests.
.token = NULL,
.token_length = 0,
.rfc7252 = {
.id = 0xABCD,
}
};
@ UNICOAP_TYPE_NON
A non-confirmable message.
Definition: constants.h:88
Properties of a CoAP message.
Definition: message.h:300
uint8_t * token
CoAP token used to correlate requests to responses.
Definition: message.h:302

Finally, call the serializer appropriate for the transport.

ssize_t res = unicoap_pdu_build_rfc7252(pdu, sizeof(pdu), message, &properties);
if (res < 0) {
if (res == -ENOBUFS) {
puts("Error: PDU buffer too small");
}
puts("Error: could not serialize message");
return;
}
printf("The final PDU has a size of %" PRIuSIZE " bytes.\n", res);
static ssize_t unicoap_pdu_build_rfc7252(uint8_t *pdu, size_t capacity, const unicoap_message_t *message, const unicoap_message_properties_t *properties)
Writes RFC 7252 PDU into buffer.
Definition: message.h:1311