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.
Helper structure for parsing PDUs manually.
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.
puts("Error: parsing failed");
return;
}
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.
unicoap_message_t message
Message.
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...
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.
struct unicoap_message_properties_t::@395 rfc7252
RFC 7252 only properties.
uint8_t token_length
Length of unicoap_message_properties_t::token.
unicoap_rfc7252_message_type_t type
RFC 7252 message type.
unicoap_message_properties_t properties
Parsed message properties.
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 * unicoap_string_from_method(unicoap_method_t method)
Obtains label for given request method.
unicoap_method_t method
CoAP request method.
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.
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
.
puts("Error: could not read Content-Format!");
}
#define assert(cond)
abort the program if assertion is false
unicoap_content_format_t
Content-Format option values
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.
@ UNICOAP_FORMAT_JSON
Content type application/json
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;
if (res < 0) {
puts("Message has no Uri-Query option");
}
printf(
"Error: could read first Uri-Query option");
}
#define ENOENT
No such file or directory.
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.
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).
if (res < 0) {
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.
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 };
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.
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.
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.
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.
The iterator you use to retrieve option values in-order.
The option iterator can also be used to iterate over all options, regardless of their type.
const uint8_t* value = NULL;
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.
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.
#define UNICOAP_OPTIONS_ALLOC(name, capacity)
Allocates options with buffer capacity.
Now, let us initialize a message. You can either use the designated initializer or initializer function.
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.
@ UNICOAP_METHOD_POST
POST request (resource processes payload)
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.
@ UNICOAP_FORMAT_TEXT
Content type text/plain; charset=utf-8
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).
if (res < 0) {
puts("Error: options buffer too small");
}
puts("Error: could not add URI path");
}
#define ENOBUFS
No buffer space available.
static ssize_t unicoap_options_add_uri_path_string(unicoap_options_t *options, char *path)
Adds multiple Uri-Path options from null-terminated string.
Or, you can add components individually as follows.
if (res < 0) {
puts("Error: could not add path component");
}
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.
The same applies to Uri-Query
.
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.
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.
The header format varies depending on the transport. Let's use CoAP over UDP or CoAP over DTLS, i.e., the RFC 7252 format.
.token_length = 0,
.rfc7252 = {
.id = 0xABCD,
}
};
@ UNICOAP_TYPE_NON
A non-confirmable message.
Properties of a CoAP message.
uint8_t * token
CoAP token used to correlate requests to responses.
Finally, call the serializer appropriate for the transport.
if (res < 0) {
puts("Error: PDU buffer too small");
}
puts("Error: could not serialize message");
return;
}
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.