net/unicoap: Unified and Modular CoAP stack: Messaging and Minimal Server (pt 2)
This PR is the second 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 and a performance analysis.
What does this PR include?
- RFC 7252 messaging implementation
- Implementation of the CoAP over UDP and CoAP over DTLS drivers, plus an additional zero-copy optimization for UDP
- Basic server functionality, including XFA resource definitions and helpful debug logs
- A sample server application
- Structured documentation, including a tutorial from the first line of code to running the example and testing it using the included client script
- Support for running
unicoapon a thread of your choice, e.g., the main thread.
The new API is more flexible. CoAP endpoints are abstracted into a unicoap_endpoint_t structure and transport-specific settings are controlled by flags. For example, this is a simple resource that responds with "Hello, World!".
// Define request handler for /hello
static int handle_hello_request(unicoap_message_t* message,
const unicoap_aux_t* aux, unicoap_request_context_t* ctx, void* arg) {
// The aux parameter provides access to auxiliary information, such as local and remote endpoints,
// transport-specific data and internal properties such as the message token.
// Retrieve remote (client) endpoint and log string description of protocol number.
printf("/hello resource invoked over %s\n", unicoap_string_from_proto(aux->remote->proto));
// Craft response using convenience initializer. Vectored payloads are supported, too.
unicoap_response_init_string(message, UNICOAP_STATUS_CONTENT, "Hello, World!");
// Send response.
return unicoap_send_response(message, ctx);
}
// Statically define resource, but dynamic registrations are also possible.
UNICOAP_RESOURCE(hello) {
.path = "/hello",
// Instruct unicoap to send confirmable messages when communicating over UDP or DTLS.
// This flag abstracts the transport-specific message type.
.flags = UNICOAP_RESOURCE_FLAG_RELIABLE,
// Specify what methods to allow. In unicoap, there are no duplicate defines for method flags.
.methods = UNICOAP_METHODS(UNICOAP_METHOD_GET, UNICOAP_METHOD_PUT),
// Optionally, you can also restrict the resource to a set of transports.
.protocols = UNICOAP_PROTOCOLS(UNICOAP_PROTO_DTLS, UNICOAP_PROTO_UDP),
.handler = handle_hello_request,
.handler_arg = NULL
};
More in the documentation (CI build now available).
Murdock results
:heavy_check_mark: PASSED
c3073397af8bfd71b4e5e95a7abfa0ff04f8071d fixup: forgot to check in util_macros.h
| Success | Failures | Total | Runtime |
|---|---|---|---|
| 10533 | 0 | 10534 | 12m:14s |
Artifacts
@LasseRosenow Does this fit your use case? https://github.com/RIOT-OS/RIOT/blob/4d00949479d4241f2568deb818cbe42277d51219/sys/include/net/unicoap/transport.h#L344-L355
One test datapoint:
$ python3 examples/networking/coap/unicoap_server/client.py -m GET -u "coap://[fe80::5c64:bbff:fe11:9231%tap0]/"
usage: client.py -m <GET|PUT|POST|DELETE|PATCH|iPATCH|FETCH> -u <URI> [--type <NON|CON>] [--observe] [-p <PAYLOAD>]
DEBUG:asyncio:Using selector: EpollSelector
using NON GET request
DEBUG:coap-server:Server ready to receive requests
DEBUG:coap-server:Sending request - Token: 1d4c, Remote: <UDP6EndpointAddress [fe80::5c64:bbff:fe11:9231%tap0] (locally ::%tap0)>
DEBUG:coap-server:Sending message <aiocoap.Message at 0x7fbaf33c74d0: NON GET (MID 43720, token 1d4c) remote <UDP6EndpointAddress [fe80::5c64:bbff:fe11:9231%tap0] (locally ::%tap0)>>
DEBUG:coap-server:Incoming message <aiocoap.Message at 0x7fbaf33ded90: CON 4.00 Bad Request (MID 4769, token 1d4c) remote <UDP6EndpointAddress [fe80::5c64:bbff:fe11:9231%tap0] (locally fe80::5c64:bbff:fe11:9230%tap0)>>
DEBUG:coap-server:Received Response: <aiocoap.Message at 0x7fbaf33ded90: CON 4.00 Bad Request (MID 4769, token 1d4c) remote <UDP6EndpointAddress [fe80::5c64:bbff:fe11:9231%tap0] (locally fe80::5c64:bbff:fe11:9230%tap0)>>
DEBUG:coap-server:Response <aiocoap.Message at 0x7fbaf33ded90: CON 4.00 Bad Request (MID 4769, token 1d4c) remote <UDP6EndpointAddress [fe80::5c64:bbff:fe11:9231%tap0] (locally fe80::5c64:bbff:fe11:9230%tap0)>> matched to request <Pipe at 0x7fbaf33dec90 around <aiocoap.Message at 0x7fbaf33c74d0: NON GET (MID 43720, token 1d4c) remote <UDP6EndpointAddress [fe80::5c64:bbff:fe11:9231%tap0] (locally ::%tap0)>> with 2 callbacks (thereof 1 interests)>
DEBUG:coap-server:Sending empty ACK: acknowledging incoming response
DEBUG:coap-server:Sending message <aiocoap.Message at 0x7fbaf331e990: ACK EMPTY (MID 4769, empty token) remote <UDP6EndpointAddress [fe80::5c64:bbff:fe11:9231%tap0] (locally fe80::5c64:bbff:fe11:9230%tap0)>>
response: 4.00 Bad Request
b''
2025-07-15 09:53:50,226 # coap.messaging.rfc7252: received <NON REQ mid=43720 token=1D4C code=0.01 GET payload=(0 bytes) options=(0; 0 bytes)>
2025-07-15 09:53:50,226 # coap.messaging: received in channel:
2025-07-15 09:53:50,226 # remote=UDP <sock_tl_ep port=5600 netif=7 ipv6=fe80::5c64:bbff:fe11:9230>
2025-07-15 09:53:50,227 # local=UDP <sock_tl_ep port=5683 netif=0 ipv6=fe80::5c64:bbff:fe11:9231>
2025-07-15 09:53:50,227 # coap.server: /greeting: found
2025-07-15 09:53:50,227 # coap.server: invoking handler
2025-07-15 09:53:50,227 # app: GET /greeting, 0 bytes
2025-07-15 09:53:50,227 # error: could not get 'name' query: -2 (No such file or directory)
2025-07-15 09:53:50,227 # coap.server: sending response 4.00 from return value
2025-07-15 09:53:50,227 # coap.messaging: sending in channel:
2025-07-15 09:53:50,227 # remote=UDP <sock_tl_ep port=5600 netif=7 ipv6=fe80::5c64:bbff:fe11:9230>
2025-07-15 09:53:50,227 # local=UDP <sock_tl_ep port=5683 netif=0 ipv6=fe80::5c64:bbff:fe11:9231>
2025-07-15 09:53:50,227 # coap.messaging.rfc7252: sending <CON RESP mid=4769 token=1D4C code=4.00 Bad Request payload=(0 bytes) options=(0; 0 bytes)>
2025-07-15 09:53:50,227 # coap.transport.udp: sendv: 6 bytes
2025-07-15 09:53:50,227 # coap.messaging.rfc7252: <carbon_copy size=6>
2025-07-15 09:53:50,227 # coap.messaging.rfc7252: received <ACK EMPTY mid=4769 token= code=0.00 Empty payload=(0 bytes) options=(0; 0 bytes)>
2025-07-15 09:53:50,228 # coap.messaging.rfc7252: [MID 4769] received ACK, stopping retransmission
2025-07-15 09:53:50,228 # coap.messaging.rfc7252: [MID 4769] transmission ended
@mguetschow You forgot the ?name= query parameter. Hence, the application, as expected, responds throws a 400.
@mguetschow You forgot the
?name=query parameter. Hence, the application, as expected, responds throws a 400.
Okay, maybe this is stupid.
@mguetschow You forgot the
?name=query parameter. Hence, the application, as expected, responds throws a 400.
But I tried to get the root resource which does not require query parameters:
$ python3 examples/networking/coap/unicoap_server/client.py -m GET -u "coap://[fe80::5c64:bbff:fe11:9231%tap0]/"
(TL;DR: something in the resource matching is off)
@mguetschow You forgot the
?name=query parameter. Hence, the application, as expected, responds throws a 400.But I tried to get the root resource which does not require query parameters:
$ python3 examples/networking/coap/unicoap_server/client.py -m GET -u "coap://[fe80::5c64:bbff:fe11:9231%tap0]/"(TL;DR: something in the resource matching is off)
This is fixed as of now. unicoap_resource_match_path_string always did a prefix match previously.
For future reference, here is how to provoke an assertion failure when running the example on the nrf52840dk. For the local setup, I've made use of sliptty as follows:
diff --git a/examples/networking/coap/unicoap_server/Makefile b/examples/networking/coap/unicoap_server/Makefile
index 081c06e489..c8e7871669 100644
--- a/examples/networking/coap/unicoap_server/Makefile
+++ b/examples/networking/coap/unicoap_server/Makefile
@@ -44,6 +44,22 @@ endif
USEMODULE += unicoap
USEMODULE += unicoap_resources_xfa
+USEMODULE += shell
+USEMODULE += shell_cmds_default
+
+# add addr on the fly:
+# ifconfig 7 add 2001:db8::2
+
+IPV6_HOST_ADDR ?= 2001:db8::1
+IPV6_PREFIX ?= 2001:db8::/64
+
+# Configure terminal parameters
+USEMODULE += slipdev_stdio
+TERMPROG ?= sudo sh $(RIOTTOOLS)/sliptty/start_network.sh
+TERMFLAGS ?= -r $(IPV6_PREFIX) $(PORT)
+
+CFLAGS += -DDEBUG_ASSERT_VERBOSE
+
# This module is needed for CoAP over UDP
USEMODULE += unicoap_driver_udp
diff --git a/examples/networking/coap/unicoap_server/main.c b/examples/networking/coap/unicoap_server/main.c
index d0ebf5622c..9d783945fa 100644
--- a/examples/networking/coap/unicoap_server/main.c
+++ b/examples/networking/coap/unicoap_server/main.c
@@ -18,6 +18,10 @@
/* This is needed for netifs_print_ipv6 in main below. */
#include "net/netif.h"
+#include "shell.h"
+
+#define MAIN_QUEUE_SIZE (4)
+static msg_t _main_msg_queue[MAIN_QUEUE_SIZE];
/* If you need CoAP over DTLS support, you need to include extra dependencies. What's more,
* you'll also need to load a DTLS credential for message encryption and verification.
@@ -305,4 +309,8 @@ int main(void) {
printf("app: running unicoap loop on main thread\n");
unicoap_loop_run();
#endif
+
+ msg_init_queue(_main_msg_queue, MAIN_QUEUE_SIZE);
+ char line_buf[SHELL_DEFAULT_BUFSIZE];
+ shell_run(NULL, line_buf, SHELL_DEFAULT_BUFSIZE);
}
Then I've added a global address in the shell with ifconfig 7 add 2001:db8::2. Calling python3 examples/networking/coap/unicoap_server/client.py -m GET -u "coap://[2001:db8::2]/greeting?name=RIOT" results in
> app: GET /greeting, 0 bytes
sys/net/application_layer/unicoap/options.c:871 => FAILED ASSERTION.
We've already narrowed it down to the type punning in https://github.com/RIOT-OS/RIOT/blob/c3073397af8bfd71b4e5e95a7abfa0ff04f8071d/sys/net/application_layer/unicoap/options.c#L870
List of open discussion points from very slowly scrolling through the Github webview on a Czech train:
- [ ] https://github.com/RIOT-OS/RIOT/pull/21582#discussion_r2198105407
- [x] https://github.com/RIOT-OS/RIOT/pull/21582#discussion_r2201430935
- [x] https://github.com/RIOT-OS/RIOT/pull/21582#discussion_r2201435769
- [x] https://github.com/RIOT-OS/RIOT/pull/21582#discussion_r2205394989
- [x] https://github.com/RIOT-OS/RIOT/pull/21582#discussion_r2205433115
- [x] https://github.com/RIOT-OS/RIOT/pull/21582#discussion_r2205473679
- [x] https://github.com/RIOT-OS/RIOT/pull/21582#discussion_r2205490664
- [x] https://github.com/RIOT-OS/RIOT/pull/21582#discussion_r2205520988
- [x] https://github.com/RIOT-OS/RIOT/pull/21582#discussion_r2205540950
- [x] https://github.com/RIOT-OS/RIOT/pull/21582#discussion_r2205551003
- [x] https://github.com/RIOT-OS/RIOT/pull/21582#discussion_r2205556762
- [x] https://github.com/RIOT-OS/RIOT/pull/21582#discussion_r2205568363
- [ ] https://github.com/RIOT-OS/RIOT/pull/21582#discussion_r2426071892
- [x] https://github.com/RIOT-OS/RIOT/pull/21582#discussion_r2433196787
- [x] https://github.com/RIOT-OS/RIOT/pull/21582#issuecomment-3411509007
- [ ] https://github.com/RIOT-OS/RIOT/pull/21582#issuecomment-3422611616
(you may check the ones that are resolved)
List of open discussion points from very slowly scrolling through the Github webview on a Czech train:
- [ ] net/unicoap: Unified and Modular CoAP stack: Messaging and Minimal Server (pt 2) #21582 (comment)
- [ ] net/unicoap: Unified and Modular CoAP stack: Messaging and Minimal Server (pt 2) #21582 (comment)
- [ ] net/unicoap: Unified and Modular CoAP stack: Messaging and Minimal Server (pt 2) #21582 (comment)
- [ ] net/unicoap: Unified and Modular CoAP stack: Messaging and Minimal Server (pt 2) #21582 (comment)
- [ ] net/unicoap: Unified and Modular CoAP stack: Messaging and Minimal Server (pt 2) #21582 (comment)
- [ ] net/unicoap: Unified and Modular CoAP stack: Messaging and Minimal Server (pt 2) #21582 (comment)
- [ ] net/unicoap: Unified and Modular CoAP stack: Messaging and Minimal Server (pt 2) #21582 (comment)
- [ ] net/unicoap: Unified and Modular CoAP stack: Messaging and Minimal Server (pt 2) #21582 (comment)
- [ ] net/unicoap: Unified and Modular CoAP stack: Messaging and Minimal Server (pt 2) #21582 (comment)
- [ ] net/unicoap: Unified and Modular CoAP stack: Messaging and Minimal Server (pt 2) #21582 (comment)
- [ ] net/unicoap: Unified and Modular CoAP stack: Messaging and Minimal Server (pt 2) #21582 (comment)
- [ ] net/unicoap: Unified and Modular CoAP stack: Messaging and Minimal Server (pt 2) #21582 (comment)
- [ ] net/unicoap: Unified and Modular CoAP stack: Messaging and Minimal Server (pt 2) #21582 (comment)
- [ ] net/unicoap: Unified and Modular CoAP stack: Messaging and Minimal Server (pt 2) #21582 (comment)
(you may check the ones that are resolved)
I fixed most of these but GitHub wouldn't let me mark them as resolved. My guess is -- not enough JavaScript.
slowly scrolling through the Github webview on a Czech train
good god...
Followup of https://github.com/RIOT-OS/RIOT/pull/21582#discussion_r2433309591 regarding dTLS:
The Network error: NoRequestInterface was due to me not installing aiocoap[all] as recommended on https://aiocoap.readthedocs.io/en/latest/installation.html. However, now I get (probably same as you)
$ python3 examples/networking/coap/unicoap_server/client.py -m GET -u "coaps://[fe80::5c64:bbff:fe11:9231%tap0]/.well-known/core"
usage: client.py -m <GET|PUT|POST|DELETE|PATCH|iPATCH|FETCH> -u <URI> [--type <NON|CON>] [--observe] [-p <PAYLOAD>]
DEBUG:asyncio:Using selector: EpollSelector
using NON GET request
timeout set to 4.0s
INFO:websockets.server:server listening on [::]:8600
DEBUG:coap-server:Server ready to receive requests
/home/mikolai/TUD/Code/RIOT/examples/networking/coap/unicoap_server/client.py:107: DeprecationWarning: Initializing messages with an mtype is deprecated. Instead, set transport_tuning=aiocoap.Reliable oraiocoap. Unreliable.
request = Message(
INFO:coap-server:No DTLS connection active to (fe80::5c64:bbff:fe11:9231%tap0, 5684, b'Client_identity'), creating one
DEBUG:coap-server:Sending request - Token: be49, Remote: <aiocoap.transports.tinydtls.DTLSClientConnection object at 0x7fdf1f8cc5d0>
DEBUG:coap-server:Sending message <aiocoap.Message: GET to <aiocoap.transports.tinydtls.DTLSClientConnection object at 0x7fdf1f8cc5d0>, 1 option(s), token be49, NON, MID 0xa9e1>
WARNING:coap-server:Unhandled alert level 1 code 0
error: timeout exceeded after waiting 4.0s
Exception ignored in: <function DTLSClientConnection.__del__ at 0x7fdf1fab9580>
Traceback (most recent call last):
File "/home/mikolai/.local/share/virtualenvs/RIOT-Q3sUo0rz/lib/python3.11/site-packages/aiocoap/transports/tinydtls.py", line 263, in __del__
self.shutdown()
File "/home/mikolai/.local/share/virtualenvs/RIOT-Q3sUo0rz/lib/python3.11/site-packages/aiocoap/transports/tinydtls.py", line 256, in shutdown
self._transport.close()
File "/usr/lib/python3.11/asyncio/selector_events.py", line 839, in close
self._loop.call_soon(self._call_connection_lost, None)
File "/usr/lib/python3.11/asyncio/base_events.py", line 761, in call_soon
self._check_closed()
File "/usr/lib/python3.11/asyncio/base_events.py", line 519, in _check_closed
raise RuntimeError('Event loop is closed')
RuntimeError: Event loop is closed
Interestingly enough, for CONFIG_UNICOAP_DEBUG_LOGGING=y, CONFIG_UNICOAP_ASSIST=y, I get coap.transport.dtls: establishing session... and then unicoap does not respond to any requests anymore (even not UDP requests). With both set to n, I was able to get responses to UDP requests even after failed dTLS requests.
On my machine, dTLS with your script and gcoap_server does (mostly) work, so indeed seems to be a unicoap-related issue :/
$ python3 examples/networking/coap/unicoap_server/client.py -m GET -u "coaps://[fe80::5c64:bbff:fe11:9231%tap0]/.well-known/core"
usage: client.py -m <GET|PUT|POST|DELETE|PATCH|iPATCH|FETCH> -u <URI> [--type <NON|CON>] [--observe] [-p <PAYLOAD>]
DEBUG:asyncio:Using selector: EpollSelector
using NON GET request
timeout set to 4.0s
INFO:websockets.server:server listening on [::]:8600
DEBUG:coap-server:Server ready to receive requests
/home/mikolai/TUD/Code/RIOT/examples/networking/coap/unicoap_server/client.py:107: DeprecationWarning: Initializing messages with an mtype is deprecated. Instead, set transport_tuning=aiocoap.Reliable oraiocoap. Unreliable.
request = Message(
INFO:coap-server:No DTLS connection active to (fe80::5c64:bbff:fe11:9231%tap0, 5684, b'Client_identity'), creating one
DEBUG:coap-server:Sending request - Token: 8647, Remote: <aiocoap.transports.tinydtls.DTLSClientConnection object at 0x7fb24d0d03d0>
DEBUG:coap-server:Sending message <aiocoap.Message: GET to <aiocoap.transports.tinydtls.DTLSClientConnection object at 0x7fb24d0d03d0>, 1 option(s), token 8647, NON, MID 0x9f40>
DEBUG:coap-server:Incoming message <aiocoap.Message: 2.05 Content from <aiocoap.transports.tinydtls.DTLSClientConnection object at 0x7fb24d0d03d0>, 1 option(s), 46 byte(s) payload, token 8647, NON, MID 0x9f40>
DEBUG:coap-server:Received Response: <aiocoap.Message: 2.05 Content from <aiocoap.transports.tinydtls.DTLSClientConnection object at 0x7fb24d0d03d0>, 1 option(s), 46 byte(s) payload, token 8647, NON, MID 0x9f40>
DEBUG:coap-server:Response <aiocoap.Message: 2.05 Content from <aiocoap.transports.tinydtls.DTLSClientConnection object at 0x7fb24d0d03d0>, 1 option(s), 46 byte(s) payload, token 8647, NON, MID 0x9f40> matched to request <Pipe at 0x7fb24d0bb490 around <aiocoap.Message: GET to <aiocoap.transports.tinydtls.DTLSClientConnection object at 0x7fb24d0d03d0>, 1 option(s), token 8647, NON, MID 0x9f40> with 2 callbacks (thereof 1 interests)>
response: 2.05 Content
b'</cli/stats>;ct=0;rt="count";obs,</riot/board>'
/home/mikolai/.local/share/virtualenvs/RIOT-Q3sUo0rz/lib/python3.11/site-packages/aiocoap/transports/tinydtls.py:169: UserWarning: DTLS module did not shut down the DTLSSocket perfectly; it still tried to call _write in vain
warnings.warn(
Exception ignored in: <function DTLSClientConnection.__del__ at 0x7fb24d2bd580>
Traceback (most recent call last):
File "/home/mikolai/.local/share/virtualenvs/RIOT-Q3sUo0rz/lib/python3.11/site-packages/aiocoap/transports/tinydtls.py", line 263, in __del__
self.shutdown()
File "/home/mikolai/.local/share/virtualenvs/RIOT-Q3sUo0rz/lib/python3.11/site-packages/aiocoap/transports/tinydtls.py", line 256, in shutdown
self._transport.close()
File "/usr/lib/python3.11/asyncio/selector_events.py", line 839, in close
self._loop.call_soon(self._call_connection_lost, None)
File "/usr/lib/python3.11/asyncio/base_events.py", line 761, in call_soon
self._check_closed()
File "/usr/lib/python3.11/asyncio/base_events.py", line 519, in _check_closed
raise RuntimeError('Event loop is closed')
RuntimeError: Event loop is closed
The assertion failure problem from https://github.com/RIOT-OS/RIOT/pull/21582#issuecomment-3411509007 seems fixed, so now I was able to test dTLS on the nrf52840dk as well. The output suggests some kind of buffer overflow
Client side
python3 examples/networking/coap/unicoap_server/client.py -m GET -u "coaps://[2001:db8::2]/greeting?name=RIOT"
usage: client.py -m <GET|PUT|POST|DELETE|PATCH|iPATCH|FETCH> -u <URI> [--type <NON|CON>] [--observe] [-p <PAYLOAD>]
DEBUG:asyncio:Using selector: EpollSelector
using NON GET request
timeout set to 4.0s
INFO:websockets.server:server listening on [::]:8600
DEBUG:coap-server:Server ready to receive requests
/home/mikolai/TUD/Code/RIOT/examples/networking/coap/unicoap_server/client.py:107: DeprecationWarning: Initializing messages with an mtype is deprecated. Instead, set transport_tuning=aiocoap.Reliable oraiocoap. Unreliable.
request = Message(
INFO:coap-server:No DTLS connection active to (2001:db8::2, 5684, b'Client_identity'), creating one
DEBUG:coap-server:Sending request - Token: 03d5, Remote: <aiocoap.transports.tinydtls.DTLSClientConnection object at 0x7fd3db0d0490>
DEBUG:coap-server:Sending message <aiocoap.Message: GET to <aiocoap.transports.tinydtls.DTLSClientConnection object at 0x7fd3db0d0490>, 2 option(s), token 03d5, NON, MID 0xe54d>
error: timeout exceeded after waiting 4.0s
server (unicoap) side:
...
coap.transport.dtls: establishing session...
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000074020020000000002EB0517BD95B01002861002000000000000000000000000000000000B4100020C132010060FA0100B410002000000000FFFFCF075833010058330100986100200000000000001500515F010050610020000000FA25000000986E12831A0000000000000074610020C7432700C4....
looks like some kind of buffer overflow to me? like in, undefined behavior, that just happens to behave differently on native and on hardware
(Aaaah, I cannot reply to a comment that is not to a file in Github? o.O)
looks like some kind of buffer overflow to me? like in, undefined behavior, that just happens to behave differently on native and on hardware
there we go (with debug/assist enabled):
https://github.com/carl-tud/RIOT-Pull-Requests/blob/47b3b000c353020c00eca8e739e77cd7ddc29785/sys/net/application_layer/unicoap/drivers/rfc7252/dtls/transport.c#L70
note that res is expected to return -18 here.
Here is the pcap after commenting that line out: unicoap-dtls.pcapng.zip
Python side gives me WARNING:coap-server:Unhandled alert level 1 code 0 now, too.
For future reference: https://github.com/mguetschow/RIOT/commit/92e63e50c0894a80256d79e03e915deb657dea36 contains some fixes to the dtls stack to make unicoap over dtls mostly work: I've encountered spurious deadlocks (I think) when sending multiple dtls requests one after another.
Other open issues encountered on the way:
- [x] unicoap sends a piggy-backed ACK for a CON request, even if the resource is marked
RELIABLE. If the request isNON, unicoap rightfully sendsCON. - [ ] unicoap holds stale dTLS session until they are explicitly closed by the peer, or until the session triage timeout is hit. I would expect it to drop stale sessions as soon as it obtains a new request (session initiation).
https://github.com/RIOT-OS/RIOT/pull/21827 together with https://github.com/mguetschow/RIOT/commit/30044debf4fbf13ad34673d98a0da2bbadb1f8e2 should be the right fixes to get dTLS to work properly.
Testing on native with LWIP_IPV6=1 or LWIP_IPV4 gives
/home/mikolai/TUD/Code/RIOT/sys/net/application_layer/unicoap/drivers/rfc7252/udp/transport.c:29: error: "UDP_DEBUG" redefined [-Werror]
29 | #define UDP_DEBUG(...) _UNICOAP_PREFIX_DEBUG(".transport.udp", __VA_ARGS__)
|
In file included from /home/mikolai/.riot/pkg/lwip/src/include/lwip/api.h:40,
from /home/mikolai/TUD/Code/RIOT/pkg/lwip/include/sock_types.h:22,
from /home/mikolai/TUD/Code/RIOT/sys/include/net/sock/udp.h:845,
from /home/mikolai/TUD/Code/RIOT/sys/include/net/sock/dtls.h:546,
from /home/mikolai/TUD/Code/RIOT/sys/include/net/sock/async/event.h:174,
from /home/mikolai/TUD/Code/RIOT/sys/include/net/unicoap/transport.h:22,
from /home/mikolai/TUD/Code/RIOT/sys/net/application_layer/unicoap/drivers/rfc7252/udp/transport.c:20:
/home/mikolai/.riot/pkg/lwip/src/include/lwip/opt.h:3535: note: this is the location of the previous definition
3535 | #define UDP_DEBUG LWIP_DBG_OFF
but otherwise works like a charm (after applying https://github.com/RIOT-OS/RIOT/pull/21833)
@carl-tud May I also interest you about tiny_strerror? see https://github.com/RIOT-OS/RIOT/pull/21833#discussion_r2478251303
On nrf52840dk, it works using gnrc over slipdev, but lwip (ipv6) currently gives me coap.transport.udp: udp_sendv_aux failed: -118. Will need further investigation.
Edit: this is with slipdev enabled, when not using that one but only the default 802.15.4 radio, this PR needs to be rebased on current master to include https://github.com/RIOT-OS/RIOT/pull/21533, to avoid an assertion failure.
With that applied, I still get -118, though. Might very well be an lwIP (setup) issue.
I changed the way paths are defined in unicoap applications to something more semantically unique instead of parsing compile-time slash-separated path strings. Why parse paths at runtime when we can force developers to give us a parsed representation directly?
~~This change will require significantly less code should Uri-Path abbreviations (such as for ./well-known/core) become an official Internet Standard.~~
This is how paths will be expressed going forward. Helpers for printing, converting, comparing, matching against /string/paths and Uri-Path options exist.
// Old
"/"; // root
"/foo/bar"; // foo → bar
"/.well-known/core"; // .well-known → core
// New
UNICOAP_PATH_ROOT; // root
UNICOAP_PATH("foo", "bar"); // foo → bar
UNICOAP_PATH_RESOURCE_DISCOVERY; // .well-known → core
UNICOAP_PATH(".well-known", "core"); // .well-known → core
// In resource definitions
UNICOAP_RESOURCE(my_resource) {
.path = UNICOAP_PATH("foo", "bar")
};
P.S.: Should storing each path component separately turn out being painful, we can just change the macro so that it creates the familiar /foo/bar null-terminated path string.
Note that this change drastically lowers the complexity of our path matching functions (UNICOAP_PATH to /a/string/path and UNCOAP_PATH to Uri-Path options)
Also, note that this change can potentially decrease ROM size given an application that defines a lot of deeply nested paths. Each path component is stored as a separate string, hence multiple paths sharing a bunch of components do not necessarily replicate the same information in memory (either through compiler optimisation or thru manually reusing the same string).
@mguetschow Do you want to have a look before I rebase this onto the current master to fix DTLS (and thereby rewrite history (at least this PR's...))
@carl-tud i need to fix this: unicoap holds stale dTLS session until they are explicitly closed by the peer
I changed the way paths are defined in
unicoapapplications to something more semantically unique instead of parsing compile-time slash-separated path strings. Why parse paths at runtime when we can force developers to give us a parsed representation directly?
This is the way! Really nice change!
https://github.com/RIOT-OS/RIOT/pull/21582#issuecomment-3443377611
@carl-tud i need to fix this: unicoap holds stale dTLS session until they are explicitly closed by the peer
@mguetschow Is there really a bug to fix here? Just tried examples/networking/coap/unicoap_server rebased on top of master's d51045bd9d..2bd24b6522 commits. Just works and the DTLS sessions is deconstructed once the server has sent its reponse.
Doesn't waiting for the client to close the session make sense given that the client may want to further observe the resource or continue to send requests to that particular host?
@carl-tud can you work around resources and handle all requests in one function? I.e., does a listener’s matcher need to return a resource? Figure out if that can be NULL.