ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
coap.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 coap.h
6 * @brief The CoAP server (RFC 7252): the message codec, a fixed resource table, and the UDP binding.
7 *
8 * RFC 7252 sec 3 gives the message: a 4-byte header carrying Version (Ver), Type (T), Token Length
9 * (TKL), Code and Message ID, then the Token of TKL bytes, then zero or more Options in TLV form,
10 * then the Payload behind the one-byte Payload Marker 0xFF.
11 *
12 * A request is answered piggybacked (RFC 7252 sec 5.2.1): a Confirmable request takes an
13 * Acknowledgement carrying the response, a Non-confirmable request takes a Non-confirmable response
14 * (sec 5.2.3). Separate responses (sec 5.2.2) are not produced - a request is answered before its
15 * handler returns - and the server never sends a Confirmable message, so nothing is retransmitted.
16 *
17 * Message deduplication (RFC 7252 sec 4.5) is kept: a Confirmable message repeated within
18 * @ref PROTOCORE_COAP_DEDUP_LIFETIME_MS is recognized by its Message ID and source endpoint and
19 * re-answered from a cache, so the request is processed only once.
20 *
21 * Resource discovery is served at "/.well-known/core" in the CoRE Link Format (RFC 6690 sec 4,
22 * sec 2). The codec reads the Uri-Path (11), Content-Format (12) and Uri-Query (15) options
23 * (RFC 7252 sec 5.10); an unrecognized option of class critical answers 4.02 Bad Option and an
24 * elective one is ignored (sec 5.4.1). Block-wise transfer with the Block2 (23) and Block1 (27)
25 * options (RFC 7959 sec 2.1) is compiled in by PROTOCORE_ENABLE_COAP_BLOCK; resource observation
26 * with the Observe (6) option (RFC 7641 sec 2) by PROTOCORE_ENABLE_COAP_OBSERVE.
27 *
28 * The resource table is a fixed array of PROTOCORE_COAP_MAX_RESOURCES rows in storage. A path is
29 * referenced by pointer and must outlive the server.
30 *
31 * The module exports one symbol, @ref Coap. Everything in coap.c has internal linkage.
32 *
33 * @author Douglas Quigg (dstroy0)
34 * @date 2026
35 */
36
37#ifndef PROTOCORE_COAP_H
38#define PROTOCORE_COAP_H
39
40#include "protocore_config.h" // the entry point: protocore_types.h for the widths
41
42#if PROTOCORE_ENABLE_COAP
43
45
46/** @brief RFC 7252 sec 3 Code: a 3-bit class and a 5-bit detail, written "c.dd". */
47#define COAP_CODE(c, dd) ((uint8_t)(((c) << 5) | ((dd) & 0x1F)))
48
49// One bit per Method Code (RFC 7252 sec 12.1.1: GET 0.01, POST 0.02, PUT 0.03, DELETE 0.04), the bit
50// position being the code. OR'd into the mask a resource registers, so they stay integer constants.
51#define COAP_ALLOW_GET (1u << 1) ///< 0x02, Method Code 0.01
52#define COAP_ALLOW_POST (1u << 2) ///< 0x04, Method Code 0.02
53#define COAP_ALLOW_PUT (1u << 3) ///< 0x08, Method Code 0.03
54#define COAP_ALLOW_DELETE (1u << 4) ///< 0x10, Method Code 0.04
55
56/** @brief RFC 7252 sec 3 Type (T), the 2-bit field. */
57typedef enum PROTO_ENUM_PACKED
58{
59 COAP_TYPE_CON = 0, ///< Confirmable
60 COAP_TYPE_NON = 1, ///< Non-confirmable
61 COAP_TYPE_ACK = 2, ///< Acknowledgement
62 COAP_TYPE_RST = 3, ///< Reset
63} CoapType;
64
65/** @brief RFC 7252 sec 12.1.1 Method Codes, the detail of a class-0 Code byte. */
66typedef enum PROTO_ENUM_PACKED
67{
68 COAP_GET = 1, ///< 0.01 GET (RFC 7252 sec 5.8.1)
69 COAP_POST = 2, ///< 0.02 POST (sec 5.8.2)
70 COAP_PUT = 3, ///< 0.03 PUT (sec 5.8.3)
71 COAP_DELETE = 4, ///< 0.04 DELETE (sec 5.8.4)
72} CoapMethod;
73
74/** @brief RFC 7252 sec 5.9 Response Codes, plus the three RFC 7959 sec 2.9 adds. */
75typedef enum PROTO_ENUM_PACKED
76{
77 COAP_RSP_CREATED = COAP_CODE(2, 1), ///< 2.01 Created
78 COAP_RSP_DELETED = COAP_CODE(2, 2), ///< 2.02 Deleted
79 COAP_RSP_VALID = COAP_CODE(2, 3), ///< 2.03 Valid
80 COAP_RSP_CHANGED = COAP_CODE(2, 4), ///< 2.04 Changed
81 COAP_RSP_CONTENT = COAP_CODE(2, 5), ///< 2.05 Content
82 COAP_RSP_CONTINUE = COAP_CODE(2, 31), ///< 2.31 Continue (RFC 7959 sec 2.9.1)
83 COAP_RSP_BAD_REQUEST = COAP_CODE(4, 0), ///< 4.00 Bad Request
84 COAP_RSP_BAD_OPTION = COAP_CODE(4, 2), ///< 4.02 Bad Option
85 COAP_RSP_NOT_FOUND = COAP_CODE(4, 4), ///< 4.04 Not Found
86 COAP_RSP_METHOD_NOT_ALLOWED = COAP_CODE(4, 5), ///< 4.05 Method Not Allowed
87 COAP_RSP_NOT_ACCEPTABLE = COAP_CODE(4, 6), ///< 4.06 Not Acceptable
88 COAP_RSP_REQUEST_ENTITY_INCOMPLETE = COAP_CODE(4, 8), ///< 4.08 Request Entity Incomplete (RFC 7959 sec 2.9.2)
89 COAP_RSP_REQUEST_ENTITY_TOO_LARGE = COAP_CODE(4, 13), ///< 4.13 Request Entity Too Large (RFC 7959 sec 2.9.3)
90 COAP_RSP_INTERNAL_SERVER_ERROR = COAP_CODE(5, 0), ///< 5.00 Internal Server Error
91 COAP_RSP_NOT_IMPLEMENTED = COAP_CODE(5, 1), ///< 5.01 Not Implemented
92} CoapResponseCode;
93
94/** @brief CoAP Content-Formats (RFC 7252 sec 12.3 Table 9; 60 from the IANA sub-registry, RFC 8949). */
95typedef enum PROTO_ENUM_PACKED
96{
97 COAP_CF_TEXT = 0, ///< text/plain;charset=utf-8
98 COAP_CF_LINK = 40, ///< application/link-format (RFC 6690)
99 COAP_CF_XML = 41, ///< application/xml
100 COAP_CF_OCTET = 42, ///< application/octet-stream
101 COAP_CF_JSON = 50, ///< application/json
102 COAP_CF_CBOR = 60, ///< application/cbor (RFC 8949)
103 COAP_CF_NONE = 0xFFFF, ///< no Content-Format option present or emitted
104} CoapContentFormat;
105
106/**
107 * @brief A decoded request handed to a resource handler.
108 *
109 * Every pointer references scratch that lives for the handler call. Copy out what outlives it.
110 */
111typedef struct
112{
113 CoapMethod method; ///< the Method Code the request carries
114 const char *path; ///< the Uri-Path segments rejoined, leading '/' included
115 const char *query; ///< the Uri-Query segments rejoined by '&', or "" when absent
116 const uint8_t *payload; ///< the request payload, NULL when payload_len is 0
117 size_t payload_len; ///< its length in bytes
118 CoapContentFormat content_format; ///< the request's Content-Format, or COAP_CF_NONE
119} CoapRequest;
120
121/**
122 * @brief The response a resource handler fills in.
123 *
124 * @c code starts at 2.05 Content. The body goes into @c payload within @c payload_cap, with
125 * @c payload_len set to what was written and @c content_format naming its format.
126 */
127typedef struct
128{
129 uint8_t code; ///< the Response Code byte, a ::CoapResponseCode
130 CoapContentFormat content_format; ///< what the body is, or COAP_CF_NONE
131 uint8_t *payload; ///< where the handler writes the body
132 size_t payload_cap; ///< how much room that has
133 size_t payload_len; ///< how much it wrote
134} CoapResponse;
135
136/** @brief Resource handler: read @p req, fill @p resp. */
137typedef void (*CoapHandler)(const CoapRequest *req, CoapResponse *resp);
138
139/** @brief One row of the resource table: a path, the methods it answers, and what answers them. */
140typedef struct
141{
142 const char *path; ///< the Uri-Path it is reached at, referenced by pointer and not copied
143 uint8_t methods; ///< the Method Codes it answers, as COAP_ALLOW_* bits
144 CoapHandler handler; ///< what an allowed method on that path dispatches to
145} CoapResourceArgs;
146
147/** @brief RFC 7252 sec 3: one request datagram in, one response datagram out. */
148typedef struct
149{
150 const uint8_t *req; ///< the request datagram's octets
151 size_t req_len; ///< how many
152 uint8_t *resp; ///< where the response datagram is built
153 size_t resp_cap; ///< how much room that has
154} CoapMessageArgs;
155
156/** @brief RFC 7641: the Observe option a response carries, and the resource a notification renders. */
157typedef struct
158{
159 int32_t seq; ///< the sequence number a 2.xx notification carries (sec 4.4); below 0 omits the option
160 const char *path; ///< the resource a notification re-renders (sec 4.2)
161} CoapObserveArgs;
162
163/** @brief RFC 7252 sec 4.5: the exchange a deduplication entry is keyed by, and what it caches. */
164typedef struct
165{
166 const char *src_ip; ///< the source endpoint's address, as text
167 uint16_t src_port; ///< its port
168 uint16_t mid; ///< the Message ID that endpoint sent
169 const uint8_t *resp; ///< the response a store caches for it
170 size_t resp_len; ///< how many octets that is
171} CoapExchangeArgs;
172
173/** @brief The UDP endpoint the server receives on (RFC 7252 sec 12.6: port 5683, service "coap"). */
174typedef struct
175{
176 uint16_t port; ///< the port a begin binds
177} CoapBindArgs;
178
179/** @brief The server's own state and the calls that reach it, described only in coap.c. */
180struct CoapInternal;
181
182/**
183 * @brief The CoAP server.
184 *
185 * A caller sets the members a call takes, invokes it through ::Coap, and reads the outcome off the
186 * same handle.
187 *
188 * No slot member: one server owns one resource table, and each call names its own subject inside its
189 * own argument group, so no member is common to all of them.
190 *
191 * @var CoapNs::resource the row an add registers
192 * @var CoapNs::msg the request datagram a process reads and the response it writes
193 * @var CoapNs::observe the Observe sequence a response carries and the resource a notify renders
194 * @var CoapNs::exchange the endpoint and Message ID a deduplication entry is keyed by
195 * @var CoapNs::bind the UDP port a begin binds
196 * @var CoapNs::ok a call's true/false outcome
197 * @var CoapNs::n the octets a call produced: the response datagram's length, or a cached response's
198 * @var CoapNs::bytes the cached response a deduplication lookup reports, NULL on a miss
199 * @var CoapNs::reset empty the resource table and every cache
200 * @var CoapNs::add_resource register @c resource, reporting false when the table is full
201 * @var CoapNs::process answer one request datagram, emitting no Observe option
202 * @var CoapNs::process_observe the same, carrying @c observe.seq in a 2.xx response (RFC 7641 sec 4.2)
203 * @var CoapNs::dedup_lookup report the response already sent for @c exchange (RFC 7252 sec 4.5)
204 * @var CoapNs::dedup_store cache the response sent for @c exchange so a repeat is answered from it
205 * @var CoapNs::begin bind @c bind.port and route its datagrams into the server
206 * @var CoapNs::notify send the current representation of @c observe.path to every observer
207 */
208typedef struct
209{
210 CoapResourceArgs resource; ///< what registering a resource takes
211 CoapMessageArgs msg; ///< what answering one datagram takes
212 CoapObserveArgs observe; ///< what the Observe option carries
213 CoapExchangeArgs exchange; ///< what a deduplication entry is keyed by
214 CoapBindArgs bind; ///< what binding the receive port takes
215 proto_bool ok;
216 size_t n;
217 const uint8_t *bytes;
218#if PROTOCORE_COAP_DEDUP_ENTRIES > 0
219#endif
220#if PROTOCORE_ENABLE_COAP_OBSERVE
221#endif
222} CoapVars;
223
224/** @brief The operands and the outcome. */
225extern CoapVars CoapV;
226
227/** @brief The entries. */
228typedef struct
229{
230 void (*const reset)(uint8_t *work);
231 void (*const add_resource)(uint8_t *work);
232 void (*const process)(uint8_t *work);
233 void (*const process_observe)(uint8_t *work);
234 void (*const dedup_lookup)(uint8_t *work);
235 void (*const dedup_store)(uint8_t *work);
236 void (*const begin)(uint8_t *work);
237 void (*const notify)(uint8_t *work);
238} CoapNs;
239
240// What the table binds, defined once in the .c and taking one parameter each: everything
241// else an entry needs is an operand in CoapV or a region of the borrow at a fixed offset.
242void protocore_coap_reset(uint8_t *work);
243void protocore_coap_add_resource(uint8_t *work);
244void protocore_coap_process(uint8_t *work);
245void protocore_coap_process_observe(uint8_t *work);
246#if PROTOCORE_COAP_DEDUP_ENTRIES > 0
247void protocore_coap_dedup_lookup(uint8_t *work);
248void protocore_coap_dedup_store(uint8_t *work);
249#endif
250void protocore_coap_begin(uint8_t *work);
251#if PROTOCORE_ENABLE_COAP_OBSERVE
252void protocore_coap_notify(uint8_t *work);
253#endif
254
255// `static const`, initialised HERE rather than `extern` against a definition in the .c: a
256// const object whose initializer every translation unit can see is a COMPILE-TIME FACT, so
257// `Coap.reset(work)` resolves to a named function and becomes a DIRECT call. An extern table
258// leaves the call indirect and the symbol live at every level, -O2 -flto included.
259static const CoapNs Coap __attribute__((unused)) = {
260 .reset = protocore_coap_reset,
261 .add_resource = protocore_coap_add_resource,
262 .process = protocore_coap_process,
263 .process_observe = protocore_coap_process_observe,
264#if PROTOCORE_COAP_DEDUP_ENTRIES > 0
265 .dedup_lookup = protocore_coap_dedup_lookup,
266 .dedup_store = protocore_coap_dedup_store,
267#endif
268 .begin = protocore_coap_begin,
269#if PROTOCORE_ENABLE_COAP_OBSERVE
270 .notify = protocore_coap_notify,
271#endif
272};
273
274/**
275 * @brief The PROTOCORE_COAP_BORROW bytes this module's state lives in.
276 *
277 * Stated beside the namespace rather than on it: an entry takes a borrow, and this is where
278 * that borrow comes from. Taken once from the end of the pool, which no mark and no release
279 * walks, so the state lasts the life of the program.
280 *
281 * @return the span.
282 */
283uint8_t *protocore_coap_span(void);
284
286
287#endif // PROTOCORE_ENABLE_COAP
288
289#endif // PROTOCORE_COAP_H
PROTO_ENUM_PACKED
Application protocol spoken on a listener port or connection slot.
#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