From 5e353a3967e43f17cd9d360c3d2b0189c91dcf4d Mon Sep 17 00:00:00 2001
From: Carl Seifert <115627588+carl-tud@users.noreply.github.com>
Date: Mon, 7 Jul 2025 17:12:21 +0200
Subject: [PATCH] net/unicoap: add documentation
---
doc/doxygen/riot.doxyfile | 5 +-
sys/net/application_layer/unicoap/docs/doc.md | 51 +
.../unicoap/docs/internals.doc.md | 158 ++
.../unicoap/docs/message-example.doc.md | 292 ++
.../unicoap/docs/message.doc.md | 24 +
.../application_layer/unicoap/docs/pdu.doc.md | 50 +
.../docs/unicoap-layers-comms-apis.svg | 2023 ++++++++++++++
.../unicoap/docs/unicoap-layers-comms.svg | 903 +++++++
.../unicoap/docs/unicoap-layers.svg | 2395 +++++++++++++++++
.../application_layer/unicoap/drivers/doc.md | 11 +
.../unicoap/drivers/rfc7252/common/pdu/doc.md | 30 +
.../unicoap/drivers/rfc7252/dtls/doc.md | 48 +
.../unicoap/drivers/rfc7252/udp/doc.md | 32 +
13 files changed, 6021 insertions(+), 1 deletion(-)
create mode 100644 sys/net/application_layer/unicoap/docs/doc.md
create mode 100644 sys/net/application_layer/unicoap/docs/internals.doc.md
create mode 100644 sys/net/application_layer/unicoap/docs/message-example.doc.md
create mode 100644 sys/net/application_layer/unicoap/docs/message.doc.md
create mode 100644 sys/net/application_layer/unicoap/docs/pdu.doc.md
create mode 100644 sys/net/application_layer/unicoap/docs/unicoap-layers-comms-apis.svg
create mode 100644 sys/net/application_layer/unicoap/docs/unicoap-layers-comms.svg
create mode 100644 sys/net/application_layer/unicoap/docs/unicoap-layers.svg
create mode 100644 sys/net/application_layer/unicoap/drivers/doc.md
create mode 100644 sys/net/application_layer/unicoap/drivers/rfc7252/common/pdu/doc.md
create mode 100644 sys/net/application_layer/unicoap/drivers/rfc7252/dtls/doc.md
create mode 100644 sys/net/application_layer/unicoap/drivers/rfc7252/udp/doc.md
diff --git a/doc/doxygen/riot.doxyfile b/doc/doxygen/riot.doxyfile
index a7765df70b..4d10e9740d 100644
--- a/doc/doxygen/riot.doxyfile
+++ b/doc/doxygen/riot.doxyfile
@@ -1324,7 +1324,10 @@ HTML_EXTRA_FILES = src/css/bootstrap.min.css \
src/js/jquery.smartmenus.bootstrap.min.js \
src/js/jquery-ui.min.js \
src/js/menu.js \
- src/js/riot-doxy.js
+ src/js/riot-doxy.js \
+ ../../sys/net/application_layer/unicoap/docs/unicoap-layers.svg \
+ ../../sys/net/application_layer/unicoap/docs/unicoap-layers-comms.svg \
+ ../../sys/net/application_layer/unicoap/docs/unicoap-layers-comms-apis.svg
# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen
# will adjust the colors in the style sheet and background images according to
diff --git a/sys/net/application_layer/unicoap/docs/doc.md b/sys/net/application_layer/unicoap/docs/doc.md
new file mode 100644
index 0000000000..2777950e93
--- /dev/null
+++ b/sys/net/application_layer/unicoap/docs/doc.md
@@ -0,0 +1,51 @@
+@defgroup net_unicoap unicoap: Unified CoAP Suite
+@ingroup net
+@brief Send requests and create server resources using the Constrained Application Protocol across different transports
+@{
+
+Module. Specify `USEMODULE += unicoap` in your application's Makefile.
+
+@warning `unicoap` is work in progress. Not all functionality is implemented in RIOT yet, however
+the documentation already exists. Do not expect everything to work yet.
+
+`unicoap` is RIOT's unified and modular framework for communication via the Constrained Application
+Protocol. `unicoap` supports different transports and several CoAP features, enabled by
+a **layered** and **modular** design. Support for each CoAP transport, such as UDP, is available through
+[drivers](@ref net_unicoap_drivers).
+
+`unicoap` aims to eventually replace @ref net_gcoap, @ref net_nanocoap, and @ref net_nanosock,
+in favor of a more beginner-friendly and easily extensible design.
+
+## CoAP
+
+The [Constrained Application Protocol (CoAP)](https://datatracker.ietf.org/doc/html/rfc7252)
+is a lightweight alternative to HTTP. HTTP as a general-purpose application protocol carries a
+significant overhead and is thus problematic for IoT networks with limited bandwidth and
+nodes with little memory. CoAP covers a range of features needed in the IoT, such as resource
+discovery, message fragmentation, and end-to-end message protection.
+
+## Quick start
+
+In your application Makefile, add
+```Makefile
+USEMODULE += unicoap
+USEMODULE += unicoap_driver_udp
+```
+
+`unicoap` enables support for CoAP over various transport protocols. Currently, `unicoap` supports
+a @ref net_unicoap_drivers_udp and @ref net_unicoap_drivers_dtls.
+You must specify at least one driver to use networking functionality. If you just want to
+[use message APIs](@ref net_unicoap_message_example), you can use the framing implementation of
+each driver, such as the @ref net_unicoap_drivers_rfc7252_pdu submodule for the RFC 7252 PDU format.
+`unicoap` implements both a client and a server.
+
+To configure `unicoap`, go to the @ref net_unicoap_config.
+For extending `unicoap`, refer to @ref net_unicoap_internal.
+
+
+
+@}
diff --git a/sys/net/application_layer/unicoap/docs/internals.doc.md b/sys/net/application_layer/unicoap/docs/internals.doc.md
new file mode 100644
index 0000000000..918dd72ad9
--- /dev/null
+++ b/sys/net/application_layer/unicoap/docs/internals.doc.md
@@ -0,0 +1,158 @@
+@defgroup net_unicoap_internal Behind The Scenes of unicoap
+@ingroup net_unicoap
+@{
+
+## CoAP 101
+CoAP was originally specified in [RFC 7252](https://datatracker.ietf.org/doc/html/rfc7252)
+and could only be used in combination with UDP and DTLS as transport protocols.
+[RFC 8223](https://datatracker.ietf.org/doc/html/rfc8323) modified the CoAP format
+for sending CoAP messages over TCP, TLS, and WebSockets (including WebSockets over TLS).
+There is also an Internet Draft for [CoAP over GATT (BLE)](https://datatracker.ietf.org/doc/draft-amsuess-core-coap-over-gatt).
+
+Each of these standards leverage different messaging models, i.e., what timeouts to apply, how
+reliable transmission is implemented, and what messages are allowed to be sent
+in response to a certain message type. A custom CoAP PDU header (i.e., another PDU
+format) has been specified for CoAP over reliable transports.
+For instance, CoAP over UDP and over DTLS share the same PDU format; so do CoAP over TCP and TLS.
+The set of protocol characteristics that vary depending on the _transport_ forms a specific version
+of CoAP, which is called a _CoAP combination_ in `unicoap`.
+For instance, CoAP over UDP is CoAP combination, so is CoAP over DTLS.
+
+## Layered Design
+
+The design of `unicoap` involves three distinct layers that reflect the layered approach of CoAP,
+as shown in the figure below. Conceptually, newly received message traverse these layers up to
+the application, and data sent by the application travels in the opposite direction.
+Located beneath the application, the _exchange_ layer embodies the REST model of CoAP.
+It is responsible for handling advanced CoAP features operating above the request-response exchanges,
+such as [resource observation](/FIXME-upcoming-pr-net_unicoap_client_resource_observation)
+and [block-wise transfer](/FIXME-upcoming-pr-net_unicoap_blockwise).
+This layer is shared between CoAP combinations, i.e., the REST semantics remain the same,
+regardless of the messaging model and transport beneath.
+Since messaging differs between CoAP combinations, a modular design to ease the addition
+of new CoAP combinations was necessary: The layer dedicated to _messaging_ covers framing and can
+accommodate a custom reliability mechanism, such as the one specified in RFC 7252
+(using the four tempers `CON`, `NON`, `ACK`, `RST`). Serializing messages and parsing PDUs received
+from the network are also handled by the messaging layer.
+The transport layer at the bottom manages different transport protocols.
+Here, `unicoap` coordinates with the operating system networking interface.
+
+
+
+### Overview of CoAP Combinations
+To better illustrate what parts of the CoAP stack differ, have a look
+at the following graph, where each node represents a version of a certain layer. Each leaf node stands for
+a different CoAP combination ("CoAP over ...") specification.
+```
+ Requests/Responses
+ RFC 7252, RFC 7641, RFC 7959, ...
+ (incl. Resource Observation, Block-Wise Transfers)
+ / \
+ / \
+ / \
+Specification: RFC 7252 RFC 8323
+ | |
++-+- Messaging shared between largely shared between
+| | Model: UDP & DTLS TCP, TLS & WebSockets
+| | | / \
+| | | / \
+| +- PDU Format: shared between shared between WebSockets
+| UDP & DTLS TCP & TLS / \
+| / \ / \ / \
+| / \ / \ / \
++-- Transport UDP DTLS TCP TLS WebSockets WebSockets
+ Protocol: over TLS
+
+Figure 2: Differences between CoAP combinations
+```
+
+#### CoAP over UDP and CoAP over DTLS (RFC 7252)
+CoAP over UDP and DTLS works with messages of different types. A message can be confirmable (a `CON`
+message), non-confirmable (`NON`), an acknowledgment message (`ACK`), or a reset message (`RST`).
+Confirmable messages elicit an acknowledgement message to be sent by the peer. Hence, RFC 7252
+provides optional reliability (i.e., retransmission using an exponential back-of mechanism)
+using confirmable and acknowledgement messages.
+
+@see [RFC 7252](https://datatracker.ietf.org/doc/html/rfc7252)
+
+#### CoAP over TCP, CoAP over TLS, and CoAP over WebScokets (RFC 8323)
+RFC 8323 eliminates the need for reliability to be implemented on the application layer, as the underlying
+transport protocol already provides reliability. While message processing looks the same for both
+CoAP over TCP/TLS ([RFC 8323, Section 3](https://datatracker.ietf.org/doc/html/rfc8323#section-3)) and
+CoAP over WebSockets ([RFC 8323, Section 4](https://datatracker.ietf.org/doc/html/rfc8323#section-4)),
+the PDU format employed *does* vary a little between them.
+
+@see [RFC 8323](https://datatracker.ietf.org/doc/html/rfc8323)
+
+#### CoAP over GATT over Bluetooth Low Energy (BLE) (IETF Draft)
+The [CoAP over GATT (BLE)](https://datatracker.ietf.org/doc/draft-amsuess-core-coap-over-gatt)
+messaging layer works entirely different from previously specified Constrained Application Protocol variants.
+Hence, the PDU format is also custom and optimized to take as little space as possible to reduce airtime.
+
+@see [`draft-amsuess-core-coap-over-gatt`](https://datatracker.ietf.org/doc/draft-amsuess-core-coap-over-gatt)
+
+### Drivers
+
+To integrate new CoAP combinations, functionality for messaging and transport layer must be added.
+The `unicoap` design refers to these integrations collectively as a _driver_ that represents
+a CoAP combination, such as CoAP over DTLS. Each driver is a RIOT module you can import. For instance,
+to use the CoAP over UDP driver, you import the `unicoap_driver_udp` by adding it to the `USEMODULE`
+Makefile variable: `USEMODULE += unicoap_driver_udp`.
+
+Drivers themselves can in turn consist of a shared module for messaging and a specific transport
+support module. For example, the CoAP over DTLS driver encompasses a transport module for DTLS networking;
+and depends on the common RFC 7252 messaging module also employed by the CoAP over UDP driver.
+You can see this relationship in `Makefile.dep` in the `unicoap` source directory: The common
+messaging module is a shared dependency of both the @ref net_unicoap_drivers_udp and @ref net_unicoap_drivers_dtls.
+driver module. We encourage you to follow the same approach for CoAP combinations that share a common
+messaging model, such as CoAP over TCP, TLS, and WebSockets when implementing these.
+
+On a high level, each driver interacts with the upper layers on these three occasions:
+
+- **Initialization and deinitialization**:
+ Drivers must provide an [initialization](/FIXME-upcoming-pr-unicoap_init) and [teardown](/FIXME-upcoming-pr-unicoap_deinit)
+ These may be used for setup work in the transport and messaging layer such as for creating
+ sockets or establishing connections to peripherals, alongside allocating objects required for messaging.
+
+- **Sending side / Outbound**:
+ A driver must expose a standardized API for [sending from the messaging layer](/FIXME-upcoming-pr-unicoap_messaging_send).
+ The exchange layer will call into this functionality, prompting the driver to perform any due
+ work in the messaging layer like attempting to retransmit the message. Apart from the message,
+ as well as the remote and local endpoint, this function accepts flags that customize transmission
+ behavior. The RFC 7252 message type is abstracted into a _reliability_ flag the messaging layer in
+ the CoAP over UDP and DTLS drivers interpret as an instruction to send a confirmable message. When
+ finished, the messaging layer serializes the message and forwards it to the transport
+ implementation.
+
+- **Receiving side / Inbound**:
+ Upon receipt of a new message, each driver will need to invoke an [exchange-layer processing function](/FIXME-upcoming-pr-unicoap_exchange_process).
+
+- **Ping**: Due to the variability in ping mechanisms (empty `CON` in CoAP over UDP and `7.03` message in CoAP over reliable transports), each driver can implement a ping function. unicoap bundles these APIs and provides a [single, generic ping function that multiplexes](/FIXME-upcoming-pr-unicoap_ping) between the driver implementations.
+
+### Communication Between Layers
+
+The following figure illustrates communication between layers in a block-wise transfer,
+where a client request from the application may result in multiple
+[`unicoap_messaging_send`](/FIXME-upcoming-pr-unicoap_messaging_send) and
+[`unicoap_exchange_process`](/FIXME-upcoming-pr-unicoap_exchange_process) calls between the
+exchange and messaging layer:
+
+
+
+The next schematic depicts how these APIs are implemented, based on the CoAP over UDP and
+CoAP over DTLS drivers that share the RFC 7252 messaging implementation:
+
+
+
+Both the CoAP over UDP and CoAP over DTLS driver support sending vectored data, hence the `sendv`
+suffixes in the function names depicted in the figure above.
+
+## Adding a New Driver
+
+In the `unicoap` codebase you will encounter several marks (`MARK: ...`)
+that help with extending the suite.
+
+- **MARK: unicoap_driver_extension_point**: Every region of code that would need to be extended to support a new transport protocol or driver is
+ annotated with this mark.
+
+@}
diff --git a/sys/net/application_layer/unicoap/docs/message-example.doc.md b/sys/net/application_layer/unicoap/docs/message-example.doc.md
new file mode 100644
index 0000000000..54673868b9
--- /dev/null
+++ b/sys/net/application_layer/unicoap/docs/message-example.doc.md
@@ -0,0 +1,292 @@
+@defgroup net_unicoap_message_example Using Message APIs
+@ingroup net_unicoap_message
+@brief 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.
+```c
+const uint8_t pdu[] = { /* ... */ };
+```
+
+Next, allocate a result structure.
+
+```c
+unicoap_parser_result_t parsed = { 0 };
+```
+
+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.
+
+```c
+if ((res = unicoap_pdu_parse_rfc7252_result(pdu, sizeof(pdu), &parsed)) < 0) {
+ puts("Error: parsing failed");
+ return;
+}
+
+unicoap_message_t* message = &parsed.message;
+```
+
+Because the header varies, transport-dependent details like the RFC 7252 message type and ID
+are accessible via the
+@ref unicoap_message_properties_t::rfc7252 member.
+
+```c
+printf("CoAP message has token=<%i bytes>\n",
+ parsed.properties.token_length);
+
+printf("CoAP over UDP/DTLS has id=%i type=%s\n",
+ parsed.properties.rfc7252.id,
+ unicoap_string_from_rfc7252_type(parsed.properties.rfc7252.type));
+```
+
+### Inspecting a Message
+
+You use the
+@ref unicoap_message_is_request,
+@ref unicoap_message_is_response, and
+@ref 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
+@ref unicoap_message_t.method,
+@ref unicoap_message_t.status, and
+@ref 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 @ref unicoap_string_from_code.
+
+```c
+const char* method_name = unicoap_string_from_method(message->method);
+```
+
+The payload and payload size in bytes can be retrieved the
+@ref unicoap_message_t.payload and
+@ref unicoap_message_t.payload_size members.
+
+### Reading Options
+
+First, let us dump all options to the standard output.
+
+```c
+unicoap_options_dump_all(message->options);
+```
+
+To read options like `Content-Format` which can occur no more than once, you use
+@ref unicoap_options_t::unicoap_options_get_content_format.
+Read accessors for non-repeatable options are prefixed with `unicoap_options_get`.
+
+```c
+unicoap_content_format_t format = 0;
+
+if (unicoap_options_get_content_format(message->options, &format) < 0) {
+ puts("Error: could not read Content-Format!");
+}
+
+assert(format == UNICOAP_FORMAT_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.
+
+```c
+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");
+}
+```
+
+The `first` getter provides a view into the PDU buffer. The returned string
+is thus not null-terminated.
+
+```c
+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).
+
+```c
+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`).
+
+```c
+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
+@ref unicoap_options_iterator_t and initialize it using
+@ref 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.
+
+```c
+unicoap_options_iterator_t iterator;
+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 option iterator can also be used to iterate over all options, regardless of their type.
+
+```c
+unicoap_options_iterator_init(&iterator, message->options);
+unicoap_option_number_t number;
+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);
+}
+```
+
+## 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
+@ref UNICOAP_OPTIONS_ALLOC and provide the desired buffer capacity.
+
+```c
+UNICOAP_OPTIONS_ALLOC(options, 100);
+```
+
+Now, let us initialize a message. You can either use the designated initializer or initializer
+function.
+
+```c
+unicoap_request_init_string_with_options(&message, UNICOAP_METHOD_POST, "Hello, World!", &options);
+```
+
+### Customizing Options
+
+To set non-repeatable options like `Content-Format`, use `unicoap_options_set` accessors.
+
+```c
+int res = unicoap_options_set_content_format(&options, UNICOAP_FORMAT_TEXT);
+if (res < 0) {
+ puts("Error: could not set Content-Format");
+}
+```
+
+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).
+
+```c
+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");
+}
+```
+
+Or, you can add _components_ individually as follows.
+
+```c
+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`.
+
+```c
+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:
+@ref unicoap_options_t::unicoap_options_add_uri_queries and
+@ref unicoap_options_t::unicoap_options_add_uri_queries_string, or
+@ref unicoap_options_t::unicoap_options_add_uri_query and
+@ref unicoap_options_t::unicoap_options_add_uri_query_string.
+
+### Serializing a Message
+
+First, allocate a buffer with a capacity of your choice.
+
+```c
+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.
+
+@remark 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.
+
+```c
+unicoap_message_properties_t properties = {
+ .token = NULL,
+ .token_length = 0,
+ .rfc7252 = {
+ .id = 0xABCD,
+ .type = UNICOAP_TYPE_NON
+ }
+};
+```
+
+Finally, call the serializer appropriate for the transport.
+
+```c
+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);
+```
+
+@}
diff --git a/sys/net/application_layer/unicoap/docs/message.doc.md b/sys/net/application_layer/unicoap/docs/message.doc.md
new file mode 100644
index 0000000000..75483859d1
--- /dev/null
+++ b/sys/net/application_layer/unicoap/docs/message.doc.md
@@ -0,0 +1,24 @@
+@defgroup net_unicoap_message Message APIs
+@ingroup net_unicoap
+@brief Create and serialize CoAP messages
+@{
+
+@ref unicoap_message_t is the central container type for CoAP messages. To see how to access
+CoAP options, see @ref net_unicoap_options. You may also look at the [guide to using CoAP
+messages](https://guides.riot-os.org/FIXME)
+
+## Example
+
+```c
+unicoap_message_t message;
+
+const char payload[] = "Hello, World!";
+unicoap_request_init_string(&message, UNICOAP_METHOD_POST, payload, &options);
+```
+
+You can access the CoAP code through different views, including as ast
+@ref unicoap_message_t.method,
+@ref unicoap_message_t.status, or
+@ref unicoap_message_t.signal number.
+
+@}
diff --git a/sys/net/application_layer/unicoap/docs/pdu.doc.md b/sys/net/application_layer/unicoap/docs/pdu.doc.md
new file mode 100644
index 0000000000..73c0332ae8
--- /dev/null
+++ b/sys/net/application_layer/unicoap/docs/pdu.doc.md
@@ -0,0 +1,50 @@
+@defgroup net_unicoap_pdu Parsing and Serialization
+@ingroup net_unicoap_message
+@brief Tools for parsing PDUs and serializing messages and options
+
+## Parsing
+To parse a message on your own, use one of the parsers in @ref net_unicoap_message and
+@ref unicoap_parser_result_t. The parsed message structure helps you allocate everything needed in
+one go.
+
+```c
+// Parse an RFC 7252 PDU
+uint8_t pdu[] = {
+ 0x40, 0x02, 0xfe, 0xb1, 0xb9, 0x61, 0x63, 0x74, 0x75, 0x61, 0x74, 0x6f, 0x72, 0x73, 0x04, 0x6c, 0x65, 0x64, 0x73, 0x11, 0x32, 0x37, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3d, 0x67, 0x21, 0x32, 0xff, 0x6d, 0x6f, 0x64, 0x65, 0x3d, 0x6f, 0x6e
+};
+
+unicoap_parser_result_t parsed = { 0 }; // Zero-initialize everything
+
+// Parse message
+ssize_t res = unicoap_pdu_parse_rfc7252_result(pdu, sizeof(pdu), &parsed);
+
+// Handle errors
+if (res < 0) {
+ // eat error
+}
+
+// parsed.message contains the parsed message
+```
+
+## Serializing
+To serialize a message on your own, decide whether you need vectored data or contiguous data.
+To get vectored data, you use `unicoap_pdu_buildv_*` functions, depending on the CoAP transport you intend to use.
+Vectored data is represented using an @ref iolist_t.
+If you want to build a contiguous storage body, refer to the `unicoap_pdu_build_*` methods, also depending
+on the transport.
+
+```c
+// Build an RFC 7252 PDU
+uint8_t pdu[MY_CAPACITY] = { 0 };
+ssize_t size = unicoap_pdu_build_rfc7252(pdu, sizeof(pdu), message, properties);
+
+// Handle errors
+if (res < 0) {
+ // eat error
+}
+```
+
+## Implementation
+Parsing and serialization are each done in two steps:
+1. Message header up until options is parsed/serialized by driver
+2. Options are parsed/serialized by common `unicoap` implementation
diff --git a/sys/net/application_layer/unicoap/docs/unicoap-layers-comms-apis.svg b/sys/net/application_layer/unicoap/docs/unicoap-layers-comms-apis.svg
new file mode 100644
index 0000000000..202ab84046
--- /dev/null
+++ b/sys/net/application_layer/unicoap/docs/unicoap-layers-comms-apis.svg
@@ -0,0 +1,2023 @@
+
+
diff --git a/sys/net/application_layer/unicoap/docs/unicoap-layers-comms.svg b/sys/net/application_layer/unicoap/docs/unicoap-layers-comms.svg
new file mode 100644
index 0000000000..580d132192
--- /dev/null
+++ b/sys/net/application_layer/unicoap/docs/unicoap-layers-comms.svg
@@ -0,0 +1,903 @@
+
+
diff --git a/sys/net/application_layer/unicoap/docs/unicoap-layers.svg b/sys/net/application_layer/unicoap/docs/unicoap-layers.svg
new file mode 100644
index 0000000000..b08e33cc70
--- /dev/null
+++ b/sys/net/application_layer/unicoap/docs/unicoap-layers.svg
@@ -0,0 +1,2395 @@
+
+
diff --git a/sys/net/application_layer/unicoap/drivers/doc.md b/sys/net/application_layer/unicoap/drivers/doc.md
new file mode 100644
index 0000000000..6c193b3e19
--- /dev/null
+++ b/sys/net/application_layer/unicoap/drivers/doc.md
@@ -0,0 +1,11 @@
+@defgroup net_unicoap_drivers Drivers
+@ingroup net_unicoap
+@brief Collection of transport drivers
+@{
+
+Drivers enable you to use different transports while still relying on the same high-level CoAP
+API. Each driver implements framing and a messaging model optimized for the given transport.
+Which transport you choose depends on the deployment. For most constrained deployments,
+we recommend the @ref net_unicoap_drivers_udp or @ref net_unicoap_drivers_dtls.
+
+@}
diff --git a/sys/net/application_layer/unicoap/drivers/rfc7252/common/pdu/doc.md b/sys/net/application_layer/unicoap/drivers/rfc7252/common/pdu/doc.md
new file mode 100644
index 0000000000..c41c7459a1
--- /dev/null
+++ b/sys/net/application_layer/unicoap/drivers/rfc7252/common/pdu/doc.md
@@ -0,0 +1,30 @@
+@defgroup net_unicoap_drivers_rfc7252_pdu RFC 7252 Framing
+@ingroup net_unicoap_drivers
+@brief Parse and serialize RFC 7252 PDUs
+@{
+
+Module. Specify `USEMODULE += unicoap_driver_rfc7252_pdu` in your application's Makefile.
+
+This module allows you to use the RFC 7252 parser without having to import the
+@ref net_unicoap_drivers_udp or @ref net_unicoap_drivers_dtls, i.e., without a network backend.
+This is particularly handy if you want to experiment with `unicoap` or write tests that don't
+need the ability to do network I/O.
+
+## PDU Format
+
+```
+0 1 2 3
+0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+|Ver| T | TKL | Code | Message ID |
++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+| Token (if any, TKL bytes) ...
++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+| Options (if any) ...
++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+|1 1 1 1 1 1 1 1| Payload (if any) ...
++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+```
+
+@see [RFC 7252, Message Format](https://datatracker.ietf.org/doc/html/rfc7252#section-3)
+@}
diff --git a/sys/net/application_layer/unicoap/drivers/rfc7252/dtls/doc.md b/sys/net/application_layer/unicoap/drivers/rfc7252/dtls/doc.md
new file mode 100644
index 0000000000..891f540a54
--- /dev/null
+++ b/sys/net/application_layer/unicoap/drivers/rfc7252/dtls/doc.md
@@ -0,0 +1,48 @@
+@defgroup net_unicoap_drivers_dtls CoAP over DTLS Driver
+@ingroup net_unicoap_drivers
+@brief Use CoAP over DTLS, with optional reliability
+@{
+
+Module. Specify `USEMODULE += unicoap_driver_dtls` in your application's Makefile.
+
+Include these headers required for managing DTLS credentials.
+```c
+#include "net/sock/dtls/creds.h"
+#include "net/credman.h"
+#include "net/dsm.h"
+```
+
+Then, in your application, call @ref sock_dtls_add_credential to add a DTLS credential.
+
+
+
+@see @ref unicoap_rfc7252_message_type_t
+
+This is the dependency graph of this driver:
+
+```
+unicoap_driver_dtls
+├── unicoap_driver_rfc7252_common
+│ ├── unicoap_driver_rfc7252_common_messaging
+│ └── unicoap_driver_rfc7252_common_pdu
+├── unicoap_sock_support
+│ ├── sock_async
+│ ├── sock_async_event
+│ ├── sock_aux_local
+│ └── sock_util
+├── ... (operating system networking modules)
+.
+```
+
+@}
diff --git a/sys/net/application_layer/unicoap/drivers/rfc7252/udp/doc.md b/sys/net/application_layer/unicoap/drivers/rfc7252/udp/doc.md
new file mode 100644
index 0000000000..f7b92430ac
--- /dev/null
+++ b/sys/net/application_layer/unicoap/drivers/rfc7252/udp/doc.md
@@ -0,0 +1,32 @@
+@defgroup net_unicoap_drivers_udp CoAP over UDP Driver
+@ingroup net_unicoap_drivers
+@brief Use CoAP over the UDP transport protocol, with optional reliability
+@{
+
+Module. Specify `USEMODULE += unicoap_driver_udp` in your application's Makefile.
+
+
+@see @ref unicoap_rfc7252_message_type_t
+
+This is the dependency graph of this driver:
+
+```
+unicoap_driver_udp
+├── unicoap_driver_rfc7252_common
+│ ├── unicoap_driver_rfc7252_common_messaging
+│ └── unicoap_driver_rfc7252_common_pdu
+├── unicoap_sock_support
+│ ├── sock_async
+│ ├── sock_async_event
+│ ├── sock_aux_local
+│ └── sock_util
+└── ... (operating system networking module)
+```
+
+@}