Loading...
Searching...
No Matches

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",
parsed.properties.rfc7252.id,
#define printf(...)
A wrapper for the printf() function that passes arguments through unmodified, but fails to compile if...
Definition stdio.h:57
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.
struct unicoap_message_properties_t::@076232207260332346251207117241173011141231142140 rfc7252
RFC 7252 only properties.
uint8_t token_length
Length of unicoap_message_properties_t::token.
Definition message.h:305
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.

unicoap_options_dump_all(message->options);
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:146
unicoap_content_format_t
Content-Format option values
Definition constants.h:713
@ 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

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");
}

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");
}

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.

unicoap_options_iterator_init(&iterator, message->options);
while ((res = unicoap_options_get_next_uri_query(&iterator, &query)) >= 0) {
printf("- URI query: '%.*s'\n", (int)res, query);
}
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.

unicoap_options_iterator_init(&iterator, message->options);
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);
}
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.

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);
@ 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.

int res = unicoap_options_set_content_format(&options, UNICOAP_FORMAT_TEXT);
if (res < 0) {
puts("Error: could not set Content-Format");
}
@ 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

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");
}

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");
}

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

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