net/unicoap: Unified and Modular CoAP stack: Parser and Message APIs (pt 1)
This PR is the first in a series to introduce unicoap, a unified and modular CoAP implementation for RIOT. An overview of all PRs related to unicoap is presented in https://github.com/RIOT-OS/RIOT/issues/21389, including reasons why unicoap is needed.
What does this PR include?
- New RIOT module
unicoap, including config/Kconfig support - Message API of
unicoap, including parsing/serializing support for RFC 7252 - Unittests for options and parser
- Example application showcasing the message APIs as well as parsing and serializing with
unicoap - Structured documentation, including an article on internals and a walk-through of the sample application
The new API aims to reduce the need for in-depth protocol knowledge and minimizes necessary boilerplate. CoAP options can now be inserted in any order, which was not possible before. For example,
// Allocate options buffer with capacity of 200 bytes
UNICOAP_OPTIONS_ALLOC(options, 200);
// Use typed accessors for predefined options
unicoap_options_set_content_format(&options, UNICOAP_FORMAT_TEXT);
// Add multiple instances of repeatable options
unicoap_options_add_uri_queries_string(&options, "unit=C&tolerance=2&flag=1");
// Use generic accessors for custom options
uint8_t value[] = { 0xc0, 0xff, 0xee };
unicoap_options_add(&options, 64999, value, sizeof(value));
unicoap_message_t message;
unicoap_request_init_string_with_options(&message, UNICOAP_METHOD_POST, "Hello, World!", &options);
More in the documentation (CI build currently unavailable).
Lines of code (C code in sources and headers, Makefiles): ≈ 3500 plus, ≈ 5000 lines of SVG, ≈ 700 in Markdown files, ≈ 3700 lines of comments
Performance Analysis
The following figures compare unicoap with nanoCoAP in terms of execution time. Even though unicoap is more flexible, it is not necessarily slower.
Click to expand the analysis:
In this experiment, we measured the time to execute get, add/insert, and remove, given a message with a specified number of options. The results show the average time over multiple runs using the same parameter set relative to the number of existing options.
For each of the operations get, add/insert, and remove, we differentiate between a trivial case (i.e., the option is located at the end of the options buffer) and a complex case (i.e., the option is located in between other options).
The step effects occurring in (a) – (d) are artifacts of the 1 microsecond visualization resolution.
Getter
The average time needed to retrieve an option value grows linearly with the number of options present in the buffer, see (a) and (b). Both implementations require slightly less time when the option is present in the middle, see (b), because of the linear search, i.e., the algorithm finds an option in the middle earlier than at the end. The growth rate of unicoap is marginally higher in both cases because of sanity checks in unicoap.
Setter
In nanoCoAP (coap_opt_add_opaque method), when adding options, the timing is independent of the number of options already present. In unicoap, the timing scales linearly, see (c).
The reason for this behavior is the following. nanoCoAP requires options to be inserted in-order and thus does not need to check whether there are adjacent options. unicoap, however, does not introduce such a requirement and, therefore, must first iterate over the list of options until the correct insertion slot is found. Moreover, unicoap implements safeguard checks to prevent adding options without sufficient buffer capacity.
Figure (d) shows the results when trailing options change and must be moved. These results do not contain data for nanoCoAP because inserting options out of order is not supported.
Remove
When removing CoAP options from message buffers, unicoap outperforms nanoCoAP, see Figures (e) and (f). On average, unicoapis faster by more than 45 microseconds. The cause of the outliers visible in Figure (f) has not been identified yet.
Parser
unicoap performs slightly better than nanoCoAP, in particular in cases of multiple options, see Figure (g).
Murdock results
:heavy_check_mark: PASSED
5e353a3967e43f17cd9d360c3d2b0189c91dcf4d net/unicoap: add documentation
| Success | Failures | Total | Runtime |
|---|---|---|---|
| 10522 | 0 | 10522 | 17m:27s |
Artifacts
@carl-tud hey, the images in the performance analysis are 404 to me.
I also took the audacity to refactor your PR description a bit, to increase its accessibility.
@carl-tud hey, the images in the performance analysis are 404 to me.
I think they are still in your private repository.
@miri64 @Teufelchen1 Thanks, they should be visible now.
Could you split out the basic message and option parsing plus unit testing into a single PR? It would be sufficient to start with just URI-Path and maybe some numeric Option (Max-Age?) in that PR.
While I like this idea, I am not sure this would make things better. The bulk of this PR is documentation, which would still be part of the basic message PR. If we go for a split, I would rather go for an opaque (byte sequence) options to keep in this PR, as they should be the easiest ones to create and parse.
How long can you / do you intend to provide maintenance for this code?
How long can you / do you intend to provide maintenance for this code?
Definitely over the summer until October. After that, I think I am going to need some help (1–2 people) maintaining it. Nevertheless, if someone was willing to help from the get-go, then that would also be great.
Do you happen to have a size (RAM/ROM) comparison with either nanocoap or gcoap?
(reviewed up to and excluding unicoap/doc)
Do you happen to have a size (RAM/ROM) comparison with either nanocoap or gcoap?
Please see the RAM/ROM comparison in https://github.com/RIOT-OS/RIOT/issues/21389 :)
(Possibly) Unresolved Review Points: https://github.com/RIOT-OS/RIOT/pull/21390#discussion_r2038214528 ~~https://github.com/RIOT-OS/RIOT/pull/21390#discussion_r2067050803~~ ~~https://github.com/RIOT-OS/RIOT/pull/21390#discussion_r2067063534~~ ~~https://github.com/RIOT-OS/RIOT/pull/21390#discussion_r2067073241~~ ~~https://github.com/RIOT-OS/RIOT/pull/21390#discussion_r2067218117~~ ~~https://github.com/RIOT-OS/RIOT/pull/21390#discussion_r2068462870~~ ~~https://github.com/RIOT-OS/RIOT/pull/21390#discussion_r2068515596~~ ~~https://github.com/RIOT-OS/RIOT/pull/21390#discussion_r2068517891~~ ~~https://github.com/RIOT-OS/RIOT/pull/21390#discussion_r2068518783~~ ~~https://github.com/RIOT-OS/RIOT/pull/21390#discussion_r2081957711~~ ~~https://github.com/RIOT-OS/RIOT/pull/21390#discussion_r2120561042~~ ~~https://github.com/RIOT-OS/RIOT/pull/21390#discussion_r2081968057~~ ~~https://github.com/RIOT-OS/RIOT/pull/21390#discussion_r2084237583~~ ~~https://github.com/RIOT-OS/RIOT/pull/21390#discussion_r2084243366~~ ~~https://github.com/RIOT-OS/RIOT/pull/21390#discussion_r2096119604~~ ~~https://github.com/RIOT-OS/RIOT/pull/21390#discussion_r2123992817~~ ~~https://github.com/RIOT-OS/RIOT/pull/21390#discussion_r2124093187~~
Ping @mguetschow
What's the status, can I squash?
What's the status, can I squash?
Yes, please do!
ready
I ran make -C examples/networking/coap/unicoap-message generate-Makefile.ci locally and this is the Makefile.ci it generated:
BOARD_INSUFFICIENT_MEMORY := \
arduino-duemilanove \
arduino-nano \
arduino-uno \
atmega328p \
atmega328p-xplained-mini \
atmega8 \
#
@carl-tud when you add the examples/networking/coap/unicoap-message/Makefile.ci file, you can squash the commit directly.
Okay, will do, but tomorrow
@crasbe thanks. done.
Congrats! :tada: