DeterministicESPAsyncWebServer v6.28.0
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
coap.h
Go to the documentation of this file.
1// 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 Zero-heap CoAP server (RFC 7252): message codec + a fixed resource table.
7 *
8 * The server is split into a pure, host-testable core and an ESP32-only UDP
9 * transport (mirroring the SNMP agent's split):
10 *
11 * - coap_server_process() takes a complete request datagram and produces a
12 * complete response datagram in a caller buffer - no sockets, no heap. It is
13 * unit-tested on the host (env:native_coap).
14 * - coap_server_begin_udp() binds the transport-layer UDP service on :5683
15 * (Arduino only) and feeds received datagrams through coap_server_process().
16 *
17 * Only the message layer's piggybacked-response model is implemented: a CON
18 * request is answered with a piggybacked ACK, a NON request with a NON response.
19 * Separate responses and retransmission/deduplication are out of scope (a
20 * deterministic, constrained server typically replies in-line); the
21 * /.well-known/core resource-discovery listing (RFC 6690) is served. The codec
22 * understands the Uri-Path, Uri-Query and
23 * Content-Format options; other options are skipped. Block-wise transfer
24 * (RFC 7959, the Block1/Block2 options) is available under DETWS_ENABLE_COAP_BLOCK;
25 * resource observation (RFC 7641) under DETWS_ENABLE_COAP_OBSERVE.
26 *
27 * The resource table is a fixed BSS array of DETWS_COAP_MAX_RESOURCES entries.
28 * Register handlers with coap_server_add_resource(); the path string is
29 * referenced by pointer and must outlive the server (point it at flash/static
30 * data, like the rest of the library's strings).
31 */
32
33#ifndef DETERMINISTICESPASYNCWEBSERVER_COAP_H
34#define DETERMINISTICESPASYNCWEBSERVER_COAP_H
35
36#include "ServerConfig.h"
37#include <stddef.h>
38#include <stdint.h>
39
40#if DETWS_ENABLE_COAP
41
42// CoAP message types (RFC 7252 §3, the 2-bit T field).
43enum class CoapType : uint8_t
44{
45 COAP_TYPE_CON = 0, ///< Confirmable (answered with a piggybacked ACK).
46 COAP_TYPE_NON = 1, ///< Non-confirmable (answered with a NON response).
47 COAP_TYPE_ACK = 2, ///< Acknowledgement.
48 COAP_TYPE_RST = 3, ///< Reset (rejects a message; sent for a malformed/empty CON).
49};
50
51// CoAP request method codes (class 0; the on-wire Code byte's detail field).
52enum class CoapMethod : uint8_t
53{
54 COAP_GET = 1,
55 COAP_POST = 2,
56 COAP_PUT = 3,
57 COAP_DELETE = 4,
58};
59
60// Allowed-methods bitmask for coap_server_add_resource() (bit per method). A mask is OR'd together,
61// so these are integer constants in a namespacing struct, not an enum class (which would force a cast
62// at every |). The bit position is the CoapMethod ordinal.
63struct CoapMethodMask
64{
65 static constexpr uint8_t COAP_ALLOW_GET = 1u << (unsigned)CoapMethod::COAP_GET; ///< 0x02
66 static constexpr uint8_t COAP_ALLOW_POST = 1u << (unsigned)CoapMethod::COAP_POST; ///< 0x04
67 static constexpr uint8_t COAP_ALLOW_PUT = 1u << (unsigned)CoapMethod::COAP_PUT; ///< 0x08
68 static constexpr uint8_t COAP_ALLOW_DELETE = 1u << (unsigned)CoapMethod::COAP_DELETE; ///< 0x10
69};
70
71/** @brief Build a CoAP response Code byte from its class and detail (e.g. COAP_CODE(2,5) = 2.05). */
72#define COAP_CODE(c, dd) ((uint8_t)(((c) << 5) | ((dd) & 0x1F)))
73
74// Common CoAP response codes (RFC 7252 §5.9; 2.31 / 4.08 / 4.13 from RFC 7959).
75enum class CoapResponseCode : uint8_t
76{
77 COAP_RSP_CREATED = COAP_CODE(2, 1), ///< 2.01
78 COAP_RSP_DELETED = COAP_CODE(2, 2), ///< 2.02
79 COAP_RSP_VALID = COAP_CODE(2, 3), ///< 2.03
80 COAP_RSP_CHANGED = COAP_CODE(2, 4), ///< 2.04
81 COAP_RSP_CONTENT = COAP_CODE(2, 5), ///< 2.05
82 COAP_RSP_CONTINUE = COAP_CODE(2, 31), ///< 2.31 (block-wise: more Block1 blocks expected)
83 COAP_RSP_BAD_REQUEST = COAP_CODE(4, 0), ///< 4.00
84 COAP_RSP_BAD_OPTION = COAP_CODE(4, 2), ///< 4.02
85 COAP_RSP_NOT_FOUND = COAP_CODE(4, 4), ///< 4.04
86 COAP_RSP_METHOD_NOT_ALLOWED = COAP_CODE(4, 5), ///< 4.05
87 COAP_RSP_NOT_ACCEPTABLE = COAP_CODE(4, 6), ///< 4.06
88 COAP_RSP_REQUEST_INCOMPLETE = COAP_CODE(4, 8), ///< 4.08 (block-wise: out-of-order / lost Block1)
89 COAP_RSP_REQUEST_TOO_LARGE = COAP_CODE(4, 13), ///< 4.13 (block-wise: reassembly buffer exceeded)
90 COAP_RSP_INTERNAL_ERROR = COAP_CODE(5, 0), ///< 5.00
91 COAP_RSP_NOT_IMPLEMENTED = COAP_CODE(5, 1), ///< 5.01
92};
93
94// CoAP Content-Format identifiers (RFC 7252 §12.3). CoapContentFormat::COAP_CF_NONE means "absent".
95enum class CoapContentFormat : uint16_t
96{
97 COAP_CF_TEXT = 0, ///< text/plain;charset=utf-8
98 COAP_CF_LINK = 40, ///< application/link-format
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
103 COAP_CF_NONE = 0xFFFF, ///< no Content-Format option present / emitted
104};
105
106/**
107 * @brief A decoded CoAP request handed to a resource handler.
108 *
109 * All pointers reference transport- or library-owned scratch valid only for the
110 * duration of the handler call; copy out anything you need to keep.
111 */
112struct CoapRequest
113{
114 CoapMethod method; ///< COAP_GET / COAP_POST / COAP_PUT / COAP_DELETE.
115 const char *path; ///< reconstructed Uri-Path, e.g. "/temp" (always begins with '/').
116 const char *query; ///< reconstructed Uri-Query (segments joined by '&'), or "" if none.
117 const uint8_t *payload; ///< request payload bytes (may be nullptr if payload_len == 0).
118 size_t payload_len; ///< request payload length in bytes.
119 CoapContentFormat content_format; ///< request Content-Format, or COAP_CF_NONE if absent.
120};
121
122/**
123 * @brief A response a resource handler fills in.
124 *
125 * @p code defaults to 2.05 Content; set it to another CoapResponseCode as
126 * appropriate. Write the response body into @p payload (capacity @p payload_cap)
127 * and set @p payload_len. Set @p content_format to describe the body, or leave it
128 * CoapContentFormat::COAP_CF_NONE for an empty/typeless response.
129 */
130struct CoapResponse
131{
132 uint8_t code; ///< response Code byte (see CoapResponseCode); defaults to CoapResponseCode::COAP_RSP_CONTENT.
133 CoapContentFormat content_format; ///< COAP_CF_* describing the body, or COAP_CF_NONE.
134 uint8_t *payload; ///< caller-provided buffer to write the response body into.
135 size_t payload_cap; ///< capacity of @p payload in bytes.
136 size_t payload_len; ///< bytes written by the handler (0 = empty body).
137};
138
139/** @brief Resource handler: read @p req, fill @p resp. */
140typedef void (*CoapHandler)(const CoapRequest *req, CoapResponse *resp);
141
142// ---------------------------------------------------------------------------
143// Server configuration / resource registration
144// ---------------------------------------------------------------------------
145
146/** @brief Reset the server and clear the resource table. Call before registering resources. */
147void coap_server_init();
148
149/**
150 * @brief Register a resource at @p path served by @p handler.
151 *
152 * @param path resource path beginning with '/' (referenced by pointer, not copied).
153 * @param methods allowed-methods bitmask (e.g. CoapMethodMask::COAP_ALLOW_GET | CoapMethodMask::COAP_ALLOW_PUT); a
154 * request using a method not in the mask is answered 4.05 Method Not Allowed.
155 * @param handler invoked for an allowed method on a matching path.
156 * @return false if the table is full.
157 */
158bool coap_server_add_resource(const char *path, uint8_t methods, CoapHandler handler);
159
160// ---------------------------------------------------------------------------
161// Core processing (host-testable; no sockets, no heap)
162// ---------------------------------------------------------------------------
163
164/**
165 * @brief Process one CoAP request datagram and build the response datagram.
166 *
167 * Parses the message, reconstructs the Uri-Path/Uri-Query, dispatches against the
168 * resource table, and encodes a piggybacked response (ACK for CON, NON for NON).
169 * A malformed or unsupported-version CON is answered with an RST; a malformed NON
170 * (or any ACK/RST received) yields no response.
171 *
172 * @param req request datagram bytes.
173 * @param req_len number of bytes in @p req.
174 * @param resp destination buffer for the response datagram.
175 * @param resp_cap capacity of @p resp.
176 * @return number of response bytes written, or 0 to send nothing.
177 */
178size_t coap_server_process(const uint8_t *req, size_t req_len, uint8_t *resp, size_t resp_cap);
179
180/**
181 * @brief Like coap_server_process(), but include an Observe option (RFC 7641) in
182 * a successful (2.xx) response carrying the notification sequence
183 * @p observe_seq (a value < 0 omits it). Used by the Observe transport.
184 */
185size_t coap_server_process_ex(const uint8_t *req, size_t req_len, uint8_t *resp, size_t resp_cap, int32_t observe_seq);
186
187// ---------------------------------------------------------------------------
188// UDP transport (binds via the transport-layer UDP service; no-op on host)
189// ---------------------------------------------------------------------------
190
191/**
192 * @brief Bind the server to UDP @p port (default 5683) via the transport-layer UDP service.
193 *
194 * Callback-driven (no per-loop servicing). Call after WiFi is up. On non-Arduino
195 * builds det_udp_listen() is a stub, so the core remains host-testable.
196 */
197void coap_server_begin_udp(uint16_t port = 5683);
198
199#if DETWS_ENABLE_COAP_OBSERVE
200/**
201 * @brief Push a notification to every observer of @p path (RFC 7641).
202 *
203 * Re-renders the resource (invokes its GET handler) and sends the current
204 * representation as a CoAP notification - from the bound server port, carrying
205 * each observer's token and an increasing Observe sequence. Call this whenever the
206 * resource's state changes. A send failure drops that observer. No-op on a host
207 * build. A client registers by sending a GET with the Observe option (0); it
208 * deregisters with Observe (1), a Reset, or by going away.
209 */
210void coap_notify(const char *path);
211#endif
212
213#endif // DETWS_ENABLE_COAP
214
215#endif // DETERMINISTICESPASYNCWEBSERVER_COAP_H
User-facing configuration for DeterministicESPAsyncWebServer.