ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
coaps_server.h
Go to the documentation of this file.
1// ProtoCore v1.0.16 - Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file coaps_server.h
6 * @brief The CoAP-over-DTLS server: a bound UDP port, a pool of DTLS connections, and the bridge.
7 *
8 * The socket half of @ref Coaps. It owns a fixed pool of DTLS 1.3 connections, binds the secured
9 * CoAP port (RFC 7252 sec 12.7 registers 5684 and the service name "coaps"; sec 6.2 gives it the
10 * "coaps" URI scheme), routes each inbound datagram to the connection for its peer, drives the
11 * handshake and its retransmission timer (RFC 9147 sec 5.8), and hands established application
12 * records to @ref Coaps. Resources are registered once through @ref Coap, so a plaintext server on
13 * 5683 and this secured one serve the same table.
14 *
15 * The receive side and the connections run in different contexts: the transport delivers datagrams
16 * as they arrive, so the receive path only copies each into a single-producer ingest ring, and the
17 * poll drains that ring, runs the bridge, fires the retransmission timer, and reclaims connections
18 * that failed or went quiet. The handshake engines therefore only ever run where the poll runs.
19 * Where the build has no network stack there is no receive path: datagrams go in through @c ingest
20 * and replies come out through the sink, which is what makes the whole server host-testable by
21 * shuttling buffers to an in-test DTLS client.
22 *
23 * Raise @ref PROTOCORE_COAPS_MAX_CONNS for more simultaneous peers; each slot is one handshake
24 * engine plus its per-connection key material.
25 *
26 * The module exports one symbol, @ref CoapsServer. Everything in coaps_server.c has internal linkage.
27 *
28 * @author Douglas Quigg (dstroy0)
29 * @date 2026
30 */
31
32#ifndef PROTOCORE_COAPS_SERVER_H
33#define PROTOCORE_COAPS_SERVER_H
34
35#include "protocore_config.h" // the entry point: protocore_types.h for the widths
36
37#if PROTOCORE_ENABLE_DTLS && PROTOCORE_ENABLE_COAP
38
40
41#ifndef PROTOCORE_COAPS_MAX_CONNS
42#define PROTOCORE_COAPS_MAX_CONNS 2 ///< simultaneous connections; each slot is one handshake engine
43#endif
44#ifndef PROTOCORE_COAPS_INGEST_RING
45#define PROTOCORE_COAPS_INGEST_RING 6 ///< datagrams buffered from the receive path until a poll drains them
46#endif
47#ifndef PROTOCORE_COAPS_PORT
48#define PROTOCORE_COAPS_PORT 5684 ///< the port a bind takes by default (RFC 7252 sec 12.7)
49#endif
50#ifndef PROTOCORE_COAPS_IDLE_MS
51#define PROTOCORE_COAPS_IDLE_MS 60000 ///< a connection with no inbound datagram for this long is reclaimed
52#endif
53
54/**
55 * @brief The server's long-lived identity and the randomness each handshake draws from.
56 *
57 * @c cert_der and @c ed25519_seed are the certificate and the signing key that matches it.
58 * @c cookie_key is the server-wide secret keying the HelloRetryRequest cookie's MAC (RFC 9147
59 * sec 5.1). @c rng must be a CSPRNG: it is called per handshake for the X25519 ephemeral private key
60 * and the ServerHello random. The certificate octets are referenced by pointer and must outlive the
61 * server; the seeds are copied into storage.
62 */
63typedef struct
64{
65 const uint8_t *cert_der; ///< the leaf certificate, DER, referenced and not copied
66 size_t cert_len; ///< its length
67 uint8_t ed25519_seed[32]; ///< the signing seed that matches @c cert_der
68 uint8_t cookie_key[32]; ///< the HelloRetryRequest cookie secret (RFC 9147 sec 5.1)
69 void (*rng)(uint8_t *out, size_t len); ///< the CSPRNG each handshake draws its ephemeral and random from
70} CoapsServerIdentityArgs;
71
72/** @brief The UDP endpoint the server receives on (RFC 7252 sec 12.7: port 5684, service "coaps"). */
73typedef struct
74{
75 uint16_t port; ///< the port a begin binds, or 0 for @ref PROTOCORE_COAPS_PORT
76} CoapsServerBindArgs;
77
78#if !PROTOCORE_HAS_NET_STACK
79/** @brief Where an outbound datagram goes where the build has no network stack. */
80typedef void (*CoapsServerOutFn)(void *ctx, const uint8_t *datagram, size_t len, const char *ip, uint16_t port);
81
82/** @brief The outbound sink and the context it is handed back. */
83typedef struct
84{
85 CoapsServerOutFn fn; ///< what every outbound datagram is given to
86 void *ctx; ///< the opaque context that sink is given back
87} CoapsServerSinkArgs;
88
89/** @brief One datagram injected in place of a receive, and the peer it is attributed to. */
90typedef struct
91{
92 const uint8_t *data; ///< the datagram's octets
93 size_t len; ///< how many
94 const char *ip; ///< the peer's address, as text
95 uint16_t port; ///< its port
96} CoapsServerIngestArgs;
97#endif
98
99/**
100 * @brief The CoAP-over-DTLS server.
101 *
102 * A caller sets the members a call takes, invokes it through ::CoapsServer, and reads the outcome off
103 * the same handle.
104 *
105 * No slot member: one server owns the pool, and a call that names an endpoint names it inside its own
106 * argument group rather than at the top.
107 *
108 * @var CoapsServerNs::identity the certificate, keys and CSPRNG a begin installs
109 * @var CoapsServerNs::bind the port a begin binds
110 * @var CoapsServerNs::sink where an outbound datagram goes with no network stack
111 * @var CoapsServerNs::dgram one datagram injected in place of a receive
112 * @var CoapsServerNs::ok a call's true/false outcome
113 * @var CoapsServerNs::u8 the pool slots in use
114 * @var CoapsServerNs::begin install @c identity, bind @c bind.port, and route its datagrams into the pool
115 * @var CoapsServerNs::poll drain the ingest ring through the bridge, fire the retransmission timer
116 * (RFC 9147 sec 5.8), and reclaim failed or quiet connections
117 * @var CoapsServerNs::active_conns report the pool slots in use
118 * @var CoapsServerNs::stop stop polling, release every slot, and empty the ingest ring
119 * @var CoapsServerNs::set_out_sink install @c sink
120 * @var CoapsServerNs::ingest queue @c dgram as though it had been received
121 */
122typedef struct
123{
124 CoapsServerIdentityArgs identity; ///< what the handshakes are run with
125 CoapsServerBindArgs bind; ///< what binding the receive port takes
126#if !PROTOCORE_HAS_NET_STACK
127 CoapsServerSinkArgs sink; ///< where the replies go
128 CoapsServerIngestArgs dgram; ///< what an injected datagram carries
129#endif
130 proto_bool ok;
131 uint8_t u8;
132#if !PROTOCORE_HAS_NET_STACK
133#endif
134} CoapsServerVars;
135
136/** @brief The operands and the outcome. */
137extern CoapsServerVars CoapsServerV;
138
139/** @brief The entries. */
140typedef struct
141{
142 void (*const begin)(uint8_t *work);
143 void (*const poll)(uint8_t *work);
144 void (*const active_conns)(uint8_t *work);
145 void (*const stop)(uint8_t *work);
146 void (*const set_out_sink)(uint8_t *work);
147 void (*const ingest)(uint8_t *work);
148} CoapsServerNs;
149
150// What the table binds, defined once in the .c and taking one parameter each: everything
151// else an entry needs is an operand in CoapsServerV or a region of the borrow at a fixed offset.
152void protocore_coaps_server_begin(uint8_t *work);
153void protocore_coaps_server_poll(uint8_t *work);
154void protocore_coaps_server_active_conns(uint8_t *work);
155void protocore_coaps_server_stop(uint8_t *work);
156#if !PROTOCORE_HAS_NET_STACK
157void protocore_coaps_server_set_out_sink(uint8_t *work);
158void protocore_coaps_server_ingest(uint8_t *work);
159#endif
160
161// `static const`, initialised HERE rather than `extern` against a definition in the .c: a
162// const object whose initializer every translation unit can see is a COMPILE-TIME FACT, so
163// `CoapsServer.begin(work)` resolves to a named function and becomes a DIRECT call. An extern table
164// leaves the call indirect and the symbol live at every level, -O2 -flto included.
165static const CoapsServerNs CoapsServer __attribute__((unused)) = {
166 .begin = protocore_coaps_server_begin,
167 .poll = protocore_coaps_server_poll,
168 .active_conns = protocore_coaps_server_active_conns,
169 .stop = protocore_coaps_server_stop,
170#if !PROTOCORE_HAS_NET_STACK
171 .set_out_sink = protocore_coaps_server_set_out_sink,
172 .ingest = protocore_coaps_server_ingest,
173#endif
174};
175
176/**
177 * @brief The PROTOCORE_COAPS_SERVER_BORROW bytes this module's state lives in.
178 *
179 * Stated beside the namespace rather than on it: an entry takes a borrow, and this is where
180 * that borrow comes from. Taken once from the end of the pool, which no mark and no release
181 * walks, so the state lasts the life of the program.
182 *
183 * @return the span.
184 */
185uint8_t *protocore_coaps_server_span(void);
186
188
189#endif // PROTOCORE_ENABLE_DTLS && PROTOCORE_ENABLE_COAP
190
191#endif // PROTOCORE_COAPS_SERVER_H
#define PROTOCORE_BEGIN_DECLS
Give a header's declarations C linkage, so their symbol names carry no parameter types.
Definition types.h:96
_Bool proto_bool
The truth value.
Definition types.h:64
#define PROTOCORE_END_DECLS
Definition types.h:97