RIOT icon indicating copy to clipboard operation
RIOT copied to clipboard

net/unicoap: Unified and Modular CoAP stack: Messaging and Minimal Server (pt 2)

Open carl-tud opened this issue 5 months ago • 18 comments

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 unicoap on 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).

carl-tud avatar Jul 07 '25 17:07 carl-tud

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

riot-ci avatar Jul 07 '25 18:07 riot-ci

@LasseRosenow Does this fit your use case? https://github.com/RIOT-OS/RIOT/blob/4d00949479d4241f2568deb818cbe42277d51219/sys/include/net/unicoap/transport.h#L344-L355

carl-tud avatar Jul 09 '25 15:07 carl-tud

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 avatar Jul 15 '25 07:07 mguetschow

@mguetschow You forgot the ?name= query parameter. Hence, the application, as expected, responds throws a 400.

carl-tud avatar Jul 17 '25 12:07 carl-tud

@mguetschow You forgot the ?name= query parameter. Hence, the application, as expected, responds throws a 400.

Okay, maybe this is stupid.

carl-tud avatar Jul 17 '25 12:07 carl-tud

@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 avatar Jul 17 '25 12:07 mguetschow

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

carl-tud avatar Sep 25 '25 15:09 carl-tud

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

mguetschow avatar Oct 16 '25 15:10 mguetschow

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)

mguetschow avatar Oct 19 '25 15:10 mguetschow

List of open discussion points from very slowly scrolling through the Github webview on a Czech train:

(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...

carl-tud avatar Oct 19 '25 20:10 carl-tud

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

mguetschow avatar Oct 20 '25 15:10 mguetschow

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)

mguetschow avatar Oct 21 '25 13:10 mguetschow

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.

mguetschow avatar Oct 21 '25 14:10 mguetschow

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 is NON, unicoap rightfully sends CON.
  • [ ] 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).

mguetschow avatar Oct 24 '25 14:10 mguetschow

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.

mguetschow avatar Oct 29 '25 16:10 mguetschow

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)

mguetschow avatar Oct 30 '25 14:10 mguetschow

@carl-tud May I also interest you about tiny_strerror? see https://github.com/RIOT-OS/RIOT/pull/21833#discussion_r2478251303

mguetschow avatar Oct 30 '25 14:10 mguetschow

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.

mguetschow avatar Oct 30 '25 15:10 mguetschow

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.

carl-tud avatar Dec 20 '25 13:12 carl-tud

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)

carl-tud avatar Dec 20 '25 16:12 carl-tud

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

carl-tud avatar Dec 20 '25 16:12 carl-tud

@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 avatar Dec 20 '25 16:12 carl-tud

@carl-tud i need to fix this: unicoap holds stale dTLS session until they are explicitly closed by the peer

carl-tud avatar Dec 20 '25 17:12 carl-tud

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 is the way! Really nice change!

LasseRosenow avatar Dec 20 '25 20:12 LasseRosenow

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 avatar Dec 21 '25 14:12 carl-tud

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

carl-tud avatar Dec 23 '25 09:12 carl-tud