Skip to content

net/unicoap: Unified and Modular CoAP Stack: Minimal Client (pt 3)#22266

Draft
carl-tud wants to merge 20 commits into
RIOT-OS:masterfrom
carl-tud:unicoap-03-client-minimal
Draft

net/unicoap: Unified and Modular CoAP Stack: Minimal Client (pt 3)#22266
carl-tud wants to merge 20 commits into
RIOT-OS:masterfrom
carl-tud:unicoap-03-client-minimal

Conversation

@carl-tud
Copy link
Copy Markdown
Contributor

@carl-tud carl-tud commented May 9, 2026

This PR is the third 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 #21389, including reasons why unicoap is needed and a performance analysis.

What does this PR include?

  • Basic client functionality, including URI support
  • A sample client shell application
  • Structured documentation, including a tutorial from the first line of code to running the example and testing it using the included server script

The new API is more flexible. CoAP resource addresses are abstracted into a unicoap_destination_t structure and transport-specific settings are controlled by flags. For example, this is how you can send a request to a

// Define asynchronous response handler (blocking version is also available)
static int handle_response(
  const unicoap_message_t* response, const unicoap_aux_t* aux,
  int error, void* arg
) {
  if (error) {
    // handle error
  }
  printf("status=%s (%zu bytes)\n", unicoap_label_from_status(response->status, response->payload_size);
  return 0;
}

int main(void) {
  // Allocate request message
  unicoap_message_t request;
  // Init as request. Multiple initialisers exist. This one accepts a null-terminated C (UTF-8) string.
  unicoap_request_init_string(&request, UNICOAP_METHOD_PUT, "nack? quack? quack-ack, nack!");
  
  // Next, set the destination. Full URIs are supported (including address literals and zones). 
  // You can also pass a `sock_tl_ep` if you like using `unicoap_destination_endpoint`.  This API leaves 
  // room for SVCB (Service Binding) records in the DNS.
  unicoap_destination_t destination = 
    unicoap_destination_uri("coap://example.org:10815/hello?name=duck&hungry=1");
  
  // Finally, send. The RELIABLE flag instruct unicoap to send confirmable messages when 
  // communicating over UDP or DTLS, abstracting transport-specific message formats.
  int res = unicoap_send_request_async(&request, &destination, 
                   handle_response, UNICOAP_CLIENT_FLAG_RELIABLE, NULL);
  // handle errors
  return 0.
}

I'll organise the monolithic commit into multiple structured commits once this has passed review, and rebase this PR onto #21582 once merged.

@github-actions github-actions Bot added Area: network Area: Networking Area: doc Area: Documentation Area: tests Area: tests and testing framework Area: build system Area: Build system Area: CoAP Area: Constrained Application Protocol implementations Area: sys Area: System Area: examples Area: Example Applications Area: Kconfig Area: Kconfig integration labels May 9, 2026
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@carl-tud carl-tud force-pushed the unicoap-03-client-minimal branch from 4d1d4f2 to 185b973 Compare May 11, 2026 15:07
Copy link
Copy Markdown
Contributor

@mguetschow mguetschow left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have just started looking at the provided example :)

@@ -0,0 +1,75 @@
# Default Makefile, for simple unicoap server sample application
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# Default Makefile, for simple unicoap server sample application
# Default Makefile, for simple unicoap client sample application

@@ -0,0 +1,30 @@
# Sample Client Application With `unicoap`

This a sample application demonstrating how you send CoAP requests using `unicoap`,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
This a sample application demonstrating how you send CoAP requests using `unicoap`,
This is a sample application demonstrating how you send CoAP requests using `unicoap`,

python3 server.py"
```

to send a CoAP request through the tap interface to the CoAP server.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
to send a CoAP request through the tap interface to the CoAP server.
Now, you can send CoAP requests from RIOT through the tap interface to the CoAP server.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe you also want to briefly mention which endpoints the server supports?

BOARD=native make flash term
```
This will compile and run the application.
The application will print a network-layer address.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
The application will print a network-layer address.

Not really relevant on the client side I'd say?

Comment on lines +28 to +49
#if IS_USED(MODULE_UNICOAP_DRIVER_DTLS)
# include "net/sock/dtls/creds.h"
# include "net/credman.h"
# include "net/dsm.h"
# include "unicoap_example_dtls.h"

/* Example credential tag for credman. Tag together with the credential type needs to be unique. */
# define EXAMPLE_DTLS_CREDENTIAL_TAG 42 /* This should answer your question. */

static const uint8_t psk_id_0[] = PSK_DEFAULT_IDENTITY;
static const uint8_t psk_key_0[] = PSK_DEFAULT_KEY;
static const credman_credential_t credential = {
.type = CREDMAN_TYPE_PSK,
.tag = EXAMPLE_DTLS_CREDENTIAL_TAG,
.params = {
.psk = {
.key = { .s = psk_key_0, .len = sizeof(psk_key_0) - 1, },
.id = { .s = psk_id_0, .len = sizeof(psk_id_0) - 1, },
}
},
};
#endif /* IS_USED(MODULE_UNICOAP_DRIVER_DTLS) */
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why commenting this out? Doesn't it work yet?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps looking at C code for hours has damaged my neuronal capacity, but I don't see anything commented out?

Comment on lines +189 to +197

#define MAIN_QUEUE_SIZE (4)
static msg_t _main_msg_queue[MAIN_QUEUE_SIZE];

static const shell_command_t commands[] = {
{ "coap", "unicoap client", _cli },
{ NULL, NULL, NULL }
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#define MAIN_QUEUE_SIZE (4)
static msg_t _main_msg_queue[MAIN_QUEUE_SIZE];
static const shell_command_t commands[] = {
{ "coap", "unicoap client", _cli },
{ NULL, NULL, NULL }
};
SHELL_COMMAND(coap, "unicoap client", _cli)

Pretty sure you don't actually need the msg queue. You can start the shell below with NULL instead of commands.

@carl-tud carl-tud changed the title net/unicoap: Unified and Modular CoAP stack: Minimal client (pt 3) net/unicoap: Unified and Modular CoAP Stack: Minimal Client (pt 3) May 11, 2026
Copy link
Copy Markdown
Contributor

@mguetschow mguetschow left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First round of review, mostly typos and nitpicks. I couldn't find the promised client tutorial and the example might benefit from further comments as you did for the server example.

Otherwise: Great work, as always!

/**
* @brief Flags for enabling advanced features in client exchanges
*
* Pass these flags to one the `unicoap_send_request` functions to modify transmission,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* Pass these flags to one the `unicoap_send_request` functions to modify transmission,
* Pass these flags to one of the `unicoap_send_request` functions to modify transmission,

* @brief Sets the type of the message to confirmable (`CON`),
* if the endpoint is an UDP or DTLS endpoint.
*
* This flag is ignored with reliable transports.. For unreliable transports, a message
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* This flag is ignored with reliable transports.. For unreliable transports, a message
* This flag is ignored with reliable transports. For unreliable transports, a message

* This flag is ignored with reliable transports.. For unreliable transports, a message
* sent with this flag will require an acknowledgement to be sent from the CoAP
* peer.
*
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the default here? Disabled I'd guess?

* @name Private request API
*/
/**
* @brief Sends off client message and creates a state object for pending request
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* @brief Sends off client message and creates a state object for pending request
* @brief Sends off client message and creates a state object for the pending request

* @param callback Callback -- either response or block callback, see @ref unicoap_callback_t
* @param callback_arg Opaque argument passed to callback
* @param flags Client flags
* @param profile Profile, e.g., OSCORE profile
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* @param profile Profile, e.g., OSCORE profile

doesn't exist (yet?)

/** @brief Callback function registered by the client API */
unicoap_callback_t callback;

/** @brief Argument to passed to @p callback */
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/** @brief Argument to passed to @p callback */
/** @brief Argument to be passed to @p callback */

const uint8_t* token, size_t token_length);


unicoap_client_memo_t* unicoap_client_memo_find_refno(int refno);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

doc missing, also below

Comment on lines +181 to +184
/* First bit is sign bit, then 3 bits for min index and then 12 bits of randomness.
* For 16 or fewer memos, we thus don't have to search at all. */
size_t index_min = (refno & 0x7000) >> 12;
uint16_t reference_id = refno & 0xfff;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we have (file-local) defines for these magic numbers?

assert(refno > 0);
#if IS_ACTIVE(CONFIG_UNICOAP_CLIENT_CANCELLABLE)
/* First bit is sign bit, then 3 bits for min index and then 12 bits of randomness.
* For 16 or fewer memos, we thus don't have to search at all. */
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why don't we just use the array index as refno?

_STATE_DEBUG("[NOTIF] use of released state obj\n");
return;
}
assert(memo->endpoint.proto != UNICOAP_PROTO_UNSPECIFIED);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is kind of trivial now that we returned in the opposite case.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area: build system Area: Build system Area: CoAP Area: Constrained Application Protocol implementations Area: doc Area: Documentation Area: examples Area: Example Applications Area: Kconfig Area: Kconfig integration Area: network Area: Networking Area: sys Area: System Area: tests Area: tests and testing framework

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants