DeterministicESPAsyncWebServer v6.27.1
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
ServerConfig.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 ServerConfig.h
6 * @brief User-facing configuration for DeterministicESPAsyncWebServer.
7 *
8 * **Compile-time sizing constants**
9 * These govern static array dimensions and must be set before the first
10 * library header is included. Define any of them in your sketch or in a
11 * build flag before including this file to override the defaults:
12 * @code
13 * // platformio.ini
14 * build_flags = -DMAX_CONNS=8 -DBODY_BUF_SIZE=512
15 * @endcode
16 *
17 * **Runtime parameters - flash or RAM, your choice**
18 * `WebServerConfig` holds values that can be changed without a rebuild.
19 * On ESP32, `PROGMEM` is a no-op (const data lands in DROM automatically).
20 * On AVR it places data in flash and requires `pgm_read_*` accessors - this
21 * library targets ESP32 only, so both forms read identically via pointer:
22 * @code
23 * // Flash (PROGMEM, no RAM cost at runtime):
24 * const WebServerConfig my_cfg PROGMEM = { .conn_timeout_ms = 10000 };
25 *
26 * // RAM (can be changed at runtime):
27 * WebServerConfig my_cfg = { .conn_timeout_ms = 10000 };
28 *
29 * server.begin(80, &my_cfg);
30 * @endcode
31 * Pass `nullptr` (or omit the argument) to use the built-in default
32 * (`CONN_TIMEOUT_MS`, 5000 ms idle timeout).
33 */
34
35#ifndef DETERMINISTICESPASYNCWEBSERVER_CONFIG_H
36#define DETERMINISTICESPASYNCWEBSERVER_CONFIG_H
37
38#include <stdint.h>
39
40// ---------------------------------------------------------------------------
41// Compile-time capacity constants (affect static array sizes)
42// ---------------------------------------------------------------------------
43
44/**
45 * @brief Maximum simultaneous TCP connections (fixed static pool; ~3.95 KB of internal RAM per slot).
46 *
47 * Default 8: a keep-alive/concurrency server needs headroom above its peak concurrent client count,
48 * because a connection closed by the keep-alive fairness cap (DETWS_KEEPALIVE_MAX_REQUESTS) briefly
49 * holds its slot in CONN_CLOSING while it drains, and a reconnecting client needs a free slot mean-
50 * while - if concurrency equals the pool size there is none, and the overflow connection is refused
51 * (correct backpressure, but it caps clean throughput at concurrency == MAX_CONNS - 1). Set lower
52 * (e.g. -DMAX_CONNS=4, ~16 KB less RAM) on a RAM-constrained target, or higher (16/32) for a
53 * connection-heavy HTTP server; the event queue tracks it automatically (EVT_QUEUE_DEPTH below).
54 */
55#ifndef MAX_CONNS
56#define MAX_CONNS 8
57#endif
58
59/**
60 * @brief Disable Nagle's algorithm (set TCP_NODELAY) on every accepted connection.
61 *
62 * A request/response server is latency-first: the response is buffered whole (`tcp_write`) and pushed with a
63 * single `tcp_output`, so Nagle only ever delays the final sub-MSS segment of a multi-segment response (or a
64 * streamed chunk) - it waits for the peer's ACK of the prior segment, costing a ~40-200 ms delayed-ACK stall
65 * for no bandwidth benefit here. Disabling it lets that tail go out immediately. Set to 0 only if the device
66 * mainly streams bulk data and you prefer Nagle's segment coalescing over per-response latency.
67 */
68#ifndef DETWS_TCP_NODELAY
69#define DETWS_TCP_NODELAY 1
70#endif
71
72/** @brief Ring-buffer capacity in bytes per connection slot. */
73#ifndef RX_BUF_SIZE
74#define RX_BUF_SIZE 1024
75#define DETWS_RX_BUF_SIZE_DEFAULTED 1 // we chose it; upsized for streaming below (see STREAM_BODY)
76#endif
77
78/**
79 * @brief Compile-time default for connection idle timeout in milliseconds.
80 *
81 * The actual runtime value is stored in `WebServerConfig::conn_timeout_ms`
82 * and loaded into `DeterministicAsyncTCP::conn_timeout_ms` by init().
83 */
84#ifndef CONN_TIMEOUT_MS
85#define CONN_TIMEOUT_MS 5000
86#endif
87
88/**
89 * @brief Upper bound (ms) a slot may dwell in ConnState::CONN_CLOSING after a graceful close
90 * before the idle sweep force-aborts it.
91 *
92 * On a graceful (local) close the slot stays in ConnState::CONN_CLOSING - keeping its PCB and
93 * callbacks - until the peer ACKs the response (then it frees itself in the sent
94 * callback). If the peer never ACKs (dead/black-holed), this bound lets the
95 * timeout sweep reclaim the slot so the fixed pool cannot leak.
96 */
97#ifndef DETWS_CLOSING_TIMEOUT_MS
98#define DETWS_CLOSING_TIMEOUT_MS 2000
99#endif
100
101// ---------------------------------------------------------------------------
102// Worker model (server task concurrency)
103// ---------------------------------------------------------------------------
104//
105// The server pipeline (drain events -> dispatch -> send) runs in one or more
106// dedicated FreeRTOS "worker" tasks instead of the user's loop(). Each worker
107// owns a disjoint partition of conn_pool slots (slot i -> worker i %
108// DETWS_WORKER_COUNT) and its own scratch arena, so no two workers ever touch
109// the same slot: shared-nothing, no hot-path locks, latency stays bounded
110// (determinism preserved) while cores run disjoint connections in parallel.
111//
112// DETWS_WORKER_COUNT == 1 (default) is byte-for-byte the single-pipeline model:
113// one worker owns every slot, one arena, the existing single event queue. N > 1
114// is opt-in. The arena BSS cost is DETWS_SCRATCH_ARENA_SIZE * DETWS_WORKER_COUNT.
115
116/** @brief Number of server worker tasks (slots partitioned i % N). Default 1. */
117#ifndef DETWS_WORKER_COUNT
118#define DETWS_WORKER_COUNT 1
119#endif
120
121/**
122 * @brief Stack (bytes) for each server worker task (ESP32).
123 *
124 * Floor note: two heavy computations run on the worker.
125 * - RSA-2048 verification (OIDC / SSH host key / JWKS via the mbedTLS bignum
126 * modexp) uses ~7 KB (measured on a DevKitV1).
127 * - SSH modern crypto (curve25519-sha256 KEX + ssh-ed25519, software field
128 * arithmetic in radix-2^16) peaks at ~10.5 KB (measured on an ESP32-S3): the
129 * deep ssh_gf call chain plus the on-accelerator field inversion nests deeper
130 * than the RSA path.
131 * So the default adapts: 12 KB when SSH is enabled (curve/ed25519 can be
132 * negotiated), 8 KB otherwise. Do NOT lower it below the matching floor
133 * (::DETWS_WORKER_STACK_CURVE_MIN for SSH, ::DETWS_WORKER_STACK_RSA_MIN for
134 * OIDC) or the first handshake overflows the task stack - a build-time guard
135 * (bottom of this file) enforces the floor so a lowered stack is caught at
136 * compile time.
137 */
138#ifndef DETWS_WORKER_TASK_STACK
139// SSH (curve25519 + ssh-ed25519) and HTTP/3 (the QUIC TLS-1.3 handshake reuses the same
140// ssh_ed25519 signer for CertificateVerify) both peak at ~10.5 KB on the worker task, so both need
141// the higher floor; everything else fits in 8 KB.
142#if (defined(DETWS_ENABLE_SSH) && DETWS_ENABLE_SSH) || (defined(DETWS_ENABLE_HTTP3) && DETWS_ENABLE_HTTP3)
143#define DETWS_WORKER_TASK_STACK 12288
144#else
145#define DETWS_WORKER_TASK_STACK 8192
146#endif
147#endif
148
149/**
150 * @brief Minimum worker-task stack (bytes) required once an RSA-2048 verifier is
151 * compiled in (OIDC / SSH).
152 *
153 * The mbedTLS bignum modexp alone consumes ~7 KB; 8 KB leaves room for the rest
154 * of the request call chain. Overridable only for an advanced build that marshals
155 * every RSA verify onto a dedicated larger-stack task (then the worker itself never
156 * runs one) - otherwise leave it at the default.
157 */
158#ifndef DETWS_WORKER_STACK_RSA_MIN
159#define DETWS_WORKER_STACK_RSA_MIN 8192
160#endif
161
162/**
163 * @brief Minimum worker-task stack (bytes) required once SSH is compiled in.
164 *
165 * SSH can negotiate curve25519-sha256 + ssh-ed25519, whose software field
166 * arithmetic peaks at ~10.5 KB of worker stack; 12 KB leaves ~1.8 KB of margin
167 * for the rest of the handshake call chain (comparable to the RSA floor's
168 * margin). Raise both this and ::DETWS_WORKER_TASK_STACK together if you extend
169 * the handshake, or force RSA/DH only (ssh_kex_set_prefer_rsa) on a very tight
170 * build - but the server still advertises the modern suite, so a modern-only
171 * client would still exercise it.
172 */
173#ifndef DETWS_WORKER_STACK_CURVE_MIN
174#define DETWS_WORKER_STACK_CURVE_MIN 12288
175#endif
176
177/** @brief FreeRTOS priority for each server worker task (ESP32). */
178#ifndef DETWS_WORKER_TASK_PRIORITY
179#define DETWS_WORKER_TASK_PRIORITY 5
180#endif
181
182/**
183 * @brief Core that worker 0 pins to (ESP32). Worker k pins to (DETWS_WORKER_CORE
184 * + k) % portNUM_PROCESSORS. Default 1 (APP_CPU), keeping Core 0 lean for the
185 * WiFi/lwIP stack and offloading the user's loop().
186 */
187#ifndef DETWS_WORKER_CORE
188#define DETWS_WORKER_CORE 1
189#endif
190
191/**
192 * @brief Depth of each worker's deferred-callback queue.
193 *
194 * App code on loop() or another task submits work to a slot's owning worker via
195 * detws_defer() / detws_defer_slot(); the worker runs it in its own single-thread
196 * context, so an async push (ws_send / sse_send from a timer) is race-free. Each
197 * worker has one queue of this depth (entries are a {fn, arg} pair, ~8 bytes).
198 */
199#ifndef DETWS_DEFER_QUEUE_DEPTH
200#define DETWS_DEFER_QUEUE_DEPTH 8
201#endif
202
203/**
204 * @brief Idle-sweep timeout, in FreeRTOS ticks, that a worker blocks between
205 * service iterations when no events are pending.
206 *
207 * The worker no longer free-runs a poll: it blocks on a task notification and a
208 * producer (a new connection event or a deferred submission) wakes it the moment
209 * work arrives, so event latency is independent of this value. The block still
210 * times out after this many ticks so the idle timeout sweep (check_timeouts) keeps
211 * reaping stale connections when nothing is in flight.
212 *
213 * Default 1 (1 tick at the Arduino 1 kHz FreeRTOS config) preserves the original
214 * idle cadence byte-for-byte. Because events now wake the worker immediately,
215 * raising it lowers idle wakeups (CPU/power on a battery device) WITHOUT the
216 * latency penalty the old poll-based knob carried - e.g. 100 -> a ~10 Hz idle
217 * sweep, still far below any connection timeout. The internal time base stays
218 * 1000 Hz regardless (see services/clock.h).
219 */
220#ifndef DETWS_WORKER_POLL_TICKS
221#define DETWS_WORKER_POLL_TICKS 1
222#endif
223
224#if DETWS_WORKER_COUNT < 1
225#error "DeterministicESPAsyncWebServer: DETWS_WORKER_COUNT must be >= 1"
226#endif
227#if DETWS_WORKER_COUNT > MAX_CONNS
228#error "DeterministicESPAsyncWebServer: DETWS_WORKER_COUNT must be <= MAX_CONNS"
229#endif
230
231// ---------------------------------------------------------------------------
232// Preempting work queue (DETWS_ENABLE_PREEMPT_QUEUE) - v5 real-time ingest
233// ---------------------------------------------------------------------------
234//
235// Fixed-capacity queues, each feeding one core-pinned processing task: a producer
236// posts a fixed-size item (from a task or an ISR) and the scheduler preempts straight
237// to the task. There are named lanes - one USER lane exposed to the app, and internal
238// DMA / forwarding / device-access lanes that run at a higher priority so internal
239// ingest always preempts user work. Queue storage is static (zero heap), so depth +
240// item size are compile-time; a task's stack is created only when its lane starts.
241// The no-lane detws_pq_* API drives the USER lane. See preempt_queue.h.
242
243/** @brief Enable the preempting work queue primitive (default off). */
244#ifndef DETWS_ENABLE_PREEMPT_QUEUE
245#define DETWS_ENABLE_PREEMPT_QUEUE 0
246#endif
247
248/** @brief Capacity of the preempting queue in items (static-allocated). */
249#ifndef DETWS_PQ_DEPTH
250#define DETWS_PQ_DEPTH 16
251#endif
252
253/** @brief Bytes per preempting-queue item (the posted item must fit). */
254#ifndef DETWS_PQ_ITEM_SIZE
255#define DETWS_PQ_ITEM_SIZE 32
256#endif
257
258/** @brief Stack (bytes) for each preempting-queue processing task (ESP32). */
259#ifndef DETWS_PQ_STACK
260#define DETWS_PQ_STACK 4096
261#endif
262
263/**
264 * @brief Base FreeRTOS priority for the internal preempting lanes (DMA / forwarding /
265 * device access). They run at this and just above, so internal ingest preempts
266 * the user lane; keep it above the user lane's priority and below the lwIP tcpip
267 * (18) / WiFi tasks so networking is never starved. See preempt_queue.h.
268 */
269#ifndef DETWS_PQ_INTERNAL_PRIORITY
270#define DETWS_PQ_INTERNAL_PRIORITY 8
271#endif
272
273#if DETWS_ENABLE_PREEMPT_QUEUE && (DETWS_PQ_DEPTH < 1 || DETWS_PQ_ITEM_SIZE < 1)
274#error "DeterministicESPAsyncWebServer: DETWS_PQ_DEPTH and DETWS_PQ_ITEM_SIZE must be >= 1"
275#endif
276
277// ---------------------------------------------------------------------------
278// DMA peripheral ingest / egress (DETWS_ENABLE_DMA) - v5 hardware ingest
279// ---------------------------------------------------------------------------
280//
281// Move peripheral bytes (UART / I2C / SPI) between the wire and a static buffer
282// with the CPU free during the transfer; a DMA-complete event carries the bytes
283// to a user callback, which typically posts a descriptor into the preempting work
284// queue (DETWS_ENABLE_PREEMPT_QUEUE) so the heavy processing runs off the ISR. RX
285// is double-buffered (ping-pong): the completed buffer is handed up while the DMA
286// engine fills the other. Storage is static (zero heap) - channel count and buffer
287// size are compile-time. See services/dma/dma.h.
288//
289// DETWS_DMA_SIMULATE routes the transfers through an in-memory ingress/egress
290// simulator (feed bytes in, capture bytes out, optional TX->RX loopback) so the
291// whole pipeline is exercised with no physical loopback wire - on the host test
292// bench and, with the flag set, on the device itself. It is the shipped, tested
293// engine; a real silicon backend plugs into det_dma_hw_* when DETWS_DMA_SIMULATE=0.
294
295/** @brief Enable the DMA peripheral ingest / egress primitive (default off). */
296#ifndef DETWS_ENABLE_DMA
297#define DETWS_ENABLE_DMA 0
298#endif
299
300/** @brief Number of DMA channels (static-allocated; each is one peripheral link). */
301#ifndef DETWS_DMA_CHANNELS
302#define DETWS_DMA_CHANNELS 2
303#endif
304
305/** @brief Bytes per DMA transfer buffer (RX is double-buffered at this size). */
306#ifndef DETWS_DMA_BUF_SIZE
307#define DETWS_DMA_BUF_SIZE 256
308#endif
309
310/**
311 * @brief Route DMA transfers through the ingress/egress simulator (default on).
312 * Set to 0 to drive real silicon via the det_dma_hw_* backend hooks.
313 */
314#ifndef DETWS_DMA_SIMULATE
315#define DETWS_DMA_SIMULATE 1
316#endif
317
318#if DETWS_ENABLE_DMA && (DETWS_DMA_CHANNELS < 1 || DETWS_DMA_BUF_SIZE < 1)
319#error "DeterministicESPAsyncWebServer: DETWS_DMA_CHANNELS and DETWS_DMA_BUF_SIZE must be >= 1"
320#endif
321
322// ---------------------------------------------------------------------------
323// Interface forwarding plane (DETWS_ENABLE_FORWARD) - v5 hardware ingest
324// ---------------------------------------------------------------------------
325//
326// A forwarding plane over the ingest pipeline: register interfaces (Wi-Fi STA / AP,
327// Ethernet, a peripheral bus, a radio), each with an egress send callback, then add
328// per-pair allow / deny rules with an optional rate cap. A frame arriving on one
329// interface (det_forward_ingress(), typically from a DMA-complete event posted onto the
330// FORWARD lane) is forwarded to every allowed destination, so the device bridges / routes
331// between its interfaces instead of only terminating traffic. Default-deny and fail-closed
332// (a full destination or an exceeded rate cap drops, never blocks). Static tables (zero
333// heap). See services/forward/forward.h.
334
335/** @brief Enable the interface forwarding plane (default off). */
336#ifndef DETWS_ENABLE_FORWARD
337#define DETWS_ENABLE_FORWARD 0
338#endif
339
340/** @brief Max interfaces the forwarding plane tracks (static-allocated). */
341#ifndef DETWS_FWD_MAX_IFACES
342#define DETWS_FWD_MAX_IFACES 4
343#endif
344
345/** @brief Max forwarding rules (src -> dst allow/deny + rate cap; static-allocated). */
346#ifndef DETWS_FWD_MAX_RULES
347#define DETWS_FWD_MAX_RULES 8
348#endif
349
350/** @brief Max ingress access-control entries (byte-pattern permit/deny; static). */
351#ifndef DETWS_FWD_MAX_ACL
352#define DETWS_FWD_MAX_ACL 8
353#endif
354
355/** @brief Bytes an ACL entry can match (its pattern / mask length). */
356#ifndef DETWS_FWD_ACL_PATLEN
357#define DETWS_FWD_ACL_PATLEN 4
358#endif
359
360/** @brief Max policy routes (byte-pattern -> egress interface; static). Policy routes take
361 * precedence over the src->dst rules, so tagged traffic leaves a chosen interface. */
362#ifndef DETWS_FWD_MAX_ROUTES
363#define DETWS_FWD_MAX_ROUTES 8
364#endif
365
366/** @brief Build-time toggle for the forwarding-path inspection hook (default off, for cost +
367 * privacy). When 1, det_forward_set_inspector() installs a runtime callback that observes
368 * / filters each ingress frame before it is forwarded; when 0 the hook is compiled out
369 * entirely (no call site). Runtime toggle: register or clear (null) the inspector. */
370#ifndef DETWS_FWD_INSPECT
371#define DETWS_FWD_INSPECT 0
372#endif
373
374#if DETWS_ENABLE_FORWARD && (DETWS_FWD_MAX_IFACES < 1 || DETWS_FWD_MAX_RULES < 1 || DETWS_FWD_ACL_PATLEN < 1)
375#error "DeterministicESPAsyncWebServer: DETWS_FWD_MAX_IFACES / DETWS_FWD_MAX_RULES / DETWS_FWD_ACL_PATLEN must be >= 1"
376#endif
377
378// ---------------------------------------------------------------------------
379// Radio / wireless gateway (DETWS_ENABLE_GATEWAY) - v5 southbound-to-northbound bridge
380// ---------------------------------------------------------------------------
381//
382// The generic gateway pattern: a southbound radio (LoRa / nRF24 / Zigbee / ... reached
383// over SPI / I2C / UART) is a "port"; a frame it receives (data-ready ISR -> DMA -> the
384// FORWARD lane -> a per-radio codec) is handed to det_gw_uplink(), which envelopes it with
385// its source node address / port / RSSI and publishes it northbound through the uplink
386// callback (wire it to MQTT / HTTP / WebSocket / UDP). A northbound command goes the other
387// way through det_gw_downlink() to the port's transmit callback. The radio TX + the
388// northbound publish are callbacks (the seam a real radio driver / protocol binding plugs
389// into), so the bridge is host- and device-testable with no radio. Static tables (zero
390// heap). See services/gateway/gateway.h.
391
392/** @brief Enable the radio / wireless gateway bridge (default off). */
393#ifndef DETWS_ENABLE_GATEWAY
394#define DETWS_ENABLE_GATEWAY 0
395#endif
396
397/** @brief Max southbound gateway ports (radios / buses; static-allocated). */
398#ifndef DETWS_GW_MAX_PORTS
399#define DETWS_GW_MAX_PORTS 4
400#endif
401
402/** @brief Default northbound topic prefix (overridable at runtime via det_gw_set_topic_prefix). */
403#ifndef DETWS_GW_DEFAULT_PREFIX
404#define DETWS_GW_DEFAULT_PREFIX "gw"
405#endif
406
407#if DETWS_ENABLE_GATEWAY && (DETWS_GW_MAX_PORTS < 1)
408#error "DeterministicESPAsyncWebServer: DETWS_GW_MAX_PORTS must be >= 1"
409#endif
410
411// ---------------------------------------------------------------------------
412// LoRa radio (DETWS_ENABLE_LORA) - Semtech SX127x / RFM95-96 codec + driver
413// ---------------------------------------------------------------------------
414//
415// A per-radio codec + driver that plugs into the gateway (DETWS_ENABLE_GATEWAY): the
416// RadioHead-compatible 4-byte frame header (to / from / id / flags) codec, and an SX127x
417// register driver over a caller-supplied register-access bus (so the SPI + chip-select
418// wiring is the integration's, and the register protocol is host-testable with a mock
419// bus). Bridge received frames northbound with det_gw_uplink(); the actual RF link needs
420// the module to verify. See services/lora/lora.h.
421
422/** @brief Enable the LoRa (SX127x) radio codec + driver (default off). */
423#ifndef DETWS_ENABLE_LORA
424#define DETWS_ENABLE_LORA 0
425#endif
426
427/** @brief Max LoRa payload bytes (SX127x FIFO is 256; RadioHead uses 251 + 4 header). */
428#ifndef DETWS_LORA_MAX_PAYLOAD
429#define DETWS_LORA_MAX_PAYLOAD 251
430#endif
431
432#if DETWS_ENABLE_LORA && (DETWS_LORA_MAX_PAYLOAD < 1 || DETWS_LORA_MAX_PAYLOAD > 251)
433#error "DeterministicESPAsyncWebServer: DETWS_LORA_MAX_PAYLOAD must be 1..251"
434#endif
435
436// ---------------------------------------------------------------------------
437// nRF24 radio (DETWS_ENABLE_NRF24) - Nordic nRF24L01+ 2.4 GHz driver
438// ---------------------------------------------------------------------------
439//
440// A radio driver that plugs into the gateway (DETWS_ENABLE_GATEWAY). The nRF24L01+ speaks
441// an SPI command protocol (not plain register r/w) and needs a separate CE pin, so the
442// driver runs over a caller-supplied SPI transfer + CE bus (nrf_bus). Its hardware pipe
443// addressing means the "source address" of a received frame is the pipe number - no
444// in-payload header, so there is no separate codec. Bridge received payloads northbound
445// with det_gw_uplink(port, pipe, ...); the RF link needs the module to verify.
446// See services/nrf24/nrf24.h.
447
448/** @brief Enable the nRF24L01+ radio driver (default off). */
449#ifndef DETWS_ENABLE_NRF24
450#define DETWS_ENABLE_NRF24 0
451#endif
452
453/** @brief nRF24 fixed payload width in bytes (1..32; the chip's static payload size). */
454#ifndef DETWS_NRF24_PAYLOAD
455#define DETWS_NRF24_PAYLOAD 32
456#endif
457
458#if DETWS_ENABLE_NRF24 && (DETWS_NRF24_PAYLOAD < 1 || DETWS_NRF24_PAYLOAD > 32)
459#error "DeterministicESPAsyncWebServer: DETWS_NRF24_PAYLOAD must be 1..32"
460#endif
461
462// ---------------------------------------------------------------------------
463// EnOcean ESP3 (DETWS_ENABLE_ENOCEAN) - energy-harvesting 868 MHz serial codec
464// ---------------------------------------------------------------------------
465//
466// A UART telegram codec for EnOcean's ESP3 (EnOcean Serial Protocol 3), the framing used
467// by USB/serial EnOcean gateways (TCM 310 / USB 300): sync 0x55, a 4-byte header (data
468// length, optional length, packet type) protected by CRC8, then data + optional data
469// protected by a second CRC8. esp3_parse() frames one telegram out of a byte stream and
470// verifies both CRCs; esp3_build() assembles one. Pure (no UART code - you feed it the
471// serial bytes), so it is fully host-testable. See services/enocean/enocean.h.
472
473/** @brief Enable the EnOcean ESP3 serial codec (default off). */
474#ifndef DETWS_ENABLE_ENOCEAN
475#define DETWS_ENABLE_ENOCEAN 0
476#endif
477
478/** @brief Reject an ESP3 telegram whose declared data length exceeds this (framing sanity). */
479#ifndef DETWS_ENOCEAN_MAX_DATA
480#define DETWS_ENOCEAN_MAX_DATA 512
481#endif
482
483#if DETWS_ENABLE_ENOCEAN && (DETWS_ENOCEAN_MAX_DATA < 1)
484#error "DeterministicESPAsyncWebServer: DETWS_ENOCEAN_MAX_DATA must be >= 1"
485#endif
486
487// ---------------------------------------------------------------------------
488// PN532 NFC (DETWS_ENABLE_PN532) - NXP PN532 NFC/RFID controller frame codec
489// ---------------------------------------------------------------------------
490//
491// The NXP PN532 (I2C / SPI / HSU) command-frame protocol - a tag read/write bridged to an
492// HTTP / MQTT event. The chip is driven by "normal information frames" (00 00 FF | LEN |
493// LCS | TFI | PData | DCS | 00) with a length checksum and a data checksum, plus a 6-byte
494// ACK frame. pn532_build_frame() / pn532_parse_frame() assemble and verify those frames
495// (the per-command PData is the application's), and pn532_is_ack() detects the ACK. Pure -
496// you carry the frame bytes over your I2C / SPI / UART - so it is fully host-testable.
497// See services/pn532/pn532.h.
498
499/** @brief Enable the PN532 NFC frame codec (default off). */
500#ifndef DETWS_ENABLE_PN532
501#define DETWS_ENABLE_PN532 0
502#endif
503
504/** @brief Reject a PN532 normal frame whose declared length exceeds this (framing sanity). */
505#ifndef DETWS_PN532_MAX_DATA
506#define DETWS_PN532_MAX_DATA 254
507#endif
508
509#if DETWS_ENABLE_PN532 && (DETWS_PN532_MAX_DATA < 1 || DETWS_PN532_MAX_DATA > 254)
510#error "DeterministicESPAsyncWebServer: DETWS_PN532_MAX_DATA must be 1..254"
511#endif
512
513// ---------------------------------------------------------------------------
514// Sigfox (DETWS_ENABLE_SIGFOX) - Wisol / Murata Sigfox modem AT-command codec
515// ---------------------------------------------------------------------------
516//
517// Tiny low-power uplinks over the Sigfox 0G network. A Wisol / Murata Sigfox modem is
518// driven by AT commands over UART: sigfox_build_uplink() formats an `AT$SF=<hex>` frame
519// for a <= 12-byte payload, and sigfox_parse_response() classifies the modem's reply
520// (OK / ERROR / still pending). Pure text-command codec - you carry it over your UART - so
521// it is fully host-testable. See services/sigfox/sigfox.h.
522
523/** @brief Enable the Sigfox AT-command codec (default off). */
524#ifndef DETWS_ENABLE_SIGFOX
525#define DETWS_ENABLE_SIGFOX 0
526#endif
527
528/** @brief Maximum Sigfox uplink payload (the network caps a message at 12 bytes). */
529#ifndef DETWS_SIGFOX_MAX_PAYLOAD
530#define DETWS_SIGFOX_MAX_PAYLOAD 12
531#endif
532
533#if DETWS_ENABLE_SIGFOX && (DETWS_SIGFOX_MAX_PAYLOAD < 1 || DETWS_SIGFOX_MAX_PAYLOAD > 12)
534#error "DeterministicESPAsyncWebServer: DETWS_SIGFOX_MAX_PAYLOAD must be 1..12"
535#endif
536
537// ---------------------------------------------------------------------------
538// Z-Wave (DETWS_ENABLE_ZWAVE) - Silicon Labs Z-Wave Serial API frame codec
539// ---------------------------------------------------------------------------
540//
541// The host-side Serial API of a Silicon Labs 500 / 700-series Z-Wave controller over UART:
542// a Z-Wave mesh bridged to the web. Data frames are SOF (0x01) | LEN | Type | Command |
543// Data | Checksum, where the checksum is 0xFF XOR-folded over LEN..last-data; single-byte
544// ACK (0x06) / NAK (0x15) / CAN (0x18) frames flow-control them. zwave_build_frame() /
545// zwave_parse_frame() assemble and verify a data frame; the per-command payload is the
546// application's. Pure - you carry the bytes over your UART - so it is fully host-testable.
547// See services/zwave/zwave.h.
548
549/** @brief Enable the Z-Wave Serial API frame codec (default off). */
550#ifndef DETWS_ENABLE_ZWAVE
551#define DETWS_ENABLE_ZWAVE 0
552#endif
553
554/** @brief Reject a Z-Wave frame whose declared length exceeds this data cap (sanity). */
555#ifndef DETWS_ZWAVE_MAX_DATA
556#define DETWS_ZWAVE_MAX_DATA 64
557#endif
558
559#if DETWS_ENABLE_ZWAVE && (DETWS_ZWAVE_MAX_DATA < 1)
560#error "DeterministicESPAsyncWebServer: DETWS_ZWAVE_MAX_DATA must be >= 1"
561#endif
562
563// ---------------------------------------------------------------------------
564// Zigbee (DETWS_ENABLE_ZIGBEE) - Silicon Labs EZSP / ASH serial framing codec
565// ---------------------------------------------------------------------------
566//
567// The ASH (Asynchronous Serial Host) data-link layer that carries EZSP frames to a Silicon
568// Labs EmberZNet NCP over UART - a Zigbee network bridged to the web. ASH delimits frames
569// with a Flag byte (0x7E), byte-stuffs the reserved control bytes, and protects each frame
570// with a CRC-16/CCITT. ash_frame_encode() wraps a control byte + payload into a stuffed,
571// CRC'd frame; ash_frame_decode() unstuffs + verifies one. The EZSP command payload the
572// frame carries (version, stack status, an incoming APS message, ...) is the application's.
573// ash_frame_decode() removes the stuffing and verifies the CRC. Pure - you carry the bytes
574// over your UART - so it is fully host-testable. See services/zigbee/zigbee.h.
575
576/** @brief Enable the Zigbee EZSP / ASH framing codec (default off). */
577#ifndef DETWS_ENABLE_ZIGBEE
578#define DETWS_ENABLE_ZIGBEE 0
579#endif
580
581/** @brief Max ASH payload bytes (an EZSP frame; the ASH data field caps near 128). */
582#ifndef DETWS_ZIGBEE_MAX_DATA
583#define DETWS_ZIGBEE_MAX_DATA 128
584#endif
585
586#if DETWS_ENABLE_ZIGBEE && (DETWS_ZIGBEE_MAX_DATA < 1)
587#error "DeterministicESPAsyncWebServer: DETWS_ZIGBEE_MAX_DATA must be >= 1"
588#endif
589
590// ---------------------------------------------------------------------------
591// Thread (DETWS_ENABLE_THREAD) - OpenThread spinel over HDLC-lite framing codec
592// ---------------------------------------------------------------------------
593//
594// The HDLC-lite framing that carries spinel frames to an OpenThread radio co-processor
595// (RCP: an nRF52840 / EFR32) over UART - an 802.15.4 / Thread mesh bridged to IP / the web.
596// Each spinel frame is wrapped by HDLC-lite: an FCS (CRC-16/X-25) is appended, the reserved
597// bytes are byte-stuffed, and a Flag byte (0x7E) terminates it. spinel_frame_encode() /
598// spinel_frame_decode() do the framing + FCS; the spinel command inside (a property
599// get/set/insert, a stream frame) is the application's. Pure - you carry the bytes over your
600// UART - so it is fully host-testable. See services/thread/thread.h.
601
602/** @brief Enable the Thread spinel / HDLC-lite framing codec (default off). */
603#ifndef DETWS_ENABLE_THREAD
604#define DETWS_ENABLE_THREAD 0
605#endif
606
607/** @brief Max spinel payload bytes carried in one HDLC-lite frame. */
608#ifndef DETWS_THREAD_MAX_DATA
609#define DETWS_THREAD_MAX_DATA 256
610#endif
611
612#if DETWS_ENABLE_THREAD && (DETWS_THREAD_MAX_DATA < 1)
613#error "DeterministicESPAsyncWebServer: DETWS_THREAD_MAX_DATA must be >= 1"
614#endif
615
616// ---------------------------------------------------------------------------
617// Wired Ethernet PHY (DETWS_ENABLE_ETHERNET) - run the server over an RMII PHY
618// ---------------------------------------------------------------------------
619//
620// Bring up a wired Ethernet link (an RMII PHY: LAN8720 / TLK110 / RTL8201 / DP83848) so the
621// server runs over Ethernet instead of (or alongside) Wi-Fi. init_eth_physical() is a thin
622// wrapper over the Arduino ETH library; the PHY pins / type / clock come from the standard
623// ETH_PHY_* build flags for your board (see example 19.Ethernet). The egress reporting
624// (det_net_egress -> DetIface::DETIFACE_ETH) and the per-route interface classifier already handle a
625// wired route, so once the link has an IP the server accepts on it with no other change.
626// Default off (zero cost / the ETH library is not linked). ESP32-only.
627
628/** @brief Enable wired Ethernet bring-up (init_eth_physical / eth_ready). Default off. */
629#ifndef DETWS_ENABLE_ETHERNET
630#define DETWS_ENABLE_ETHERNET 0
631#endif
632
633// W5500 SPI Ethernet (arduino-esp32 3.x only). Set DETWS_ETH_W5500=1 to select the SPI PHY over the RMII
634// default; the pins below are the ESP32-S3-DevKitC wiring (HSPI / SPI3). The 2.x ETH library has no W5500,
635// so init_eth_physical() falls back to the RMII ETH.begin() when the core is older.
636#ifndef DETWS_ETH_W5500
637#define DETWS_ETH_W5500 0
638#endif
639#ifndef DETWS_ETH_W5500_CS
640#define DETWS_ETH_W5500_CS 7 ///< chip select
641#endif
642#ifndef DETWS_ETH_W5500_RST
643#define DETWS_ETH_W5500_RST 6 ///< reset
644#endif
645#ifndef DETWS_ETH_W5500_INT
646#define DETWS_ETH_W5500_INT 5 ///< interrupt
647#endif
648#ifndef DETWS_ETH_W5500_SCK
649#define DETWS_ETH_W5500_SCK 12 ///< HSPI clock (S3-DevKitC default)
650#endif
651#ifndef DETWS_ETH_W5500_MISO
652#define DETWS_ETH_W5500_MISO 13 ///< HSPI MISO (S3-DevKitC default)
653#endif
654#ifndef DETWS_ETH_W5500_MOSI
655#define DETWS_ETH_W5500_MOSI 11 ///< HSPI MOSI (S3-DevKitC default)
656#endif
657// W5500 SPI clock in MHz. The W5500 datasheet allows up to 33.3 MHz; 20 is the arduino-esp32 default and
658// a safe value for breadboard jumper wiring. Higher clocks raise throughput (the link is SPI-bound, not
659// PHY-bound) but need clean, short wiring - marginal signal integrity at high MHz corrupts frames.
660#ifndef DETWS_ETH_W5500_SPI_MHZ
661#define DETWS_ETH_W5500_SPI_MHZ 20 ///< W5500 SPI clock (MHz); raise for throughput on clean wiring
662#endif
663
664/**
665 * @brief Enable IPv6 on the network interface (dual-stack). Default off.
666 *
667 * When set, init_ipv6_physical() turns on IPv6 for the Wi-Fi netif (SLAAC link-local plus any
668 * router-advertised global address). The TCP and UDP listeners already bind IPADDR_TYPE_ANY, so
669 * the server accepts IPv6 connections the moment the interface has a v6 address; the DetIp core
670 * (network_drivers/network/ip.h) parses / formats / classifies both families. Requires an
671 * lwIP built with LWIP_IPV6=1 (the stock Arduino-ESP32 core ships it).
672 */
673#ifndef DETWS_ENABLE_IPV6
674#define DETWS_ENABLE_IPV6 0
675#endif
676
677/**
678 * @brief Wi-Fi promiscuous (monitor) capture (DETWS_ENABLE_PROMISC). Default off.
679 *
680 * Passive 802.11 sniffing: promisc_begin() puts the radio in promiscuous mode on a channel and
681 * delivers every frame to a sink (services/promisc). Wire that sink into the forwarding plane
682 * (DETWS_ENABLE_FORWARD) to bridge captured Wi-Fi frames to another interface - e.g. stream them
683 * to a wired collector over Ethernet. Ships a pure 802.11 header parser and libpcap framing
684 * (DLT_IEEE802_11) so a forwarded frame is a valid PCAP a wired Wireshark / tcpdump can read.
685 */
686#ifndef DETWS_ENABLE_PROMISC
687#define DETWS_ENABLE_PROMISC 0
688#endif
689
690/**
691 * @brief Wired field-bus listen-only capture (DETWS_ENABLE_BUS_CAPTURE). Default off.
692 *
693 * The wired counterpart to promiscuous Wi-Fi capture: bus_capture_begin() installs the CAN (TWAI)
694 * controller in listen-only mode - it decodes every frame on the bus but never ACKs or transmits,
695 * so it stays invisible - and delivers each CanFrame to a sink (services/bus_capture). Wire the
696 * sink into the forwarding plane (DETWS_ENABLE_FORWARD) to bridge captured CAN frames to another
697 * interface. can_to_socketcan() formats a frame as a Linux SocketCAN frame so, with the libpcap
698 * DLT_CAN_SOCKETCAN link type, the stream is a capture Wireshark reads.
699 */
700#ifndef DETWS_ENABLE_BUS_CAPTURE
701#define DETWS_ENABLE_BUS_CAPTURE 0
702#endif
703
704// Feature / service / codec tuning knobs are consolidated at the END of this file,
705// under "Feature tuning knobs (grouped and gated by feature)" - placed there so every
706// DETWS_ENABLE_* flag is already resolved and each group can gate on its own feature.
707
708/** @brief Maximum HTTP headers stored per request. */
709#ifndef MAX_HEADERS
710#define MAX_HEADERS 8
711#endif
712
713/** @brief Maximum URL path length (including leading `/`). */
714#ifndef MAX_PATH_LEN
715#define MAX_PATH_LEN 64
716#endif
717
718/**
719 * @brief Maximum header field-name length (e.g. `"Content-Type"`).
720 *
721 * Must accommodate the longest header name the app needs to read by key.
722 * Standard names reach 30+ chars (`Sec-WebSocket-Extensions` = 24,
723 * `Access-Control-Request-Headers` = 30), so the default leaves margin; an
724 * over-long key is truncated (not rejected) by the parser.
725 */
726#ifndef MAX_KEY_LEN
727#define MAX_KEY_LEN 32
728#endif
729
730/** @brief Maximum header field-value length. */
731#ifndef MAX_VAL_LEN
732#define MAX_VAL_LEN 48
733#endif
734
735/** @brief Maximum raw query-string length (everything after `?`). */
736#ifndef MAX_QUERY_LEN
737#define MAX_QUERY_LEN 128
738#endif
739
740/** @brief Maximum number of parsed query-string parameters. */
741#ifndef MAX_QUERY_PARAMS
742#define MAX_QUERY_PARAMS 8
743#endif
744
745/** @brief Maximum number of `:name` path parameters captured per route match. */
746#ifndef MAX_PATH_PARAMS
747#define MAX_PATH_PARAMS 4
748#endif
749
750/**
751 * @brief Capacity for the full `Authorization` header value (Digest auth).
752 *
753 * A Digest `Authorization` header (username, realm, nonce, uri, response,
754 * qop, nc, cnonce) is far longer than MAX_VAL_LEN, so when DETWS_ENABLE_AUTH
755 * is set the parser captures it whole into a dedicated per-request buffer.
756 */
757#ifndef DIGEST_AUTH_HDR_MAX
758#define DIGEST_AUTH_HDR_MAX 384
759#endif
760
761/**
762 * @brief Lifetime of a Digest `nonce`, in milliseconds (default 5 minutes).
763 *
764 * The server mints a stateless, keyed, timestamped nonce (RFC 7616 3.3) rather
765 * than a fixed one: each challenge carries the issue time plus a MAC over the
766 * server secret, so no per-nonce table is needed. A client `Authorization` whose
767 * nonce is older than this window is treated as @c stale - the credentials are
768 * re-checked and, if correct, the server reissues a fresh challenge with
769 * `stale=true` so the client retries transparently (no re-prompt). This bounds
770 * how long a captured Digest response can be replayed without any server-side
771 * state, which the shared-nothing worker model could not hold safely.
772 */
773#ifndef DETWS_DIGEST_NONCE_LIFETIME_MS
774#define DETWS_DIGEST_NONCE_LIFETIME_MS (5u * 60u * 1000u)
775#endif
776
777/** @brief Maximum query-parameter key length. */
778#ifndef QUERY_KEY_LEN
779#define QUERY_KEY_LEN 24
780#endif
781
782/** @brief Maximum query-parameter value length. */
783#ifndef QUERY_VAL_LEN
784#define QUERY_VAL_LEN 48
785#endif
786
787/**
788 * @brief Maximum request body bytes stored in `HttpReq::body`.
789 *
790 * Bodies larger than this trigger a 413 Payload Too Large response -
791 * the parser detects the overflow via `Content-Length` before any body
792 * bytes arrive, so no data is read or stored for oversized requests.
793 */
794#ifndef BODY_BUF_SIZE
795#define BODY_BUF_SIZE 256
796#endif
797
798/** @brief Maximum simultaneously registered routes. */
799#ifndef MAX_ROUTES
800#define MAX_ROUTES 16
801#endif
802
803/**
804 * @brief Maximum globally-registered middleware functions.
805 *
806 * The middleware chain is a fixed array of function pointers run in
807 * registration order before a request reaches its route handler (see
808 * DetWebServer::use()). Costs MAX_MIDDLEWARE pointers of BSS; an empty chain
809 * adds no per-request work.
810 */
811#ifndef MAX_MIDDLEWARE
812#define MAX_MIDDLEWARE 4
813#endif
814
815/**
816 * @brief Per-chunk staging buffer for send_chunked()'s ChunkSource (max bytes a
817 * source produces per call, hence the largest single chunk on the wire).
818 *
819 * Allocated on the worker stack only while a chunk is being framed - no persistent
820 * RAM cost. The pump asks the source for at most this many bytes (or fewer when the
821 * send window is smaller), so it bounds the chunk size, not the total body.
822 *
823 * Sized to one TCP segment (~MSS): the pump frames + sends each chunk in a single
824 * tcpip_thread round-trip (~23 us on-device), so a bigger chunk = fewer round-trips per
825 * byte. 1440 keeps the framed chunk within one segment; raise it (up to the send window)
826 * to cut the round-trip count further on a fast transport (e.g. Ethernet), at more stack.
827 */
828#ifndef CHUNK_BUF_SIZE
829#define CHUNK_BUF_SIZE 1440
830#endif
831
832/**
833 * @brief Maximum object/array nesting depth for the JsonWriter (see json.h).
834 *
835 * Bounds the writer's per-level comma-tracking stack (one bool per level);
836 * begin_object()/begin_array() beyond this fail the writer instead of
837 * overflowing. No heap; ~JSON_MAX_DEPTH bytes of stack inside the writer object.
838 */
839#ifndef JSON_MAX_DEPTH
840#define JSON_MAX_DEPTH 8
841#endif
842
843/**
844 * @brief Step budget for the regex route matcher (see on_regex()).
845 *
846 * The matcher is a bounded backtracker: it counts match steps and fails closed
847 * (no match) once this budget is exhausted, so a pathological pattern can never
848 * backtrack unboundedly. Keeps regex routing deterministic. Routing patterns hit
849 * only a handful of steps; the default leaves wide margin.
850 */
851#ifndef RE_MAX_STEPS
852#define RE_MAX_STEPS 2000
853#endif
854
855// ---------------------------------------------------------------------------
856// WebSocket sizing constants
857// ---------------------------------------------------------------------------
858
859/**
860 * @brief Maximum simultaneous WebSocket connections.
861 *
862 * Each connection occupies one TCP slot from MAX_CONNS and one entry in
863 * ws_pool[]. MAX_WS_CONNS + MAX_SSE_CONNS must not exceed MAX_CONNS.
864 */
865#ifndef MAX_WS_CONNS
866#define MAX_WS_CONNS 2
867#endif
868
869/**
870 * @brief Maximum WebSocket frame payload in bytes.
871 *
872 * Frames larger than this are rejected with Close code 1009 (Message Too Big).
873 * Fragmented messages are not supported; each message must fit in one frame.
874 */
875#ifndef WS_FRAME_SIZE
876#define WS_FRAME_SIZE 512
877#endif
878
879// ---------------------------------------------------------------------------
880// Server-Sent Events sizing constants
881// ---------------------------------------------------------------------------
882
883/**
884 * @brief Maximum simultaneous SSE connections.
885 *
886 * Each connection occupies one TCP slot from MAX_CONNS and one entry in
887 * sse_pool[]. MAX_WS_CONNS + MAX_SSE_CONNS must not exceed MAX_CONNS.
888 */
889#ifndef MAX_SSE_CONNS
890#define MAX_SSE_CONNS 2
891#endif
892
893/**
894 * @brief Output buffer size in bytes for a single SSE event.
895 *
896 * An event larger than this is silently truncated. The buffer holds the
897 * formatted `data: ...\n\n` line before it is handed to tcp_write().
898 */
899#ifndef SSE_BUF_SIZE
900#define SSE_BUF_SIZE 256
901#endif
902
903// ---------------------------------------------------------------------------
904// Static file serving sizing constants
905// ---------------------------------------------------------------------------
906
907/**
908 * @brief Bytes read from the filesystem and passed to tcp_write() per loop().
909 *
910 * Each read+send is one tcpip_thread round-trip (~23 us on-device), so a larger chunk =
911 * fewer round-trips per byte (better throughput on a fast transport), at more peak stack.
912 * Must be <= RX_BUF_SIZE to avoid stalling the TCP send window; 1024 tracks the default
913 * RX_BUF_SIZE. Lower it (e.g. -DFILE_CHUNK_SIZE=512) on a stack-constrained target.
914 */
915#ifndef FILE_CHUNK_SIZE
916#define FILE_CHUNK_SIZE 1024
917#endif
918
919// ---------------------------------------------------------------------------
920// Basic Auth sizing constants
921// ---------------------------------------------------------------------------
922
923/**
924 * @brief Maximum username or password length for HTTP Basic Authentication.
925 *
926 * Both username and password must fit in this many bytes including the
927 * null terminator. Longer credentials are silently rejected with 401.
928 */
929#ifndef MAX_AUTH_LEN
930#define MAX_AUTH_LEN 32
931#endif
932
933// ---------------------------------------------------------------------------
934// Multipart form-data sizing constants
935// ---------------------------------------------------------------------------
936
937/**
938 * @brief Maximum simultaneously parsed multipart parts per request.
939 *
940 * Parts beyond this limit are silently ignored. A typical upload form
941 * has 1-4 fields; increase this for forms with more.
942 */
943#ifndef MAX_MULTIPART_PARTS
944#define MAX_MULTIPART_PARTS 4
945#endif
946
947/**
948 * @brief Maximum MIME boundary length (RFC 2046 allows up to 70 characters).
949 */
950#ifndef MAX_BOUNDARY_LEN
951#define MAX_BOUNDARY_LEN 72
952#endif
953
954// ---------------------------------------------------------------------------
955// Event queue depth
956// ---------------------------------------------------------------------------
957
958/**
959 * @brief Depth of the FreeRTOS event queue shared between lwIP callbacks and
960 * the main-loop task.
961 *
962 * Each slot holds one TcpEvt (8 bytes). The queue is the only heap
963 * allocation the library makes at begin() time:
964 *
965 * heap = sizeof(StaticQueue_t) + EVT_QUEUE_DEPTH * sizeof(TcpEvt)
966 *
967 * Must be large enough to absorb a burst of MAX_CONNS * 4 events without
968 * blocking the lwIP thread, so it tracks MAX_CONNS automatically (a raised
969 * MAX_CONNS never trips the EVT_QUEUE_DEPTH >= MAX_CONNS * 4 guard below).
970 */
971#ifndef EVT_QUEUE_DEPTH
972#define EVT_QUEUE_DEPTH (MAX_CONNS * 4)
973#endif
974
975// ---------------------------------------------------------------------------
976// Internal response buffer sizing constants
977// ---------------------------------------------------------------------------
978
979/**
980 * @brief Stack buffer for HTTP response header lines in send() / send_empty() /
981 * send_unauth() / serve_file().
982 *
983 * Must be large enough to hold the status line, Content-Type, Content-Length,
984 * Connection, and any CORS headers. The CORS block alone can reach
985 * CORS_HDR_BUF_SIZE bytes, so this value should be at least
986 * CORS_HDR_BUF_SIZE + 96.
987 */
988#ifndef RESP_HDR_BUF_SIZE
989#define RESP_HDR_BUF_SIZE 768
990#endif
991
992/**
993 * @brief Per-connection buffer for app-supplied custom response headers and
994 * cookies.
995 *
996 * Filled by add_response_header() / set_cookie() and injected into send() /
997 * send_empty() / redirect() the same way the CORS block is. RESP_HDR_BUF_SIZE
998 * must be large enough to hold the status line plus the CORS block plus this
999 * block (see the assert below).
1000 */
1001#ifndef EXTRA_HDR_BUF_SIZE
1002#define EXTRA_HDR_BUF_SIZE 256
1003#endif
1004
1005/**
1006 * @brief Stack buffer for the HTTP 101 Switching Protocols response sent during
1007 * the WebSocket handshake.
1008 *
1009 * Must hold: status line + Upgrade + Connection + Sec-WebSocket-Accept (28
1010 * base64 chars) + CRLF pairs. Minimum is ~120 bytes; default leaves margin.
1011 */
1012#ifndef WS_HDR_BUF_SIZE
1013#define WS_HDR_BUF_SIZE 256
1014#endif
1015
1016/**
1017 * @brief Size of the pre-built CORS header block stored in DetWebServer.
1018 *
1019 * Built once by set_cors() and injected into every response. Must hold
1020 * Access-Control-Allow-Origin, Access-Control-Allow-Methods, and
1021 * Access-Control-Allow-Headers lines for the configured origin.
1022 */
1023#ifndef CORS_HDR_BUF_SIZE
1024#define CORS_HDR_BUF_SIZE 192
1025#endif
1026
1027/**
1028 * @brief Size of the optional Cache-Control header line stored in DetWebServer.
1029 *
1030 * Built once by set_cache_control() and injected into static-file responses
1031 * (serve_file / serve_static) beside the ETag. Holds "Cache-Control: <value>\r\n".
1032 */
1033#ifndef CACHE_CONTROL_BUF_SIZE
1034#define CACHE_CONTROL_BUF_SIZE 64
1035#endif
1036
1037// ---------------------------------------------------------------------------
1038// Feature flags
1039// ---------------------------------------------------------------------------
1040// Set any of these to 0 in your sketch BEFORE including this library to strip
1041// the feature from the build entirely (no code, no RAM, no flash cost).
1042//
1043// #define DETWS_ENABLE_WEBSOCKET 0
1044// #include <dwserver.h>
1045//
1046// ---------------------------------------------------------------------------
1047// BUILD-FLAG DEPENDENCY TREE
1048// ---------------------------------------------------------------------------
1049// Most features are independent. A few build on another feature and cannot
1050// compile without it; those HARD dependencies are enforced near the bottom of
1051// this file with a clear #error, so an illegal combination fails fast at
1052// compile time instead of producing a cryptic linker error. Enable a child
1053// only together with its parent(s).
1054//
1055// Hard dependencies (child requires parent):
1056//
1057// FILE_SERVING
1058// |-- WEBDAV
1059// `-- RANGE
1060// TLS
1061// |-- MTLS
1062// |-- TLS_RESUMPTION
1063// |-- HTTP_CLIENT_TLS (also requires HTTP_CLIENT)
1064// |-- MQTT_TLS (also requires MQTT)
1065// `-- WS_CLIENT_TLS (also requires WS_CLIENT)
1066// WEBSOCKET
1067// |-- WS_DEFLATE
1068// `-- WEB_TERMINAL
1069// SSE
1070// `-- DASHBOARD
1071// STATS
1072// `-- METRICS
1073// AUTH
1074// `-- AUTH_LOCKOUT
1075// SNMP
1076// |-- SnmpVersion::SNMP_V3
1077// `-- SNMP_TRAP
1078// COAP
1079// |-- COAP_OBSERVE
1080// `-- COAP_BLOCK
1081// OPCUA
1082// `-- OPCUA_CLIENT
1083// CONFIG_STORE
1084// `-- CONFIG_IO
1085//
1086// Optional integrations (these build fine on their own; the named feature is
1087// simply inert or reduced until you also enable the other flag):
1088//
1089// WEBHOOK + HTTP_CLIENT : without it, detws_webhook_post() returns -1
1090// OAUTH2 + HTTP_CLIENT : the token-endpoint POST helpers compile only with it
1091// DASHBOARD + WEBSOCKET : adds live control widgets; the SSE value stream works alone
1092//
1093// Auto-derived (do NOT set these yourself; the library computes them):
1094//
1095// STREAM_BODY = OTA || UPLOAD
1096// CLIENT_TLS = HTTP_CLIENT_TLS || MQTT_TLS || WS_CLIENT_TLS
1097// CAPTURE_AUTH_HEADER = AUTH || JWT || OIDC
1098//
1099// The same tree appears in README.md and examples/Foundation/05.Configuration.
1100// ---------------------------------------------------------------------------
1101
1102/** @brief WebSocket support (RFC 6455 framing + SHA-1/base64 handshake). */
1103#ifndef DETWS_ENABLE_WEBSOCKET
1104#define DETWS_ENABLE_WEBSOCKET 1
1105#endif
1106
1107/**
1108 * @brief WebSocket permessage-deflate (RFC 7692) - bidirectional compression.
1109 *
1110 * When set (and DETWS_ENABLE_WEBSOCKET is on), the server negotiates the
1111 * `permessage-deflate` extension and both decompresses inbound compressed (RSV1)
1112 * messages via a bounded INFLATE (network_drivers/presentation/inflate.*) and
1113 * compresses outbound data frames via a bounded DEFLATE
1114 * (network_drivers/presentation/deflate.*); both borrow their table scratch from
1115 * the shared per-dispatch arena. The extension is negotiated with
1116 * `{client,server}_no_context_takeover` so every message (de)compresses
1117 * independently - no window is carried between messages. An outbound frame that
1118 * would not shrink is sent uncompressed (the per-message RSV1 flag permits this).
1119 * Default off.
1120 */
1121#ifndef DETWS_ENABLE_WS_DEFLATE
1122#define DETWS_ENABLE_WS_DEFLATE 0
1123#endif
1124
1125/**
1126 * @brief WebSocket outbound fragmentation size (RFC 6455 sec 5.4), in payload bytes. 0 = off.
1127 *
1128 * When >0, an outbound data message (text/binary) longer than this many payload bytes is split into
1129 * that-sized WebSocket frames - the first carrying the opcode (and the RFC 7692 RSV1 bit if the message
1130 * is compressed), the rest CONTINUATION, the last with FIN - instead of one large frame. Sizing it near
1131 * the TCP MSS (e.g. 1400) keeps each frame within whole segments (MTU-aligned) and lets a peer with a
1132 * bounded per-frame reassembly buffer receive an arbitrarily long message. The runtime override is
1133 * ws_set_frag_size(). Compression applies to the whole message first, then the compressed bytes are
1134 * split. Default 0 (one frame per message, unchanged).
1135 */
1136#ifndef DETWS_WS_FRAG_SIZE
1137#define DETWS_WS_FRAG_SIZE 0
1138#endif
1139
1140/** @brief Server-Sent Events push support. */
1141#ifndef DETWS_ENABLE_SSE
1142#define DETWS_ENABLE_SSE 1
1143#endif
1144
1145/** @brief multipart/form-data body parser. */
1146#ifndef DETWS_ENABLE_MULTIPART
1147#define DETWS_ENABLE_MULTIPART 1
1148#endif
1149
1150/**
1151 * @brief Zero-heap CBOR (RFC 8949) encoder for compact binary payloads.
1152 *
1153 * Default off. When set, network_drivers/presentation/cbor/cbor.h provides a writer
1154 * that serializes ints, strings, byte strings, arrays, maps, booleans, null, and
1155 * float32 into a caller-provided buffer - a compact binary alternative to the JSON
1156 * writer for telemetry. Pure, no heap, host-tested against the RFC 8949 vectors.
1157 */
1158#ifndef DETWS_ENABLE_CBOR
1159#define DETWS_ENABLE_CBOR 0
1160#endif
1161
1162/**
1163 * @brief Zero-heap MessagePack encoder and decoder for compact binary payloads.
1164 *
1165 * Default off. When set, network_drivers/presentation/msgpack/msgpack.h provides a
1166 * writer that serializes ints, strings, byte strings, arrays, maps, booleans, nil,
1167 * and float32 into a caller-provided buffer, plus a cursor decoder (msgpack_peek /
1168 * msgpack_read_*, no-copy strings) over a caller buffer - the MessagePack-format
1169 * sibling of the CBOR / JSON readers and writers. Pure, no heap, host-tested
1170 * against the spec encodings and round-trip.
1171 */
1172#ifndef DETWS_ENABLE_MSGPACK
1173#define DETWS_ENABLE_MSGPACK 0
1174#endif
1175
1176/** @brief Static file serving via Arduino FS (LittleFS, SPIFFS, SD). */
1177#ifndef DETWS_ENABLE_FILE_SERVING
1178#define DETWS_ENABLE_FILE_SERVING 1
1179#endif
1180
1181/**
1182 * @brief WebDAV server (RFC 4918, class 1 + advisory locks) over the file system.
1183 *
1184 * Default off. When set (requires DETWS_ENABLE_FILE_SERVING), dav() mounts an FS
1185 * subtree that answers the WebDAV methods - OPTIONS, PROPFIND (Depth 0/1),
1186 * PROPPATCH, GET, HEAD, PUT, DELETE, MKCOL, COPY, MOVE, and advisory LOCK/UNLOCK -
1187 * so a client (rclone, cadaver, curl, or a mounted network drive) can browse and
1188 * edit files. PROPFIND returns a 207 Multi-Status document built into a fixed
1189 * buffer (DETWS_WEBDAV_BUF_SIZE); a Depth-1 listing is capped at
1190 * DETWS_WEBDAV_MAX_ENTRIES children. PROPPATCH returns a 207 with each requested
1191 * property refused 403 Forbidden (the live properties are read-only, no dead-
1192 * property store) - this keeps Windows Explorer / macOS Finder, which PROPPATCH a
1193 * timestamp right after a PUT, from erroring on a 405. PUT streams the request
1194 * body straight to the file (via the shared streaming-body sink), so an upload is
1195 * not bounded by BODY_BUF_SIZE. Locks are advisory (a synthetic token is issued
1196 * but not enforced). See docs/SECURITY.md before exposing it.
1197 */
1198#ifndef DETWS_ENABLE_WEBDAV
1199#define DETWS_ENABLE_WEBDAV 0
1200#endif
1201
1202/** @brief Buffer (BSS) for a WebDAV 207 Multi-Status response, in bytes (see DETWS_ENABLE_WEBDAV). */
1203#ifndef DETWS_WEBDAV_BUF_SIZE
1204#define DETWS_WEBDAV_BUF_SIZE 2048
1205#endif
1206
1207/** @brief Maximum children listed in a WebDAV Depth-1 PROPFIND (bounds the response). */
1208#ifndef DETWS_WEBDAV_MAX_ENTRIES
1209#define DETWS_WEBDAV_MAX_ENTRIES 32
1210#endif
1211
1212/** @brief Maximum properties echoed in a WebDAV PROPPATCH 207 response (bounds the response). */
1213#ifndef DETWS_WEBDAV_MAX_PROPS
1214#define DETWS_WEBDAV_MAX_PROPS 16
1215#endif
1216
1217/**
1218 * @brief HTTP method-token buffer size (bytes, including the NUL).
1219 *
1220 * Sized for the longest method the server must recognize: 8 normally (OPTIONS),
1221 * grown to fit the WebDAV methods (PROPPATCH is 9 chars) when WebDAV is enabled.
1222 */
1223#ifndef DETWS_METHOD_BUF_SIZE
1224#if DETWS_ENABLE_WEBDAV
1225#define DETWS_METHOD_BUF_SIZE 12
1226#else
1227#define DETWS_METHOD_BUF_SIZE 8
1228#endif
1229#endif
1230
1231/** @brief HTTP Basic Authentication per-route. */
1232#ifndef DETWS_ENABLE_AUTH
1233#define DETWS_ENABLE_AUTH 1
1234#endif
1235
1236/** @brief Telnet server support (RFC 854 / IAC option negotiation). */
1237#ifndef DETWS_ENABLE_TELNET
1238#define DETWS_ENABLE_TELNET 0
1239#endif
1240
1241/** @brief SSH server support (RFC 4253/4252/4254). */
1242#ifndef DETWS_ENABLE_SSH
1243#define DETWS_ENABLE_SSH 0
1244#endif
1245
1246/**
1247 * @brief Post-quantum hybrid key exchange: ML-KEM-768 + X25519 (FIPS 203 / RFC 9370 combiner).
1248 *
1249 * Adds the mlkem768x25519-sha256 SSH KEX method (draft-ietf-sshm-mlkem-hybrid-kex) and the
1250 * X25519MLKEM768 TLS 1.3 group (IANA 0x11ec) for HTTP/3, so a PQC-capable peer (OpenSSH 9.x+ and
1251 * current browsers, which now DEFAULT to hybrid) negotiates a quantum-resistant handshake instead of
1252 * down-negotiating to classical X25519. The device is always the responder, so only ML-KEM Encaps
1253 * ships (no KeyGen/Decaps, so none of the constant-time FO-comparison surface). The NTT core is
1254 * software with Montgomery reduction over q=3329 (the MPI accelerator is for RSA/DH-sized operands,
1255 * not 12-bit coefficients). Requires DETWS_ENABLE_SSH and/or DETWS_ENABLE_HTTP3.
1256 */
1257#ifndef DETWS_ENABLE_PQC_KEX
1258#define DETWS_ENABLE_PQC_KEX 0
1259#endif
1260
1261/**
1262 * @brief Modbus TCP slave/server (Modbus Application Protocol v1.1b3) on TCP/502.
1263 *
1264 * Default off. When set, listen(502, ConnProto::PROTO_MODBUS) serves a fixed data model
1265 * (coils, discrete inputs, holding + input registers, all in BSS) over Modbus
1266 * TCP: Read/Write Coils (FC 1/5/15), Read Discrete Inputs (FC 2), Read/Write
1267 * Holding Registers (FC 3/6/16), and Read Input Registers (FC 4). The codec
1268 * (MBAP framing + PDU dispatch) is pure and host-tested; the TCP transport is
1269 * ESP32-only. The application reads/writes the model with the accessor functions
1270 * and is notified of client writes via modbus_on_write(). Modbus has no
1271 * authentication or encryption - run it only on a trusted control network.
1272 */
1273#ifndef DETWS_ENABLE_MODBUS
1274#define DETWS_ENABLE_MODBUS 0
1275#endif
1276
1277/**
1278 * @brief Modbus RTU framing (serial / RS-485) over the same data model + PDU dispatch.
1279 *
1280 * Default off; implies DETWS_ENABLE_MODBUS. Adds the RTU ADU codec
1281 * `modbus_rtu_process_adu()` - a `[slave addr][PDU][CRC16]` frame (CRC16-Modbus,
1282 * little-endian) around the existing host-tested PDU dispatch: a CRC mismatch or a
1283 * non-matching unit address is dropped silently (no reply, per the spec), and a
1284 * broadcast (address 0) is executed without a reply. The codec is pure and
1285 * host-tested; feed it from a UART/RS-485 driver (the serial transport is the
1286 * application's, framed by the 3.5-char inter-frame idle).
1287 */
1288#ifndef DETWS_ENABLE_MODBUS_RTU
1289#define DETWS_ENABLE_MODBUS_RTU 0
1290#endif
1291#if DETWS_ENABLE_MODBUS_RTU && !DETWS_ENABLE_MODBUS
1292#undef DETWS_ENABLE_MODBUS
1293#define DETWS_ENABLE_MODBUS 1
1294#endif
1295
1296/**
1297 * @brief CloudEvents v1.0 (CNCF) event envelope (structured JSON + binary headers).
1298 *
1299 * Default off. Adds `services/cloudevents`: `cloudevents_build_json()` emits a
1300 * structured `application/cloudevents+json` envelope (required `id`/`source`/`type`
1301 * + optional `subject`/`datacontenttype`/`data`) via the JSON writer, and
1302 * `cloudevents_from_headers()` reads an inbound binary-mode event's `ce-*` headers.
1303 * Makes the device's events interoperable with serverless / event-mesh consumers.
1304 */
1305#ifndef DETWS_ENABLE_CLOUDEVENTS
1306#define DETWS_ENABLE_CLOUDEVENTS 0
1307#endif
1308
1309/**
1310 * @brief Redis RESP2 wire codec (`services/redis_resp`).
1311 *
1312 * Default off. A zero-heap command encoder (`resp_encode_command`, array of bulk
1313 * strings) + a cursor reply parser (`resp_parse`: simple / error / integer / bulk /
1314 * array / nil) so the device can drive a Redis server over the shipped outbound
1315 * client transport. Pure codec, host-tested; the connection is the application's.
1316 */
1317#ifndef DETWS_ENABLE_REDIS
1318#define DETWS_ENABLE_REDIS 0
1319#endif
1320
1321/**
1322 * @brief STOMP 1.2 frame codec (`services/stomp`).
1323 *
1324 * Default off. A zero-heap frame builder (`stomp_build_frame`, command + escaped
1325 * headers + NUL-terminated body) + a non-mutating parser (`stomp_parse_frame`,
1326 * command / header slices / body, honoring `content-length`) so the device can talk
1327 * to a STOMP broker (ActiveMQ / RabbitMQ / Artemis) over the shipped outbound client
1328 * transport, or STOMP-over-WebSocket via the WS client. Pure codec, host-tested.
1329 */
1330#ifndef DETWS_ENABLE_STOMP
1331#define DETWS_ENABLE_STOMP 0
1332#endif
1333
1334/** @brief Max header lines parsed per STOMP frame (extras beyond this are ignored). */
1335#ifndef DETWS_STOMP_MAX_HEADERS
1336#define DETWS_STOMP_MAX_HEADERS 16
1337#endif
1338
1339/**
1340 * @brief MQTT-SN v1.2 wire codec (`services/mqtt/mqtt_sn`).
1341 *
1342 * Default off. A zero-heap message builder + parser for MQTT for Sensor Networks - the
1343 * UDP / non-TCP MQTT variant for constrained, lossy links (numeric topic IDs instead of
1344 * strings, gateway discovery, sleeping-client keep-alive). Builds CONNECT / REGISTER /
1345 * PUBLISH / SUBSCRIBE / PINGREQ / DISCONNECT / SEARCHGW and parses CONNACK / REGACK /
1346 * PUBACK / SUBACK / PUBLISH / REGISTER, including the 1- and 3-octet Length forms. Pure
1347 * codec, host-tested; the datagram send (det_udp_sendto) and topic registry are the app's.
1348 */
1349#ifndef DETWS_ENABLE_MQTT_SN
1350#define DETWS_ENABLE_MQTT_SN 0
1351#endif
1352
1353/**
1354 * @brief Flow-record export codec (`services/flow_export`).
1355 *
1356 * Default off. A zero-heap exporter-side codec for on-device flow accounting: NetFlow v5
1357 * (fixed 24-octet header + 48-octet records), NetFlow v9 (RFC 3954), and IPFIX (RFC 7011),
1358 * the latter two via a small cursor that emits a Template then matching Data records and
1359 * patches the message length (IPFIX) or record count (v9) on finish. Pure codec,
1360 * host-tested; the flow cache (5-tuple + counters) and the UDP send (det_udp_sendto) are
1361 * the application's. Pairs with the telemetry / observability services.
1362 */
1363#ifndef DETWS_ENABLE_FLOW_EXPORT
1364#define DETWS_ENABLE_FLOW_EXPORT 0
1365#endif
1366
1367/**
1368 * @brief Protocol Buffers wire codec (`services/protobuf`).
1369 *
1370 * Default off. A zero-heap streaming Protobuf encoder + cursor reader over caller buffers
1371 * (the same shape as the CBOR / MessagePack codecs): varint / ZigZag / fixed32 / fixed64 /
1372 * length-delimited fields, with embedded messages built into a sub-buffer and added via
1373 * `pb_bytes`. Pure codec, host-tested against the spec vectors. This is the standalone
1374 * Protobuf deliverable; gRPC (framed Protobuf over HTTP/2) is gated on the HTTP/2 item.
1375 */
1376#ifndef DETWS_ENABLE_PROTOBUF
1377#define DETWS_ENABLE_PROTOBUF 0
1378#endif
1379
1380/**
1381 * @brief WAMP messaging codec (`services/wamp`).
1382 *
1383 * Default off. A zero-heap codec for the Web Application Messaging Protocol (unified RPC +
1384 * PubSub over WebSocket): builders for HELLO / SUBSCRIBE / PUBLISH / CALL / REGISTER /
1385 * YIELD / GOODBYE (JSON arrays emitted via the shared JsonWriter) and a positional parser
1386 * that pulls the message type, ids, and URIs out of an inbound array. Rides the shipped
1387 * WebSocket layer; the session / subscription / registration tables are the application's.
1388 * Pure codec, host-tested. Builds on the always-on JSON writer.
1389 */
1390#ifndef DETWS_ENABLE_WAMP
1391#define DETWS_ENABLE_WAMP 0
1392#endif
1393
1394/**
1395 * @brief SunSpec Modbus device-information-model codec (`services/sunspec`).
1396 *
1397 * Default off. A zero-heap codec for the SunSpec Alliance register maps layered on the
1398 * holding-register model: a model-chain walker (verify the `SunS` marker, then iterate each
1399 * model's id / length / body) + typed point readers (u16 / i16 / u32 / i32 / string) and a
1400 * map writer (marker, model headers + points, end model). Makes a solar inverter / meter /
1401 * battery interoperable. Pure codec, host-tested; pairs with the Modbus service.
1402 */
1403#ifndef DETWS_ENABLE_SUNSPEC
1404#define DETWS_ENABLE_SUNSPEC 0
1405#endif
1406
1407/**
1408 * @brief IEEE C37.118.2 synchrophasor frame codec (`services/c37118`).
1409 *
1410 * Default off. A zero-heap builder + CRC-validating parser for the PMU / PDC wide-area
1411 * measurement wire protocol: `c37118_build_frame` / `c37118_build_command` emit a
1412 * `SYNC FRAMESIZE IDCODE SOC FRACSEC DATA CHK` frame (CHK = CRC-CCITT) and
1413 * `c37118_parse_frame` validates the CRC and reports the frame type / ids / timestamp /
1414 * payload slice. Frames any payload and fully handles the fixed Command frame. Pure codec,
1415 * host-tested.
1416 */
1417#ifndef DETWS_ENABLE_C37118
1418#define DETWS_ENABLE_C37118 0
1419#endif
1420
1421/**
1422 * @brief DNP3 (IEEE 1815) data-link frame codec (`services/dnp3`).
1423 *
1424 * Default off. A zero-heap builder + CRC-validating parser for the SCADA / utility
1425 * outstation data-link layer: `dnp3_build_frame` emits the `0x0564 LEN CTRL DEST SRC CRC`
1426 * header block + the CRC'd 16-octet user-data blocks, and `dnp3_parse_frame` validates the
1427 * header and every block CRC (CRC-16/DNP) and de-blocks the user data. Pure codec,
1428 * host-tested; the transport-function reassembly and the application layer are layered on
1429 * the de-blocked user data.
1430 */
1431#ifndef DETWS_ENABLE_DNP3
1432#define DETWS_ENABLE_DNP3 0
1433#endif
1434
1435/**
1436 * @brief CANopen (CiA 301) message codec (`services/canopen`).
1437 *
1438 * Default off. A zero-heap builder + parser for the CANopen messaging set over classic CAN
1439 * frames (`shared_primitives/can.h`): NMT node control, SYNC, TIME, heartbeat / boot-up,
1440 * EMCY, PDO process data, and expedited SDO read / write / abort. The 11-bit COB-ID is a
1441 * 4-bit function code plus a 7-bit node id; builders compute it, parsers classify it back.
1442 * The object dictionary is the application's; SDO is expedited only (segmented / block not
1443 * yet covered). Pure codec, host-tested. Drive it from the ESP32 TWAI peripheral or an
1444 * MCP2515 over SPI to bridge a CANopen bus onto Wi-Fi.
1445 */
1446#ifndef DETWS_ENABLE_CANOPEN
1447#define DETWS_ENABLE_CANOPEN 0
1448#endif
1449
1450/**
1451 * @brief CiA 402 / IEC 61800-7-201 drive + motion profile (`services/cia402`).
1452 *
1453 * Default off. Requires CANOPEN. The standardised servo / stepper drive profile over CANopen:
1454 * `cia402_state` decodes the power state machine from the Statusword (the CiA 402 mask/value
1455 * table), `cia402_controlword` / `cia402_enable_sequence` produce the Controlword commands that
1456 * walk an axis to Operation Enabled, and the `cia402_sdo_set_*` / `cia402_pack_command` helpers
1457 * write Controlword / Modes of Operation / target position-velocity-torque via the shipped
1458 * CANopen SDO / PDO codec. State masks + command values + object indices verified against
1459 * IEC 61800-7-201. Pure profile, host-tested. Turns the CAN stack into a motion master; close
1460 * the loop with a `services/control` PID.
1461 */
1462#ifndef DETWS_ENABLE_CIA402
1463#define DETWS_ENABLE_CIA402 0
1464#endif
1465
1466/**
1467 * @brief Closed-loop control law (`services/control`).
1468 *
1469 * Default off. A zero-heap, FPU-accelerated PID controller (single-precision float, FMA-folded,
1470 * IRAM-placeable with DETWS_CONTROL_IRAM=1) with derivative-on-measurement, an optional
1471 * derivative low-pass, output clamping, and anti-windup by back-calculation plus a hard integral
1472 * clamp, and a feed-forward term - plus inline control-law primitives (clamp / deadband / slew /
1473 * low-pass). `pid_update` runs one loop; `pid_update_n` runs a batch of axes off one tick. Pair
1474 * it with a plant it can command (a `services/cia402` drive, a dshot ESC, a heater) and tune the
1475 * gains offline with `tools/pid_tune.py`. Pure math, host-tested.
1476 */
1477#ifndef DETWS_ENABLE_CONTROL
1478#define DETWS_ENABLE_CONTROL 0
1479#endif
1480
1481/**
1482 * @brief SAE J1939 message codec (`services/j1939`).
1483 *
1484 * Default off. A zero-heap codec for the heavy-duty-vehicle / agriculture / marine / genset
1485 * CAN higher-layer protocol over 29-bit extended frames (`shared_primitives/can.h`):
1486 * `j1939_encode_id` / `j1939_decode_id` pack and unpack the priority / PGN / SA / DA
1487 * identifier (PDU1 peer + PDU2 broadcast), `j1939_build_message` emits single frames,
1488 * `j1939_build_request` / `j1939_build_address_claim` (+ `j1939_build_name`) handle the
1489 * Request PGN and Address Claimed messages, and the Transport Protocol (BAM announce +
1490 * TP.DT packets) reassembles multi-packet messages up to `DETWS_J1939_TP_MAX` octets. Pure
1491 * codec, host-tested. Drive it from the ESP32 TWAI peripheral or an MCP2515 over SPI.
1492 */
1493#ifndef DETWS_ENABLE_J1939
1494#define DETWS_ENABLE_J1939 0
1495#endif
1496
1497/**
1498 * @brief DeviceNet link-adaptation codec (`services/devicenet`).
1499 *
1500 * Default off. The CAN-specific layer of "CIP over CAN": the 11-bit DeviceNet identifier as a
1501 * Message Group (1..4) + Message ID + MAC ID (`devicenet_encode_id` / `devicenet_decode_id`),
1502 * the explicit-message header octet, single-frame explicit messages, and the fragmentation
1503 * protocol with a reassembler (`devicenet_frag_feed`) for bodies longer than one CAN frame.
1504 * The CIP application layer (services / EPATH / data) is the same one EtherNet/IP uses, so
1505 * build the body with the existing `cip_*` functions (`DETWS_ENABLE_CIP`). Pure codec,
1506 * host-tested. Drive it from the ESP32 TWAI peripheral or an MCP2515 over SPI.
1507 */
1508#ifndef DETWS_ENABLE_DEVICENET
1509#define DETWS_ENABLE_DEVICENET 0
1510#endif
1511
1512/**
1513 * @brief NMEA 2000 codec (`services/nmea2000`).
1514 *
1515 * Default off; implies DETWS_ENABLE_J1939 (NMEA 2000 is J1939 at the transport layer). A
1516 * zero-heap codec for the marine instrumentation network over CAN: it reuses the J1939 29-bit
1517 * identifier codec and adds the NMEA-specific Fast Packet transport - `n2k_fastpacket_build_frame`
1518 * splits a 9..223-octet message across frames (a control octet of sequence + frame counter,
1519 * the first frame carrying the total length) and `n2k_fastpacket_feed` reassembles it;
1520 * `n2k_build_single` wraps a single-frame message. Pure codec, host-tested. Drive it from the
1521 * ESP32 TWAI peripheral or an MCP2515 over SPI to bridge an NMEA 2000 backbone onto Wi-Fi.
1522 */
1523#ifndef DETWS_ENABLE_NMEA2000
1524#define DETWS_ENABLE_NMEA2000 0
1525#endif
1526#if DETWS_ENABLE_NMEA2000 && !DETWS_ENABLE_J1939
1527#undef DETWS_ENABLE_J1939
1528#define DETWS_ENABLE_J1939 1 // NMEA 2000 reuses the J1939 identifier codec
1529#endif
1530
1531/**
1532 * @brief Wired M-Bus (Meter-Bus, EN 13757) frame codec (`services/mbus`).
1533 *
1534 * Default off. A zero-heap builder + parser for the M-Bus link-layer frames used by utility
1535 * meters (water / gas / heat / electricity): the single-character ACK, the short frame
1536 * (`10 C A CS 16`), and the long / control frame (`68 L L 68 C A CI ... CS 16`, 8-bit sum
1537 * checksum), plus `mbus_record_next` which walks the EN 13757-3 variable-data records
1538 * (DIF / VIF, skipping DIFE / VIFE extension chains and decoding the data length). Pure codec,
1539 * host-tested. Talk to the powered two-wire bus over a UART through an M-Bus level converter
1540 * (e.g. a TSS721-based master) and bridge meter readings onto Wi-Fi.
1541 */
1542#ifndef DETWS_ENABLE_MBUS
1543#define DETWS_ENABLE_MBUS 0
1544#endif
1545
1546/**
1547 * @brief IEC 60870-5-101 / -104 telecontrol (SCADA) codec (`services/iec60870`).
1548 *
1549 * Default off. The utility-SCADA protocol in both transports: the -104 APCI over TCP
1550 * (`68 LEN` + 4 control octets in I / S / U formats via `iec104_build_i/_s/_u` + `iec104_parse`),
1551 * the shared ASDU header + 3-octet Information Object Address (`iec_asdu_build_header` /
1552 * `iec_asdu_parse_header`, `iec_put_ioa` / `iec_get_ioa`), and the -101 FT1.2 serial link
1553 * frames (fixed + variable, 8-bit sum checksum, via `iec101_build_fixed` / `_variable` +
1554 * `iec101_parse`). Named type-id / cause-of-transmission constants are provided; the
1555 * per-type information elements are the application's. Pure codec, host-tested. Run -104 over
1556 * the shipped TCP stack or -101 over a UART/RS-485 transceiver to bridge an RTU onto Wi-Fi.
1557 */
1558#ifndef DETWS_ENABLE_IEC60870
1559#define DETWS_ENABLE_IEC60870 0
1560#endif
1561
1562/**
1563 * @brief SDI-12 sensor-bus codec (`services/sdi12`).
1564 *
1565 * Default off. A zero-heap command / response codec for the 1200-baud single-wire ASCII bus
1566 * used by environmental / agricultural sensors: builders for the standard commands
1567 * (`sdi12_build_measure` / `_concurrent` / `_data` / `_identify` / `_change_address` /
1568 * `_query_address`), a parser for the measurement response (`sdi12_parse_measure`: seconds
1569 * until ready + value count), a data-value splitter (`sdi12_parse_values`), and the SDI-12
1570 * CRC (`sdi12_crc16` / `sdi12_crc_encode` / `sdi12_check_crc`) for the CRC-protected `aMC!` /
1571 * `aCC!` variants. Pure codec, host-tested. Drive the single 1200-baud line over a UART (with
1572 * a small level / direction circuit) and bridge sensor readings onto Wi-Fi.
1573 */
1574#ifndef DETWS_ENABLE_SDI12
1575#define DETWS_ENABLE_SDI12 0
1576#endif
1577
1578/**
1579 * @brief DMX512 + RDM (ANSI E1.20) lighting codec (`services/dmx`).
1580 *
1581 * Default off. A zero-heap codec for stage / architectural lighting over RS-485: `dmx_build` /
1582 * `dmx_get_channel` assemble and read the positional DMX512 slot packet (a start code + up to
1583 * 512 channels), and the RDM (Remote Device Management) functions build / parse the addressed
1584 * management packet that shares the wire - `rdm_build` / `rdm_parse` with 48-bit source /
1585 * destination UIDs (`rdm_uid`), a command class + parameter id, and the 16-bit additive
1586 * checksum (`rdm_checksum`). Pure codec, host-tested. Drive a `MAX485`-class transceiver on a
1587 * UART (250 kbit/s, 8N2; the break is the application's) and bridge a lighting rig onto Wi-Fi.
1588 */
1589#ifndef DETWS_ENABLE_DMX
1590#define DETWS_ENABLE_DMX 0
1591#endif
1592
1593/**
1594 * @brief NMEA 0183 sentence codec (`services/nmea0183`).
1595 *
1596 * Default off. A zero-heap codec for the marine / GPS ASCII protocol (`$GPGGA,...*47`):
1597 * `nmea0183_build` emits a sentence (adding the `$`, XOR checksum, and CR/LF), `nmea0183_parse`
1598 * validates the `*HH` checksum and splits the comma-separated fields (deriving talker id +
1599 * sentence type from the address field), and `nmea0183_field_float` / `_int` decode field
1600 * values. Sentence framing + checksum verified against the NMEA 0183 standard (the canonical
1601 * GGA example); pure and host-tested. GPS / marine receivers are cheap UART breakouts, so this
1602 * is a plain HardwareSerial link (4800 / 9600 baud); bridge position / wind / depth onto Wi-Fi.
1603 */
1604#ifndef DETWS_ENABLE_NMEA0183
1605#define DETWS_ENABLE_NMEA0183 0
1606#endif
1607
1608/**
1609 * @brief IO-Link (SDCI, IEC 61131-9) data-link message codec (`services/iolink`).
1610 *
1611 * Default off. The point-to-point smart-sensor link's data-link message layer: the M-sequence
1612 * Control octet (`iol_mc` + decoders), the checksum / type octet of a master message
1613 * (`iol_ckt`), the checksum / status octet of a device reply (`iol_cks`), and the SDCI message
1614 * checksum (`iol_checksum6` / `iol_finalize` / `iol_verify`) implemented straight from IO-Link
1615 * spec v1.1.4 Annex A.1.6 (the 0x52 seed + the 8-to-6-bit compression of equation A.1). Lay
1616 * the M-sequence / ISDU octets out per your device profile, then finalize / verify with this
1617 * codec. Pure codec, host-tested. The wire is a UART through an IO-Link transceiver
1618 * (e.g. MAX14819 / L6360); bridge sensor data onto Wi-Fi.
1619 */
1620#ifndef DETWS_ENABLE_IOLINK
1621#define DETWS_ENABLE_IOLINK 0
1622#endif
1623
1624/**
1625 * @brief gRPC-Web message framing (`services/grpcweb`).
1626 *
1627 * Default off. A zero-heap length-prefixed frame builder + parser for gRPC-Web, the
1628 * HTTP/1.1-reachable subset of gRPC (gRPC proper needs HTTP/2). `grpcweb_frame_message`
1629 * wraps a Protobuf message in the 5-octet `[flags][len BE32]` prefix, `grpcweb_frame_trailer`
1630 * emits the 0x80 trailers frame (`grpc-status` / `grpc-message`), and `grpcweb_parse` reads
1631 * one frame back. Wraps the Protobuf codec (`DETWS_ENABLE_PROTOBUF`) over the shipped
1632 * HTTP/1.1 server/client. Pure codec, host-tested.
1633 */
1634#ifndef DETWS_ENABLE_GRPC_WEB
1635#define DETWS_ENABLE_GRPC_WEB 0
1636#endif
1637
1638/**
1639 * @brief OMA LwM2M TLV codec (`services/lwm2m`).
1640 *
1641 * Default off. A zero-heap writer + cursor reader for the LwM2M `application/vnd.oma.lwm2m+tlv`
1642 * resource encoding (Type / Identifier / Length / Value, 8-/16-bit ids, 0-/8-/16-/24-bit
1643 * lengths), carried over the shipped CoAP service for device management. Value helpers for
1644 * shortest-form integers, booleans, strings, and floats. Pure codec, host-tested.
1645 */
1646#ifndef DETWS_ENABLE_LWM2M
1647#define DETWS_ENABLE_LWM2M 0
1648#endif
1649
1650/**
1651 * @brief Omron FINS frame codec (`services/fins`).
1652 *
1653 * Default off. A zero-heap command/response builder + parser for the Factory Interface
1654 * Network Service (FINS/UDP): `fins_build_command` / `fins_build_memory_area_read` emit the
1655 * 10-octet routing header + command code + parameters, and `fins_parse_command` /
1656 * `fins_parse_response` read them back (the response end code MRES/SRES included). Talks to
1657 * an Omron PLC over the shipped UDP transport (det_udp_sendto). Pure codec, host-tested.
1658 */
1659#ifndef DETWS_ENABLE_FINS
1660#define DETWS_ENABLE_FINS 0
1661#endif
1662
1663/**
1664 * @brief Omron Host Link (C-mode) frame codec (`services/hostlink`).
1665 *
1666 * Default off. A zero-heap ASCII command/response codec for the Omron serial host-link
1667 * protocol (the RS-232/485 sibling of FINS): `hostlink_build` emits `@UU` + header code +
1668 * text + FCS + `*`CR, and `hostlink_parse` FCS-validates and splits a frame
1669 * (`hostlink_end_code` reads a response's end code). FCS is the 8-bit XOR from `@` through
1670 * the text. Pure codec, host-tested; the serial transport is the application's.
1671 */
1672#ifndef DETWS_ENABLE_HOSTLINK
1673#define DETWS_ENABLE_HOSTLINK 0
1674#endif
1675
1676/**
1677 * @brief SenML (RFC 8428) measurement-pack builder (`services/senml`).
1678 *
1679 * Default off; implies DETWS_ENABLE_CBOR (the SenML-CBOR form uses the CBOR writer). A
1680 * zero-heap SenML-JSON + SenML-CBOR encoder over the shipped JSON / CBOR codecs: the caller
1681 * fills a `SenmlRecord` array (base name/time, name, unit, one value, time) and
1682 * `senml_json_build` / `senml_cbor_build` emit the whole pack. Numbers are emitted as
1683 * integers when integral (so timestamps keep precision), else floats. The standard
1684 * measurement format for CoAP / LwM2M / HTTP telemetry. Pure codec, host-tested.
1685 */
1686#ifndef DETWS_ENABLE_SENML
1687#define DETWS_ENABLE_SENML 0
1688#endif
1689#if DETWS_ENABLE_SENML && !DETWS_ENABLE_CBOR
1690#undef DETWS_ENABLE_CBOR
1691#define DETWS_ENABLE_CBOR 1
1692#endif
1693
1694/**
1695 * @brief Allen-Bradley DF1 full-duplex frame codec (`services/df1`).
1696 *
1697 * Default off. A zero-heap framing + DLE byte-stuffing + BCC/CRC codec for the Rockwell
1698 * serial PLC data-link layer (pub. 1770-6.5.16): `df1_build_frame` wraps application data in
1699 * `DLE STX ... DLE ETX` with a doubled-DLE escape and a BCC (2's complement of the data sum)
1700 * or CRC-16 (over the data + ETX, low byte first), and `df1_parse_frame` validates the check
1701 * and un-stuffs the data. Pure codec, host-tested; the application header is the app's.
1702 */
1703#ifndef DETWS_ENABLE_DF1
1704#define DETWS_ENABLE_DF1 0
1705#endif
1706
1707/**
1708 * @brief TPKT (RFC 1006) + COTP (X.224 class 0) frame codec (`services/cotp`).
1709 *
1710 * Default off. A zero-heap "ISO transport on TCP" framing codec - the reusable foundation
1711 * under S7comm and IEC 61850 MMS. `tpkt_build` / `tpkt_parse` handle the 4-octet TPKT
1712 * envelope; `cotp_build_dt` wraps user data in a Data TPDU, `cotp_build_cr` builds a
1713 * Connection Request (with the TPDU-size parameter + caller TSAP params), and `cotp_parse`
1714 * reports the TPDU type and the DT data / CR-CC refs. Pure codec, host-tested.
1715 */
1716#ifndef DETWS_ENABLE_COTP
1717#define DETWS_ENABLE_COTP 0
1718#endif
1719
1720/**
1721 * @brief Siemens S7comm PDU codec (`services/s7comm`).
1722 *
1723 * Default off. A zero-heap builder + parser for the S7-300/400 communication PDUs carried
1724 * inside a COTP Data TPDU (DETWS_ENABLE_COTP) over ISO-on-TCP (port 102): `s7_build_setup`
1725 * (Setup Communication), `s7_build_read_request` (Read Var, S7-ANY items over DB/I/Q/M),
1726 * `s7_parse_header`, and `s7_read_next_item` (the response data items, honoring the
1727 * length-in-bits transport sizes + even-item padding). Constants verified against the
1728 * Wireshark S7comm dissector. Pure codec, host-tested; wrap the PDU with COTP + TPKT.
1729 */
1730#ifndef DETWS_ENABLE_S7COMM
1731#define DETWS_ENABLE_S7COMM 0
1732#endif
1733
1734/**
1735 * @brief Mitsubishi MELSEC MC protocol (binary 3E) codec (`services/melsec`).
1736 *
1737 * Default off. A zero-heap batch-read request builder + response parser for MELSEC PLCs over
1738 * TCP/UDP: `melsec_build_read` emits the binary 3E batch-read (word) frame (little-endian
1739 * fields, subheader 0x5000, command 0x0401, the device code + 24-bit head device + point
1740 * count), and `melsec_parse_response` validates the 0xD000 response and reports the end code
1741 * + the read data. Frame layout + device codes verified against a third-party MC impl. Pure
1742 * codec, host-tested. Completes the major-vendor PLC read set (FINS / Host Link / DF1 / S7).
1743 */
1744#ifndef DETWS_ENABLE_MELSEC
1745#define DETWS_ENABLE_MELSEC 0
1746#endif
1747
1748/**
1749 * @brief Beckhoff ADS / AMS protocol codec (`services/ads`).
1750 *
1751 * Default off. A zero-heap builder + parser for the TwinCAT PC-based-control protocol over TCP
1752 * 48898: `ads_build_*` emit complete AMS/TCP + AMS-header frames (little-endian, target-before-
1753 * source addressing, cmd id + state flags + cbData + invoke id) for ReadDeviceInfo / Read /
1754 * Write / ReadWrite / ReadState / WriteControl / Add+DeleteNotification, and `ads_parse_*` decode
1755 * the responses (including the DeviceNotification stamp/sample stream). ReadWrite drives symbol-
1756 * by-name access (name -> handle via index group 0xF003, value via 0xF005). AMS header layout +
1757 * command ids verified against the Beckhoff InfoSys spec. Pure codec, host-tested; the caller
1758 * owns the TCP socket and the AMS route on the target router.
1759 */
1760#ifndef DETWS_ENABLE_ADS
1761#define DETWS_ENABLE_ADS 0
1762#endif
1763
1764/**
1765 * @brief FANUC FOCAS Ethernet protocol codec (`services/focas`).
1766 *
1767 * Default off. A zero-heap builder + parser for the FANUC CNC data protocol over TCP 8193:
1768 * `focas_build_*` emit the complete on-wire frames (a 10-octet big-endian envelope + payload) for
1769 * the open/close handshake and the documented read functions (SysInfo, alarm status, CNC
1770 * parameters, macro variables, position/axis data, actual feed / spindle), and `focas_parse_*`
1771 * decode the responses (echoed selector + status + data), including the ODBSYS SysInfo layout and
1772 * the FANUC 8-octet `data / base^exp` value encoding. Frame layout, selector encoding, and value
1773 * decoding reverse-engineered by and cross-checked against diohpix/pyfanuc. Pure codec, host-
1774 * tested; the caller owns the TCP socket and drives the open -> command -> close sequence.
1775 */
1776#ifndef DETWS_ENABLE_FOCAS
1777#define DETWS_ENABLE_FOCAS 0
1778#endif
1779
1780/**
1781 * @brief BACnet/IP BVLC + NPDU codec (`services/bacnet`).
1782 *
1783 * Default off. A zero-heap framing codec for the ASHRAE 135 building-automation network
1784 * layer over UDP (47808): `bvlc_build` / `bvlc_parse` handle the BVLC envelope (type 0x81,
1785 * function, length), and `npdu_build` / `npdu_parse` handle the NPDU (version + NPCI control
1786 * + optional DNET/DADR destination addressing + hop count) and slice the APDU. The APDU
1787 * (application-layer services / object model) layers on top. Pure codec, host-tested.
1788 */
1789#ifndef DETWS_ENABLE_BACNET
1790#define DETWS_ENABLE_BACNET 0
1791#endif
1792
1793/**
1794 * @brief EtherNet/IP encapsulation codec (`services/enip`).
1795 *
1796 * Default off. A zero-heap builder + parser for the ODVA EtherNet/IP encapsulation layer
1797 * (TCP/UDP 44818) that carries CIP: `eip_build` / `eip_parse` handle the 24-octet header
1798 * (little-endian command / length / session handle / status / sender context / options),
1799 * `eip_build_register_session` opens a session, and `eip_build_send_rr_data` /
1800 * `eip_parse_send_rr_data` wrap + unwrap a CIP message as an unconnected message (Common
1801 * Packet Format: Null Address + Unconnected Data items). Commands + CPF item types verified
1802 * against the Wireshark ENIP dissector. Pure codec, host-tested; the CIP message is the app's.
1803 */
1804#ifndef DETWS_ENABLE_ENIP
1805#define DETWS_ENABLE_ENIP 0
1806#endif
1807
1808/**
1809 * @brief AMQP 0-9-1 frame codec (`services/amqp`).
1810 *
1811 * Default off. A zero-heap frame builder + parser for the RabbitMQ wire protocol so a device
1812 * can be an AMQP client: `amqp_protocol_header` (the `"AMQP" 0 0 9 1` preamble),
1813 * `amqp_build_frame` / `amqp_parse_frame` (type + channel + size + payload + the 0xCE
1814 * frame-end), `amqp_build_method` / `amqp_parse_method` (a METHOD frame's class-id /
1815 * method-id / arguments), and `amqp_build_heartbeat`. Pure codec, host-tested; the method
1816 * arguments and the connection are the application's. Rides the outbound client transport.
1817 */
1818#ifndef DETWS_ENABLE_AMQP
1819#define DETWS_ENABLE_AMQP 0
1820#endif
1821
1822/**
1823 * @brief CIP (Common Industrial Protocol) message codec (`services/cip`).
1824 *
1825 * Default off. A zero-heap CIP request builder + response parser for the message that rides
1826 * inside an EtherNet/IP Unconnected Data item (DETWS_ENABLE_ENIP): `cip_build_epath` (the
1827 * class/instance/attribute logical-segment EPATH), `cip_build_request` /
1828 * `cip_build_get_attr_single`, and `cip_parse_response` (service / general status / data).
1829 * Service codes + the logical-segment encoding verified against the Wireshark CIP dissector.
1830 * Pure codec, host-tested; wrap the request with `eip_build_send_rr_data` for a working read.
1831 */
1832#ifndef DETWS_ENABLE_CIP
1833#define DETWS_ENABLE_CIP 0
1834#endif
1835
1836/**
1837 * @brief NATS client protocol codec (`services/nats`).
1838 *
1839 * Default off. A zero-heap builder + parser for the text-based NATS pub/sub protocol so a
1840 * device can be a NATS client: `nats_build_connect` / `nats_build_pub` / `nats_build_sub` /
1841 * `nats_build_unsub` / `nats_build_ping` / `nats_build_pong`, and `nats_parse` which decodes
1842 * an inbound MSG / INFO / PING / PONG / +OK / -ERR (MSG yields subject / sid / reply-to /
1843 * payload). Line-oriented (CRLF), space-delimited; only PUB and MSG carry a payload. Pure
1844 * codec, host-tested; rides the outbound client transport.
1845 */
1846#ifndef DETWS_ENABLE_NATS
1847#define DETWS_ENABLE_NATS 0
1848#endif
1849
1850/**
1851 * @brief HAProxy PROXY protocol codec (`services/proxy_protocol`).
1852 *
1853 * Default off. A zero-heap parser + builder for the PROXY protocol header a load balancer /
1854 * reverse proxy prepends, so the server recovers the real client IPv4 behind one.
1855 * `proxy_parse` detects + decodes a v1 (text `PROXY TCP4 ...`) or v2 (binary signature +
1856 * ver_cmd / fam / address block) header and reports the bytes to skip; `proxy_v1_build` /
1857 * `proxy_v2_build` emit a TCP/IPv4 header. Pure codec, host-tested; the application feeds it
1858 * the first bytes of an accepted connection.
1859 */
1860#ifndef DETWS_ENABLE_PROXY_PROTOCOL
1861#define DETWS_ENABLE_PROXY_PROTOCOL 0
1862#endif
1863
1864/**
1865 * @brief Sparkplug B payload + topic codec (`services/sparkplug`).
1866 *
1867 * Default off; implies DETWS_ENABLE_PROTOBUF (the payload is a Protobuf message). A zero-heap
1868 * builder for the Eclipse Sparkplug B industrial-IoT MQTT payload (`spb_build_payload` /
1869 * `spb_build_metric`, over the protobuf codec) and its topic namespace (`spb_build_topic`,
1870 * `spBv1.0/group/type/node[/device]`). Field numbers + datatype codes verified against the
1871 * Eclipse Tahu sparkplug_b.proto. Pure codec, host-tested; publish it with the MQTT client.
1872 */
1873#ifndef DETWS_ENABLE_SPARKPLUG
1874#define DETWS_ENABLE_SPARKPLUG 0
1875#endif
1876#if DETWS_ENABLE_SPARKPLUG && !DETWS_ENABLE_PROTOBUF
1877#undef DETWS_ENABLE_PROTOBUF
1878#define DETWS_ENABLE_PROTOBUF 1
1879#endif
1880
1881/** @brief Max serialized size of one Sparkplug B metric submessage (stack temp, bytes). */
1882#ifndef DETWS_SPB_METRIC_MAX
1883#define DETWS_SPB_METRIC_MAX 256
1884#endif
1885
1886/**
1887 * @brief Opt-in Modbus master codec + register scanner (DETWS_ENABLE_MODBUS_MASTER).
1888 *
1889 * Default off. services/modbus/modbus_master builds Modbus TCP read-request ADUs
1890 * and parses the responses (register values or exception), so an app can poll /
1891 * auto-discover a slave's registers. Pure and host-tested as a full round-trip
1892 * against the slave codec (modbus_process_adu); the actual send is the app's TCP.
1893 */
1894#ifndef DETWS_ENABLE_MODBUS_MASTER
1895#define DETWS_ENABLE_MODBUS_MASTER 0
1896#endif
1897
1898/** @brief Number of Modbus coils (FC 1/5/15), single-bit R/W (BSS, bit-packed). */
1899#ifndef DETWS_MODBUS_COILS
1900#define DETWS_MODBUS_COILS 64
1901#endif
1902
1903/** @brief Number of Modbus discrete inputs (FC 2), single-bit read-only (BSS, bit-packed). */
1904#ifndef DETWS_MODBUS_DISCRETE_INPUTS
1905#define DETWS_MODBUS_DISCRETE_INPUTS 64
1906#endif
1907
1908/** @brief Number of Modbus holding registers (FC 3/6/16), 16-bit R/W (BSS). */
1909#ifndef DETWS_MODBUS_HOLDING_REGS
1910#define DETWS_MODBUS_HOLDING_REGS 64
1911#endif
1912
1913/** @brief Number of Modbus input registers (FC 4), 16-bit read-only (BSS). */
1914#ifndef DETWS_MODBUS_INPUT_REGS
1915#define DETWS_MODBUS_INPUT_REGS 64
1916#endif
1917
1918/**
1919 * @brief TLS (HTTPS/WSS) via mbedTLS with a static memory pool (ESP32-only).
1920 *
1921 * When set, the server can accept TLS connections using mbedTLS configured with
1922 * MBEDTLS_MEMORY_BUFFER_ALLOC_C over a fixed BSS arena (DETWS_TLS_ARENA_SIZE) -
1923 * no system heap, so the determinism guarantee is preserved. The TLS engine is
1924 * compiled only on Arduino/ESP32 (mbedTLS is not part of the native build).
1925 * Default off.
1926 */
1927#ifndef DETWS_ENABLE_TLS
1928#define DETWS_ENABLE_TLS 0
1929#endif
1930
1931/** @brief Maximum simultaneous TLS connections (each holds mbedTLS record buffers). */
1932#ifndef MAX_TLS_CONNS
1933#define MAX_TLS_CONNS 1
1934#endif
1935
1936/**
1937 * @brief TLS session resumption via RFC 5077 session tickets (requires DETWS_ENABLE_TLS).
1938 *
1939 * Default off. When set, the TLS 1.2 server issues encrypted session tickets and
1940 * accepts them on reconnect, so a returning client completes an abbreviated
1941 * handshake (no certificate or full key exchange) - much faster and far less CPU
1942 * than the ~RSA/ECDHE full handshake. Resumption is stateless: the session state
1943 * lives in the client's ticket, sealed with a server-held key, so there is no
1944 * growing per-session cache (the determinism / zero-heap-growth guarantee holds;
1945 * only a small fixed ticket key and a little arena headroom are added). The ticket
1946 * key rotates automatically on the DETWS_TLS_TICKET_LIFETIME_S schedule. Needs the
1947 * mbedTLS build to provide MBEDTLS_SSL_TICKET_C (stock arduino-esp32 does).
1948 */
1949#ifndef DETWS_ENABLE_TLS_RESUMPTION
1950#define DETWS_ENABLE_TLS_RESUMPTION 0
1951#endif
1952
1953/** @brief Session-ticket lifetime / key-rotation period in seconds (see DETWS_ENABLE_TLS_RESUMPTION). */
1954#ifndef DETWS_TLS_TICKET_LIFETIME_S
1955#define DETWS_TLS_TICKET_LIFETIME_S 86400
1956#endif
1957
1958/**
1959 * @brief Mutual TLS - require and verify a client certificate (mTLS).
1960 *
1961 * Default off. When set (requires DETWS_ENABLE_TLS), the server can be given a
1962 * trust-anchor CA via DetWebServer::tls_require_client_cert(): the TLS handshake
1963 * then demands a client certificate chaining to that CA
1964 * (MBEDTLS_SSL_VERIFY_REQUIRED) and aborts the connection if the client presents
1965 * none or an untrusted one. The verified peer's subject DN is available to
1966 * handlers via DetWebServer::tls_client_subject(). Strong transport-level client
1967 * authentication with no passwords.
1968 */
1969#ifndef DETWS_ENABLE_MTLS
1970#define DETWS_ENABLE_MTLS 0
1971#endif
1972
1973/** @brief Maximum length of a verified mTLS peer subject DN string (incl. NUL). */
1974#ifndef DETWS_MTLS_SUBJECT_MAX
1975#define DETWS_MTLS_SUBJECT_MAX 128
1976#endif
1977
1978/**
1979 * @brief SNMP agent (v1/v2c, + v3 USM when DETWS_ENABLE_SNMP_V3) over lwIP UDP.
1980 *
1981 * Zero-heap ASN.1 BER codec + a fixed MIB table on UDP/161. Default off. The BER
1982 * codec itself is gated by this flag and is otherwise unit-tested standalone
1983 * (env:native_snmp).
1984 */
1985#ifndef DETWS_ENABLE_SNMP
1986#define DETWS_ENABLE_SNMP 0
1987#endif
1988
1989/** @brief Add SNMPv3 USM (auth via HMAC-SHA, privacy via AES-128-CFB). Default off. */
1990#ifndef DETWS_ENABLE_SNMP_V3
1991#define DETWS_ENABLE_SNMP_V3 0
1992#endif
1993
1994/**
1995 * @brief Outbound SNMP notifications - traps and informs (requires DETWS_ENABLE_SNMP).
1996 *
1997 * Default off. When set, src/services/snmp/snmp_notify.h sends SNMPv2c (and, with
1998 * DETWS_ENABLE_SNMP_V3, SNMPv3 USM) Trap / InformRequest PDUs to a manager over
1999 * UDP - so the agent can push alerts instead of only answering polls. Reuses the
2000 * BER codec and the transport-layer UDP service; the PDU builder is host-testable.
2001 */
2002#ifndef DETWS_ENABLE_SNMP_TRAP
2003#define DETWS_ENABLE_SNMP_TRAP 0
2004#endif
2005
2006/** @brief Maximum extra variable-bindings (beyond sysUpTime/snmpTrapOID) in one notification. */
2007#ifndef DETWS_SNMP_TRAP_MAX_VARBINDS
2008#define DETWS_SNMP_TRAP_MAX_VARBINDS 8
2009#endif
2010
2011/** @brief Static datagram buffer for an outbound SNMP notification, bytes. */
2012#ifndef DETWS_SNMP_TRAP_BUF_SIZE
2013#define DETWS_SNMP_TRAP_BUF_SIZE 1024
2014#endif
2015
2016/** @brief Maximum sub-identifiers (arcs) in an SNMP object identifier. */
2017#ifndef SNMP_MAX_OID_LEN
2018#define SNMP_MAX_OID_LEN 32
2019#endif
2020
2021/**
2022 * @brief Maximum registered MIB objects (the agent's fixed OID table).
2023 *
2024 * Each entry holds its OID, a value descriptor, and optional get/set callbacks
2025 * (see src/services/snmp/snmp_agent.h). The table lives in BSS; entries are
2026 * scanned linearly (small table) and need not be registered in OID order.
2027 */
2028#ifndef SNMP_MAX_MIB_ENTRIES
2029#define SNMP_MAX_MIB_ENTRIES 16
2030#endif
2031
2032/**
2033 * @brief Maximum variable bindings the agent will emit in one response.
2034 *
2035 * Bounds GetBulk expansion (max-repetitions is clamped so the total response
2036 * varbind count never exceeds this) and the per-request decode scratch.
2037 */
2038#ifndef SNMP_MAX_VARBINDS
2039#define SNMP_MAX_VARBINDS 16
2040#endif
2041
2042/**
2043 * @brief Static request/response datagram buffers for the SNMP UDP agent.
2044 *
2045 * Two buffers of this size live in BSS (one in, one out) - no heap. 484 is the
2046 * RFC 1157 minimum maximum message size; the default holds a one-frame UDP
2047 * payload so GetBulk walks fit without IP fragmentation.
2048 */
2049#ifndef SNMP_MSG_BUF_SIZE
2050#define SNMP_MSG_BUF_SIZE 1472
2051#endif
2052
2053/** @brief Maximum SNMP community-string length (including null terminator). */
2054#ifndef SNMP_COMMUNITY_MAX
2055#define SNMP_COMMUNITY_MAX 32
2056#endif
2057
2058/** @brief Default read-only community (overridable at runtime via snmp_agent_init). Deployments
2059 * SHOULD change this from the RFC-1157 well-known "public" for anything but a closed network. */
2060#ifndef DETWS_SNMP_DEFAULT_RO_COMMUNITY
2061#define DETWS_SNMP_DEFAULT_RO_COMMUNITY "public"
2062#endif
2063
2064/** @brief Maximum SNMPv3 USM user-name length (including null terminator). */
2065#ifndef SNMP_V3_USER_MAX
2066#define SNMP_V3_USER_MAX 32
2067#endif
2068
2069/** @brief Maximum SNMPv3 authoritative engine-ID length in bytes (RFC 3411 allows 5..32). */
2070#ifndef SNMP_V3_ENGINEID_MAX
2071#define SNMP_V3_ENGINEID_MAX 32
2072#endif
2073
2074// ---------------------------------------------------------------------------
2075// CoAP server sizing constants (DETWS_ENABLE_COAP must be 1)
2076// ---------------------------------------------------------------------------
2077
2078/**
2079 * @brief CoAP server (RFC 7252) over UDP/5683.
2080 *
2081 * A zero-heap Constrained Application Protocol endpoint: a fixed resource table
2082 * dispatched against the request's Uri-Path, with a pure host-testable message
2083 * codec (parse/build) and an ESP32 UDP binding via the transport-layer UDP
2084 * service. Default off; the codec is otherwise unit-tested standalone
2085 * (env:native_coap).
2086 */
2087#ifndef DETWS_ENABLE_COAP
2088#define DETWS_ENABLE_COAP 0
2089#endif
2090
2091/**
2092 * @brief CoAP resource observation - RFC 7641 (requires DETWS_ENABLE_COAP).
2093 *
2094 * Default off. When set, a client GET with the Observe option registers as an
2095 * observer of a resource; the application calls coap_notify(path) to push the
2096 * resource's current representation to every observer (a CoAP notification from
2097 * the server port with an increasing Observe sequence). Observers are dropped on
2098 * a deregister GET, a client RST, or send failure.
2099 */
2100#ifndef DETWS_ENABLE_COAP_OBSERVE
2101#define DETWS_ENABLE_COAP_OBSERVE 0
2102#endif
2103
2104/** @brief Maximum simultaneous CoAP observers (one slot per observed resource per client). */
2105#ifndef DETWS_COAP_MAX_OBSERVERS
2106#define DETWS_COAP_MAX_OBSERVERS 4
2107#endif
2108
2109/**
2110 * @brief CoAP block-wise transfer - RFC 7959 (requires DETWS_ENABLE_COAP).
2111 *
2112 * Default off. When set, the server understands the Block2 (descriptive,
2113 * responses) and Block1 (control, request uploads) options:
2114 * - Block2: a representation larger than one block, or any GET that carries a
2115 * Block2 option, is served one block at a time. A constrained client requests
2116 * a small block size (SZX) and pages through with ascending block numbers; the
2117 * server re-renders the (idempotent) resource and slices out the asked-for
2118 * block, setting the More bit until the last.
2119 * - Block1: a POST/PUT payload larger than one block is reassembled into a
2120 * single BSS buffer. Each non-final block is acknowledged 2.31 Continue; the
2121 * final block dispatches the handler with the whole reassembled payload.
2122 *
2123 * One block-wise transfer is reassembled at a time (deterministic, single
2124 * buffer); an out-of-order or oversized block yields 4.08 / 4.13. Size1/Size2
2125 * options and the /.well-known/core listing are out of scope.
2126 */
2127#ifndef DETWS_ENABLE_COAP_BLOCK
2128#define DETWS_ENABLE_COAP_BLOCK 0
2129#endif
2130
2131/** @brief Largest block-size exponent (SZX) the server will use: block size = 2^(SZX+4) bytes, SZX 0..6 (16..1024). */
2132#ifndef DETWS_COAP_BLOCK_SZX_MAX
2133#define DETWS_COAP_BLOCK_SZX_MAX 6
2134#endif
2135
2136/**
2137 * @brief Reassembly buffer for a block-wise (Block1) request upload, in bytes.
2138 *
2139 * One buffer of this size lives in BSS only when DETWS_ENABLE_COAP_BLOCK is set.
2140 * It bounds the largest payload a chunked POST/PUT can deliver to a handler.
2141 */
2142#ifndef DETWS_COAP_BLOCK1_MAX
2143#define DETWS_COAP_BLOCK1_MAX 1024
2144#endif
2145
2146/**
2147 * @brief Maximum registered CoAP resources (the server's fixed routing table).
2148 *
2149 * Each entry holds a path pointer, an allowed-methods bitmask, and a handler.
2150 * The table lives in BSS and is scanned linearly (small table).
2151 */
2152#ifndef DETWS_COAP_MAX_RESOURCES
2153#define DETWS_COAP_MAX_RESOURCES 8
2154#endif
2155
2156/** @brief Maximum reconstructed Uri-Path length, including separators and the leading '/'. */
2157#ifndef DETWS_COAP_MAX_PATH
2158#define DETWS_COAP_MAX_PATH 64
2159#endif
2160
2161/** @brief Maximum reconstructed Uri-Query length (segments joined by '&'). */
2162#ifndef DETWS_COAP_MAX_QUERY
2163#define DETWS_COAP_MAX_QUERY 64
2164#endif
2165
2166/**
2167 * @brief Maximum CoAP request/response payload in bytes.
2168 *
2169 * Sizes the static scratch a handler writes its response body into and bounds
2170 * the request payload handed to it. One buffer of this size lives in BSS.
2171 */
2172#ifndef DETWS_COAP_MAX_PAYLOAD
2173#define DETWS_COAP_MAX_PAYLOAD 256
2174#endif
2175
2176/**
2177 * @brief Static response-datagram buffer for the CoAP UDP server.
2178 *
2179 * One buffer of this size lives in BSS (the request is transport-owned). Must
2180 * hold a 4-byte header + token (<=8) + the Content-Format option + a 0xFF marker
2181 * + DETWS_COAP_MAX_PAYLOAD bytes. When block-wise transfer is enabled it must
2182 * also hold one full block (2^(DETWS_COAP_BLOCK_SZX_MAX+4) bytes) + option
2183 * overhead, so the default grows accordingly.
2184 */
2185#ifndef DETWS_COAP_MSG_BUF_SIZE
2186#if DETWS_ENABLE_COAP_BLOCK
2187#define DETWS_COAP_MSG_BUF_SIZE 1152
2188#else
2189#define DETWS_COAP_MSG_BUF_SIZE 512
2190#endif
2191#endif
2192
2193/** @brief Default UDP port the CoAP observe transport notifies from (IANA well-known 5683). */
2194#ifndef DETWS_COAP_OBSERVE_PORT
2195#define DETWS_COAP_OBSERVE_PORT 5683
2196#endif
2197
2198/**
2199 * @brief Bytes of the static BSS arena mbedTLS allocates from (DETWS_ENABLE_TLS).
2200 *
2201 * All mbedTLS allocations (per-connection record buffers, handshake temporaries,
2202 * cert/key parsing) are served from this fixed arena via a custom allocator
2203 * installed with mbedtls_platform_set_calloc_free() - never the system heap. Must
2204 * cover the worst-case handshake peak for MAX_TLS_CONNS; if undersized the
2205 * handshake fails cleanly (no corruption). Measured peak for ONE ECDSA P-256
2206 * connection on Arduino-esp32 (16 KB IN + 16 KB OUT records) is ~41.5 KB, so the
2207 * default leaves a small margin. An RSA cert/larger chain needs more; query the
2208 * live peak via det_tls_arena_peak(). NOTE: a second concurrent TLS connection
2209 * roughly doubles the record-buffer cost (~32 KB more), which overflows the
2210 * static DRAM budget - keep MAX_TLS_CONNS at 1 unless you shrink the IDF record
2211 * sizes (CONFIG_MBEDTLS_SSL_IN/OUT_CONTENT_LEN, needs an ESP-IDF build).
2212 */
2213#ifndef DETWS_TLS_ARENA_SIZE
2214#define DETWS_TLS_ARENA_SIZE 49152
2215#endif
2216
2217/**
2218 * @brief Place the TLS arena in external PSRAM instead of internal DRAM (ESP32).
2219 *
2220 * The internal static-DRAM ceiling (`dram0_0_seg`) is only ~122 KB, so a single
2221 * ~48 KB arena already uses a large slice and a second concurrent connection
2222 * (MAX_TLS_CONNS > 1) overflows it. On a board with PSRAM, set this to 1 to move
2223 * the arena to external RAM via `EXT_RAM_BSS_ATTR` / `EXT_RAM_ATTR`, freeing the
2224 * whole `DETWS_TLS_ARENA_SIZE` back to internal DRAM so many connections fit.
2225 * Requires `CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY` (and PSRAM enabled) in the
2226 * ESP-IDF/PlatformIO config; without it the attribute is a no-op and the arena
2227 * stays in DRAM (safe fallback). No effect on the native host build.
2228 */
2229#ifndef DETWS_TLS_ARENA_IN_PSRAM
2230#define DETWS_TLS_ARENA_IN_PSRAM 0
2231#endif
2232
2233/**
2234 * @brief Cap TLS records via the Maximum Fragment Length extension (RFC 6066).
2235 *
2236 * 0 (default) leaves the 16 KB TLS record ceiling. Set to 512, 1024, 2048, or 4096
2237 * to negotiate a smaller maximum record. On a mbedTLS build with variable-length
2238 * record buffers this shrinks the per-connection arena footprint (so more concurrent
2239 * connections fit); on a fixed-buffer build it still bounds the on-wire record size
2240 * (bandwidth / latency on a constrained link) and honors a client's MFL request.
2241 * Applied to both the server and the outbound client config. Needs an mbedTLS build
2242 * with `MBEDTLS_SSL_MAX_FRAGMENT_LENGTH` (else it is a no-op).
2243 */
2244#ifndef DETWS_TLS_MAX_FRAG_LEN
2245#define DETWS_TLS_MAX_FRAG_LEN 0
2246#endif
2247
2248/**
2249 * @brief Acknowledge that a MAX_TLS_CONNS > 1 build has been sized to fit.
2250 *
2251 * The whole TLS arena is static `.bss` and the internal `dram0_0_seg` ceiling is only
2252 * ~122 KB, so a second concurrent connection's arena overflows it on a stock build.
2253 * A validation guard (bottom of this file) therefore rejects MAX_TLS_CONNS > 1 unless
2254 * you have taken one of the paths in docs/KNOWN_LIMITATIONS.md - move the arena to
2255 * PSRAM (`DETWS_TLS_ARENA_IN_PSRAM`, which satisfies the guard on its own), shrink the
2256 * mbedTLS records in a custom ESP-IDF build, or reclaim internal DRAM - and then set
2257 * this to 1 to confirm the build was sized deliberately.
2258 */
2259#ifndef DETWS_TLS_ACK_MULTI_CONN_DRAM
2260#define DETWS_TLS_ACK_MULTI_CONN_DRAM 0
2261#endif
2262
2263// ---------------------------------------------------------------------------
2264// Optional network services (ESP32-only thin wrappers; each default-off so it
2265// costs no code/RAM/flash unless explicitly enabled).
2266// ---------------------------------------------------------------------------
2267
2268/** @brief mDNS / DNS-SD advertisement (`name.local` + `_http._tcp`) via ESPmDNS. */
2269#ifndef DETWS_ENABLE_MDNS
2270#define DETWS_ENABLE_MDNS 0
2271#endif
2272
2273/** @brief SNTP wall-clock time sync via the ESP-IDF SNTP client. */
2274#ifndef DETWS_ENABLE_NTP
2275#define DETWS_ENABLE_NTP 0
2276#endif
2277
2278/**
2279 * @brief NTP/SNTP time server (RFC 5905 / RFC 4330 server mode) on UDP/123 (services/ntp_server).
2280 *
2281 * Turns the device into a local time source: it answers client NTP requests from its own
2282 * clock (`detws_time_now()` + the `detws_millis()` sub-second fraction), so an offline or
2283 * air-gapped LAN can keep its devices in sync without reaching the public NTP pool. The
2284 * 48-byte response codec is pure and host-tested; the wire binding is the transport UDP
2285 * service. Get the device's own time first (e.g. DETWS_ENABLE_NTP upstream, an RTC, or GPS
2286 * via a time source) - when it has none, the server stays silent rather than serve bad time.
2287 */
2288#ifndef DETWS_ENABLE_NTP_SERVER
2289#define DETWS_ENABLE_NTP_SERVER 0
2290#endif
2291
2292/** @brief Stratum the NTP server advertises (distance from a reference clock; 1-15). */
2293#ifndef DETWS_NTP_SERVER_STRATUM
2294#define DETWS_NTP_SERVER_STRATUM 3
2295#endif
2296
2297/**
2298 * @brief Authoritative DNS server (services/dns_server) on UDP/53.
2299 *
2300 * Default off. Resolves a small fixed table of `name -> IPv4` A records you register with
2301 * dns_server_add(), so devices on an offline / air-gapped LAN can use names instead of raw
2302 * IPs (a companion to the NTP server for offline infrastructure). Answers A/IN queries from
2303 * the table, returns NXDOMAIN for unknown names, and ignores other query types. The response
2304 * builder is pure and host-tested; the wire binding is the transport UDP service. This is a
2305 * general resolver, distinct from the provisioning captive-portal DNS (which answers every
2306 * query with the softAP IP) - do not enable both (they both bind :53).
2307 */
2308#ifndef DETWS_ENABLE_DNS_SERVER
2309#define DETWS_ENABLE_DNS_SERVER 0
2310#endif
2311
2312/** @brief Max A records in the DNS server's fixed table. */
2313#ifndef DETWS_DNS_SERVER_MAX_RECORDS
2314#define DETWS_DNS_SERVER_MAX_RECORDS 8
2315#endif
2316
2317/** @brief TTL (seconds) the DNS server puts on its answers. */
2318#ifndef DETWS_DNS_SERVER_TTL
2319#define DETWS_DNS_SERVER_TTL 60
2320#endif
2321
2322/** @brief Max length of a queried/stored DNS name (bytes, incl NUL). */
2323#ifndef DETWS_DNS_NAME_MAX
2324#define DETWS_DNS_NAME_MAX 128
2325#endif
2326
2327/**
2328 * @brief Auto-inject a `Date` response header (RFC 7231 7.1.1.2) when a wall-clock
2329 * time is available.
2330 *
2331 * Default off: a clock-less device must not emit a wrong `Date`, and most embedded
2332 * responses do not need one, so it stays off the hot path. When set, every dynamic
2333 * response carries `Date: <IMF-fixdate>` - but only once a real time exists; before a
2334 * source has valid time it is silently omitted (still correct for a clock-less boot).
2335 *
2336 * The time is taken from the multi-source registry (any enabled NTP / GPS / RTC / ...
2337 * by priority) when DETWS_ENABLE_TIME_SOURCE is set - register your sources with
2338 * detws_time_source_add() (rtc_time_source, ntp_time_source, ...). Otherwise it comes
2339 * straight from NTP (detws_ntp_http_date). Needs at least one such time source to emit.
2340 */
2341#ifndef DETWS_HTTP_EMIT_DATE
2342#define DETWS_HTTP_EMIT_DATE 0
2343#endif
2344
2345/**
2346 * @brief Multi-source time fallback (NTP / RTC / GPS / ... by priority).
2347 *
2348 * When set, src/services/time_source/time_source.h provides a small registry of
2349 * user-defined time sources, each a callback returning Unix epoch seconds (0 when
2350 * that source has no valid time). detws_time_now() queries them in priority order
2351 * (lowest value first) and returns the first valid result, so the device falls
2352 * back automatically when its preferred clock is unavailable. Pure and zero-heap
2353 * (a fixed source table); host-testable. Default off.
2354 */
2355#ifndef DETWS_ENABLE_TIME_SOURCE
2356#define DETWS_ENABLE_TIME_SOURCE 0
2357#endif
2358
2359/** @brief Maximum registered time sources (DETWS_ENABLE_TIME_SOURCE). */
2360#ifndef DETWS_TIME_SOURCE_MAX
2361#define DETWS_TIME_SOURCE_MAX 4
2362#endif
2363
2364/**
2365 * @brief Shared I2C bus pins for the sensor / peripheral drivers (RTC, SHT3x, MPR121, ADS1115,
2366 * INA219, PCA9685). All of them share one bus via detws_i2c_begin() (services/i2c.h), so
2367 * this is the single place to move it. The default -1 uses the platform's default pins (GPIO 21
2368 * SDA / 22 SCL on the classic ESP32). Set both to free GPIOs when those pins are taken - most
2369 * importantly a **wired-Ethernet PHY**: the LAN8720 RMII uses GPIO 21 (TX_EN) and GPIO 22
2370 * (TXD1) on the classic ESP32 (WROOM/WROVER) and the ESP32-P4 (which have the RMII EMAC), so
2371 * with that Ethernet on, move the I2C bus off them (e.g. 32 / 33). The ESP32-S3/C3 have no RMII
2372 * MAC and use an SPI Ethernet (W5500) instead - relocate the bus off whatever SPI pins that
2373 * uses. UART peripherals (LD2410) take their RX/TX pins at ld2410_begin(), so remap those too.
2374 */
2375#ifndef DETWS_I2C_SDA_PIN
2376#define DETWS_I2C_SDA_PIN -1
2377#endif
2378#ifndef DETWS_I2C_SCL_PIN
2379#define DETWS_I2C_SCL_PIN -1
2380#endif
2381
2382/**
2383 * @brief I2C real-time-clock driver (DS1307 / DS3231) - a battery-backed time source.
2384 *
2385 * Default off. services/rtc reads and sets a DS1307/DS3231 RTC over I2C (Wire), so the device
2386 * keeps accurate wall-clock time across reboots and power loss with no network - the ideal
2387 * fallback below GPS and above upstream NTP in a time-source chain (feeds `detws_time_now()`
2388 * and the NTP server). The BCD<->epoch conversion (7 time registers, 12/24-hour, leap years,
2389 * range validation) is pure and host-tested; only the register read/write touches I2C.
2390 */
2391#ifndef DETWS_ENABLE_RTC
2392#define DETWS_ENABLE_RTC 0
2393#endif
2394
2395/** @brief I2C address of the RTC (DS1307/DS3231 are fixed at 0x68). */
2396#ifndef DETWS_RTC_I2C_ADDR
2397#define DETWS_RTC_I2C_ADDR 0x68
2398#endif
2399
2400/**
2401 * @brief HLK-LD2410 24 GHz mmWave presence / motion radar (UART).
2402 *
2403 * Default off. services/ld2410 syncs to the LD2410's framed serial output (256000 baud) and
2404 * decodes the target report - presence state (none / moving / stationary / both), the moving
2405 * and stationary target distance (cm) and energy (0-100), and, in engineering mode, the
2406 * per-gate energies - plus encodes the config commands (enter / exit config, enable / disable
2407 * engineering mode, restart). The frame sync + decode is pure and host-tested; only the UART
2408 * read/write touches hardware. A cheap solder-and-test breakout: wave a hand, watch presence.
2409 */
2410#ifndef DETWS_ENABLE_LD2410
2411#define DETWS_ENABLE_LD2410 0
2412#endif
2413
2414/** @brief LD2410 UART baud rate (the module's fixed factory default is 256000). */
2415#ifndef DETWS_LD2410_BAUD
2416#define DETWS_LD2410_BAUD 256000
2417#endif
2418
2419/**
2420 * @brief DFRobot SEN0192 10.525 GHz microwave Doppler motion sensor (single digital OUT line).
2421 *
2422 * Default off. services/sen0192 tracks the module's OUT line as a debounced presence signal: it asserts
2423 * presence on an active sample and holds it for DETWS_SEN0192_HOLD_MS after the last active sample, so
2424 * brief gaps between Doppler returns don't make presence flap. The presence state machine is pure and
2425 * host-tested; only the GPIO read touches hardware. Unlike a PIR it senses motion through thin non-metal
2426 * enclosures and is unaffected by ambient light / temperature.
2427 */
2428#ifndef DETWS_ENABLE_SEN0192
2429#define DETWS_ENABLE_SEN0192 0
2430#endif
2431
2432/** @brief GPIO the SEN0192 OUT line is wired to. */
2433#ifndef DETWS_SEN0192_PIN
2434#define DETWS_SEN0192_PIN 4
2435#endif
2436
2437/** @brief Presence is held this many ms after the last active (motion) sample before it clears. */
2438#ifndef DETWS_SEN0192_HOLD_MS
2439#define DETWS_SEN0192_HOLD_MS 2000
2440#endif
2441
2442/** @brief SEN0192 OUT polarity: 1 = the OUT line reads HIGH on motion, 0 = active-LOW. */
2443#ifndef DETWS_SEN0192_ACTIVE_HIGH
2444#define DETWS_SEN0192_ACTIVE_HIGH 1
2445#endif
2446
2447/**
2448 * @brief NXP MPR121 12-channel capacitive-touch controller (I2C).
2449 *
2450 * Default off. services/mpr121 decodes the touch-status word (12 electrode bits + proximity +
2451 * over-current) and the 10-bit filtered / baseline per-electrode data, and builds the register
2452 * init sequence (soft reset, the NXP filter/AFE defaults, per-electrode touch/release
2453 * thresholds, and the electrode-configuration start). The decode + init-sequence builder are
2454 * pure and host-tested; only the register read/write touches I2C. A cheap solder-and-test
2455 * breakout for touch buttons / sliders: wire it up, touch a pad, watch the bit set.
2456 */
2457#ifndef DETWS_ENABLE_MPR121
2458#define DETWS_ENABLE_MPR121 0
2459#endif
2460
2461/** @brief I2C address of the MPR121 (0x5A default; 0x5B/0x5C/0x5D via the ADDR pin). */
2462#ifndef DETWS_MPR121_I2C_ADDR
2463#define DETWS_MPR121_I2C_ADDR 0x5A
2464#endif
2465
2466/** @brief MPR121 per-electrode touch threshold (delta counts from baseline; NXP AN3944 suggests ~4..12).
2467 * Higher = less sensitive. Keep the release threshold below it for hysteresis. */
2468#ifndef DETWS_MPR121_TOUCH_THRESHOLD
2469#define DETWS_MPR121_TOUCH_THRESHOLD 12
2470#endif
2471
2472/** @brief MPR121 per-electrode release threshold (delta counts; should be below the touch threshold). */
2473#ifndef DETWS_MPR121_RELEASE_THRESHOLD
2474#define DETWS_MPR121_RELEASE_THRESHOLD 6
2475#endif
2476
2477/**
2478 * @brief Sensirion SHT3x temperature / humidity sensor (I2C).
2479 *
2480 * Default off. services/sht3x issues the single-shot measurement command, checks the CRC-8 on
2481 * each returned word (polynomial 0x31, init 0xFF - the Sensirion check value 0xBEEF -> 0x92),
2482 * and converts the raw 16-bit ticks to temperature and relative humidity in integer milli-units
2483 * (no float printf needed). The CRC + conversion are pure and host-tested; only the command
2484 * write / data read touches I2C. A cheap solder-and-test breakout (GY-SHT31 etc.) for
2485 * environmental telemetry: read it, bridge it onto the network.
2486 */
2487#ifndef DETWS_ENABLE_SHT3X
2488#define DETWS_ENABLE_SHT3X 0
2489#endif
2490
2491/** @brief I2C address of the SHT3x (0x44 with ADDR low; 0x45 with ADDR high). */
2492#ifndef DETWS_SHT3X_I2C_ADDR
2493#define DETWS_SHT3X_I2C_ADDR 0x44
2494#endif
2495
2496/**
2497 * @brief NXP PCA9685 16-channel 12-bit PWM / servo driver (I2C).
2498 *
2499 * Default off. services/pca9685 computes the PRESCALE value for a PWM frequency from the 25 MHz
2500 * oscillator, the per-channel register address, the 12-bit ON/OFF pulse counts, and a servo
2501 * pulse-width (microseconds) -> count conversion; it also emits the 5-byte channel PWM write.
2502 * The prescale / count math + the register encoder are pure and host-tested; only the register
2503 * writes touch I2C. A cheap solder-and-test breakout for driving up to 16 servos or LEDs.
2504 */
2505#ifndef DETWS_ENABLE_PCA9685
2506#define DETWS_ENABLE_PCA9685 0
2507#endif
2508
2509/** @brief I2C address of the PCA9685 (0x40 default; the six address pins select 0x40..0x7F). */
2510#ifndef DETWS_PCA9685_I2C_ADDR
2511#define DETWS_PCA9685_I2C_ADDR 0x40
2512#endif
2513
2514/** @brief Default PWM output frequency in Hz (50 Hz suits hobby servos). */
2515#ifndef DETWS_PCA9685_FREQ
2516#define DETWS_PCA9685_FREQ 50
2517#endif
2518
2519/**
2520 * @brief TI ADS1115 16-bit ADC (I2C) - a precise external analog input.
2521 *
2522 * Default off. services/ads1115 builds the 16-bit config register (OS start, single-ended
2523 * channel MUX, programmable gain, single-shot mode, data rate, comparator disabled) for a
2524 * single-shot reading, and converts the signed 16-bit result to microvolts for the selected
2525 * gain's full-scale range. The config encoder + conversion are pure and host-tested; only the
2526 * register write / conversion read touches I2C. A cheap solder-and-test breakout for reading
2527 * batteries, potentiometers, and analog sensors with far more resolution than the ESP32 ADC.
2528 */
2529#ifndef DETWS_ENABLE_ADS1115
2530#define DETWS_ENABLE_ADS1115 0
2531#endif
2532
2533/** @brief I2C address of the ADS1115 (0x48 with ADDR to GND; 0x49/0x4A/0x4B for VDD/SDA/SCL). */
2534#ifndef DETWS_ADS1115_I2C_ADDR
2535#define DETWS_ADS1115_I2C_ADDR 0x48
2536#endif
2537
2538/** @brief Default ADS1115 PGA gain code (ADS1115_GAIN_*): 0=+/-6.144V, 1=+/-4.096V, 2=+/-2.048V (default),
2539 * 3=+/-1.024V, 4=+/-0.512V, 5=+/-0.256V. Also the fallback when a read passes an invalid gain. */
2540#ifndef DETWS_ADS1115_GAIN
2541#define DETWS_ADS1115_GAIN 2 // ADS1115_GAIN_2 (+/- 2.048 V)
2542#endif
2543
2544/** @brief Default ADS1115 data-rate code (ADS1115_DR_*): 0=8, 1=16, 2=32, 3=64, 4=128 (default), 5=250,
2545 * 6=475, 7=860 SPS. The single-shot read waits the matching conversion time. */
2546#ifndef DETWS_ADS1115_DR
2547#define DETWS_ADS1115_DR 4 // ADS1115_DR_128 (128 SPS)
2548#endif
2549
2550/** @brief ADS1115 input mode: 0 = single-ended (AINx vs GND), 1 = differential. In differential mode the
2551 * channel selects the pair: 0=AIN0-AIN1, 1=AIN0-AIN3, 2=AIN1-AIN3, 3=AIN2-AIN3. */
2552#ifndef DETWS_ADS1115_DIFFERENTIAL
2553#define DETWS_ADS1115_DIFFERENTIAL 0
2554#endif
2555
2556/**
2557 * @brief TI INA219 high-side current / power monitor (I2C).
2558 *
2559 * Default off. services/ina219 decodes the bus-voltage register (LSB 4 mV) and the shunt-voltage
2560 * register (LSB 10 uV), computes the calibration register from the shunt resistance and the
2561 * chosen current LSB, and scales the raw current / power registers to microamps / microwatts.
2562 * The decode + calibration + scaling math are pure and host-tested; only the register read /
2563 * write touches I2C. A cheap solder-and-test breakout for measuring how much current and power a
2564 * circuit actually draws.
2565 */
2566#ifndef DETWS_ENABLE_INA219
2567#define DETWS_ENABLE_INA219 0
2568#endif
2569
2570/** @brief I2C address of the INA219 (0x40 default; the A0/A1 pins select 0x40..0x4F). */
2571#ifndef DETWS_INA219_I2C_ADDR
2572#define DETWS_INA219_I2C_ADDR 0x40
2573#endif
2574
2575/** @brief Default INA219 current LSB in microamps per bit (calibration input). The fallback when
2576 * ina219_begin() is passed 0. 100 uA/bit with a 100 mohm shunt -> a 2 A full-scale range. */
2577#ifndef DETWS_INA219_CURRENT_LSB_UA
2578#define DETWS_INA219_CURRENT_LSB_UA 100
2579#endif
2580
2581/** @brief Default INA219 shunt resistance in milliohms (calibration input). The fallback when
2582 * ina219_begin() is passed 0. 100 mohm is the common breakout value. */
2583#ifndef DETWS_INA219_SHUNT_MOHM
2584#define DETWS_INA219_SHUNT_MOHM 100
2585#endif
2586
2587/**
2588 * @brief Typed NVS configuration store (WiFi creds, IP config, ... as blobs).
2589 *
2590 * When set, src/services/config_store/config_store.h provides a typed key/value
2591 * API (string / u32 / blob) that routes core settings into the ESP32's native
2592 * NVS partition (via `Preferences`) instead of a JSON file on the filesystem -
2593 * which survives FS corruption and is the corruption-resistant home for
2594 * credentials. On host builds it is backed by a fixed in-memory table so the
2595 * typed contract is unit-testable. Default off.
2596 */
2597#ifndef DETWS_ENABLE_CONFIG_STORE
2598#define DETWS_ENABLE_CONFIG_STORE 0
2599#endif
2600
2601/** @brief Max key/value entries in the host (test) config backend. */
2602#ifndef DETWS_CONFIG_MAX_ENTRIES
2603#define DETWS_CONFIG_MAX_ENTRIES 16
2604#endif
2605
2606/** @brief Max key length incl. null (NVS caps keys at 15 chars). */
2607#ifndef DETWS_CONFIG_KEY_MAX
2608#define DETWS_CONFIG_KEY_MAX 16
2609#endif
2610
2611/** @brief Max value bytes per entry in the host (test) config backend. */
2612#ifndef DETWS_CONFIG_VAL_MAX
2613#define DETWS_CONFIG_VAL_MAX 64
2614#endif
2615
2616/**
2617 * @brief Stable device UUID derived from the chip MAC (RFC 4122 v5).
2618 *
2619 * When set, src/services/device_id/device_id.h derives a deterministic v5 UUID
2620 * from a MAC (via the library's SHA-1) - a storage-free, stable identity for
2621 * mDNS hostnames, MQTT client IDs, etc. The MAC->UUID core is host-testable;
2622 * detws_device_uuid() reads the ESP32 factory MAC. Default off.
2623 */
2624#ifndef DETWS_ENABLE_DEVICE_ID
2625#define DETWS_ENABLE_DEVICE_ID 0
2626#endif
2627
2628/**
2629 * @brief Telemetry math helpers (moving-window stats, rate-of-change, totalizer).
2630 *
2631 * Default off. When set, src/services/telemetry/telemetry.h provides zero-heap
2632 * pure-computation helpers over caller-supplied storage: a moving-window stats
2633 * accumulator (mean / variance / stddev / min / max), a derivative / rate-of-
2634 * change tracker, and a trapezoidal run-time totalizer. No ESP32 dependency, so
2635 * the whole cluster is host-testable; it feeds dashboards, alert triggers, and
2636 * odometer-style counters.
2637 */
2638#ifndef DETWS_ENABLE_TELEMETRY
2639#define DETWS_ENABLE_TELEMETRY 0
2640#endif
2641
2642/**
2643 * @brief Real-time SVG dashboard (DETWS_ENABLE_DASHBOARD; requires DETWS_ENABLE_SSE).
2644 *
2645 * Default off. Serves a self-contained, hand-rolled SVG dashboard page whose
2646 * widgets are declared in a fixed compile-time DetwsWidget table (zero-heap,
2647 * deterministic). The page fetches the widget layout as JSON and subscribes to an
2648 * SSE stream of live values; detws_dashboard_set() + detws_dashboard_publish()
2649 * push the current readings. The widget-table -> JSON serializers are
2650 * host-testable; WebSocket controls are a follow-up.
2651 */
2652#ifndef DETWS_ENABLE_DASHBOARD
2653#define DETWS_ENABLE_DASHBOARD 0
2654#endif
2655
2656/**
2657 * @brief Embed the theme stylesheet library as runtime-selectable blobs (default off).
2658 *
2659 * Off by default: build-time theme injection (`<!--#theme NAME-->`) costs nothing extra, but
2660 * embedding the whole library for runtime switching links every theme's CSS into flash (~1 KB each).
2661 * When set, application/binary_asset_blobs.{h,cpp} exposes `detws_theme_css(name)` + the registry
2662 * `DETWS_THEME_BLOBS`, so a route (e.g. `/themes/<name>.css`) or a picker can switch themes live.
2663 * Regenerate with `src/web/wizard/gen_theme_blobs.py` after adding a theme.
2664 */
2665#ifndef DETWS_ENABLE_THEMES
2666#define DETWS_ENABLE_THEMES 0
2667#endif
2668
2669/**
2670 * @brief Include the trademark-named themes in the embedded set (default on / open-source).
2671 *
2672 * A few themes are named after a company or product (Darcula, Windows XP, Discord, Spotify, ...). The
2673 * palette is just colors, but a commercial product should not ship the branded name, so a commercial
2674 * build sets this to 0 to drop those blobs from the registry (the list is `RESTRICTED` in
2675 * `src/web/wizard/gen_themes.py`). The open-source (AGPL) build keeps them.
2676 */
2677#ifndef DETWS_THEMES_INCLUDE_TRADEMARKED
2678#define DETWS_THEMES_INCLUDE_TRADEMARKED 1
2679#endif
2680
2681/** @brief Maximum widgets in the dashboard table (BSS value array). */
2682#ifndef DETWS_DASHBOARD_MAX_WIDGETS
2683#define DETWS_DASHBOARD_MAX_WIDGETS 16
2684#endif
2685
2686/** @brief Stack buffer for the dashboard layout / values JSON (bytes). */
2687#ifndef DETWS_DASHBOARD_JSON_BUF
2688#define DETWS_DASHBOARD_JSON_BUF 1024
2689#endif
2690
2691/**
2692 * @brief Opt-in flash partition-map monitor endpoint (DETWS_ENABLE_PARTITION_MONITOR).
2693 *
2694 * Default off. When set, services/partition_monitor reports the device's flash
2695 * partition table (label, kind, type / subtype, offset, size, and which app slot
2696 * is running) as JSON, for diagnostics and OTA dashboards. The partition walk uses
2697 * esp_partition / esp_ota_ops; the JSON serializer and the kind classifier are
2698 * pure and host-testable.
2699 */
2700#ifndef DETWS_ENABLE_PARTITION_MONITOR
2701#define DETWS_ENABLE_PARTITION_MONITOR 0
2702#endif
2703
2704/** @brief Maximum partitions the monitor reports (BSS table). */
2705#ifndef DETWS_PARTITION_MAX
2706#define DETWS_PARTITION_MAX 16
2707#endif
2708
2709/** @brief Stack buffer for the partition-map JSON (bytes). */
2710#ifndef DETWS_PARTITION_JSON_BUF
2711#define DETWS_PARTITION_JSON_BUF 1024
2712#endif
2713
2714/**
2715 * @brief Opt-in browser GPIO pin-mapper / diagnostics endpoint (DETWS_ENABLE_GPIO_MAP).
2716 *
2717 * Default off. When set, services/gpio_map serves a compile-time table of GPIO
2718 * pins (number, label, direction, live level) as JSON for a browser diag panel,
2719 * and accepts a control POST (`pin`, `level`) to drive an output. The live read /
2720 * write uses the Arduino digital API on ESP32; the JSON serializer and the control
2721 * parser are pure and host-testable.
2722 */
2723#ifndef DETWS_ENABLE_GPIO_MAP
2724#define DETWS_ENABLE_GPIO_MAP 0
2725#endif
2726
2727/** @brief Maximum GPIO pins the mapper reports (BSS table). */
2728#ifndef DETWS_GPIO_MAX
2729#define DETWS_GPIO_MAX 40
2730#endif
2731
2732/** @brief Stack buffer for the GPIO-map JSON (bytes). */
2733#ifndef DETWS_GPIO_JSON_BUF
2734#define DETWS_GPIO_JSON_BUF 1024
2735#endif
2736
2737/**
2738 * @brief Opt-in fire-and-forget UDP telemetry cast (DETWS_ENABLE_UDP_TELEMETRY).
2739 *
2740 * Default off. When set, services/udp_telemetry casts metric lines (InfluxDB line
2741 * protocol: `measurement field=val,field2=val2`) to a configured collector over
2742 * UDP via det_udp_sendto - zero-heap, fire-and-forget (no ACK, no retry), ideal
2743 * for shipping device metrics to Telegraf/InfluxDB/a log sink. The line builder is
2744 * pure and host-tested; only the send touches the network.
2745 */
2746#ifndef DETWS_ENABLE_UDP_TELEMETRY
2747#define DETWS_ENABLE_UDP_TELEMETRY 0
2748#endif
2749
2750/** @brief Stack buffer for one telemetry line (bytes). */
2751#ifndef DETWS_UDP_TELEMETRY_BUF
2752#define DETWS_UDP_TELEMETRY_BUF 256
2753#endif
2754
2755/**
2756 * @brief Opt-in StatsD metrics client (DETWS_ENABLE_STATSD).
2757 *
2758 * Default off. When set, services/statsd pushes metrics in the StatsD wire format
2759 * (`name:value|type`, e.g. `api.hits:1|c`) over UDP to a StatsD-speaking collector -
2760 * Graphite/StatsD, Telegraf, Datadog, InfluxDB, etc. Counters, gauges (absolute + delta),
2761 * timings, and sets, with optional sample-rate (`|@0.1`) and DogStatsD tags (`|#env:prod`).
2762 * This is the push counterpart to the pull-based Prometheus `/metrics`. The line formatter
2763 * is pure and host-tested; only the send (det_udp_sendto) touches the network. Zero heap.
2764 */
2765#ifndef DETWS_ENABLE_STATSD
2766#define DETWS_ENABLE_STATSD 0
2767#endif
2768
2769/** @brief Default StatsD collector UDP port (StatsD/Graphite standard). */
2770#ifndef DETWS_STATSD_PORT
2771#define DETWS_STATSD_PORT 8125
2772#endif
2773
2774/** @brief Stack buffer for one StatsD line (bytes; caps metric name + value + tags). */
2775#ifndef DETWS_STATSD_LINE_MAX
2776#define DETWS_STATSD_LINE_MAX 256
2777#endif
2778
2779/**
2780 * @brief Opt-in runtime heap/stack guardrails (DETWS_ENABLE_GUARDRAILS).
2781 *
2782 * Default off. When set, services/guardrails samples free heap, the heap low-water
2783 * mark, the largest free block (fragmentation), and the calling task's remaining
2784 * stack, and fires a callback when any crosses its threshold - a proactive
2785 * fail-safe hook beyond the passive numbers in /metrics. The threshold evaluator
2786 * and the JSON serializer are pure and host-tested; the sample reads esp_* / the
2787 * FreeRTOS stack high-water on ESP32.
2788 */
2789#ifndef DETWS_ENABLE_GUARDRAILS
2790#define DETWS_ENABLE_GUARDRAILS 0
2791#endif
2792
2793/** @brief Free-heap floor (bytes); below this trips the heap guardrail. */
2794#ifndef DETWS_GUARDRAIL_HEAP_MIN
2795#define DETWS_GUARDRAIL_HEAP_MIN 8192
2796#endif
2797
2798/** @brief Largest-free-block floor (bytes); below this trips the fragmentation guardrail. */
2799#ifndef DETWS_GUARDRAIL_FRAG_MIN_BLOCK
2800#define DETWS_GUARDRAIL_FRAG_MIN_BLOCK 4096
2801#endif
2802
2803/** @brief Task remaining-stack floor (bytes); below this trips the stack guardrail. */
2804#ifndef DETWS_GUARDRAIL_STACK_MIN
2805#define DETWS_GUARDRAIL_STACK_MIN 512
2806#endif
2807
2808/**
2809 * @brief Opt-in software watchdog: deadlock detection + fail-safe safe-state (DETWS_ENABLE_FAILSAFE).
2810 *
2811 * When set, services/failsafe provides a fixed registry of "lifelines" (a task / worker / control loop
2812 * that must check in within its deadline). detws_failsafe_check() detects one that stopped feeding (a
2813 * hang / deadlock) and fires a breach callback once per episode so the app can enter a known-safe
2814 * state. App-defined and per-lifeline, on top of the hardware task watchdog. Pure core, zero heap.
2815 * Default off.
2816 */
2817#ifndef DETWS_ENABLE_FAILSAFE
2818#define DETWS_ENABLE_FAILSAFE 0
2819#endif
2820
2821/** @brief Max monitored lifelines in the fail-safe registry (static, zero-heap). */
2822#ifndef DETWS_FAILSAFE_MAX_LIFELINES
2823#define DETWS_FAILSAFE_MAX_LIFELINES 8
2824#endif
2825
2826/**
2827 * @brief Opt-in dynamic sleep-cycle scheduler (DETWS_ENABLE_SLEEP_SCHED).
2828 *
2829 * When set, services/sleep_sched provides detws_sleep_next(): from the time since the last activity it
2830 * returns how long a low-power device should sleep (0 = stay awake), ramping the window from a floor up
2831 * to a ceiling the longer the idle streak runs. Pure decision core (the app applies the window via
2832 * light / modem / deep sleep). Complements services/radio_power. Default off.
2833 */
2834#ifndef DETWS_ENABLE_SLEEP_SCHED
2835#define DETWS_ENABLE_SLEEP_SCHED 0
2836#endif
2837
2838/**
2839 * @brief Opt-in flash wear-leveling slot selector (DETWS_ENABLE_WEARLEVEL).
2840 *
2841 * When set, services/wearlevel provides detws_wearlevel_pick(): given per-slot write counts it returns
2842 * the least-worn slot to write next, so repeated flash/NVS writes spread evenly and the region ages
2843 * together instead of burning out one block. Pure core (the app owns the slots + persisted counts).
2844 * Default off.
2845 */
2846#ifndef DETWS_ENABLE_WEARLEVEL
2847#define DETWS_ENABLE_WEARLEVEL 0
2848#endif
2849
2850/**
2851 * @brief Opt-in network adaptation decisions (DETWS_ENABLE_NETADAPT).
2852 *
2853 * When set, services/netadapt provides two pure decisions: detws_netadapt_window() sizes the TCP
2854 * receive window from the free heap (bigger when RAM is plentiful, shrinking when tight), and
2855 * detws_netadapt_dhcp_fallback() decides when to give up on DHCP and use a static IP. The app applies
2856 * the results (lwIP window / netif config). Default off.
2857 */
2858#ifndef DETWS_ENABLE_NETADAPT
2859#define DETWS_ENABLE_NETADAPT 0
2860#endif
2861
2862/**
2863 * @brief Opt-in DShot ESC throttle protocol codec (DETWS_ENABLE_DSHOT).
2864 *
2865 * When set, services/dshot provides detws_dshot_encode() / _decode(): the 16-bit DShot frame
2866 * (11-bit throttle/command + telemetry bit + 4-bit CRC), the bidirectional/extended inverted-CRC
2867 * variant, and the per-rate bit timing for an RMT driver. Pure codec (the app clocks it out via RMT).
2868 * Default off.
2869 */
2870#ifndef DETWS_ENABLE_DSHOT
2871#define DETWS_ENABLE_DSHOT 0
2872#endif
2873
2874/**
2875 * @brief Opt-in HART / HART-IP process-instrument protocol codec (DETWS_ENABLE_HART).
2876 *
2877 * When set, services/hart provides the HART command-frame codec (build/parse with the longitudinal XOR
2878 * checksum, short + long addressing) and the 8-octet HART-IP message header, so a device speaks HART
2879 * over UDP/TCP 5094 (front-end-free) or, with a HART FSK modem, over the 4-20 mA loop. Pure, host-tested.
2880 * Default off.
2881 */
2882#ifndef DETWS_ENABLE_HART
2883#define DETWS_ENABLE_HART 0
2884#endif
2885
2886/**
2887 * @brief Opt-in Network Time Security (NTS, RFC 8915) wire codec (DETWS_ENABLE_NTS).
2888 *
2889 * When set, services/nts provides the NTS-KE record codec (build/parse the TLV records - next protocol,
2890 * AEAD, cookies, server/port) and the NTS NTP extension-field framing (Unique Identifier, Cookie,
2891 * Authenticator). Pure framing (the AES-SIV-CMAC-256 AEAD + TLS-exporter key derivation are the crypto
2892 * integration on top). Default off.
2893 */
2894#ifndef DETWS_ENABLE_NTS
2895#define DETWS_ENABLE_NTS 0
2896#endif
2897
2898/**
2899 * @brief Opt-in DDS / RTPS wire-protocol codec (DETWS_ENABLE_DDS).
2900 *
2901 * When set, services/dds provides the RTPS (DDSI-RTPS) message + submessage framing: the 20-octet
2902 * header (magic / version / vendor / guidPrefix) and the typed submessages (INFO_TS, DATA, HEARTBEAT,
2903 * ACKNACK, ...) with the endianness flag, built by detws_rtps_header / _submessage and walked by
2904 * detws_rtps_parse. Pure framing (CDR payloads + SPDP/SEDP discovery layer on top). Default off.
2905 */
2906#ifndef DETWS_ENABLE_DDS
2907#define DETWS_ENABLE_DDS 0
2908#endif
2909
2910/**
2911 * @brief Opt-in XMPP (RFC 6120) stanza codec (DETWS_ENABLE_XMPP).
2912 *
2913 * When set, services/xmpp builds correctly XML-escaped `<stream:stream>` / `<message>` / `<presence>` /
2914 * `<iq>` stanzas into a caller buffer and reads the stanza element name + an attribute value out of a
2915 * received stanza, so a device is an IoT XMPP client. Pure text framing (TLS/SASL ride the client TLS
2916 * path; the IoT XEPs layer inside `<iq>`). Default off.
2917 */
2918#ifndef DETWS_ENABLE_XMPP
2919#define DETWS_ENABLE_XMPP 0
2920#endif
2921
2922/**
2923 * @brief Opt-in raw Layer-2 Ethernet frame codec (DETWS_ENABLE_RAWL2).
2924 *
2925 * When set, services/rawl2 builds/parses Ethernet II + 802.1Q VLAN frames (no FCS - the MAC appends it;
2926 * detws_eth_fcs is provided for the cases that need it), so the app can inject/receive arbitrary L2
2927 * frames via esp_eth_transmit / esp_wifi_80211_tx - the basis for the raw-L2 industrial protocols
2928 * (PROFINET DCP, GOOSE, POWERLINK). Pure codec, host-tested. Default off.
2929 */
2930#ifndef DETWS_ENABLE_RAWL2
2931#define DETWS_ENABLE_RAWL2 0
2932#endif
2933
2934/**
2935 * @brief Opt-in single-page-app micro-routing decision (DETWS_ENABLE_SPA_ROUTER).
2936 *
2937 * When set, services/spa_router provides detws_spa_route(): given a request path it returns whether to
2938 * serve a real asset file, serve the SPA shell (index.html) for a client-side route, or pass through to
2939 * the app's handlers under an API prefix - so a single-page UI's client routing works. Pure decision
2940 * core (the caller wires the result into serve_static / the router). Default off.
2941 */
2942#ifndef DETWS_ENABLE_SPA_ROUTER
2943#define DETWS_ENABLE_SPA_ROUTER 0
2944#endif
2945
2946/**
2947 * @brief Opt-in IEC 61850 GOOSE publisher codec (DETWS_ENABLE_GOOSE).
2948 *
2949 * When set, services/goose builds the BER-encoded IECGoosePdu (gocbRef / timeAllowedToLive / datSet /
2950 * goID / t / stNum / sqNum / simulation / confRev / ndsCom / numDatSetEntries / allData) and wraps it in
2951 * the 8-octet GOOSE header + Ethernet frame (ethertype 0x88B8) for the fast raw-L2 substation-event
2952 * publish. Pure codec (allData is a caller-encoded BER blob; the raw-L2 transmit is the device step).
2953 * Default off.
2954 */
2955#ifndef DETWS_ENABLE_GOOSE
2956#define DETWS_ENABLE_GOOSE 0
2957#endif
2958
2959/**
2960 * @brief Opt-in MTConnect agent response codec (DETWS_ENABLE_MTCONNECT).
2961 *
2962 * When set, services/mtconnect builds the MTConnectStreams (current/sample) and MTConnectError XML
2963 * response documents (ANSI/MTC1.4) into a caller buffer - header with instanceId + nextSequence, then
2964 * per-DataItem Samples/Events/Condition observations - so the web server is an MTConnect agent over the
2965 * existing HTTP stack. Pure text framing (values XML-escaped). Default off.
2966 */
2967#ifndef DETWS_ENABLE_MTCONNECT
2968#define DETWS_ENABLE_MTCONNECT 0
2969#endif
2970
2971/**
2972 * @brief MTConnect rolling sample buffer sizing (DETWS_ENABLE_MTCONNECT).
2973 *
2974 * The agent retains the most recent ::DETWS_MTC_SAMPLE_BUFFER observations in a fixed ring so a
2975 * subscriber can replay them with the `sample` from/count long-poll cursor (MTC1.4 §6.7): a request
2976 * asks for observations starting at a sequence number, and the response header reports firstSequence /
2977 * lastSequence / nextSequence so the client knows what it received and where to resume. Each retained
2978 * observation stores its type / dataItemId / timestamp / value in fixed char fields; when the ring is
2979 * full the oldest is evicted and firstSequence advances. Zero-heap, compile-time sized; the buffer costs
2980 * ~DETWS_MTC_SAMPLE_BUFFER * (48 + the four string caps) bytes only where a DetwsMtcSampleBuffer is used.
2981 */
2982#ifndef DETWS_MTC_SAMPLE_BUFFER
2983#define DETWS_MTC_SAMPLE_BUFFER 32 // observations retained for `sample` replay
2984#endif
2985#ifndef DETWS_MTC_STR_MAX
2986#define DETWS_MTC_STR_MAX 24 // max stored type / dataItemId length (excl NUL)
2987#endif
2988#ifndef DETWS_MTC_TS_MAX
2989#define DETWS_MTC_TS_MAX 32 // max stored ISO-8601 timestamp length (excl NUL)
2990#endif
2991#ifndef DETWS_MTC_VAL_MAX
2992#define DETWS_MTC_VAL_MAX 32 // max stored observation value length (excl NUL)
2993#endif
2994
2995/**
2996 * @brief Opt-in write-ahead store for atomic buffer-to-flash storage (DETWS_ENABLE_WAL).
2997 *
2998 * services/wal is a power-loss-safe write-ahead log over any fs::FS backend (SD card, LittleFS): records
2999 * are CRC32-framed, a checkpoint is atomic via an A/B superblock, and a recovery scan on mount replays
3000 * past the last checkpoint and stops at the first bad CRC (the torn tail), bounding the loss window. Sized
3001 * from the measured SD envelope (docs/FEATURE_PERFORMANCE.md): append sequentially in ~32 KiB pages,
3002 * checkpoint every ~128-256 KiB (never scatter small durable writes). The substrate for on-device data
3003 * stores (dbm / sqlite / nosql). Zero heap. Default off.
3004 */
3005#ifndef DETWS_ENABLE_WAL
3006#define DETWS_ENABLE_WAL 0
3007#endif
3008#ifndef DETWS_WAL_PAGE_SIZE
3009#define DETWS_WAL_PAGE_SIZE 32768 // sequential write unit (the measured durable-throughput knee)
3010#endif
3011#ifndef DETWS_WAL_MAX_RECORD
3012#define DETWS_WAL_MAX_RECORD 4096 // largest single record payload
3013#endif
3014
3015/**
3016 * @brief Opt-in dbm: a log-structured hash key-value store on the WAL (DETWS_ENABLE_DBM, requires WAL).
3017 *
3018 * services/dbm is a Bitcask-style key-value store: each put/delete appends one WAL record (so writes are
3019 * the WAL's fast sequential appends, not slow durable random writes), and an in-RAM open-addressed hash
3020 * index (fixed BSS, no heap) maps each live key to where its value sits in the log. Mount rebuilds the
3021 * index by scanning the WAL. Keys are bounded by DETWS_DBM_KEY_MAX, values by DETWS_DBM_VAL_MAX, and the
3022 * index holds up to DETWS_DBM_SLOTS live keys. Default off.
3023 */
3024#ifndef DETWS_ENABLE_DBM
3025#define DETWS_ENABLE_DBM 0
3026#endif
3027#ifndef DETWS_DBM_SLOTS
3028#define DETWS_DBM_SLOTS 256 // max live keys (in-RAM index capacity; open-addressed, keep load < ~0.7)
3029#endif
3030#ifndef DETWS_DBM_KEY_MAX
3031#define DETWS_DBM_KEY_MAX 32 // largest key in bytes
3032#endif
3033#ifndef DETWS_DBM_VAL_MAX
3034#define DETWS_DBM_VAL_MAX 256 // largest value in bytes
3035#endif
3036
3037/**
3038 * @brief Opt-in local JSON document store on the WAL (DETWS_ENABLE_DOCSTORE, requires DBM + WAL).
3039 *
3040 * services/docstore is a small NoSQL document store: JSON documents addressed by an id, kept durably on
3041 * the write-ahead log. It is a thin layer over dbm (id = key, JSON body = value) and adds the document
3042 * capability - top-level field queries (find documents whose JSON field equals a value) via the zero-heap
3043 * JSON reader. Ids are bounded by DETWS_DBM_KEY_MAX, bodies by DETWS_DBM_VAL_MAX. Default off.
3044 */
3045#ifndef DETWS_ENABLE_DOCSTORE
3046#define DETWS_ENABLE_DOCSTORE 0
3047#endif
3048#ifndef DETWS_DOCSTORE_FIELD_MAX
3049#define DETWS_DOCSTORE_FIELD_MAX 128 // largest string field value a find can compare
3050#endif
3051
3052/**
3053 * @brief Opt-in SQLite3 on-disk file-format reader (DETWS_ENABLE_SQLITE).
3054 *
3055 * services/sqlite parses the documented SQLite database file structure by hand - the 100-byte database
3056 * header, the b-tree page header, the record varint, and record serial types - so a device can read a
3057 * SQLite file (from wal_fs / fs::FS) without the SQLite amalgamation (which needs a heap + stdio and does
3058 * not fit the no-stdlib zero-heap model). Read-first (a bounded writer is a later step); pure, host-tested
3059 * against real files from the sqlite3 CLI. Default off.
3060 */
3061#ifndef DETWS_ENABLE_SQLITE
3062#define DETWS_ENABLE_SQLITE 0
3063#endif
3064
3065/**
3066 * @brief Opt-in Redis RESP wire codec (DETWS_ENABLE_REDIS).
3067 *
3068 * services/redis is the pure wire layer of a Redis client: a RESP command encoder (a command becomes a
3069 * RESP array of bulk strings) and a streaming, zero-heap reply decoder that reads one value at a time, so
3070 * arbitrarily nested replies are walked with only the caller's loop state (no tree allocation). Covers
3071 * RESP2 and the RESP3 additions (null / boolean / double / big number / bulk error / verbatim / map / set
3072 * / push). Pure (no I/O; you hand it byte buffers); host-tested against spec vectors and a real
3073 * redis-server. Default off.
3074 */
3075#ifndef DETWS_ENABLE_REDIS
3076#define DETWS_ENABLE_REDIS 0
3077#endif
3078
3079/**
3080 * @brief Opt-in CNC RS-232 DNC drip-feed codec (DETWS_ENABLE_DNC).
3081 *
3082 * services/dnc is the transport-agnostic framing + tape-code layer that streams a G-code program
3083 * (RS-274 / ISO 6983) to a machine-tool controller over RS-232 or a socket: block framing with a `%`
3084 * rewind-stop, ISO 7-bit (ASCII, optional even parity) or EIA RS-244 (odd-parity punched-tape code)
3085 * character translation, a streaming block encoder + reassembling decoder, and XON/XOFF software
3086 * flow-control state. Pure codec (you own the UART / socket); host-tested. Default off.
3087 */
3088#ifndef DETWS_ENABLE_DNC
3089#define DETWS_ENABLE_DNC 0
3090#endif
3091
3092/**
3093 * @brief Largest G-code block (one line) the DNC decoder reassembles (DETWS_ENABLE_DNC).
3094 *
3095 * A block longer than this overflows the decoder's fixed line buffer and is dropped whole
3096 * (::DNC_EV_OVERFLOW) rather than truncated. Sized for a normal G-code line; raise it only for
3097 * unusually long blocks (many parameters). Zero heap - this is the static per-decoder buffer.
3098 */
3099#ifndef DETWS_DNC_LINE_MAX
3100#define DETWS_DNC_LINE_MAX 128
3101#endif
3102
3103/**
3104 * @brief Default leader/trailer runout length for the DNC encoder (DETWS_ENABLE_DNC).
3105 *
3106 * The number of NUL runout bytes ::dnc_encode_leader emits before the program (and can emit after
3107 * it). The reader skips them until the first `%`. Traditional tape leaders were a few inches of
3108 * blank feed; 32 bytes is a serial-link equivalent. Overridable per call via DncCfg::leader_len.
3109 */
3110#ifndef DETWS_DNC_LEADER_LEN
3111#define DETWS_DNC_LEADER_LEN 32
3112#endif
3113
3114/**
3115 * @brief Safety cap on how many times the DNC stream engine polls the reverse channel while paused
3116 * by an XOFF, before giving up with an I/O error (DETWS_ENABLE_DNC).
3117 *
3118 * `dnc_stream` pauses on XOFF and polls `recv` for the XON that resumes it; a well-behaved transport
3119 * paces `recv` (blocks briefly when idle) so this cap is only a backstop against a `recv` that spins
3120 * returning no data forever. Raise it if a slow controller legitimately holds XOFF for a long time.
3121 */
3122#ifndef DETWS_DNC_XOFF_MAX_POLLS
3123#define DETWS_DNC_XOFF_MAX_POLLS 200000
3124#endif
3125
3126/**
3127 * @brief Opt-in TCP relay / DNAT port forwarding (DETWS_ENABLE_RELAY).
3128 *
3129 * services/relay is a bidirectional byte pump that publishes an internal `host:port` through the
3130 * server: an inbound connection is relayed to an origin (an outbound det_client connection), moving
3131 * bytes both ways with backpressure and independent half-close, so the device fronts a service that
3132 * lives behind it. The engine is a pure step function over two send/recv seams (host-testable); the
3133 * app owns the two sockets. Default off.
3134 */
3135#ifndef DETWS_ENABLE_RELAY
3136#define DETWS_ENABLE_RELAY 0
3137#endif
3138
3139/**
3140 * @brief User-defined address:port -> hardware-bus bridge (services/iface_bridge).
3141 *
3142 * A configurable "device server": the app registers rules mapping a listen `x.x.x.x:nnnn` (TCP/UDP) to a
3143 * UART, SPI chip-select, or I2C address. A network client talking to the port is bridged to that bus -
3144 * raw stream passthrough for UART, or framed write-then-read transactions (uint16 write_len || uint16
3145 * read_len || write_bytes) for SPI/I2C. The rule table + transaction frame codec are a pure, zero-heap,
3146 * host-tested core; the bus I/O (Serial/SPI/Wire) and the PROTO_BRIDGE listener are the ESP32 step.
3147 * Default off.
3148 */
3149#ifndef DETWS_ENABLE_IFACE_BRIDGE
3150#define DETWS_ENABLE_IFACE_BRIDGE 0
3151#endif
3152
3153/** @brief Max concurrent address:port -> bus rules (services/iface_bridge). */
3154#ifndef DETWS_BRIDGE_MAX_RULES
3155#define DETWS_BRIDGE_MAX_RULES 8
3156#endif
3157
3158/**
3159 * @brief Max write / read payload (bytes) per TRANSACTION frame (services/iface_bridge).
3160 *
3161 * Bounds the per-transaction stack scratch used to clock an SPI/I2C write-then-read, and rejects a frame
3162 * whose write_len or read_len exceeds it. Device-server transactions are small register accesses, so the
3163 * default is modest; a frame over the cap closes the connection (protocol error). Keep it comfortably
3164 * under the transport RX ring so a whole frame can buffer before it is parsed.
3165 */
3166#ifndef DETWS_BRIDGE_TXN_MAX
3167#define DETWS_BRIDGE_TXN_MAX 256
3168#endif
3169
3170/** @brief STREAM (UART) pipe chunk size (bytes) for services/iface_bridge - one socket<->UART hop. */
3171#ifndef DETWS_BRIDGE_STREAM_CHUNK
3172#define DETWS_BRIDGE_STREAM_CHUNK 256
3173#endif
3174
3175/** @brief UART TRANSACTION read window (ms): how long a write-then-read waits for the read_len reply. */
3176#ifndef DETWS_BRIDGE_UART_TXN_MS
3177#define DETWS_BRIDGE_UART_TXN_MS 50
3178#endif
3179
3180/**
3181 * @brief GNSS RTK base station + NTRIP caster (services/gnss).
3182 *
3183 * Turns the device into a differential-GNSS correction source: it surveys in a fixed antenna position and
3184 * serves RTCM 3.x corrections to rovers over the network as an NTRIP caster, so a rover applies them for
3185 * RTK / DGPS accuracy. The RTCM3 frame codec (0xD3 preamble, 10-bit length, CRC-24Q, message-type parse,
3186 * MSB-first bit I/O, station-reference 1005/1006 encode/decode) is a pure, zero-heap, host-tested core;
3187 * the caster server (rover connections + sourcetable) and the receiver bring-up (UBX / NMEA over UART) are
3188 * the ESP32 step. Generating RTCM3 *observation* (MSM) messages needs a receiver that outputs raw
3189 * measurements (u-blox RXM-RAWX: F9P / M8T class); a raw-less module (NEO-6/7, GT-U7) can still serve the
3190 * surveyed reference point + sourcetable. Default off.
3191 */
3192#ifndef DETWS_ENABLE_NTRIP_CASTER
3193#define DETWS_ENABLE_NTRIP_CASTER 0
3194#endif
3195
3196/** @brief Max concurrent rover connections a caster serves corrections to (services/gnss). */
3197#ifndef DETWS_NTRIP_MAX_ROVERS
3198#define DETWS_NTRIP_MAX_ROVERS 4
3199#endif
3200
3201// The base surveys in from the receiver's GGA fixes, so the NTRIP caster implies the NMEA 0183 codec.
3202#if DETWS_ENABLE_NTRIP_CASTER && !DETWS_ENABLE_NMEA0183
3203#undef DETWS_ENABLE_NMEA0183
3204#define DETWS_ENABLE_NMEA0183 1
3205#endif
3206
3207/** @brief Max length (incl. NUL) of an NTRIP mountpoint name the caster serves. */
3208#ifndef DETWS_NTRIP_MOUNT_MAX
3209#define DETWS_NTRIP_MOUNT_MAX 32
3210#endif
3211
3212/** @brief Max NTRIP client request size (bytes) the caster buffers while reading the request headers. */
3213#ifndef DETWS_NTRIP_REQ_MAX
3214#define DETWS_NTRIP_REQ_MAX 512
3215#endif
3216
3217/** @brief Max distinct mountpoints a single caster serves (each = one RTCM stream). */
3218#ifndef DETWS_NTRIP_MAX_MOUNTS
3219#define DETWS_NTRIP_MAX_MOUNTS 2
3220#endif
3221
3222/**
3223 * @brief Per-direction relay buffer size (bytes) for services/relay (DETWS_ENABLE_RELAY).
3224 *
3225 * Each active relay holds two buffers of this size (one per direction) for bytes read from one peer
3226 * but not yet accepted by the other (backpressure carry). Larger buffers raise throughput per step
3227 * (fewer cross-thread det_conn_send marshals per KB) at the cost of RAM per concurrent relay
3228 * (2 * DETWS_RELAY_BUF * DETWS_RELAY_MAX_CONNS bytes).
3229 */
3230#ifndef DETWS_RELAY_BUF
3231#define DETWS_RELAY_BUF 2048
3232#endif
3233
3234/**
3235 * @brief Max det_relay_step passes per poll for the relay listener (DETWS_ENABLE_RELAY).
3236 *
3237 * One poll drains up to this many DETWS_RELAY_BUF chunks per direction, so a single event forwards the
3238 * whole buffered origin RX ring (DETWS_CLIENT_RX_BUF) instead of one chunk - the difference between a
3239 * ~0.4 Mbps and a multi-Mbps port-forward. Bounded so one busy bridge cannot starve the others.
3240 */
3241#ifndef DETWS_RELAY_DRAIN_MAX
3242#define DETWS_RELAY_DRAIN_MAX 8
3243#endif
3244
3245/**
3246 * @brief Max published relay ports (bind table size) for the relay listener (DETWS_ENABLE_RELAY).
3247 *
3248 * Each det_relay_publish() call binds one listener port to one origin `host:port`. This caps how
3249 * many distinct ports the device can front at once.
3250 */
3251#ifndef DETWS_RELAY_MAX_PUBLISH
3252#define DETWS_RELAY_MAX_PUBLISH 4
3253#endif
3254
3255/**
3256 * @brief Max concurrent relayed connections (bridge table size) for the relay listener
3257 * (DETWS_ENABLE_RELAY). Each holds a DetRelay (two DETWS_RELAY_BUF buffers) + an origin slot.
3258 */
3259#ifndef DETWS_RELAY_MAX_CONNS
3260#define DETWS_RELAY_MAX_CONNS 4
3261#endif
3262
3263/** @brief Max origin hostname length (bytes, incl. NUL) stored per published relay port. */
3264#ifndef DETWS_RELAY_HOST_MAX
3265#define DETWS_RELAY_HOST_MAX 64
3266#endif
3267
3268/** @brief Blocking connect timeout (ms) when the relay listener dials the origin on a new inbound. */
3269#ifndef DETWS_RELAY_CONNECT_MS
3270#define DETWS_RELAY_CONNECT_MS 5000
3271#endif
3272
3273/**
3274 * @brief Opt-in FTP client wire codec (DETWS_ENABLE_FTP).
3275 *
3276 * services/ftp is the pure protocol layer of an FTP client (RFC 959 + RFC 2428 EPSV/EPRT):
3277 * `ftp_build_command` / `ftp_build_port` / `ftp_build_eprt` build control-channel commands,
3278 * `ftp_parse_reply` detects a complete single- or multi-line 3-digit reply, and
3279 * `ftp_parse_pasv` / `ftp_parse_epsv` decode the data-channel address the server returns. So a
3280 * device can push/pull files - e.g. drip a `.nc` program to a CNC controller's FTP store. Pure
3281 * codec (you own the control + data sockets); host-tested. Default off.
3282 */
3283#ifndef DETWS_ENABLE_FTP
3284#define DETWS_ENABLE_FTP 0
3285#endif
3286
3287/**
3288 * @brief Suggested FTP control-command buffer size (DETWS_ENABLE_FTP).
3289 *
3290 * A convenience cap for callers sizing the buffer they hand `ftp_build_command`; the builders
3291 * are all length-checked against the caller's `cap`, so this is only a sensible default. Large
3292 * enough for a RETR / STOR with a long path.
3293 */
3294#ifndef DETWS_FTP_CMD_MAX
3295#define DETWS_FTP_CMD_MAX 256
3296#endif
3297
3298/**
3299 * @brief Opt-in HTTP Cache-Control directive helpers (DETWS_ENABLE_HTTP_CACHE).
3300 *
3301 * services/httpcache is the origin-side of edge caching (RFC 9111 + RFC 8246 + RFC 5861): a
3302 * structured `Cache-Control` builder (`cache_control_build` + first-class presets like
3303 * `cache_immutable_asset` / `cache_shared`) so app routes emit correct, edge-cacheable responses
3304 * (hand the value to DetWebServer::set_cache_control()), a tolerant directive parser
3305 * (`cache_control_parse`), and the RFC 9111 freshness-lifetime calculation. Pure text, host-tested.
3306 * Groundwork for the CDN roadmap; the caching tier itself is a separate piece. Default off.
3307 */
3308#ifndef DETWS_ENABLE_HTTP_CACHE
3309#define DETWS_ENABLE_HTTP_CACHE 0
3310#endif
3311
3312/**
3313 * @brief Opt-in CDN edge-cache tier (DETWS_ENABLE_EDGE_CACHE, requires HTTP_CACHE).
3314 *
3315 * services/edge_cache is the caching reverse-proxy edge that services/httpcache is the origin-side
3316 * groundwork for: a device sits in front of a remote upstream origin, fetches a response once, and
3317 * serves subsequent hits from a bounded local store - honoring `Cache-Control` / `Expires` / `ETag` /
3318 * `Last-Modified`, revalidating stale entries with conditional requests (`If-None-Match` /
3319 * `If-Modified-Since` -> 304), and serving `Range` / `206` straight from the cache. A two-tier store:
3320 * bounded RAM (L1, hot) plus an optional dbm/WAL-backed SD tier (L2, persistent, when DETWS_ENABLE_DBM
3321 * is set). Misses/revalidations fetch the origin asynchronously (the client request is suspended and
3322 * resumed from the poll loop, never stalling the worker) and always fail open. Zero heap. Default off.
3323 */
3324#ifndef DETWS_ENABLE_EDGE_CACHE
3325#define DETWS_ENABLE_EDGE_CACHE 0
3326#endif
3327#if DETWS_ENABLE_EDGE_CACHE && !DETWS_ENABLE_HTTP_CACHE
3328#error "DETWS_ENABLE_EDGE_CACHE requires DETWS_ENABLE_HTTP_CACHE"
3329#endif
3330#if DETWS_ENABLE_EDGE_CACHE && !DETWS_ENABLE_HTTP_CLIENT
3331#error "DETWS_ENABLE_EDGE_CACHE requires DETWS_ENABLE_HTTP_CLIENT (it fetches the upstream origin)"
3332#endif
3333#ifndef DETWS_EDGE_CACHE_SLOTS
3334#define DETWS_EDGE_CACHE_SLOTS 4 // L1 RAM entries (each holds one cached object; bump up on PSRAM)
3335#endif
3336#ifndef DETWS_EDGE_BODY_MAX
3337#define DETWS_EDGE_BODY_MAX 2048 // largest cacheable body in bytes (per L1 entry; bump up on PSRAM)
3338#endif
3339#ifndef DETWS_EDGE_KEY_MAX
3340#define DETWS_EDGE_KEY_MAX 128 // largest canonical cache key (method\nhost\npath[\nquery])
3341#endif
3342#ifndef DETWS_EDGE_VARY_MAX
3343#define DETWS_EDGE_VARY_MAX 64 // stored Vary field-name list / captured request values (each)
3344#endif
3345#ifndef DETWS_EDGE_MAP_MAX
3346#define DETWS_EDGE_MAP_MAX 4 // path-prefix -> origin route mappings
3347#endif
3348#ifndef DETWS_EDGE_ORIGIN_URL_MAX
3349#define DETWS_EDGE_ORIGIN_URL_MAX 128 // largest origin base URL in a route mapping
3350#endif
3351#ifndef DETWS_EDGE_FETCH_SLOTS
3352#define DETWS_EDGE_FETCH_SLOTS 2 // concurrent in-flight origin fetches (<= DETWS_CLIENT_CONNS)
3353#endif
3354#ifndef DETWS_EDGE_FETCH_BUF
3355#define DETWS_EDGE_FETCH_BUF 2560 // per-fetch origin-response accumulation buffer (>= body max + headers)
3356#endif
3357#ifndef DETWS_EDGE_FETCH_TIMEOUT_MS
3358#define DETWS_EDGE_FETCH_TIMEOUT_MS 8000 // origin fetch deadline before fail-open
3359#endif
3360#ifndef DETWS_EDGE_DEFAULT_TTL_S
3361#define DETWS_EDGE_DEFAULT_TTL_S 60 // fallback freshness when no directive and no wall clock
3362#endif
3363// L2 (SD) tier: when DETWS_ENABLE_DBM is also set, the edge cache spills evicted entries to a dbm store
3364// (edge_cache_sd) so the cached set survives a reboot. Each entry serializes to ~ its body plus ~470 B of
3365// response metadata; for a full-body spill, DETWS_DBM_VAL_MAX must be >= that size (>= DETWS_EDGE_BODY_MAX
3366// + ~470). Entries that do not fit simply stay L1-only, so a small DETWS_DBM_VAL_MAX is safe but persists
3367// less. The L2 key is the 32-byte cache-key digest, so DETWS_DBM_KEY_MAX must be >= 32 (its default).
3368
3369/**
3370 * @brief Opt-in SMB2 client (DETWS_ENABLE_SMB).
3371 *
3372 * services/smb is an SMB2 client (MS-SMB2) so a device can read/write files on a Windows share -
3373 * e.g. a CNC controller's program store. The full read/write-a-file path: the Direct-TCP transport
3374 * frame + SMB2 sync header, NEGOTIATE, the two-round NTLMv2 SESSION_SETUP (NTLM digests MD4/MD5/
3375 * HMAC-MD5, the NTLMv2 response, the NTLMSSP messages, SPNEGO wrapping), TREE_CONNECT, CREATE, READ,
3376 * WRITE, and CLOSE. smb_client ties the codecs into the exchange over a send/recv seam (host-tested
3377 * with a scripted mock server); you own the TCP socket (det_client). All little-endian. Default off.
3378 */
3379#ifndef DETWS_ENABLE_SMB
3380#define DETWS_ENABLE_SMB 0
3381#endif
3382
3383/**
3384 * @brief SMB2 client work-buffer size (bytes) for smb_client's request/response framing.
3385 *
3386 * Two buffers of this size live on the stack during a call, plus a few half-size scratch buffers for
3387 * the NTLM auth tokens, so the engine needs roughly 4x this in stack. 1024 covers the NEGOTIATE ->
3388 * SESSION_SETUP -> TREE_CONNECT -> CREATE handshake; raise it if a server's SPNEGO/target-info token
3389 * or your share path is unusually large.
3390 */
3391#ifndef DETWS_SMB_BUF
3392#define DETWS_SMB_BUF 1024
3393#endif
3394
3395/**
3396 * @brief Opt-in SAE J2735 V2X codec (DETWS_ENABLE_J2735).
3397 *
3398 * When set, services/j2735 provides the ASN.1 UPER (Unaligned Packed Encoding Rules) bit-level primitive
3399 * codec (constrained INTEGER / BOOLEAN / bit fields) and, on top of it, the J2735 BSMcore safety-message
3400 * block (msgCnt / id / secMark / lat / long / elev / speed / heading) encode + decode, for connected-
3401 * vehicle messaging. Pure codec (the DSRC / C-V2X radio is an external module). Default off.
3402 */
3403#ifndef DETWS_ENABLE_J2735
3404#define DETWS_ENABLE_J2735 0
3405#endif
3406
3407/**
3408 * @brief Opt-in NEMA TS 2 traffic-cabinet SDLC frame codec (DETWS_ENABLE_NEMA_TS2).
3409 *
3410 * When set, services/nema_ts2 builds/validates the TS 2 SDLC bus frames ([address][control][frame-type]
3411 * [data][CRC-16/X-25]) that link a traffic-signal controller to the MMU, BIUs, and detector racks. Pure
3412 * codec (the synchronous serial PHY + BIU timing are hardware-gated). Default off.
3413 */
3414#ifndef DETWS_ENABLE_NEMA_TS2
3415#define DETWS_ENABLE_NEMA_TS2 0
3416#endif
3417
3418/**
3419 * @brief Opt-in GE Fanuc SNP (Series Ninety Protocol) serial frame codec (DETWS_ENABLE_SNP).
3420 *
3421 * When set, services/snp builds/validates the SNP master-slave serial frame ([control][length][data]
3422 * [arithmetic-sum BCC]) for reading/writing registers on a GE Fanuc Series 90 (90-30/90-70) PLC over
3423 * RS-485. Pure codec (the UART transport + SNP-X session are the device step). Default off.
3424 */
3425#ifndef DETWS_ENABLE_SNP
3426#define DETWS_ENABLE_SNP 0
3427#endif
3428
3429/**
3430 * @brief Opt-in AutomationDirect / Koyo DirectNET serial frame codec (DETWS_ENABLE_DIRECTNET).
3431 *
3432 * When set, services/directnet builds/validates the DirectNET master-slave serial frames - the header
3433 * (SOH + slave/type/address/blocks ASCII-hex + ETB + LRC) and the data frame (STX + data + ETX + LRC) -
3434 * for V-memory read/write on an AutomationDirect DirectLOGIC PLC. Pure codec (the UART transport +
3435 * ACK/NAK handshake are the device step). Default off.
3436 */
3437#ifndef DETWS_ENABLE_DIRECTNET
3438#define DETWS_ENABLE_DIRECTNET 0
3439#endif
3440
3441/**
3442 * @brief Opt-in IEEE 2030.5 (Smart Energy Profile 2.0) resource codec (DETWS_ENABLE_SEP2).
3443 *
3444 * When set, services/sep2 builds the core 2030.5 XML resource documents (DeviceCapability, EndDevice,
3445 * DERControl) in the urn:ieee:std:2030.5:ns namespace, so the web server is a 2030.5 smart-grid
3446 * server/client over the existing HTTP stack (DER dispatch / curtailment). Pure text framing. Default off.
3447 */
3448#ifndef DETWS_ENABLE_SEP2
3449#define DETWS_ENABLE_SEP2 0
3450#endif
3451
3452/**
3453 * @brief Opt-in PROFINET DCP (Discovery and Configuration Protocol) frame codec (DETWS_ENABLE_PROFINET).
3454 *
3455 * When set, services/profinet builds/parses the DCP frames (10-octet header + option/suboption blocks)
3456 * PROFINET uses to discover and name IO-Devices over raw L2 (ethertype 0x8892) - Identify request/
3457 * response and Set (assign NameOfStation / IP). Pure codec (the raw-L2 transmit via services/rawl2 +
3458 * esp_eth is the device step). Default off.
3459 */
3460#ifndef DETWS_ENABLE_PROFINET
3461#define DETWS_ENABLE_PROFINET 0
3462#endif
3463
3464/**
3465 * @brief Opt-in NTCIP transportation-device object identifiers (DETWS_ENABLE_NTCIP).
3466 *
3467 * When set, services/ntcip provides the NTCIP (National Transportation Communications for ITS Protocol)
3468 * object OID definitions for the common device classes - NTCIP 1202 (actuated signal controller: phases,
3469 * timing, live states) and 1203 (dynamic message sign) - plus an OID builder, so an app exposes them via
3470 * the shipped SNMP agent (services/snmp). Pure OID data. Default off.
3471 */
3472#ifndef DETWS_ENABLE_NTCIP
3473#define DETWS_ENABLE_NTCIP 0
3474#endif
3475
3476/**
3477 * @brief Opt-in OpenADR 3.0 (Automated Demand Response) JSON codec (DETWS_ENABLE_OPENADR).
3478 *
3479 * When set, services/openadr builds the OpenADR 3.0 event (a demand-response signal: programID +
3480 * eventName + interval payload points) and report (a VEN reading back to the VTN) JSON objects into a
3481 * caller buffer, over the existing HTTP client/server + OAuth2. Pure JSON framing. Default off.
3482 */
3483#ifndef DETWS_ENABLE_OPENADR
3484#define DETWS_ENABLE_OPENADR 0
3485#endif
3486
3487/**
3488 * @brief Opt-in IEC 61850 MMS PDU codec (DETWS_ENABLE_MMS).
3489 *
3490 * When set, services/mms builds/parses the MMS (ISO 9506) confirmed-request/response Read PDUs
3491 * (BER-encoded, the ACSI client/server core of IEC 61850) - detws_mms_read_request builds a Read of a
3492 * named Data Object, detws_mms_read_response the data reply. Carried over ISO-on-TCP (TPKT + COTP via
3493 * the shipped services/cotp) on port 102. Pure BER codec. Default off.
3494 */
3495#ifndef DETWS_ENABLE_MMS
3496#define DETWS_ENABLE_MMS 0
3497#endif
3498
3499/**
3500 * @brief Opt-in CC-Link (CLPA) cyclic fieldbus frame codec (DETWS_ENABLE_CCLINK).
3501 *
3502 * When set, services/cclink builds/validates the CC-Link cyclic frame ([station][command][RX/RY bit
3503 * data][RWr/RWw word data][sum checksum]) a Mitsubishi CC-Link master exchanges with remote stations
3504 * over RS-485, plus bit/word process-image accessors. Pure codec (the RS-485 timing + CC-Link IE Field
3505 * PHY are hardware-gated). Default off.
3506 */
3507#ifndef DETWS_ENABLE_CCLINK
3508#define DETWS_ENABLE_CCLINK 0
3509#endif
3510
3511/**
3512 * @brief Opt-in Ethernet POWERLINK (EPSG) basic frame codec (DETWS_ENABLE_POWERLINK).
3513 *
3514 * When set, services/powerlink builds/parses the EPL basic frames ([messageType][dest][source][payload])
3515 * of the isochronous managed-node cycle - SoC (start of cycle), PReq (poll request), PRes (poll
3516 * response with process data), SoA (start of async) - over raw L2 (ethertype 0x88AB, on the shipped
3517 * services/rawl2). Pure codec (the raw-L2 transmit + isochronous timing are the device step). Default off.
3518 */
3519#ifndef DETWS_ENABLE_POWERLINK
3520#define DETWS_ENABLE_POWERLINK 0
3521#endif
3522
3523/**
3524 * @brief Opt-in SERCOS III motion-bus telegram codec (DETWS_ENABLE_SERCOS).
3525 *
3526 * When set, services/sercos builds/parses the SERCOS III MDT/AT telegrams (type + phase + cycle + cyclic
3527 * device data) the real-time drive/motion bus exchanges over raw L2 (ethertype 0x88CD, on the shipped
3528 * services/rawl2), plus the IDN (IDentification Number) encode/decode for drive-parameter addressing.
3529 * Pure codec (the isochronous timing + ring topology are hardware-gated). Default off.
3530 */
3531#ifndef DETWS_ENABLE_SERCOS
3532#define DETWS_ENABLE_SERCOS 0
3533#endif
3534
3535/**
3536 * @brief Opt-in PROFIBUS-DP FDL telegram codec (DETWS_ENABLE_PROFIBUS).
3537 *
3538 * When set, services/profibus builds/validates the PROFIBUS-DP FDL telegrams - SD1 (no-data: SD1 DA SA
3539 * FC FCS ED) and SD2 (variable-data: SD2 LE LEr SD2 DA SA FC data FCS ED, arithmetic-sum FCS) - a
3540 * Siemens DP master exchanges with slaves over RS-485 (the DP-V0 cyclic I/O exchange). Pure codec (the
3541 * RS-485 timing + DP state machine are the device step). Default off.
3542 */
3543#ifndef DETWS_ENABLE_PROFIBUS
3544#define DETWS_ENABLE_PROFIBUS 0
3545#endif
3546
3547/**
3548 * @brief Opt-in LonWorks / LON-IP (ISO/IEC 14908) network-variable codec (DETWS_ENABLE_LONWORKS).
3549 *
3550 * When set, services/lonworks builds/parses the LonTalk network-variable PDU ([msg-code][14-bit
3551 * selector][value]) that a building-automation device exchanges - over LON/IP (14908-4) UDP, so no
3552 * Neuron chip is needed - plus the common SNVT scalar encodings (SNVT_temp, SNVT_switch). Pure codec
3553 * (the UDP transport is the shipped UDP layer). Default off.
3554 */
3555#ifndef DETWS_ENABLE_LONWORKS
3556#define DETWS_ENABLE_LONWORKS 0
3557#endif
3558
3559/**
3560 * @brief Opt-in Modbus Plus HDLC token-bus frame codec (DETWS_ENABLE_MBPLUS).
3561 *
3562 * When set, services/mbplus builds/validates the Modbus Plus HDLC frame (7E addr ctrl payload CRC-16/X-25
3563 * 7E) that Schneider's token-passing peer bus exchanges, plus the token-rotation helper (next station in
3564 * the logical ring). Reuses the shipped Modbus PDU model for the data. Pure codec (the 1 Mbit/s bus is
3565 * hardware-gated). Default off.
3566 */
3567#ifndef DETWS_ENABLE_MBPLUS
3568#define DETWS_ENABLE_MBPLUS 0
3569#endif
3570
3571/**
3572 * @brief Opt-in INTERBUS summation-frame fieldbus codec (DETWS_ENABLE_INTERBUS).
3573 *
3574 * When set, services/interbus assembles/disassembles the INTERBUS summation frame (loopback word +
3575 * per-device 16-bit process-image slices + CRC-16/CCITT FCS) of the Phoenix Contact ring fieldbus,
3576 * where every device is a shift-register slice of one circulating frame. Pure codec (the physical ring
3577 * clocking is hardware-gated). Default off.
3578 */
3579#ifndef DETWS_ENABLE_INTERBUS
3580#define DETWS_ENABLE_INTERBUS 0
3581#endif
3582
3583/**
3584 * @brief Opt-in ICCP / TASE.2 (IEC 60870-6) inter-control-center telemetry codec (DETWS_ENABLE_ICCP).
3585 *
3586 * When set, services/iccp builds the TASE.2 Data_Value BER structures - StateQ (a discrete state +
3587 * quality) and RealQ (a scaled real + quality), each with an optional timestamp - the indication points
3588 * a control center transfers as MMS Reads (on the shipped services/mms + services/cotp). Pure BER codec.
3589 * Default off.
3590 */
3591#ifndef DETWS_ENABLE_ICCP
3592#define DETWS_ENABLE_ICCP 0
3593#endif
3594
3595/**
3596 * @brief Opt-in IEEE 1609 WAVE (WSMP + 1609.2 envelope) codec (DETWS_ENABLE_WAVE).
3597 *
3598 * When set, services/wave builds/parses the IEEE 1609 vehicular-radio framing that carries J2735: the
3599 * 1609.3 WSMP header (version + P-encoded PSID + length + payload) and the 1609.2 secured-message
3600 * envelope header (version + content type). Pairs with services/j2735. Pure codec (the DSRC / C-V2X
3601 * radio is an external module). Default off.
3602 */
3603#ifndef DETWS_ENABLE_WAVE
3604#define DETWS_ENABLE_WAVE 0
3605#endif
3606
3607/**
3608 * @brief Opt-in UTMC (Urban Traffic Management and Control) common-database codec (DETWS_ENABLE_UTMC).
3609 *
3610 * When set, services/utmc builds/parses the UTMC common-database HTTP+XML messages - a UTMCRequest for
3611 * an object id and a UTMCResponse carrying the object value + a data-quality flag + a timestamp - the UK
3612 * modular framework for sharing traffic data across municipal systems, over the existing HTTP server.
3613 * Pure text framing. Default off.
3614 */
3615#ifndef DETWS_ENABLE_UTMC
3616#define DETWS_ENABLE_UTMC 0
3617#endif
3618
3619/**
3620 * @brief Opt-in OCIT-Outstations message codec (DETWS_ENABLE_OCIT).
3621 *
3622 * When set, services/ocit builds/parses the OCIT (DE/AT/CH road-traffic-control) object messages
3623 * ([msg-type][object-type][instance][data-type][value]) between central traffic computers and field
3624 * controllers / detectors, with typed values (bool / byte / u16 / u32 / octets). Pure codec (the OCIT
3625 * transport is the shipped transport). Default off.
3626 */
3627#ifndef DETWS_ENABLE_OCIT
3628#define DETWS_ENABLE_OCIT 0
3629#endif
3630
3631/**
3632 * @brief Opt-in ATC (Advanced Traffic Controller) field-I/O interop snapshot (DETWS_ENABLE_ATC).
3633 *
3634 * When set, services/atc exposes this device's field-I/O (a fixed table of named input/output points it
3635 * already gathers via the NTCIP / NEMA-TS2 / gpio services) to an ATC Linux engine over the existing
3636 * HTTP surface: detws_atc_snapshot_json serializes the FIO map as JSON, and detws_atc_set_output drives
3637 * an output point from an ATC command. Pure interop codec (ATC is a platform spec, not a wire protocol).
3638 * Default off.
3639 */
3640#ifndef DETWS_ENABLE_ATC
3641#define DETWS_ENABLE_ATC 0
3642#endif
3643
3644/**
3645 * @brief Opt-in southbound protocol-driver framework (DETWS_ENABLE_SOUTHBOUND).
3646 *
3647 * The uniform seam every field-device driver plugs into so the app polls/drives any southbound device
3648 * (a Modbus slave, a BACnet controller, a raw sensor over SPI/I2C/UART) through one facade: register a
3649 * SouthboundDriver (a read/write/read_block/write_block vtable + its transport ctx), then address points
3650 * by driver name via detws_southbound_read / _write / _read_block / _write_block. The block calls are the
3651 * atomic multi-point (register-matrix) path. Bounded registry (DETWS_SOUTHBOUND_MAX_DRIVERS, default 8),
3652 * no heap; Modbus master is the one such driver today. Default off.
3653 */
3654#ifndef DETWS_ENABLE_SOUTHBOUND
3655#define DETWS_ENABLE_SOUTHBOUND 0
3656#endif
3657
3658/**
3659 * @brief Opt-in ESP32 panic / exception decoder for a live diagnostics panel (DETWS_ENABLE_EXC_DECODER).
3660 *
3661 * When set, services/exc_decoder parses a captured Guru Meditation panic dump (the cause, the register
3662 * PC + EXCVADDR, and the backtrace PC:SP frames) into a structured ExcInfo and serializes it as JSON for
3663 * a "/exception" panel; the browser or a build server resolves the PCs to file:line against the firmware
3664 * ELF (addr2line lives off-device). Pure, no heap/stdlib. Default off.
3665 */
3666#ifndef DETWS_ENABLE_EXC_DECODER
3667#define DETWS_ENABLE_EXC_DECODER 0
3668#endif
3669
3670/**
3671 * @brief Opt-in HTTP delivery optimizations (DETWS_ENABLE_HTTP_DELIVERY).
3672 *
3673 * Three pure cores for cheaper HTTP serving, each a real web standard: RFC 5861 stale-while-revalidate
3674 * (detws_delivery_swr decision + detws_delivery_cache_control header), RFC 7233 byte-range delta/offset
3675 * fetch (detws_delivery_range parse of X-Y / X- / -N + detws_delivery_content_range for a 206), and a
3676 * versioned service-worker precache manifest (detws_delivery_sw_manifest). No heap/stdlib. Default off.
3677 */
3678#ifndef DETWS_ENABLE_HTTP_DELIVERY
3679#define DETWS_ENABLE_HTTP_DELIVERY 0
3680#endif
3681
3682/**
3683 * @brief Opt-in hardware-health diagnostics (DETWS_ENABLE_HW_HEALTH).
3684 *
3685 * Four pure decision cores fed with samples the app reads from the hardware: a power-rail voltage-drop
3686 * logger (detws_hwhealth_rail_sample tracks worst droop + sag/brownout counts), a SPI-bus CRC audit with
3687 * hysteretic clock backoff (detws_hwhealth_spi_result halves/doubles the clock on fail/ok streaks), a
3688 * GPIO short-circuit test (detws_hwhealth_gpio_short: driven vs readback), and a capacitor-leakage diag
3689 * (detws_hwhealth_cap_leak: measured vs expected RC decay). No heap/stdlib. Default off.
3690 */
3691#ifndef DETWS_ENABLE_HW_HEALTH
3692#define DETWS_ENABLE_HW_HEALTH 0
3693#endif
3694
3695/**
3696 * @brief Opt-in adaptive mDNS beacon scheduling (DETWS_ENABLE_MDNS_ADAPTIVE).
3697 *
3698 * Pure scheduling decisions on top of the shipped mDNS service: detws_mdns_beacon_adapt backs the
3699 * announce interval off toward a ceiling under RF contention and recovers it when the air is quiet,
3700 * detws_mdns_refresh_interval gives the TTL/2 continuous-refresher cadence, detws_mdns_beacon_due says
3701 * when an announce is due, and detws_mdns_beacon_presleep_due says whether to announce before a sleep
3702 * window that would otherwise let the record lapse. Wrap-safe time math, no heap/stdlib. Default off.
3703 */
3704#ifndef DETWS_ENABLE_MDNS_ADAPTIVE
3705#define DETWS_ENABLE_MDNS_ADAPTIVE 0
3706#endif
3707
3708/**
3709 * @brief Opt-in dynamic socket recycling: an LRU connection-slot pool (DETWS_ENABLE_SOCKPOOL).
3710 *
3711 * The transport-pool half of the adaptive-networking work: services/sockpool keeps a fixed table of
3712 * connection slots and, when saturated, recycles the least-recently-used slot for a new peer
3713 * (detws_sockpool_acquire returns the evicted id so the transport closes it), plus touch / release /
3714 * find. The app owns the real sockets; this owns which slot a connection lives in and which to reclaim
3715 * under pressure. No heap/stdlib. Default off.
3716 */
3717#ifndef DETWS_ENABLE_SOCKPOOL
3718#define DETWS_ENABLE_SOCKPOOL 0
3719#endif
3720
3721/**
3722 * @brief Opt-in buffer placement policy (DRAM vs PSRAM) + SPI DMA ping-pong manager (DETWS_ENABLE_PSRAM_POOL).
3723 *
3724 * Pure buffer-management decisions for a PSRAM-equipped ESP32: detws_psram_place picks DRAM vs PSRAM for
3725 * a buffer by size, DMA requirement, and free-heap headroom (large/cold to PSRAM, small/hot + DMA to
3726 * DRAM, always leaving an internal-DRAM reserve), and detws_pingpong_* keeps the classic SPI DMA
3727 * double-buffer bookkeeping (CPU fills one buffer while DMA drains the other; swap flips their roles).
3728 * The actual heap_caps_calloc is the app's. No heap/stdlib. Default off.
3729 */
3730#ifndef DETWS_ENABLE_PSRAM_POOL
3731#define DETWS_ENABLE_PSRAM_POOL 0
3732#endif
3733
3734/**
3735 * @brief Opt-in dual-stack Happy Eyeballs destination selection (DETWS_ENABLE_HAPPY_EYEBALLS).
3736 *
3737 * The client-side IPv6/IPv4 fallback decision on top of the shipped DetIp: detws_he_pref scores a
3738 * destination (RFC 6724 scope + family), detws_he_order sorts a candidate list and interleaves the
3739 * address families (RFC 8305) so successive connection attempts alternate v6/v4, and
3740 * detws_he_attempt_due gates the next attempt by the Connection Attempt Delay. Fast IPv6 when it works,
3741 * quick fallback to IPv4 when it does not. Needs DETWS_ENABLE_IPV6 to matter. No heap/stdlib. Default off.
3742 */
3743#ifndef DETWS_ENABLE_HAPPY_EYEBALLS
3744#define DETWS_ENABLE_HAPPY_EYEBALLS 0
3745#endif
3746
3747/**
3748 * @brief Opt-in 802.11 sniffer / traffic analyzer (DETWS_ENABLE_WIFI_SNIFFER).
3749 *
3750 * The decode + decision layer for a promiscuous-mode WiFi sniffer: detws_wifi_parse decodes an 802.11
3751 * MAC header (frame-control type/subtype + flags and the addresses whose roles depend on ToDS/FromDS),
3752 * detws_wifi_stats_* tallies frames by type for a traffic panel, and detws_wifi_should_roam decides when
3753 * a candidate AP is enough stronger (RSSI hysteresis) to justify channel-agility roaming. The
3754 * promiscuous-mode radio callback stays the app's. No heap/stdlib. Default off.
3755 */
3756#ifndef DETWS_ENABLE_WIFI_SNIFFER
3757#define DETWS_ENABLE_WIFI_SNIFFER 0
3758#endif
3759
3760/**
3761 * @brief Opt-in multi-interface egress selection / failover policy (DETWS_ENABLE_LINK_MANAGER).
3762 *
3763 * The policy that drives which interface carries traffic once a device has more than one (a wired
3764 * Ethernet PHY alongside WiFi STA / softAP): services/link_manager keeps a small table of interfaces
3765 * (kind + priority + up/down) and deterministically selects the best link that is up, escalating to a
3766 * higher-priority interface when it comes up and failing over when it drops, reporting only real
3767 * transitions so the app reconfigures the netif once. The PHY bring-up (esp_eth) stays the app's. No
3768 * heap/stdlib. Default off.
3769 */
3770#ifndef DETWS_ENABLE_LINK_MANAGER
3771#define DETWS_ENABLE_LINK_MANAGER 0
3772#endif
3773
3774/**
3775 * @brief Opt-in CC1101 sub-GHz radio driver (DETWS_ENABLE_CC1101).
3776 *
3777 * A gateway radio plugin (DETWS_ENABLE_GATEWAY) for the TI CC1101 300-928 MHz transceiver over SPI:
3778 * services/cc1101 drives the chip's SPI header protocol (config registers, command strobes, status
3779 * registers, TX/RX FIFO) - reset + apply a SmartRF register table + set channel + verify VERSION
3780 * (cc1101_init), send a variable-length packet (cc1101_send), poll TX-done, enter RX, and read a packet
3781 * with appended RSSI/LQI (cc1101_recv), plus the RSSI-to-dBm decode. The huge modem config is a
3782 * caller-supplied register table. Host-tested against a mock; the RF link needs the module. Default off.
3783 */
3784#ifndef DETWS_ENABLE_CC1101
3785#define DETWS_ENABLE_CC1101 0
3786#endif
3787
3788/**
3789 * @brief Opt-in FDC2114/2214 capacitance-to-digital field sensor (DETWS_ENABLE_FDC2214).
3790 *
3791 * A field-perturbation sensing peripheral: services/fdc2214 decodes the FDC2x14's 28-bit conversion
3792 * result (a capacitance shift moves the LC-tank frequency, giving contactless proximity / liquid-level /
3793 * material sensing) - fdc2214_data combines the register pair, fdc2214_error pulls the flags,
3794 * fdc2214_sensor_freq_hz scales to frequency, and fdc2214_build_config emits a single-channel
3795 * bring-up; the ESP32 binding replays it and reads the channel over I2C. Pure codec host-tested. Default off.
3796 */
3797#ifndef DETWS_ENABLE_FDC2214
3798#define DETWS_ENABLE_FDC2214 0
3799#endif
3800
3801/**
3802 * @brief Opt-in LDC1614 inductance-to-digital field sensor (DETWS_ENABLE_LDC1614).
3803 *
3804 * A field-perturbation sensing peripheral: services/ldc1614 decodes the LDC1614's 28-bit conversion
3805 * result (a nearby conductor changes the coil inductance via eddy currents, giving contactless metal
3806 * proximity / displacement / EM-field sensing) - ldc1614_data combines the register pair, ldc1614_error
3807 * pulls the flags, ldc1614_sensor_freq_hz scales to frequency, and ldc1614_build_config emits a
3808 * single-channel bring-up; the ESP32 binding replays it and reads the channel over I2C. Pure codec
3809 * host-tested. Default off.
3810 */
3811#ifndef DETWS_ENABLE_LDC1614
3812#define DETWS_ENABLE_LDC1614 0
3813#endif
3814
3815/**
3816 * @brief Opt-in VL53L0X optical time-of-flight ranging sensor (DETWS_ENABLE_VL53L0X).
3817 *
3818 * A field-perturbation sensing peripheral for contactless distance / gesture: services/vl53l0x decodes
3819 * the ST VL53L0X ranging registers - vl53l0x_range_mm combines the range byte pair, vl53l0x_data_ready
3820 * decodes the interrupt-status byte, and vl53l0x_range_valid checks the device range-status field; the
3821 * ESP32 binding verifies the model id, starts continuous ranging, and reads the distance over I2C.
3822 * Default-settings ranging (ST's tuning blob is not applied). Pure codec host-tested. Default off.
3823 */
3824#ifndef DETWS_ENABLE_VL53L0X
3825#define DETWS_ENABLE_VL53L0X 0
3826#endif
3827
3828/**
3829 * @brief Opt-in receive-only radio channel sniffer to pcap (DETWS_ENABLE_RADIO_SNIFF).
3830 *
3831 * Feeds frames pulled off the air by the RF gateway drivers (CC1101 / LoRa / 802.15.4) in receive-only
3832 * mode into the capture pipeline: services/radio_sniff wraps each 802.15.4 MAC frame in the Wireshark
3833 * TAP pseudo-header (carrying per-frame RSSI + channel) and a pcap record so the forwarded stream is a
3834 * valid .pcap. detws_radiosniff_global writes the DLT-TAP global header and detws_radiosniff_tap_record
3835 * writes one record. Pure framing (no heap/stdlib); the radio drivers own the receive. Default off.
3836 */
3837#ifndef DETWS_ENABLE_RADIO_SNIFF
3838#define DETWS_ENABLE_RADIO_SNIFF 0
3839#endif
3840
3841/**
3842 * @brief Opt-in Bluetooth ATT protocol codec + GATT characteristic bridge (DETWS_ENABLE_BLE_GATT).
3843 *
3844 * The wire protocol under GATT for bridging the on-chip BLE radio to the web: services/ble_gatt builds
3845 * and parses the common ATT PDUs (read / write / notify / error, Bluetooth Core Vol 3 Part F) and
3846 * serializes a GATT characteristic table as JSON for the web stack (att_read_req / att_write_req /
3847 * att_notify / att_error_rsp / att_parse / gatt_char_json). The BLE stack owns the radio; this owns the
3848 * ATT bytes + the northbound JSON. Pure, no heap/stdlib. Default off.
3849 */
3850#ifndef DETWS_ENABLE_BLE_GATT
3851#define DETWS_ENABLE_BLE_GATT 0
3852#endif
3853
3854/**
3855 * @brief Opt-in TLS version negotiation + pinned cipher-suite policy (DETWS_ENABLE_TLS_POLICY).
3856 *
3857 * A policy layer on top of the mbedTLS-backed transport TLS (which already runs the 1.2 / 1.3 record +
3858 * handshake): services/tls_policy pins the version to an audited [min,max] and makes the negotiated
3859 * version observable (detws_tls_negotiate_version / detws_tls_version_name), and pins the cipher suites
3860 * to an audited allowlist selected by server preference (detws_tls_select_cipher), with an AEAD-only
3861 * classifier (detws_tls_is_aead) for a hardened profile. Pure, host-tested; the app feeds the results to
3862 * the mbedTLS config. Default off.
3863 */
3864#ifndef DETWS_ENABLE_TLS_POLICY
3865#define DETWS_ENABLE_TLS_POLICY 0
3866#endif
3867
3868/**
3869 * @brief Opt-in Wi-SUN FAN border-router connector (DETWS_ENABLE_WISUN).
3870 *
3871 * Wi-SUN FAN is an IPv6/UDP/CoAP mesh terminated by a border router, so the connector rides the existing
3872 * IP stack rather than driving a radio: services/wisun keeps a table of FAN nodes (their DetIp addresses +
3873 * join state) behind the border router and builds the CoAP client requests to their resources
3874 * (wisun_build_coap frames an RFC 7252 header + Uri-Path options + payload; the CoAP service ships only a
3875 * server). wisun_nodes_json exposes the mesh to the web. The app sends the built PDU over det_udp; the
3876 * chosen devboard only sets which border router you point at. Pure, no heap/stdlib. Default off.
3877 */
3878#ifndef DETWS_ENABLE_WISUN
3879#define DETWS_ENABLE_WISUN 0
3880#endif
3881
3882/**
3883 * @brief Opt-in fixed-RAM rotating log buffer with severity traps (DETWS_ENABLE_LOGBUF).
3884 *
3885 * Default off. When set, services/logbuf keeps the last DETWS_LOG_LINES log lines
3886 * in a fixed ring (oldest pruned on overflow - no heap, bounded), dumps them
3887 * oldest-first for a `/logs` endpoint, and fires a trap callback when a line is
3888 * logged at/above a severity threshold (forward criticals as an SNMP trap /
3889 * webhook). The ring + trap logic is pure and host-tested.
3890 */
3891#ifndef DETWS_ENABLE_LOGBUF
3892#define DETWS_ENABLE_LOGBUF 0
3893#endif
3894
3895/** @brief Number of log lines retained in the ring. */
3896#ifndef DETWS_LOG_LINES
3897#define DETWS_LOG_LINES 32
3898#endif
3899
3900/** @brief Maximum length of one stored log line (bytes, including null). */
3901#ifndef DETWS_LOG_LINE_LEN
3902#define DETWS_LOG_LINE_LEN 96
3903#endif
3904
3905/**
3906 * @brief Opt-in schema-driven config export / restore (DETWS_ENABLE_CONFIG_IO).
3907 *
3908 * Default off. Requires DETWS_ENABLE_CONFIG_STORE. The app declares a fixed schema
3909 * (key + type); services/config_io serializes the current values to a portable
3910 * `key=value` text blob (backup / migrate) and parses one back into the store
3911 * (restore / bulk template). Schema-driven rather than enumerating NVS, so it
3912 * stays deterministic and zero-heap; the serialize / parse is host-tested.
3913 */
3914#ifndef DETWS_ENABLE_CONFIG_IO
3915#define DETWS_ENABLE_CONFIG_IO 0
3916#endif
3917
3918/** @brief Authenticated OTA firmware update (streaming POST to the ESP32 Update API). */
3919#ifndef DETWS_ENABLE_OTA
3920#define DETWS_ENABLE_OTA 0
3921#endif
3922
3923/**
3924 * @brief Opt-in OTA rollback protection / soft-brick safeguard (DETWS_ENABLE_OTA_ROLLBACK).
3925 *
3926 * Default off. After an OTA update the new image boots in PENDING_VERIFY; this
3927 * service confirms it (esp_ota_mark_app_valid) once a self-test passes, or rolls
3928 * back to the previous image if the self-test fails or the confirm window elapses
3929 * without success - so a bad update self-heals instead of soft-bricking. The
3930 * decision logic is pure and host-tested; the commit / rollback use esp_ota_ops.
3931 * Requires the bootloader's app-rollback support (CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE).
3932 */
3933#ifndef DETWS_ENABLE_OTA_ROLLBACK
3934#define DETWS_ENABLE_OTA_ROLLBACK 0
3935#endif
3936
3937/** @brief Confirm window (ms): a pending image not confirmed within this rolls back. */
3938#ifndef DETWS_OTA_CONFIRM_WINDOW_MS
3939#define DETWS_OTA_CONFIRM_WINDOW_MS 30000
3940#endif
3941
3942/**
3943 * @brief Opt-in TOTP two-factor auth (RFC 6238) (DETWS_ENABLE_TOTP).
3944 *
3945 * Default off. services/totp computes and verifies time-based one-time passwords
3946 * (HMAC-SHA1 over the existing SHA-1, Google Authenticator compatible) and decodes
3947 * base32 shared secrets, for a second factor on top of password / JWT auth. Pure
3948 * and host-tested against the RFC 6238 vectors; the verifier checks a +/- step
3949 * window for clock skew.
3950 */
3951#ifndef DETWS_ENABLE_TOTP
3952#define DETWS_ENABLE_TOTP 0
3953#endif
3954
3955/**
3956 * @brief Opt-in outbound webhooks / IFTTT (DETWS_ENABLE_WEBHOOK).
3957 *
3958 * Default off. Needs DETWS_ENABLE_HTTP_CLIENT to actually send: the API always
3959 * compiles, but without the HTTP client detws_webhook_post() returns -1.
3960 * services/webhook builds an IFTTT Maker URL and a value1/value2/value3 JSON
3961 * payload (pure, host-tested) and fires them - or any JSON to any URL - via the
3962 * outbound http_client (POST). Use it to
3963 * push an event from the device to IFTTT, a Slack/Discord hook, or your own API.
3964 */
3965#ifndef DETWS_ENABLE_WEBHOOK
3966#define DETWS_ENABLE_WEBHOOK 0
3967#endif
3968
3969/**
3970 * @brief Opt-in radio power controls (DETWS_ENABLE_RADIO_POWER).
3971 *
3972 * Default off. services/radio_power applies the WiFi modem-sleep mode and an
3973 * optional max-TX-power cap in one call (esp_wifi_set_ps / esp_wifi_set_max_tx_power)
3974 * - trade throughput/latency for lower average power on a battery device. The mode
3975 * names are host-tested; the apply is ESP32-only.
3976 */
3977#ifndef DETWS_ENABLE_RADIO_POWER
3978#define DETWS_ENABLE_RADIO_POWER 0
3979#endif
3980
3981/** @brief WiFi modem-sleep mode: 0 = none (max perf), 1 = min modem, 2 = max modem. */
3982#ifndef DETWS_RADIO_WIFI_PS
3983#define DETWS_RADIO_WIFI_PS 0
3984#endif
3985
3986/** @brief Max TX power cap in dBm (2..20); 0 = leave the platform default. */
3987#ifndef DETWS_RADIO_MAX_TX_DBM
3988#define DETWS_RADIO_MAX_TX_DBM 0
3989#endif
3990
3991/**
3992 * @brief Opt-in DNS resolver with answer verification (DETWS_ENABLE_DNS_RESOLVER).
3993 *
3994 * Default off. services/dns_resolver resolves a hostname to an IPv4 address (lwIP
3995 * dns_gethostbyname, marshalled to tcpip_thread like the http_client) and can
3996 * reject suspicious answers - 0.0.0.0, broadcast, loopback, multicast - which are
3997 * spoofing / DNS-rebinding indicators for a remote host. The address classifier /
3998 * verifier is pure and host-tested; the resolve is ESP32-only (blocking, so call
3999 * it off the request hot path).
4000 */
4001#ifndef DETWS_ENABLE_DNS_RESOLVER
4002#define DETWS_ENABLE_DNS_RESOLVER 0
4003#endif
4004
4005/** @brief DNS resolve timeout in milliseconds. */
4006#ifndef DETWS_DNS_TIMEOUT_MS
4007#define DETWS_DNS_TIMEOUT_MS 5000
4008#endif
4009
4010/**
4011 * @brief Tamper-evident audit log (DETWS_ENABLE_AUDIT_LOG).
4012 *
4013 * Default off. services/audit_log keeps an append-only, hash-chained security
4014 * log: each record carries SHA-256(prev_hash || fields), so altering, deleting,
4015 * or reordering any retained record breaks the chain (detws_audit_verify()
4016 * detects it). Storage is a fixed RAM ring of DETWS_AUDIT_LOG_ENTRIES records
4017 * (no heap); when it wraps, a moving anchor keeps the retained window verifiable.
4018 * Install a sink (detws_audit_set_sink) to forward every record at creation time
4019 * to a durable / remote store - SD-card file, syslog or HTTP log service, serial
4020 * console - preserving the same chain off-device. Pure and host-tested.
4021 */
4022#ifndef DETWS_ENABLE_AUDIT_LOG
4023#define DETWS_ENABLE_AUDIT_LOG 0
4024#endif
4025
4026// Ring depth and per-record message length are tunable in audit_log.h
4027// (DETWS_AUDIT_LOG_ENTRIES, DETWS_AUDIT_MSG_LEN); define them before include to
4028// override. The RAM cost is roughly DETWS_AUDIT_LOG_ENTRIES * (DETWS_AUDIT_MSG_LEN
4029// + 41) bytes.
4030
4031/**
4032 * @brief OpenID Connect ID-token verification, RS256 (DETWS_ENABLE_OIDC).
4033 *
4034 * Default off. services/oidc verifies an OIDC ID token (JWT) as a relying party:
4035 * requires alg RS256, selects the issuer key by kid from a JWKS, verifies the
4036 * RSASSA-PKCS1-v1.5 SHA-256 signature (real RSA modexp via ssh_rsa, mbedTLS-
4037 * accelerated on ESP32), and checks iss / aud / exp / nbf, extracting sub / email.
4038 * Pure and host-tested; the caller fetches + caches the JWKS over HTTPS (off the
4039 * request hot path) and passes the JSON in. Builds on the SSH RSA primitive, not
4040 * the HS256 JWT module (services/jwt), so the two are independent.
4041 */
4042#ifndef DETWS_ENABLE_OIDC
4043#define DETWS_ENABLE_OIDC 0
4044#endif
4045
4046/** @brief Max accepted OIDC ID-token length (also sizes the Authorization buffer). */
4047#ifndef DETWS_OIDC_MAX_LEN
4048#define DETWS_OIDC_MAX_LEN 1600
4049#endif
4050
4051/**
4052 * @brief Unified virtual filesystem wrapper (DETWS_ENABLE_VFS).
4053 *
4054 * Default off. services/vfs exposes one small file API (open/read/write/close,
4055 * exists/size/remove/rename, whole-file helpers) over a pluggable backend, so a
4056 * feature can target storage without knowing the medium. A built-in zero-heap RAM
4057 * backend (fixed BSS pool - deterministic, host-identical) ships for scratch /
4058 * tests; an Arduino-FS backend (ESP32) wraps a real fs::FS (LittleFS / SD /
4059 * SPIFFS) for persistence. Mount one at startup; the API fails closed otherwise.
4060 * Pool dimensions are tunable in this config (DETWS_VFS_RAM_FILES, _RAM_FILE_SIZE,
4061 * _MAX_OPEN, _NAME_MAX).
4062 */
4063#ifndef DETWS_ENABLE_VFS
4064#define DETWS_ENABLE_VFS 0
4065#endif
4066
4067/**
4068 * @brief GraphQL query subset (DETWS_ENABLE_GRAPHQL).
4069 *
4070 * Default off. services/graphql parses a GraphQL query into a fixed AST node pool
4071 * (no heap) and emits a `{"data":{...}}` response shaped exactly by the requested
4072 * selection. Schema-free: a field with a sub-selection is an object (the engine
4073 * recurses), a leaf field calls your single resolver, and arguments collected
4074 * along the path are handed to it. Supports nested selections, field arguments,
4075 * and the anonymous / `query` forms; mutations, subscriptions, fragments, and
4076 * variables are out of scope. Pure and host-tested; bounds are compile-time
4077 * (DETWS_GQL_* in this config). Serve it from a POST /graphql route.
4078 */
4079#ifndef DETWS_ENABLE_GRAPHQL
4080#define DETWS_ENABLE_GRAPHQL 0
4081#endif
4082
4083/**
4084 * @brief ESP-NOW peer messaging (DETWS_ENABLE_ESPNOW).
4085 *
4086 * Default off. services/espnow wraps ESP-NOW connectionless peer-to-peer radio
4087 * messaging in a 3-byte typed envelope (magic + type + length) so a receiver can
4088 * demux by message type and reject a truncated frame, plus a bounded peer
4089 * registry (DETWS_ESPNOW_MAX_PEERS, no heap). The envelope codec + registry are
4090 * pure and host-tested; the radio path (begin / add_peer / send / broadcast over
4091 * esp_now, decoded frames to a callback) is ESP32-only and can bridge to
4092 * WebSocket/SSE. No stdlib.
4093 */
4094#ifndef DETWS_ENABLE_ESPNOW
4095#define DETWS_ENABLE_ESPNOW 0
4096#endif
4097
4098/**
4099 * @brief OAuth2 token-endpoint client (DETWS_ENABLE_OAUTH2).
4100 *
4101 * Default off. services/oauth2 obtains tokens - the counterpart to the OIDC
4102 * ID-token verifier. It builds the percent-encoded form body for the
4103 * authorization_code and refresh_token grants (RFC 6749), supporting a
4104 * confidential client (client_secret) or a public client with PKCE
4105 * (code_verifier, RFC 7636), and parses the JSON token response (reusing the
4106 * zero-heap JSON reader). The build + parse core is pure and host-tested; the POST
4107 * to the token endpoint uses the HTTP(S) client (needs DETWS_ENABLE_HTTP_CLIENT).
4108 * No heap, no stdlib.
4109 */
4110#ifndef DETWS_ENABLE_OAUTH2
4111#define DETWS_ENABLE_OAUTH2 0
4112#endif
4113
4114/**
4115 * @brief OPC UA Binary server (DETWS_ENABLE_OPCUA).
4116 *
4117 * Default off. services/opcua provides an OPC UA (IEC 62541) Binary server: the
4118 * little-endian built-in-type codec (incl. NodeId / ExtensionObject / DateTime /
4119 * Variant / DataValue / ReferenceDescription), UA-TCP (UACP) message framing, the
4120 * Hello/Acknowledge handshake, the SecureChannel (OpenSecureChannel, SecurityPolicy
4121 * None), the Session (CreateSession + ActivateSession), GetEndpoints, the Read, Write
4122 * and Browse services (registered resolvers map a NodeId to a value / accept a written
4123 * value / list child references), plus CloseSession + CloseSecureChannel and a
4124 * ServiceFault for unsupported services, served on TCP via ConnProto::PROTO_OPCUA
4125 * (`listen(4840, ConnProto::PROTO_OPCUA)`). The MSG framing is spec-faithful (incl.
4126 * SecureChannelId), so standard clients interoperate (verified with python asyncua:
4127 * connect + browse + read + write/read-back). All pure and host-tested. No heap, no stdlib.
4128 */
4129#ifndef DETWS_ENABLE_OPCUA
4130#define DETWS_ENABLE_OPCUA 0
4131#endif
4132
4133/**
4134 * @brief OPC UA Binary client (DETWS_ENABLE_OPCUA_CLIENT).
4135 *
4136 * Default off. Requires DETWS_ENABLE_OPCUA (shares the codec). services/opcua_client
4137 * provides the client side of the OPC UA Binary protocol: request builders (Hello,
4138 * OpenSecureChannel, CreateSession, ActivateSession, Read, Browse, CloseSession,
4139 * CloseSecureChannel) and response parsers, reusing the opcua.h codec. It is
4140 * transport-agnostic - the app supplies the outbound socket (e.g. an Arduino
4141 * WiFiClient) and feeds bytes through these pure builders/parsers. No heap, no stdlib.
4142 */
4143#ifndef DETWS_ENABLE_OPCUA_CLIENT
4144#define DETWS_ENABLE_OPCUA_CLIENT 0
4145#endif
4146
4147/**
4148 * @brief umati - OPC UA for Machine Tools information model (DETWS_ENABLE_UMATI).
4149 *
4150 * Default off. Requires DETWS_ENABLE_OPCUA (builds on the OPC UA Binary server). services/umati
4151 * exposes the umati / OPC UA for Machine Tools companion model (OPC 40501-1, namespace
4152 * `http://opcfoundation.org/UA/MachineTool/`): a fixed MachineTool node hierarchy -
4153 * Identification, Monitoring (MachineTool / Channel / Spindle / Axis_X..Z), Production, and
4154 * Notification - served through the OPC UA Browse + Read resolvers out of a caller-owned
4155 * UmatiMachineTool struct you refresh each loop. Faithful BrowseNames per OPC 40501-1; a monitoring
4156 * (read-only) model any umati / OPC UA client browses and reads by BrowseName. No heap, no stdlib.
4157 */
4158#ifndef DETWS_ENABLE_UMATI
4159#define DETWS_ENABLE_UMATI 0
4160#endif
4161
4162/** @brief NamespaceIndex the umati MachineTool nodes live at (default 1). */
4163#ifndef DETWS_UMATI_NS
4164#define DETWS_UMATI_NS 1
4165#endif
4166
4167/**
4168 * @brief Streaming file upload: POST a body straight to a file on the filesystem.
4169 *
4170 * Default off. When set, src/services/upload_service/upload_service.h registers a POST route
4171 * that streams the request body directly into an Arduino FS file (LittleFS /
4172 * SPIFFS / SD) - the upload never has to fit in RAM. Reuses the same parser
4173 * streaming-body hook as OTA.
4174 *
4175 * For reliable streamed uploads the RX ring must hold at least one full TCP
4176 * receive window (RX_BUF_SIZE >= TCP_WND, ~5.7 KB by default): the transport
4177 * reopens the window only as the consumer drains the ring (ack-on-consume), so a
4178 * ring smaller than the window lets the peer overrun it and the transfer
4179 * deadlocks - you cannot advertise a window larger than your buffer. When a
4180 * streaming feature is enabled and RX_BUF_SIZE was left at its default, it is
4181 * automatically upsized below; an explicit RX_BUF_SIZE is honored as-is (set it
4182 * >= TCP_WND yourself). The 1024 default suits ordinary requests, not uploads.
4183 */
4184#ifndef DETWS_ENABLE_UPLOAD
4185#define DETWS_ENABLE_UPLOAD 0
4186#endif
4187
4188/**
4189 * @brief Internal: the parser's streaming-body machinery (OTA, file upload, WebDAV PUT).
4190 *
4191 * Each streams the request body to a sink instead of buffering it into body[]; the
4192 * parser support is shared and compiled when any of these features is enabled. The
4193 * sink is a single global hook, so only one streaming consumer is active per build
4194 * (the last to register wins) - do not combine OTA / upload / WebDAV streaming in
4195 * the same firmware.
4196 */
4197#if DETWS_ENABLE_OTA || DETWS_ENABLE_UPLOAD || DETWS_ENABLE_WEBDAV
4198#define DETWS_ENABLE_STREAM_BODY 1
4199#else
4200#define DETWS_ENABLE_STREAM_BODY 0
4201#endif
4202
4203// Streamed uploads need the RX ring to hold a full TCP receive window or the peer
4204// overruns it and the transfer deadlocks (ack-on-consume reopens the window only as
4205// the ring drains). If RX_BUF_SIZE was left at its default, upsize it to a value
4206// that comfortably exceeds the usual TCP_WND (~5.7 KB) when streaming is enabled.
4207// An explicit RX_BUF_SIZE (build flag) is respected unchanged - set it >= TCP_WND.
4208#if DETWS_ENABLE_STREAM_BODY && defined(DETWS_RX_BUF_SIZE_DEFAULTED) && RX_BUF_SIZE < 8192
4209#undef RX_BUF_SIZE
4210#define RX_BUF_SIZE 8192
4211#endif
4212
4213// A modern SSH client's first flight (identification banner + KEXINIT) is ~1.5 KB:
4214// post-quantum/curve kex names, cert host-key algs, EtM MACs, ext-info-c. The RX
4215// ring must hold it or the handshake resets at key exchange, so when SSH is enabled
4216// and RX_BUF_SIZE was left at its default, upsize it to fit a full KEXINIT. An
4217// explicit RX_BUF_SIZE (build flag) is respected unchanged - keep it >= 2 KB.
4218#if DETWS_ENABLE_SSH && defined(DETWS_RX_BUF_SIZE_DEFAULTED) && RX_BUF_SIZE < 2048
4219#undef RX_BUF_SIZE
4220#define RX_BUF_SIZE 2048
4221#endif
4222
4223// A modern TLS ClientHello (TLS 1.3 key shares + cipher/sig-alg lists + the RFC 7685 padding real
4224// clients send) is ~1.5 KB and arrives as one TCP segment - larger than the 1024 default ring. The
4225// recv callback refuses a whole segment that will not fit the ring (ERR_MEM, lossless backpressure),
4226// so a ClientHello bigger than the ring is refused forever and the handshake stalls to an idle-timeout
4227// RST: every 1.3-leading client (curl, browsers, Python) then fails to connect while a 1.2-only client
4228// squeaks in. The ring must hold a full segment, so when TLS is enabled and RX_BUF_SIZE was left at its
4229// default, upsize it to fit a full ClientHello. An explicit RX_BUF_SIZE (build flag) is respected
4230// unchanged - keep it >= 2 KB.
4231#if DETWS_ENABLE_TLS && defined(DETWS_RX_BUF_SIZE_DEFAULTED) && RX_BUF_SIZE < 2048
4232#undef RX_BUF_SIZE
4233#define RX_BUF_SIZE 2048
4234#endif
4235
4236/** @brief First-boot WiFi provisioning: softAP + captive-portal credentials form. */
4237#ifndef DETWS_ENABLE_PROVISIONING
4238#define DETWS_ENABLE_PROVISIONING 0
4239#endif
4240
4241/**
4242 * @brief Syslog client (RFC 5424 over UDP).
4243 *
4244 * Default off. When set, the device can ship log lines to a remote syslog server
4245 * (e.g. rsyslog / journald / a SIEM) as RFC 5424 UDP datagrams via the
4246 * transport-layer UDP service - a zero-heap structured-logging sink for fleets
4247 * of constrained devices. See src/services/syslog/syslog.h.
4248 */
4249#ifndef DETWS_ENABLE_SYSLOG
4250#define DETWS_ENABLE_SYSLOG 0
4251#endif
4252
4253/** @brief Maximum formatted syslog datagram length in bytes (RFC 5424 line). */
4254#ifndef DETWS_SYSLOG_MSG_MAX
4255#define DETWS_SYSLOG_MSG_MAX 256
4256#endif
4257
4258/** @brief Maximum syslog HOSTNAME / APP-NAME field length (including NUL). */
4259#ifndef DETWS_SYSLOG_FIELD_MAX
4260#define DETWS_SYSLOG_FIELD_MAX 32
4261#endif
4262
4263/** @brief Default syslog collector UDP port (RFC 5426 well-known 514; overridable at runtime
4264 * via syslog_init and here for a non-standard collector). */
4265#ifndef DETWS_SYSLOG_DEFAULT_PORT
4266#define DETWS_SYSLOG_DEFAULT_PORT 514
4267#endif
4268
4269/**
4270 * @brief JWT bearer-token authentication (HS256).
4271 *
4272 * Default off. When set, src/services/jwt/jwt.h verifies `Authorization: Bearer
4273 * <jwt>` tokens signed with HMAC-SHA-256 (reusing the SSH crypto layer) and can
4274 * read integer claims (e.g. `exp`) so a handler/middleware can gate routes on a
4275 * stateless token. Signature verification is constant-time.
4276 */
4277#ifndef DETWS_ENABLE_JWT
4278#define DETWS_ENABLE_JWT 0
4279#endif
4280
4281/** @brief Maximum accepted JWT length in bytes (header.payload.signature). */
4282#ifndef DETWS_JWT_MAX_LEN
4283#define DETWS_JWT_MAX_LEN 512
4284#endif
4285
4286/**
4287 * @brief Outbound HTTP(S) client (raw lwIP, optional client-side mbedTLS).
4288 *
4289 * Default off. When set, src/services/http_client/http_client.h can issue a
4290 * blocking GET/POST to a remote server: it resolves the host (DNS), opens a raw
4291 * lwIP TCP connection (https:// goes through client-side mbedTLS over the same
4292 * static arena as the server TLS), sends the request, and returns the status +
4293 * body in caller buffers. For webhooks, telemetry push, REST calls from the
4294 * device. The request builder + response parser are host-testable; the transport
4295 * is ESP32-only.
4296 */
4297#ifndef DETWS_ENABLE_HTTP_CLIENT
4298#define DETWS_ENABLE_HTTP_CLIENT 0
4299#endif
4300
4301/** @brief HTTPS client support inside the HTTP client (needs DETWS_ENABLE_TLS). */
4302#ifndef DETWS_ENABLE_HTTP_CLIENT_TLS
4303#define DETWS_ENABLE_HTTP_CLIENT_TLS 0
4304#endif
4305
4306/** @brief Receive buffer (and max response size) for the outbound HTTP client, bytes. */
4307#ifndef DETWS_HTTP_CLIENT_BUF_SIZE
4308#define DETWS_HTTP_CLIENT_BUF_SIZE 2048
4309#endif
4310
4311/**
4312 * @brief Ciphertext receive-ring size for the https:// client, bytes.
4313 *
4314 * The lwIP recv callback feeds TLS wire bytes into this draining ring while the
4315 * TLS engine pulls and decrypts them, so it holds only the in-flight (not yet
4316 * decrypted) ciphertext: a multi-KB handshake flight fits without loss thanks to
4317 * the refuse-and-redeliver backpressure. Must exceed one TCP segment (TCP_MSS,
4318 * ~1460) or a full segment could never fit. Only used when
4319 * DETWS_ENABLE_HTTP_CLIENT_TLS is set.
4320 */
4321#ifndef DETWS_HTTP_CLIENT_CT_BUF_SIZE
4322#define DETWS_HTTP_CLIENT_CT_BUF_SIZE 4096
4323#endif
4324
4325/** @brief Outbound HTTP client connect/response timeout in milliseconds. */
4326#ifndef DETWS_HTTP_CLIENT_TIMEOUT_MS
4327#define DETWS_HTTP_CLIENT_TIMEOUT_MS 8000
4328#endif
4329
4330/**
4331 * @brief Outbound SMTP client (RFC 5321) for device email alerts (services/smtp).
4332 *
4333 * A blocking one-shot `smtp_send()`: EHLO, optional AUTH LOGIN, MAIL FROM / RCPT TO /
4334 * DATA over the shared client transport (`det_client`), with implicit TLS (SMTPS, e.g.
4335 * :465) when the message config sets `tls` and DETWS_ENABLE_TLS is on. Zero heap; the
4336 * dialogue engine (`smtp_run`) takes a send/recv seam so it is host-tested without lwIP.
4337 * "SMS fallback" rides on top - most carriers accept an email-to-SMS gateway address.
4338 */
4339#ifndef DETWS_ENABLE_SMTP
4340#define DETWS_ENABLE_SMTP 0
4341#endif
4342
4343/** @brief Max length of one SMTP command / address line (bytes, incl. CRLF). */
4344#ifndef DETWS_SMTP_LINE_MAX
4345#define DETWS_SMTP_LINE_MAX 256
4346#endif
4347
4348/** @brief Max size of the assembled DATA payload (headers + dot-stuffed body), bytes. */
4349#ifndef DETWS_SMTP_MSG_MAX
4350#define DETWS_SMTP_MSG_MAX 2048
4351#endif
4352
4353/** @brief Max size of one (possibly multi-line) server reply held while parsing, bytes. */
4354#ifndef DETWS_SMTP_REPLY_MAX
4355#define DETWS_SMTP_REPLY_MAX 512
4356#endif
4357
4358/** @brief SMTP connect / per-reply timeout in milliseconds. */
4359#ifndef DETWS_SMTP_TIMEOUT_MS
4360#define DETWS_SMTP_TIMEOUT_MS 10000
4361#endif
4362
4363/** @brief Ciphertext receive-ring size for SMTPS, bytes (only used when the message is TLS). */
4364#ifndef DETWS_SMTP_CT_BUF_SIZE
4365#define DETWS_SMTP_CT_BUF_SIZE 4096
4366#endif
4367
4368/**
4369 * @brief MQTT 3.1.1 publish/subscribe client (raw lwIP, optional MQTTS over TLS).
4370 *
4371 * Default off. When set, src/services/mqtt/mqtt.h provides a persistent outbound
4372 * client: connect to a broker, PUBLISH (QoS 0/1/2) and SUBSCRIBE to topics, receive
4373 * incoming messages via a callback, with keep-alive pings - the dominant IoT
4374 * messaging pattern, for telemetry push and remote command. The packet codec is
4375 * host-testable; the transport (DNS + raw lwIP TCP, MQTTS via client-side mbedTLS)
4376 * is ESP32-only. Full QoS 0/1/2 (outbound DUP retransmit, inbound QoS-2
4377 * de-duplication by packet id) and Last-Will are supported.
4378 */
4379#ifndef DETWS_ENABLE_MQTT
4380#define DETWS_ENABLE_MQTT 0
4381#endif
4382
4383/** @brief MQTTS: run the MQTT client over client-side TLS (needs DETWS_ENABLE_TLS). */
4384#ifndef DETWS_ENABLE_MQTT_TLS
4385#define DETWS_ENABLE_MQTT_TLS 0
4386#endif
4387
4388/**
4389 * @brief MQTT packet buffer size in bytes (bounds one outgoing/incoming packet).
4390 *
4391 * Two buffers of this size live in BSS (one tx, one rx). Must hold the largest
4392 * CONNECT/PUBLISH the client sends and the largest incoming PUBLISH it accepts
4393 * (topic + payload + a few header bytes); larger incoming packets are dropped.
4394 */
4395#ifndef DETWS_MQTT_BUF_SIZE
4396#define DETWS_MQTT_BUF_SIZE 1024
4397#endif
4398
4399/** @brief Default MQTT keep-alive interval in seconds (PINGREQ cadence / CONNECT field). */
4400#ifndef DETWS_MQTT_KEEPALIVE_S
4401#define DETWS_MQTT_KEEPALIVE_S 30
4402#endif
4403
4404/** @brief Ciphertext receive-ring size for MQTTS (draining ring; must exceed one TCP_MSS). */
4405#ifndef DETWS_MQTT_CT_BUF_SIZE
4406#define DETWS_MQTT_CT_BUF_SIZE 4096
4407#endif
4408
4409/** @brief Maximum inbound MQTT topic length (including NUL) delivered to the callback. */
4410#ifndef DETWS_MQTT_MAX_TOPIC
4411#define DETWS_MQTT_MAX_TOPIC 128
4412#endif
4413
4414/**
4415 * @brief Outbound QoS 1/2 in-flight slots (unacknowledged messages held for DUP retransmit).
4416 *
4417 * Each slot stores its serialized packet (up to DETWS_MQTT_INFLIGHT_BUF bytes) until
4418 * the broker acknowledges it; a publish is refused when all slots are busy. The pool
4419 * costs DETWS_MQTT_MAX_INFLIGHT * (DETWS_MQTT_INFLIGHT_BUF + a few bytes) of BSS.
4420 */
4421#ifndef DETWS_MQTT_MAX_INFLIGHT
4422#define DETWS_MQTT_MAX_INFLIGHT 4
4423#endif
4424
4425/** @brief Stored-packet size per in-flight QoS 1/2 slot (caps a retransmittable PUBLISH). */
4426#ifndef DETWS_MQTT_INFLIGHT_BUF
4427#define DETWS_MQTT_INFLIGHT_BUF 256
4428#endif
4429
4430/** @brief Retransmit timeout (ms) for an unacknowledged in-flight QoS 1/2 message. */
4431#ifndef DETWS_MQTT_RETRANSMIT_MS
4432#define DETWS_MQTT_RETRANSMIT_MS 5000
4433#endif
4434
4435/** @brief Inbound QoS 2 packet-id de-duplication ring depth (PUBREC-acknowledged, awaiting PUBREL). */
4436#ifndef DETWS_MQTT_RX_QOS2_SLOTS
4437#define DETWS_MQTT_RX_QOS2_SLOTS 8
4438#endif
4439
4440/**
4441 * @brief Outbound WebSocket client (RFC 6455 over raw lwIP, optional wss:// TLS).
4442 *
4443 * Default off. When set, src/services/ws_client/ws_client.h connects to a remote
4444 * WebSocket endpoint (ws://, or wss:// over client-side mbedTLS), performs the
4445 * RFC 6455 client handshake (Sec-WebSocket-Key/Accept), and sends masked text /
4446 * binary frames + receives server frames via a callback - for streaming to cloud
4447 * dashboards or bidirectional control. The frame/handshake codec is host-testable.
4448 */
4449#ifndef DETWS_ENABLE_WS_CLIENT
4450#define DETWS_ENABLE_WS_CLIENT 0
4451#endif
4452
4453/** @brief wss://: run the WebSocket client over client-side TLS (needs DETWS_ENABLE_TLS). */
4454#ifndef DETWS_ENABLE_WS_CLIENT_TLS
4455#define DETWS_ENABLE_WS_CLIENT_TLS 0
4456#endif
4457
4458/** @brief WebSocket client send/receive buffer size in bytes (bounds one frame). */
4459#ifndef DETWS_WS_CLIENT_BUF_SIZE
4460#define DETWS_WS_CLIENT_BUF_SIZE 1024
4461#endif
4462
4463/** @brief Ciphertext receive-ring size for wss:// (draining ring; must exceed one TCP_MSS). */
4464#ifndef DETWS_WS_CLIENT_CT_BUF_SIZE
4465#define DETWS_WS_CLIENT_CT_BUF_SIZE 4096
4466#endif
4467
4468/**
4469 * @brief Internal: client-side TLS engine is compiled (HTTPS client, MQTTS, and/or wss client).
4470 *
4471 * The outbound HTTP client (one-shot exchange) and the MQTT / WebSocket clients
4472 * (persistent sessions) share the same client mbedTLS code in det_tls - the
4473 * CA/pin trust config, the BIO typedefs, and the session API - gated by this.
4474 */
4475#if DETWS_ENABLE_HTTP_CLIENT_TLS || DETWS_ENABLE_MQTT_TLS || DETWS_ENABLE_WS_CLIENT_TLS
4476#define DETWS_ENABLE_CLIENT_TLS 1
4477#else
4478#define DETWS_ENABLE_CLIENT_TLS 0
4479#endif
4480
4481// The outbound clients (det_client) resolve hostnames through the shared DNS
4482// resolver (detws_dns_resolve), so enabling any client implies the resolver - one
4483// owner of the gethostbyname-marshal pattern instead of a private copy per client.
4484// DETWS_NEED_DET_CLIENT marks when the client transport is actually used; the
4485// det_client translation unit compiles its body only then (a server-only Arduino
4486// build that does not enable a client must not reference the resolver symbols).
4487// Every feature that drives the outbound client transport must pull it in: the direct callers
4488// (http_client / mqtt / ws_client / relay / smtp / ssh port-forward) and the seam-based engines
4489// whose shipped example binds the seam to det_client (smb / dnc). Miss one and its det_client_open
4490// resolves to the !NEED stub that returns -1, so the feature silently never connects on device.
4491#if DETWS_ENABLE_HTTP_CLIENT || DETWS_ENABLE_MQTT || DETWS_ENABLE_WS_CLIENT || DETWS_ENABLE_RELAY || \
4492 DETWS_ENABLE_SMTP || DETWS_SSH_PORT_FORWARD || DETWS_ENABLE_SMB || DETWS_ENABLE_DNC
4493#undef DETWS_ENABLE_DNS_RESOLVER
4494#define DETWS_ENABLE_DNS_RESOLVER 1
4495#define DETWS_NEED_DET_CLIENT 1
4496#endif
4497#ifndef DETWS_NEED_DET_CLIENT
4498#define DETWS_NEED_DET_CLIENT 0
4499#endif
4500
4501// ---------------------------------------------------------------------------
4502// Full Authorization-header capture (internal)
4503// ---------------------------------------------------------------------------
4504// Digest auth and JWT bearer tokens both carry an Authorization value far longer
4505// than MAX_VAL_LEN, so the parser captures the whole header into a dedicated
4506// per-request buffer (HttpReq::authorization) when either feature is enabled.
4507
4508/** @brief True when the parser must capture the full Authorization header value. */
4509#if DETWS_ENABLE_AUTH || DETWS_ENABLE_JWT || DETWS_ENABLE_OIDC
4510#define DETWS_CAPTURE_AUTH_HEADER 1
4511#else
4512#define DETWS_CAPTURE_AUTH_HEADER 0
4513#endif
4514
4515/**
4516 * @brief Capacity of HttpReq::authorization (full Authorization header value).
4517 *
4518 * Sized to the largest enabled consumer: a Digest header (DIGEST_AUTH_HDR_MAX), a
4519 * `Bearer <jwt>` HS256 token (DETWS_JWT_MAX_LEN), or a `Bearer <id_token>` OIDC
4520 * RS256 token (DETWS_OIDC_MAX_LEN), each plus the scheme.
4521 */
4522#if DETWS_ENABLE_OIDC
4523#define DETWS_AUTH_HDR_CAP_OIDC (DETWS_OIDC_MAX_LEN + 16)
4524#else
4525#define DETWS_AUTH_HDR_CAP_OIDC 0
4526#endif
4527#if DETWS_ENABLE_JWT
4528#define DETWS_AUTH_HDR_CAP_JWT (DETWS_JWT_MAX_LEN + 16)
4529#else
4530#define DETWS_AUTH_HDR_CAP_JWT 0
4531#endif
4532#define DETWS_AUTH_HDR_CAP_M1 \
4533 (DETWS_AUTH_HDR_CAP_JWT > DIGEST_AUTH_HDR_MAX ? DETWS_AUTH_HDR_CAP_JWT : DIGEST_AUTH_HDR_MAX)
4534#define DETWS_AUTH_HDR_CAP \
4535 (DETWS_AUTH_HDR_CAP_OIDC > DETWS_AUTH_HDR_CAP_M1 ? DETWS_AUTH_HDR_CAP_OIDC : DETWS_AUTH_HDR_CAP_M1)
4536
4537/** @brief Runtime stats endpoint (uptime, request/error counts, pool usage, heap). */
4538#ifndef DETWS_ENABLE_STATS
4539#define DETWS_ENABLE_STATS 0
4540#endif
4541
4542/**
4543 * @brief Transport-layer observability: connection event hook + counters.
4544 *
4545 * Default off (zero cost when unset - the notify points compile to nothing).
4546 * When set, the transport (L4) fires an application callback on every connection
4547 * state transition - det_conn_on_event(slot, old_state, new_state, reason) - and
4548 * maintains lock-free counters (accepts, closes by reason, idle timeouts, RX
4549 * backpressure events, dropped deferred events, and a live ConnState::CONN_CLOSING gauge)
4550 * readable via det_conn_counters(). This is the only state-transition trace the
4551 * L4/L5 core exposes; pair it with DETWS_ENABLE_STATS for request-level metrics.
4552 */
4553#ifndef DETWS_ENABLE_OBSERVABILITY
4554#define DETWS_ENABLE_OBSERVABILITY 0
4555#endif
4556
4557/**
4558 * @brief Prometheus `/metrics` endpoint (text exposition format 0.0.4).
4559 *
4560 * Default off (requires DETWS_ENABLE_STATS for the underlying counters). When
4561 * set, DetWebServer::metrics() emits the runtime stats as Prometheus metrics
4562 * (`detws_uptime_seconds`, `detws_http_requests_total`,
4563 * `detws_http_responses_total{class=...}`, `detws_active_connections`,
4564 * `detws_free_heap_bytes`, ...) so a Prometheus server can scrape the device.
4565 */
4566#ifndef DETWS_ENABLE_METRICS
4567#define DETWS_ENABLE_METRICS 0
4568#endif
4569
4570/**
4571 * @brief Browser "web serial" terminal over WebSocket (src/services/web_terminal).
4572 *
4573 * Serves a self-contained terminal page and a WebSocket endpoint: device output
4574 * is broadcast to all connected browsers, browser input is delivered to a
4575 * command callback. Requires DETWS_ENABLE_WEBSOCKET. Default off.
4576 */
4577#ifndef DETWS_ENABLE_WEB_TERMINAL
4578#define DETWS_ENABLE_WEB_TERMINAL 0
4579#endif
4580
4581/**
4582 * @brief Stack scratch for detws_web_terminal_printf()/println() formatting.
4583 *
4584 * One formatted terminal line must fit in this many bytes (longer is truncated).
4585 * Allocated on the stack only during the call - no persistent RAM cost.
4586 */
4587#ifndef TERM_TX_BUF_SIZE
4588#define TERM_TX_BUF_SIZE 256
4589#endif
4590
4591/**
4592 * @brief Conditional GET (ETag + Last-Modified) for served files.
4593 *
4594 * When set, serve_file()/serve_static() emit a strong `ETag` (from file size +
4595 * mtime) and a `Last-Modified` date, and answer a conditional request with
4596 * `304 Not Modified` when either the client's `If-None-Match` matches the ETag or
4597 * - per RFC 9110, only if no `If-None-Match` is present - its `If-Modified-Since`
4598 * is not older than the file. Saves bandwidth on repeat fetches of static assets.
4599 * (If-Modified-Since needs a real wall clock for the file mtime; with no clock the
4600 * date validator is skipped and the ETag validator still works.)
4601 */
4602#ifndef DETWS_ENABLE_ETAG
4603#define DETWS_ENABLE_ETAG 0
4604#endif
4605
4606/**
4607 * @brief Expose a diagnostic JSON endpoint via server.diag().
4608 *
4609 * Disabled by default - enabling it exposes compile-time configuration
4610 * (buffer sizes, feature flags) which could aid an attacker. Only
4611 * enable in development or behind an authenticated route.
4612 *
4613 * When enabled, DETWS_DIAG_JSON is a compile-time string constant you can
4614 * serve from any route handler:
4615 * @code
4616 * server.on("/diag", HttpMethod::HTTP_GET, [](uint8_t id, HttpReq *) {
4617 * server.diag(id); // convenience wrapper
4618 * // or:
4619 * server.send(id, 200, "application/json", DETWS_DIAG_JSON);
4620 * });
4621 * @endcode
4622 */
4623#ifndef DETWS_ENABLE_DIAG
4624#define DETWS_ENABLE_DIAG 0
4625#endif
4626
4627/**
4628 * @brief HTTP/1.1 persistent connections (keep-alive).
4629 *
4630 * Default off (every response carries `Connection: close` and the connection is
4631 * closed after one request - the long-standing behavior). When set to 1, a
4632 * cleanly-parsed request is answered with `Connection: keep-alive` and the slot
4633 * is recycled for the next request on the same socket: HTTP/1.1 keeps the
4634 * connection open unless the client sends `Connection: close`; HTTP/1.0 closes
4635 * unless the client sends `Connection: keep-alive`. Error responses (400/413/414
4636 * and any non-ParseState::PARSE_COMPLETE path) always close, since the next request boundary
4637 * is unknown. Idle keep-alive connections are still reclaimed by the existing
4638 * conn_timeout sweep, and each connection serves at most
4639 * DETWS_KEEPALIVE_MAX_REQUESTS requests before a deliberate close.
4640 */
4641#ifndef DETWS_ENABLE_KEEPALIVE
4642#define DETWS_ENABLE_KEEPALIVE 1
4643#endif
4644
4645/**
4646 * @brief Maximum requests served on one keep-alive connection before it is closed.
4647 *
4648 * A fairness bound so a single client cannot hold a connection slot
4649 * indefinitely with a steady request stream. After this many responses the
4650 * server emits `Connection: close` and drops the link; the client simply
4651 * reconnects. Only meaningful when DETWS_ENABLE_KEEPALIVE is set.
4652 */
4653#ifndef DETWS_KEEPALIVE_MAX_REQUESTS
4654#define DETWS_KEEPALIVE_MAX_REQUESTS 100
4655#endif
4656
4657/**
4658 * @brief HTTP/2 (RFC 9113) over the version-agnostic request/response core.
4659 *
4660 * Default off. When set, the server negotiates HTTP/2 via TLS ALPN ("h2") and speaks the binary
4661 * framing + HPACK header compression (RFC 7541) on top of the same routes/handlers as HTTP/1.1
4662 * (the response serializer is version-neutral). The HPACK codec and the frame layer are pure and
4663 * host-tested; the connection/stream state machine plugs in as a ProtoHandler.
4664 */
4665#ifndef DETWS_ENABLE_HTTP2
4666#define DETWS_ENABLE_HTTP2 0
4667#endif
4668
4669/**
4670 * @brief Per-connection HPACK dynamic-table size in bytes (our decoder; advertised to the peer
4671 * as SETTINGS_HEADER_TABLE_SIZE). RFC 7541's default is 4096; lower it to save per-connection
4672 * RAM (each active HTTP/2 connection holds one table).
4673 */
4674#ifndef DETWS_HPACK_TABLE_BYTES
4675#define DETWS_HPACK_TABLE_BYTES 4096
4676#endif
4677
4678/** @brief Max HPACK dynamic-table entries (>= DETWS_HPACK_TABLE_BYTES / 32, the min entry size). */
4679#ifndef DETWS_HPACK_MAX_ENTRIES
4680#define DETWS_HPACK_MAX_ENTRIES 128
4681#endif
4682
4683/**
4684 * @brief Largest HTTP/2 frame we accept, in bytes (advertised as SETTINGS_MAX_FRAME_SIZE). RFC
4685 * 9113 requires accepting at least 16384; a whole frame is buffered for reassembly, so this
4686 * (plus the HPACK table) sets the per-HTTP/2-connection RAM. Range: [16384, 16777215].
4687 */
4688#ifndef DETWS_H2_MAX_FRAME
4689#define DETWS_H2_MAX_FRAME 16384
4690#endif
4691
4692/** @brief Max concurrent HTTP/2 streams per connection (advertised as MAX_CONCURRENT_STREAMS). */
4693#ifndef DETWS_H2_MAX_STREAMS
4694#define DETWS_H2_MAX_STREAMS 8
4695#endif
4696
4697/**
4698 * @brief Header-block reassembly buffer for HTTP/2 requests that span HEADERS + CONTINUATION
4699 * frames (a single END_HEADERS frame decodes in place and needs no copy). Caps the compressed
4700 * request-header size; a larger block is rejected (RFC 9113 sec 6.10).
4701 */
4702#ifndef DETWS_H2_HDR_BLOCK
4703#define DETWS_H2_HDR_BLOCK 4096
4704#endif
4705
4706/**
4707 * @brief Place the HTTP/2 connection-engine pool in external PSRAM (ESP32).
4708 *
4709 * Each HTTP/2 connection needs a ~28 KB engine, so the pool (MAX_CONNS of them) does not fit the
4710 * ~122 KB internal DRAM alongside a TLS server - HTTP/2 therefore requires PSRAM. Set this to 1
4711 * on a PSRAM board (S3 / P4 / WROVER) to move the pool to external RAM via `EXT_RAM_BSS_ATTR`.
4712 * Like DETWS_TLS_ARENA_IN_PSRAM it needs a framework built with
4713 * `CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY=y` (the stock arduino-esp32 core ships it off; see
4714 * tools/psram/README.md). A compile-time guard rejects DETWS_ENABLE_HTTP2 without this on ARDUINO.
4715 */
4716#ifndef DETWS_H2_POOL_IN_PSRAM
4717#define DETWS_H2_POOL_IN_PSRAM 0
4718#endif
4719
4720/**
4721 * @brief HTTP/3 (RFC 9114) over QUIC (RFC 9000) - implemented, host-tested end-to-end (HW verification pending).
4722 *
4723 * Default off. HTTP/3 runs over QUIC (a reliable transport over UDP) with QPACK (RFC 9204)
4724 * header compression and its own binary framing. The full stack is in place and exercised by a
4725 * host end-to-end test - the QUIC variable-length integer (RFC 9000 sec 16), packet protection +
4726 * framing, the TLS 1.3-in-QUIC handshake, the transport connection engine, and the HTTP/3 + QPACK
4727 * codecs. On-device (ESP32) HW verification and an example are still pending. Like HTTP/2 this is a
4728 * PSRAM-class feature.
4729 */
4730#ifndef DETWS_ENABLE_HTTP3
4731#define DETWS_ENABLE_HTTP3 0
4732#endif
4733
4734/**
4735 * @brief DTLS 1.3 datagram security (RFC 9147) - the record layer.
4736 *
4737 * DTLS 1.3 secures datagram (UDP) transports - CoAP-over-DTLS and other constrained-device
4738 * telemetry - reusing the hand-rolled TLS 1.3 handshake crypto that already backs HTTP/3. This
4739 * flag gates the DTLS 1.3 **record layer** (dtls_record): the DTLSCiphertext unified header,
4740 * per-record AEAD protection (AEAD_AES_128_GCM), the RFC 9147 sequence-number encryption,
4741 * sequence-number reconstruction, and the anti-replay window; the **handshake framing and
4742 * reliability** layer (dtls_handshake, RFC 9147 §5 + §7): the 12-byte handshake header,
4743 * overlap-tolerant message reassembly, the ACK message, and the stateless HelloRetryRequest
4744 * cookie; and the **server handshake state machine** (dtls_conn, RFC 9147 §5-6): the
4745 * one-round-trip full handshake (TLS_AES_128_GCM_SHA256 / X25519 / Ed25519), epoch 0->2->3
4746 * transitions, reusing the TLS 1.3 messages + key schedule (tls13_msg / tls13_kdf). The
4747 * HelloRetryRequest cookie round-trip and ACK/timeout retransmission, plus a CoAPs front-end, are
4748 * the following phases. Enabling this also compiles the shared quic_hkdf / quic_aead / tls13_*
4749 * primitives (otherwise gated behind HTTP/3). Default off.
4750 */
4751#ifndef DETWS_ENABLE_DTLS
4752#define DETWS_ENABLE_DTLS 0
4753#endif
4754
4755// Internal request-dispatch slots appended to the connection pool for non-TCP transports.
4756// HTTP/3 runs over QUIC/UDP and has no accept-time TCP slot, but it reuses the same request
4757// pipeline (match_and_execute + send), which is indexed by a connection-pool slot. One reserved
4758// slot at index MAX_CONNS lets an HTTP/3 request run through that pipeline. The TCP accept path only
4759// ever scans [0, MAX_CONNS), and this slot is driven synchronously by the HTTP/3 poll on the worker
4760// thread, so there is no accept race. CONN_POOL_SLOTS sizes conn_pool / http_pool / the per-slot
4761// response-header buffer; every TCP loop still bounds itself with MAX_CONNS.
4762#if DETWS_ENABLE_HTTP3
4763#define DETWS_INTERNAL_SLOTS 1
4764#define DETWS_H3_DISPATCH_SLOT MAX_CONNS ///< reserved conn-pool slot an HTTP/3 request dispatches through
4765#else
4766#define DETWS_INTERNAL_SLOTS 0
4767#endif
4768#define CONN_POOL_SLOTS (MAX_CONNS + DETWS_INTERNAL_SLOTS)
4769
4770/** @brief UDP port the HTTP/3 (QUIC) server binds by default (used by DetWebServer::h3_cert). */
4771#ifndef DETWS_HTTP3_PORT
4772#define DETWS_HTTP3_PORT 443
4773#endif
4774
4775/**
4776 * @brief Maximum bytes of one QUIC/TLS handshake CRYPTO flight (RFC 9001).
4777 *
4778 * The server's second flight - EncryptedExtensions + Certificate + CertificateVerify + Finished -
4779 * is assembled whole before it is fragmented into CRYPTO frames across Handshake packets. The
4780 * Certificate (a DER X.509 chain) dominates the size, so this bounds the certificate the server can
4781 * present. The default fits a single Ed25519 leaf certificate comfortably; raise it for a chain.
4782 */
4783#ifndef DETWS_H3_CRYPTO_BUF
4784#define DETWS_H3_CRYPTO_BUF 2048
4785#endif
4786
4787/**
4788 * @brief Maximum concurrent request streams per HTTP/3 connection.
4789 *
4790 * Bounds the per-connection QUIC stream table (client-initiated bidirectional request streams plus
4791 * the handful of unidirectional control / QPACK streams). Each slot is small; 8 matches the HTTP/2
4792 * default (DETWS_H2_MAX_STREAMS).
4793 */
4794#ifndef DETWS_H3_MAX_STREAMS
4795#define DETWS_H3_MAX_STREAMS 8
4796#endif
4797
4798/**
4799 * @brief HTTP Range requests / 206 Partial Content (requires DETWS_ENABLE_FILE_SERVING or
4800 * DETWS_ENABLE_EDGE_CACHE).
4801 *
4802 * Default off. When set, serve_file() / serve_static() and the CDN edge cache honor a single-range
4803 * `Range: bytes=...` request header: they answer `206 Partial Content` with a `Content-Range` header
4804 * and stream only the requested bytes (file serving seeks the file; the edge cache windows the cached
4805 * body), advertise `Accept-Ranges: bytes` on full responses, and answer an unsatisfiable range with
4806 * `416 Range Not Satisfiable`. This enables resumable downloads and media seeking. Multi-range
4807 * (multipart/byteranges) requests are not supported - the server falls back to a full 200 response,
4808 * which is RFC 7233 §3.1 compliant. The parser is shared (server/http_range.h).
4809 */
4810#ifndef DETWS_ENABLE_RANGE
4811#define DETWS_ENABLE_RANGE 0
4812#endif
4813
4814/**
4815 * @brief Enforce the RFC 7230 §5.4 Host-header requirement (default on).
4816 *
4817 * When 1, an HTTP/1.1 request that lacks a Host header - or carries more than
4818 * one - is rejected with 400 Bad Request. When 0, the Host header is not
4819 * required (useful for constrained clients or test harnesses that feed bare
4820 * request lines). The multiple-Host rule and Content-Length validation are
4821 * always active regardless of this flag.
4822 */
4823#ifndef DETWS_ENFORCE_HOST_HEADER
4824#define DETWS_ENFORCE_HOST_HEADER 1
4825#endif
4826
4827/**
4828 * @brief Allow SSH password authentication (default on).
4829 *
4830 * Set to 0 to harden the SSH server to publickey-only authentication
4831 * (RFC 4252 §7): the "password" method is then refused outright and is not
4832 * advertised in the USERAUTH_FAILURE method list. Publickey auth is always
4833 * available regardless of this flag.
4834 */
4835#ifndef DETWS_SSH_ALLOW_PASSWORD
4836#define DETWS_SSH_ALLOW_PASSWORD 1
4837#endif
4838
4839/**
4840 * @brief Maximum failed SSH authentication attempts per connection.
4841 *
4842 * RFC 4252 §4 permits the server to disconnect after a small bounded number of
4843 * failed USERAUTH_REQUESTs. After this many SSH_MSG_USERAUTH_FAILURE responses
4844 * on one connection the server sends SSH_MSG_DISCONNECT and drops the link.
4845 * (The publickey "would-be-accepted" probe and a SUCCESS do not count.)
4846 */
4847#ifndef SSH_MAX_AUTH_ATTEMPTS
4848#define SSH_MAX_AUTH_ATTEMPTS 6
4849#endif
4850
4851// ---------------------------------------------------------------------------
4852// Listener pool
4853// ---------------------------------------------------------------------------
4854
4855/** @brief Maximum number of simultaneously active listener ports. */
4856#ifndef MAX_LISTENERS
4857#define MAX_LISTENERS 3
4858#endif
4859
4860/**
4861 * @brief Maximum simultaneously bound UDP ports (transport-layer UDP service).
4862 *
4863 * Sizes the fixed pool in udp.cpp. One slot per bound port, e.g. SNMP
4864 * (:161) and the captive-portal DNS responder (:53). Costs only a few pointers
4865 * of BSS each.
4866 */
4867#ifndef DETWS_MAX_UDP_LISTENERS
4868#define DETWS_MAX_UDP_LISTENERS 2
4869#endif
4870
4871/**
4872 * @brief Shared receive-scratch size for the transport-layer UDP service.
4873 *
4874 * One static buffer (lwIP delivers a single datagram at a time) into which each
4875 * incoming datagram is copied before the handler runs. Must hold the largest
4876 * datagram any UDP service expects (SNMP messages are the largest user).
4877 */
4878#ifndef DETWS_UDP_RX_BUF_SIZE
4879#define DETWS_UDP_RX_BUF_SIZE 1472
4880#endif
4881
4882/**
4883 * @brief Opt-in global accept-rate throttle (connection-flood defense).
4884 *
4885 * Default off (zero cost / no behavior change). When set to 1 the accept
4886 * callback rejects new connections once more than DETWS_ACCEPT_THROTTLE_MAX
4887 * have been accepted within a DETWS_ACCEPT_THROTTLE_WINDOW_MS fixed window
4888 * (global across all listeners, two static counters - no per-IP table). This
4889 * bounds connection churn (e.g. reconnect brute-force) on top of the bounded
4890 * connection pool and the per-connection auth limits. mitigate finer-grained /
4891 * per-IP attacks at the network layer.
4892 */
4893#ifndef DETWS_ENABLE_ACCEPT_THROTTLE
4894#define DETWS_ENABLE_ACCEPT_THROTTLE 0
4895#endif
4896
4897/** @brief Max accepted connections per throttle window (see DETWS_ENABLE_ACCEPT_THROTTLE). */
4898#ifndef DETWS_ACCEPT_THROTTLE_MAX
4899#define DETWS_ACCEPT_THROTTLE_MAX 20
4900#endif
4901
4902/** @brief Throttle window length in milliseconds (see DETWS_ENABLE_ACCEPT_THROTTLE). */
4903#ifndef DETWS_ACCEPT_THROTTLE_WINDOW_MS
4904#define DETWS_ACCEPT_THROTTLE_WINDOW_MS 1000
4905#endif
4906
4907/**
4908 * @brief Opt-in per-IP accept-rate throttle (connection-flood defense, keyed by source IPv4).
4909 *
4910 * Default off (zero cost / no behavior change). Complements the global accept
4911 * throttle: the accept callback rejects a new connection once one source IPv4
4912 * address has opened more than DETWS_PER_IP_THROTTLE_MAX connections within a
4913 * DETWS_PER_IP_THROTTLE_WINDOW_MS fixed window. A fixed BSS table of
4914 * DETWS_PER_IP_THROTTLE_SLOTS buckets tracks the most-recently-seen source
4915 * addresses; when a new address arrives and the table is full, an expired or
4916 * least-recently-started bucket is reused, so memory stays bounded (no heap).
4917 *
4918 * This bounds reconnect/brute-force churn from a single host (the gap left by the
4919 * global throttle, which cannot tell one noisy client from many). It is
4920 * best-effort: an attacker spreading across many source addresses can still churn
4921 * the bounded connection pool, so combine it with the global throttle and
4922 * network-layer filtering.
4923 */
4924#ifndef DETWS_ENABLE_PER_IP_THROTTLE
4925#define DETWS_ENABLE_PER_IP_THROTTLE 0
4926#endif
4927
4928/** @brief Number of source IPv4 addresses tracked by the per-IP throttle (BSS bucket table). */
4929#ifndef DETWS_PER_IP_THROTTLE_SLOTS
4930#define DETWS_PER_IP_THROTTLE_SLOTS 16
4931#endif
4932
4933/** @brief Max accepted connections per window from one source IP (see DETWS_ENABLE_PER_IP_THROTTLE). */
4934#ifndef DETWS_PER_IP_THROTTLE_MAX
4935#define DETWS_PER_IP_THROTTLE_MAX 10
4936#endif
4937
4938/** @brief Per-IP throttle window length in milliseconds (see DETWS_ENABLE_PER_IP_THROTTLE). */
4939#ifndef DETWS_PER_IP_THROTTLE_WINDOW_MS
4940#define DETWS_PER_IP_THROTTLE_WINDOW_MS 10000
4941#endif
4942
4943// ---------------------------------------------------------------------------
4944// Source-IP allowlist (accept-time firewall; DETWS_ENABLE_IP_ALLOWLIST)
4945// ---------------------------------------------------------------------------
4946
4947/**
4948 * @brief Opt-in source-IP allowlist (accept-time firewall, IPv4 and IPv6).
4949 *
4950 * Default off (zero cost / no behavior change). When set, the accept callback
4951 * drops any connection whose source address is not contained in a configured
4952 * CIDR rule (add rules with listener_ip_allow_add_cidr("192.168.1.0/24") /
4953 * "2001:db8::/32"). Matching is a full-address prefix compare per family, so a v4
4954 * peer never matches a v6 rule and vice versa. An empty allowlist allows
4955 * everything, so enabling the feature before adding rules never locks the device
4956 * out. Rules live in a fixed BSS table of DETWS_IP_ALLOWLIST_SLOTS entries (no heap).
4957 *
4958 * This is a coarse first-line filter - a spoofed source address can still pass
4959 * it - so combine it with the accept throttles and network-layer filtering.
4960 */
4961#ifndef DETWS_ENABLE_IP_ALLOWLIST
4962#define DETWS_ENABLE_IP_ALLOWLIST 0
4963#endif
4964
4965/** @brief Number of CIDR rules the source-IP allowlist can hold (BSS table). */
4966#ifndef DETWS_IP_ALLOWLIST_SLOTS
4967#define DETWS_IP_ALLOWLIST_SLOTS 8
4968#endif
4969
4970// ---------------------------------------------------------------------------
4971// Brute-force auth lockout (per-source-IP; DETWS_ENABLE_AUTH_LOCKOUT)
4972// ---------------------------------------------------------------------------
4973
4974/**
4975 * @brief Opt-in per-IP brute-force lockout for HTTP auth (requires DETWS_ENABLE_AUTH).
4976 *
4977 * Default off (zero cost / no behavior change). When set, the auth gate counts
4978 * consecutive failed authentications per source address (IPv4 or IPv6, keyed on
4979 * the full address) in a fixed BSS table; after
4980 * DETWS_AUTH_LOCKOUT_THRESHOLD failures the address is locked out for
4981 * DETWS_AUTH_LOCKOUT_BASE_MS, doubling on each further failure up to
4982 * DETWS_AUTH_LOCKOUT_MAX_MS. A locked address gets 429 (Retry-After) with no
4983 * credential check; a successful auth clears it. Bounded memory (no heap); the
4984 * table evicts idle, then least-recently-used, addresses when full.
4985 */
4986#ifndef DETWS_ENABLE_AUTH_LOCKOUT
4987#define DETWS_ENABLE_AUTH_LOCKOUT 0
4988#endif
4989
4990/** @brief Number of source IPs the auth lockout tracks (BSS bucket table). */
4991#ifndef DETWS_AUTH_LOCKOUT_SLOTS
4992#define DETWS_AUTH_LOCKOUT_SLOTS 16
4993#endif
4994
4995/** @brief Consecutive failed auths from one IP before it is locked out. */
4996#ifndef DETWS_AUTH_LOCKOUT_THRESHOLD
4997#define DETWS_AUTH_LOCKOUT_THRESHOLD 5
4998#endif
4999
5000/** @brief First lockout duration in ms; doubles on each further failure. */
5001#ifndef DETWS_AUTH_LOCKOUT_BASE_MS
5002#define DETWS_AUTH_LOCKOUT_BASE_MS 1000
5003#endif
5004
5005/** @brief Maximum lockout duration in ms (the exponential backoff cap). */
5006#ifndef DETWS_AUTH_LOCKOUT_MAX_MS
5007#define DETWS_AUTH_LOCKOUT_MAX_MS 300000
5008#endif
5009
5010// ---------------------------------------------------------------------------
5011// CSRF protection (DETWS_ENABLE_CSRF)
5012// ---------------------------------------------------------------------------
5013
5014/**
5015 * @brief Opt-in CSRF protection for state-changing HTTP requests.
5016 *
5017 * Default off (zero cost / no behavior change). When set, every POST / PUT /
5018 * PATCH / DELETE must carry a valid `X-CSRF-Token` header (a stateless,
5019 * HMAC-signed token); requests without one get 403 Forbidden. GET / HEAD /
5020 * OPTIONS are exempt (they are not state-changing). Clients fetch a token from
5021 * the built-in `GET /csrf` endpoint, which also sets it as the `csrf` cookie.
5022 * No server-side session storage - the token self-validates against an HMAC
5023 * secret seeded from the hardware RNG at begin(); it is independent of
5024 * DETWS_ENABLE_AUTH.
5025 */
5026#ifndef DETWS_ENABLE_CSRF
5027#define DETWS_ENABLE_CSRF 0
5028#endif
5029
5030// ---------------------------------------------------------------------------
5031// Telnet sizing constants (DETWS_ENABLE_TELNET must be 1)
5032// ---------------------------------------------------------------------------
5033
5034/** @brief Maximum simultaneous Telnet connections. */
5035#ifndef MAX_TELNET_CONNS
5036#define MAX_TELNET_CONNS 2
5037#endif
5038
5039/** @brief Stack buffer for one Telnet I/O chunk. */
5040#ifndef TELNET_BUF_SIZE
5041#define TELNET_BUF_SIZE 256
5042#endif
5043
5044// ---------------------------------------------------------------------------
5045// SSH sizing constants (DETWS_ENABLE_SSH must be 1)
5046// ---------------------------------------------------------------------------
5047
5048/** @brief Maximum simultaneous SSH connections. */
5049#ifndef MAX_SSH_CONNS
5050#define MAX_SSH_CONNS 1
5051#endif
5052
5053/**
5054 * @brief Maximum concurrent SSH channels per connection (RFC 4254 multiplexing).
5055 *
5056 * Default 1 - one "session" channel per connection, byte-for-byte the original
5057 * single-channel behavior. Raise it to multiplex several channels (e.g. several
5058 * concurrent shells/exec, or - with the forwarding build flags - tunnels) over one
5059 * SSH connection; each channel gets its own id, window, and peer state. Fixed BSS
5060 * (ssh_chan[MAX_SSH_CONNS][DETWS_SSH_MAX_CHANNELS]), no heap.
5061 */
5062#ifndef DETWS_SSH_MAX_CHANNELS
5063#define DETWS_SSH_MAX_CHANNELS 1
5064#endif
5065#if DETWS_SSH_MAX_CHANNELS < 1
5066#error "DeterministicESPAsyncWebServer: DETWS_SSH_MAX_CHANNELS must be >= 1"
5067#endif
5068
5069/**
5070 * @brief SSH TCP port forwarding (`direct-tcpip`, i.e. `ssh -L`). Default off.
5071 *
5072 * When set, the SSH server can open an outbound TCP connection to a client-named
5073 * host:port and bridge bytes between that socket and the SSH channel - the
5074 * `ssh_forward` owner does the I/O via the outbound client transport (det_client),
5075 * so it needs `DETWS_CLIENT_CONNS >= DETWS_SSH_FWD_MAX` and a channel pool
5076 * (`DETWS_SSH_MAX_CHANNELS > 1`) to be useful. Forwarding is still opt-in at
5077 * runtime: nothing is forwarded until the application calls `ssh_forward_begin()`.
5078 * Off = the channel codec refuses every `direct-tcpip` open (no open relay).
5079 */
5080#ifndef DETWS_SSH_PORT_FORWARD
5081#define DETWS_SSH_PORT_FORWARD 0
5082#endif
5083
5084/** @brief Maximum concurrent forwarded TCP connections (must be <= DETWS_CLIENT_CONNS). */
5085#ifndef DETWS_SSH_FWD_MAX
5086#define DETWS_SSH_FWD_MAX 2
5087#endif
5088
5089/** @brief Maximum forward target hostname length including null terminator. */
5090#ifndef DETWS_SSH_FWD_HOST_MAX
5091#define DETWS_SSH_FWD_HOST_MAX 64
5092#endif
5093
5094/** @brief Blocking connect timeout (ms) when opening a forward target. */
5095#ifndef DETWS_SSH_FWD_CONNECT_MS
5096#define DETWS_SSH_FWD_CONNECT_MS 3000
5097#endif
5098
5099/** @brief Max bytes moved per forward channel per poll, target -> client (<= SSH_PKT_BUF_SIZE). */
5100#ifndef DETWS_SSH_FWD_CHUNK
5101#define DETWS_SSH_FWD_CHUNK 1024
5102#endif
5103
5104/**
5105 * @brief Maximum concurrent remote-forward listeners (`ssh -R` / `tcpip-forward`).
5106 *
5107 * Each accepted client that requests remote forwarding can bind up to this many
5108 * ports on the device; each binding consumes one `listener_pool[]` slot, so
5109 * `MAX_LISTENERS` must have that much headroom above the app's own listeners.
5110 * Remote forwarding shares `DETWS_SSH_PORT_FORWARD` (compiled in) and is inert
5111 * until `ssh_forward_begin()`.
5112 */
5113#ifndef DETWS_SSH_RFWD_MAX
5114#define DETWS_SSH_RFWD_MAX 1
5115#endif
5116
5117/**
5118 * @brief Maximum concurrent bridged connections across all remote forwards.
5119 *
5120 * Each connection accepted on a forwarded port occupies one transport `conn_pool`
5121 * slot plus one SSH channel (so it needs `DETWS_SSH_MAX_CHANNELS` headroom) and one
5122 * entry here while it is bridged back to the client.
5123 */
5124#ifndef DETWS_SSH_RFWD_BRIDGE_MAX
5125#define DETWS_SSH_RFWD_BRIDGE_MAX 2
5126#endif
5127
5128/** @brief Packet assembly buffer per SSH connection (bytes). */
5129#ifndef SSH_PKT_BUF_SIZE
5130#define SSH_PKT_BUF_SIZE 2048
5131#endif
5132
5133/**
5134 * @brief SSH server-to-client compression (`zlib@openssh.com` / `zlib`, RFC 4253 sec 6.2). Default off.
5135 *
5136 * When set, the server advertises `zlib@openssh.com` (delayed, OpenSSH's default) and `zlib` for the
5137 * SERVER->CLIENT direction and, once active, compresses every outbound packet payload with a
5138 * context-takeover DEFLATE stream (a persistent sliding window carried across packets, sync-flushed
5139 * per packet - RFC 1951 / RFC 1950). Client->server stays `none`: SSH negotiates each direction
5140 * independently, and the inbound direction (keystrokes / uploads to the device) is tiny and, because
5141 * OpenSSH compresses outbound with Z_PARTIAL_FLUSH, would need a far larger resumable inflate engine
5142 * for little gain. `ssh -o Compression=yes` still gets real compression on the high-volume direction.
5143 *
5144 * PSRAM-class: each connection holds a compressor (~window + hash tables, tens of KB). A compile-time
5145 * guard rejects this on ARDUINO without DETWS_SSH_ZLIB_IN_PSRAM. Requires DETWS_ENABLE_SSH.
5146 */
5147#ifndef DETWS_ENABLE_SSH_ZLIB
5148#define DETWS_ENABLE_SSH_ZLIB 0
5149#endif
5150
5151/**
5152 * @brief Place the per-connection SSH compression state in external PSRAM (ESP32).
5153 *
5154 * Like DETWS_H2_POOL_IN_PSRAM / DETWS_TLS_ARENA_IN_PSRAM: moves the compressor pool
5155 * (MAX_SSH_CONNS of them) to external RAM via `EXT_RAM_BSS_ATTR`. Needs a framework built with
5156 * `CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY=y` (tools/psram/README.md).
5157 */
5158#ifndef DETWS_SSH_ZLIB_IN_PSRAM
5159#define DETWS_SSH_ZLIB_IN_PSRAM 0
5160#endif
5161
5162/**
5163 * @brief Acknowledge placing the SSH compressor in internal DRAM (no PSRAM).
5164 *
5165 * The per-connection compressor is ~48 KB. With MAX_SSH_CONNS=1 and no TLS server it fits internal
5166 * DRAM on a roomy chip (S3 / P4). Rather than force PSRAM, this mirrors DETWS_TLS_ACK_MULTI_CONN_DRAM:
5167 * set it to 1 to consciously accept the internal-DRAM cost when DETWS_SSH_ZLIB_IN_PSRAM is off. The
5168 * build otherwise fails fast on ARDUINO with guidance (below) instead of a raw linker overflow.
5169 */
5170#ifndef DETWS_SSH_ZLIB_ACK_DRAM
5171#define DETWS_SSH_ZLIB_ACK_DRAM 0
5172#endif
5173
5174/**
5175 * @brief SSH s2c DEFLATE sliding-window size in bytes (max back-reference distance). Power of two,
5176 * 256..32768. Larger = better ratio + more per-connection RAM (the compressor holds a window-sized
5177 * work buffer + a window-sized hash chain). The client always allocates a 32 KB inflate window, so
5178 * any value here interoperates; 8 KB is a good ratio/RAM balance for terminal + command output.
5179 */
5180#ifndef DETWS_SSH_ZLIB_WINDOW
5181#define DETWS_SSH_ZLIB_WINDOW 8192
5182#endif
5183
5184/**
5185 * @brief Largest uncompressed payload the s2c compressor accepts in one call (bytes). Outbound SSH
5186 * payloads are bounded by SSH_PKT_BUF_SIZE; this sizes the compressor's history+input work buffer.
5187 */
5188#ifndef DETWS_SSH_ZLIB_MAX_IN
5189#define DETWS_SSH_ZLIB_MAX_IN 2048
5190#endif
5191
5192/** @brief Maximum SSH username length including null terminator. */
5193#ifndef SSH_MAX_USERNAME_LEN
5194#define SSH_MAX_USERNAME_LEN 32
5195#endif
5196
5197/** @brief Maximum SSH password length including null terminator. */
5198#ifndef SSH_MAX_PASSWORD_LEN
5199#define SSH_MAX_PASSWORD_LEN 64
5200#endif
5201
5202/**
5203 * @brief Shared scratch buffer for SSH big-number operations.
5204 *
5205 * Holds Montgomery multiplication temporaries and RSA padding workspace.
5206 * Must be large enough for the biggest single crypto operation:
5207 *
5208 * DH expmod:
5209 * base_mont (SshBigNum = 256 B)
5210 * result (SshBigNum = 256 B)
5211 * tmp (SshBigNum = 256 B)
5212 * mont_t (uint32_t[129] = 516 B)
5213 * ─────
5214 * 1284 B → round up with margin → 1536 B
5215 *
5216 * The buffer is zeroed via volatile memset immediately after each operation.
5217 * Only one SSH KEX may run at a time (guaranteed by the single Arduino loop
5218 * task and MAX_SSH_CONNS synchronous handshake model).
5219 */
5220#ifndef SSH_CRYPTO_WORK_SIZE
5221#define SSH_CRYPTO_WORK_SIZE 1536
5222#endif
5223
5224/**
5225 * @brief Size in bytes of the shared per-dispatch scratch arena.
5226 *
5227 * Codec / protocol handlers borrow transient working memory from this single BSS
5228 * arena (see network_drivers/session/scratch.h) instead of each feature owning a
5229 * dedicated buffer. The session layer empties it before every event dispatch, so
5230 * it only needs to hold the *peak concurrent* scratch of any one dispatch, not
5231 * the sum across features. Tune from the scratch_high_water() reading on a real
5232 * workload; an over-budget borrow fails closed (scratch_alloc returns nullptr).
5233 */
5234#ifndef DETWS_SCRATCH_ARENA_SIZE
5235#define DETWS_SCRATCH_ARENA_SIZE 8192
5236#endif
5237
5238// ---------------------------------------------------------------------------
5239// Static RAM (BSS) usage table
5240// ---------------------------------------------------------------------------
5241//
5242// All library memory is in BSS - allocated at link time, zero-initialized by
5243// the C runtime, never heap-allocated after begin(). The table below shows
5244// the contribution of every feature at its default constant values.
5245//
5246// Sizes are for ESP32 (32-bit pointers, int = 4 B). Where a size depends on
5247// a macro the formula is given so you can compute the impact of any change.
5248//
5249// ┌──────────────────────────────┬──────────────────────────────────────────────────────────────┬──────────┐
5250// │ Symbol / pool │ Size formula │ Default │
5251// ├──────────────────────────────┼──────────────────────────────────────────────────────────────┼──────────┤
5252// │ TRANSPORT LAYER (always on) │ │ │
5253// │ conn_pool[MAX_CONNS] │ MAX_CONNS × (RX_BUF_SIZE + 22) │ 4 168 B │
5254// │ listener_pool[MAX_LISTENERS]│ MAX_LISTENERS × (StaticQueue_t≈48 + EVT_QUEUE_DEPTH×12 + 18)│ 654 B │
5255// │ conn_timeout_ms │ 4 B │ 4 B │
5256// │ TRANSPORT SUBTOTAL │ │ 4 826 B │
5257// ├──────────────────────────────┼──────────────────────────────────────────────────────────────┼──────────┤
5258// │ HTTP PRESENTATION (always on)│ │ │
5259// │ http_pool[MAX_CONNS] │ MAX_CONNS × (MAX_PATH_LEN + MAX_QUERY_LEN │ │
5260// │ │ + MAX_HEADERS×(MAX_KEY_LEN+MAX_VAL_LEN) │ │
5261// │ │ + MAX_QUERY_PARAMS×(QUERY_KEY_LEN+QUERY_VAL_LEN) │ │
5262// │ │ + BODY_BUF_SIZE + 50) │ 6 668 B │
5263// │ HTTP SUBTOTAL │ │ 6 668 B │
5264// ├──────────────────────────────┼──────────────────────────────────────────────────────────────┼──────────┤
5265// │ WEBSOCKET (DETWS_ENABLE_WEBSOCKET=1) │ │
5266// │ ws_pool[MAX_WS_CONNS] │ MAX_WS_CONNS × (WS_FRAME_SIZE + 29) │ 1 082 B │
5267// ├──────────────────────────────┼──────────────────────────────────────────────────────────────┼──────────┤
5268// │ SSE (DETWS_ENABLE_SSE=1) │ │ │
5269// │ sse_pool[MAX_SSE_CONNS] │ MAX_SSE_CONNS × (MAX_PATH_LEN + 3) │ 134 B │
5270// ├──────────────────────────────┼──────────────────────────────────────────────────────────────┼──────────┤
5271// │ SSH (DETWS_ENABLE_SSH=1) │ │ │
5272// │ ssh_pool[MAX_SSH_CONNS] │ MAX_SSH_CONNS × (SSH_PKT_BUF_SIZE + 22) │ 2 070 B │
5273// │ ssh_keys[MAX_SSH_CONNS] │ MAX_SSH_CONNS × (2×SshAesCtrCtx + 64) │ │
5274// │ └─ SshAesCtrCtx (native) │ rk[60]=240 + counter[16] + keystream[16] + pos[1] = 273 B │ 610 B │
5275// │ └─ SshAesCtrCtx (ARDUINO) │ mbedtls_aes_context (≈284 B) + 33 B = ≈317 B per ctx │ 698 B │
5276// │ ssh_dh[MAX_SSH_CONNS] │ MAX_SSH_CONNS × (3×SshBigNum[256] + H[32] + 1) │ 801 B │
5277// │ crypto_work[] │ SSH_CRYPTO_WORK_SIZE (scratch, wiped after each use) │ 1 536 B │
5278// │ SSH SUBTOTAL │ │ 5 017 B │
5279// ├──────────────────────────────┼──────────────────────────────────────────────────────────────┼──────────┤
5280// │ GRAND TOTAL (all features) │ │ ≈18 KB │
5281// └──────────────────────────────┴──────────────────────────────────────────────────────────────┴──────────┘
5282//
5283// ESP32 has 320 KB of SRAM; the library uses ~5–18 KB depending on features.
5284// Stack usage is separate; the largest frame is during SSH DH key exchange
5285// (~256 B for the SshBigNum private scalar on the call stack before it is
5286// zeroed by ssh_dh_finish()).
5287//
5288// SSH KEY MATERIAL IS NOT IN THE TABLE ABOVE intentionally:
5289// - The RSA host private key is NEVER stored in any static array. It is
5290// loaded from NVS into a local stack frame at sign time, used once, then
5291// explicitly zeroed (volatile memset) before the function returns.
5292// - AES session keys and HMAC keys live in ssh_keys[] (above), which is a
5293// separate BSS symbol from ssh_pool[]. Physical separation means a
5294// buffer overflow in the packet receive path (ssh_pool[].pkt_buf) cannot
5295// reach the key material without crossing a distinct linker symbol - a
5296// significant barrier against heap/BSS spray attacks.
5297// - The DH ephemeral private scalar y lives in ssh_dh[].y and is zeroed
5298// immediately after the shared secret K is derived.
5299// - crypto_work[] is zeroed via volatile memset after every use so that
5300// bignum intermediates (including partial products that contain key
5301// material) do not persist in memory.
5302
5303// ---------------------------------------------------------------------------
5304// Runtime configuration struct
5305// ---------------------------------------------------------------------------
5306
5307/**
5308 * @brief Runtime-tunable server parameters.
5309 *
5310 * Can be declared as `const PROGMEM` (flash) or as a mutable variable (RAM).
5311 * Pass a pointer to DetWebServer::begin() or DeterministicAsyncTCP::init().
5312 */
5314{
5315 /** Milliseconds of inactivity before a connection is force-closed. */
5317};
5318
5319// ---------------------------------------------------------------------------
5320// Protocol identifier
5321// ---------------------------------------------------------------------------
5322
5323/**
5324 * @brief Application protocol spoken on a listener port or connection slot.
5325 *
5326 * Stored in both Listener::proto and TcpConn::proto. The session layer uses
5327 * this to route events to the correct protocol handler without branching on
5328 * port numbers.
5329 *
5330 * All values are always present regardless of feature flags - the enum is
5331 * part of the listener API. Feature flags gate the implementation, not the
5332 * identifier.
5333 */
5334enum class ConnProto : uint8_t
5335{
5336 PROTO_NONE = 0, ///< Unassigned slot.
5337 PROTO_HTTP = 1, ///< HTTP/1.1 with optional WS and SSE upgrades.
5338 PROTO_TELNET = 2, ///< Telnet (RFC 854).
5339 PROTO_SSH = 3, ///< SSH (RFC 4253/4252/4254).
5340 PROTO_MODBUS = 4, ///< Modbus TCP slave (Modbus Application Protocol).
5341 PROTO_OPCUA = 5, ///< OPC UA Binary (UA-TCP) server.
5342 PROTO_SSH_RFWD = 6, ///< SSH remote-forward listener (ssh -R): accepts bridge to a forwarded-tcpip channel.
5343 PROTO_RELAY = 7, ///< TCP relay / DNAT (DETWS_ENABLE_RELAY): bridge to an origin det_client connection.
5344 PROTO_BRIDGE = 8, ///< address:port -> hardware bus (DETWS_ENABLE_IFACE_BRIDGE): UART/SPI/I2C device server.
5345 PROTO_NTRIP_CASTER = 9, ///< NTRIP caster (DETWS_ENABLE_NTRIP_CASTER): serves RTCM3 corrections to rovers.
5346};
5347
5348/**
5349 * @brief Network interface a connection arrived on (for per-route filtering).
5350 *
5351 * Stamped onto each TcpConn at accept time by comparing the connection's local
5352 * IP to the softAP IP (see DetWebServer::set_ap_ip()). Used to gate routes to
5353 * the station or softAP interface only (DetWebServer::on(..., DetIface)).
5354 */
5355enum class DetIface : uint8_t
5356{
5357 DETIFACE_ANY = 0, ///< Unknown / no filter (matches any interface).
5358 DETIFACE_STA = 1, ///< Station interface (joined to an AP / your LAN).
5359 DETIFACE_AP = 2, ///< softAP interface (clients joined to the device).
5360 DETIFACE_ETH = 3, ///< Ethernet interface (wired PHY).
5361};
5362
5363// ---------------------------------------------------------------------------
5364// Diagnostic JSON string (only defined when DETWS_ENABLE_DIAG == 1)
5365// ---------------------------------------------------------------------------
5366// DETWS_DIAG_JSON is a compile-time string literal - zero runtime cost.
5367// Adjacent string literals are concatenated by the compiler; DETWS_STR()
5368// stringifies an integer macro value without evaluating it twice.
5369
5370#if DETWS_ENABLE_DIAG
5371
5372#define _DETWS_STR_(x) #x
5373#define _DETWS_STR(x) _DETWS_STR_(x)
5374
5375#if DETWS_ENABLE_WEBSOCKET
5376#define _DETWS_F_WS "true"
5377#else
5378#define _DETWS_F_WS "false"
5379#endif
5380
5381#if DETWS_ENABLE_SSE
5382#define _DETWS_F_SSE "true"
5383#else
5384#define _DETWS_F_SSE "false"
5385#endif
5386
5387#if DETWS_ENABLE_MULTIPART
5388#define _DETWS_F_MP "true"
5389#else
5390#define _DETWS_F_MP "false"
5391#endif
5392
5393#if DETWS_ENABLE_FILE_SERVING
5394#define _DETWS_F_FS "true"
5395#else
5396#define _DETWS_F_FS "false"
5397#endif
5398
5399#if DETWS_ENABLE_AUTH
5400#define _DETWS_F_AUTH "true"
5401#else
5402#define _DETWS_F_AUTH "false"
5403#endif
5404
5405// Every DETWS_ENABLE_* defaults to 0/1, so a two-level token-paste stringifies each to "true"/"false" and
5406// the protocol keys below stay one line each (no 5-line #if per flag). Lets tooling (pentesting/detws_pentest.py)
5407// auto-detect which protocols are built and select the applicable attacks without a manual --all.
5408#define _DETWS_BOOL_0 "false"
5409#define _DETWS_BOOL_1 "true"
5410#define _DETWS_FB2(v) _DETWS_BOOL_##v
5411#define _DETWS_FB(v) _DETWS_FB2(v)
5412
5413#define DETWS_DIAG_JSON \
5414 "{" \
5415 "\"lib\":\"DeterministicESPAsyncWebServer\"," \
5416 "\"features\":{" \
5417 "\"websocket\":" _DETWS_F_WS "," \
5418 "\"sse\":" _DETWS_F_SSE "," \
5419 "\"multipart\":" _DETWS_F_MP "," \
5420 "\"file_serving\":" _DETWS_F_FS "," \
5421 "\"auth\":" _DETWS_F_AUTH "," \
5422 "\"webdav\":" _DETWS_FB( \
5423 DETWS_ENABLE_WEBDAV) "," \
5424 "\"coap\":" _DETWS_FB(DETWS_ENABLE_COAP) "," \
5425 "\"snmp\":" _DETWS_FB( \
5426 DETWS_ENABLE_SNMP) "," \
5427 "\"opcua\":" _DETWS_FB( \
5428 DETWS_ENABLE_OPCUA) "," \
5429 "\"umati\":" _DETWS_FB(DETWS_ENABLE_UMATI) "," \
5430 "\"modbus\":" _DETWS_FB(DETWS_ENABLE_MODBUS) "," \
5431 "\"mqtt\":" _DETWS_FB(DETWS_ENABLE_MQTT) "," \
5432 "\"mtconnect\":" _DETWS_FB(DETWS_ENABLE_MTCONNECT) "," \
5433 "\"redis\":" _DETWS_FB(DETWS_ENABLE_REDIS) "," \
5434 "\"ftp\":" _DETWS_FB(DETWS_ENABLE_FTP) "," \
5435 "\"smtp\":" _DETWS_FB( \
5436 DETWS_ENABLE_SMTP) "," \
5437 "\"smb\":" _DETWS_FB(DETWS_ENABLE_SMB) "," \
5438 "\"syslog\":" _DETWS_FB( \
5439 DETWS_ENABLE_SYSLOG) "," \
5440 "\"ntp_server\":" _DETWS_FB( \
5441 DETWS_ENABLE_NTP_SERVER) "," \
5442 "\"dns_server\":" _DETWS_FB(DETWS_ENABLE_DNS_SERVER) "," \
5443 "\"nats\":" _DETWS_FB(DETWS_ENABLE_NATS) "," \
5444 "\"stomp\":" _DETWS_FB( \
5445 DETWS_ENABLE_STOMP) "," \
5446 "\"statsd\":" _DETWS_FB(DETWS_ENABLE_STATSD) "," \
5447 "\"jwt\":" _DETWS_FB(DETWS_ENABLE_JWT) "," \
5448 "\"tls\":" _DETWS_FB(DETWS_ENABLE_TLS) "," \
5449 "\"http2\":" _DETWS_FB(DETWS_ENABLE_HTTP2) "," \
5450 "\"http3\":" _DETWS_FB(DETWS_ENABLE_HTTP3) "," \
5451 "\"ssh\":" _DETWS_FB(DETWS_ENABLE_SSH) "," \
5452 "\"ws_deflate\":" _DETWS_FB(DETWS_ENABLE_WS_DEFLATE) "," \
5453 "\"range\":" _DETWS_FB( \
5454 DETWS_ENABLE_RANGE) "," \
5455 "\"csrf\":" _DETWS_FB(DETWS_ENABLE_CSRF) "," \
5456 "\"accept_throttle\":" _DETWS_FB(DETWS_ENABLE_ACCEPT_THROTTLE) "," \
5457 "\"per_ip_throttle\":" _DETWS_FB( \
5458 DETWS_ENABLE_PER_IP_THROTTLE) "," \
5459 "\"auth_lockout\":" _DETWS_FB(DETWS_ENABLE_AUTH_LOCKOUT) "}," \
5460 "\"config\":{" \
5461 "\"MAX_CONNS\":" _DETWS_STR( \
5462 MAX_CONNS) "," \
5463 "\"RX_BUF_SIZE\":" _DETWS_STR( \
5464 RX_BUF_SIZE) "," \
5465 "\"BODY_BUF_SIZE\":" _DETWS_STR(BODY_BUF_SIZE) "," \
5466 "\"MAX_ROUTES\":" _DETWS_STR(MAX_ROUTES) "," \
5467 "\"MAX_" \
5468 "HEADERS\"" \
5469 ":" _DETWS_STR( \
5470 MAX_HEADERS) "," \
5471 "\"MAX_PATH_" \
5472 "LEN\"" \
5473 ":" _DETWS_STR( \
5474 MAX_PATH_LEN) "," \
5475 "\"MAX_KEY_LEN\":" _DETWS_STR(MAX_KEY_LEN) "," \
5476 "\"MAX_VAL_LEN\":" _DETWS_STR( \
5477 MAX_VAL_LEN) "," \
5478 "\"MAX_QUERY_LEN\":" _DETWS_STR( \
5479 MAX_QUERY_LEN) "," \
5480 "\"MAX_QUERY_PARAMS\":" _DETWS_STR( \
5481 MAX_QUERY_PARAMS) "," \
5482 "\"CONN_TIMEOUT_MS\":" _DETWS_STR( \
5483 CONN_TIMEOUT_MS) "," \
5484 "\"RESP_HDR_BUF_SIZE\":" _DETWS_STR(RESP_HDR_BUF_SIZE) "," \
5485 "\"WS_HDR_BUF_SIZE\":" _DETWS_STR( \
5486 WS_HDR_BUF_SIZE) "," \
5487 "\"CORS_HDR_BUF_SIZE\":" _DETWS_STR(CORS_HDR_BUF_SIZE) "," \
5488 "\"EVT_QUEUE_DEPTH\":" _DETWS_STR( \
5489 EVT_QUEUE_DEPTH) "}" \
5490 "}"
5491
5492#endif // DETWS_ENABLE_DIAG
5493
5494// ---------------------------------------------------------------------------
5495// Compile-time sanity checks
5496// ---------------------------------------------------------------------------
5497// These produce a clear #error message in the compiler output rather than a
5498// cryptic linker failure or silent misbehavior.
5499
5500#if EVT_QUEUE_DEPTH < MAX_CONNS * 4
5501#error \
5502 "DeterministicESPAsyncWebServer: EVT_QUEUE_DEPTH must be >= MAX_CONNS * 4 to absorb event bursts without blocking lwIP"
5503#endif
5504
5505#if MAX_CONNS < 1
5506#error "DeterministicESPAsyncWebServer: MAX_CONNS must be >= 1"
5507#endif
5508
5509#if MAX_CONNS > 255
5510#error "DeterministicESPAsyncWebServer: MAX_CONNS must be <= 255 (slot IDs are uint8_t)"
5511#endif
5512
5513#if DETWS_ENABLE_WEBSOCKET && DETWS_ENABLE_SSE
5514#if MAX_WS_CONNS + MAX_SSE_CONNS > MAX_CONNS
5515#error "DeterministicESPAsyncWebServer: MAX_WS_CONNS + MAX_SSE_CONNS must not exceed MAX_CONNS"
5516#endif
5517#elif DETWS_ENABLE_WEBSOCKET
5518#if MAX_WS_CONNS > MAX_CONNS
5519#error "DeterministicESPAsyncWebServer: MAX_WS_CONNS must not exceed MAX_CONNS"
5520#endif
5521#elif DETWS_ENABLE_SSE
5522#if MAX_SSE_CONNS > MAX_CONNS
5523#error "DeterministicESPAsyncWebServer: MAX_SSE_CONNS must not exceed MAX_CONNS"
5524#endif
5525#endif
5526
5527#if BODY_BUF_SIZE < 1
5528#error "DeterministicESPAsyncWebServer: BODY_BUF_SIZE must be >= 1"
5529#endif
5530
5531#if BODY_BUF_SIZE > RX_BUF_SIZE
5532#error "DeterministicESPAsyncWebServer: BODY_BUF_SIZE must not exceed RX_BUF_SIZE (parser reads from the ring buffer)"
5533#endif
5534
5535#if DETWS_ENABLE_FILE_SERVING && FILE_CHUNK_SIZE > RX_BUF_SIZE
5536#error "DeterministicESPAsyncWebServer: FILE_CHUNK_SIZE must not exceed RX_BUF_SIZE"
5537#endif
5538
5539#if MAX_KEY_LEN < 4
5540#error "DeterministicESPAsyncWebServer: MAX_KEY_LEN must be >= 4 (minimum valid HTTP header name length)"
5541#endif
5542
5543#if MAX_VAL_LEN < 1
5544#error "DeterministicESPAsyncWebServer: MAX_VAL_LEN must be >= 1"
5545#endif
5546
5547#if MAX_PATH_LEN < 2
5548#error "DeterministicESPAsyncWebServer: MAX_PATH_LEN must be >= 2 (minimum: \"/\")"
5549#endif
5550
5551#if MAX_ROUTES < 1
5552#error "DeterministicESPAsyncWebServer: MAX_ROUTES must be >= 1"
5553#endif
5554
5555#if MAX_MIDDLEWARE < 1
5556#error "DeterministicESPAsyncWebServer: MAX_MIDDLEWARE must be >= 1"
5557#endif
5558
5559#if CHUNK_BUF_SIZE < 16
5560#error "DeterministicESPAsyncWebServer: CHUNK_BUF_SIZE must be >= 16"
5561#endif
5562
5563#if JSON_MAX_DEPTH < 1
5564#error "DeterministicESPAsyncWebServer: JSON_MAX_DEPTH must be >= 1"
5565#endif
5566
5567#if RE_MAX_STEPS < 64
5568#error "DeterministicESPAsyncWebServer: RE_MAX_STEPS must be >= 64"
5569#endif
5570
5571// RSA-2048 verification (OIDC / SSH host key / JWKS) runs on a worker task and consumes
5572// ~7 KB of stack via the mbedTLS bignum modexp. Enforce the documented floor so a lowered
5573// worker stack is caught at build time instead of overflowing on the first verify.
5574#if DETWS_ENABLE_OIDC && !DETWS_ENABLE_SSH && (DETWS_WORKER_TASK_STACK < DETWS_WORKER_STACK_RSA_MIN)
5575#error \
5576 "DeterministicESPAsyncWebServer: DETWS_WORKER_TASK_STACK is below DETWS_WORKER_STACK_RSA_MIN; RSA-2048 verification (OIDC) needs ~7 KB of worker stack - raise DETWS_WORKER_TASK_STACK (>= 8192) or marshal RSA verifies onto a dedicated larger-stack task"
5577#endif
5578
5579// SSH additionally can negotiate curve25519-sha256 + ssh-ed25519, whose software field
5580// arithmetic peaks at ~10.5 KB of worker stack (deeper than the RSA path). Enforce the
5581// higher floor so a lowered stack is caught at build time instead of tripping the task
5582// stack canary on the first modern-crypto handshake.
5583#if (DETWS_ENABLE_SSH || DETWS_ENABLE_HTTP3) && (DETWS_WORKER_TASK_STACK < DETWS_WORKER_STACK_CURVE_MIN)
5584#error \
5585 "DeterministicESPAsyncWebServer: DETWS_WORKER_TASK_STACK is below DETWS_WORKER_STACK_CURVE_MIN; SSH and HTTP/3 (QUIC TLS-1.3) curve25519/ed25519 need ~10.5 KB of worker stack - raise DETWS_WORKER_TASK_STACK (>= 12288) or marshal the handshake onto a dedicated larger-stack task"
5586#endif
5587
5588// The PQ/T hybrid KEX (DETWS_ENABLE_PQC_KEX) runs ML-KEM-768 Encaps in the handshake path, whose NTT
5589// + sampling peak at ~7 KB of worker stack on top of the classical curve/ed25519 work. Enforce a
5590// higher floor so it is caught at build time rather than overflowing on the first hybrid handshake.
5591#ifndef DETWS_WORKER_STACK_PQC_MIN
5592#define DETWS_WORKER_STACK_PQC_MIN 16384
5593#endif
5594#if DETWS_ENABLE_PQC_KEX && (DETWS_ENABLE_SSH || DETWS_ENABLE_HTTP3) && \
5595 (DETWS_WORKER_TASK_STACK < DETWS_WORKER_STACK_PQC_MIN)
5596#error \
5597 "DeterministicESPAsyncWebServer: DETWS_WORKER_TASK_STACK is below DETWS_WORKER_STACK_PQC_MIN; the ML-KEM-768 hybrid KEX (DETWS_ENABLE_PQC_KEX) needs ~7 KB more worker stack - raise DETWS_WORKER_TASK_STACK (>= 16384) or marshal the handshake onto a dedicated larger-stack task"
5598#endif
5599
5600#if DETWS_ENABLE_TLS
5601#if MAX_TLS_CONNS < 1 || MAX_TLS_CONNS > MAX_CONNS
5602#error "DeterministicESPAsyncWebServer: MAX_TLS_CONNS must be between 1 and MAX_CONNS"
5603#endif
5604#if DETWS_TLS_ARENA_SIZE < 8192
5605#error "DeterministicESPAsyncWebServer: DETWS_TLS_ARENA_SIZE is far too small for a TLS handshake"
5606#endif
5607// Concurrent TLS guard: the whole arena is static .bss and the ESP32 internal
5608// dram0_0_seg ceiling is only ~122 KB (ROM-reserved at both ends), so a 2nd
5609// connection's arena overflows the link. Reject MAX_TLS_CONNS > 1 with a clear
5610// message unless the arena is offloaded to PSRAM or the build was consciously sized -
5611// far friendlier than the raw "region `dram0_0_seg' overflowed" linker error.
5612#if defined(ARDUINO) && (MAX_TLS_CONNS > 1) && !DETWS_TLS_ARENA_IN_PSRAM && !DETWS_TLS_ACK_MULTI_CONN_DRAM
5613#error \
5614 "DeterministicESPAsyncWebServer: MAX_TLS_CONNS > 1 - the static TLS arena will not fit the ~122 KB internal dram0_0_seg. Pick a path (docs/KNOWN_LIMITATIONS.md): set DETWS_TLS_ARENA_IN_PSRAM=1 on a PSRAM board, OR shrink records via a custom ESP-IDF build (CONFIG_MBEDTLS_SSL_IN/OUT_CONTENT_LEN + DETWS_TLS_MAX_FRAG_LEN), OR reclaim internal DRAM; then set DETWS_TLS_ACK_MULTI_CONN_DRAM=1 to confirm."
5615#endif
5616#endif
5617
5618// HTTP/2's per-connection engine pool (~MAX_CONNS x 28 KB) cannot fit internal DRAM alongside
5619// TLS, so it must live in PSRAM. Fail fast with guidance instead of the raw linker overflow.
5620#if DETWS_ENABLE_HTTP2 && defined(ARDUINO) && !DETWS_H2_POOL_IN_PSRAM
5621#error \
5622 "DeterministicESPAsyncWebServer: DETWS_ENABLE_HTTP2 needs PSRAM - the HTTP/2 engine pool (~MAX_CONNS x 28 KB) overflows the ~122 KB internal dram0_0_seg alongside TLS. Set DETWS_H2_POOL_IN_PSRAM=1 on a PSRAM board (S3 / P4 / WROVER) built with CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY=y (tools/psram/README.md)."
5623#endif
5624
5625#if DETWS_ENABLE_SNMP
5626#if SNMP_MAX_OID_LEN < 4
5627#error "DeterministicESPAsyncWebServer: SNMP_MAX_OID_LEN must be >= 4"
5628#endif
5629#if SNMP_MAX_MIB_ENTRIES < 1
5630#error "DeterministicESPAsyncWebServer: SNMP_MAX_MIB_ENTRIES must be >= 1"
5631#endif
5632#if SNMP_MAX_VARBINDS < 1
5633#error "DeterministicESPAsyncWebServer: SNMP_MAX_VARBINDS must be >= 1"
5634#endif
5635#if SNMP_MSG_BUF_SIZE < 484
5636#error "DeterministicESPAsyncWebServer: SNMP_MSG_BUF_SIZE must be >= 484 (RFC 1157 minimum)"
5637#endif
5638#endif
5639
5640#if DETWS_ENABLE_COAP
5641#if DETWS_COAP_MAX_RESOURCES < 1
5642#error "DeterministicESPAsyncWebServer: DETWS_COAP_MAX_RESOURCES must be >= 1"
5643#endif
5644#if DETWS_COAP_MAX_PATH < 2
5645#error "DeterministicESPAsyncWebServer: DETWS_COAP_MAX_PATH must be >= 2 (minimum: \"/\")"
5646#endif
5647#if DETWS_COAP_MAX_PAYLOAD < 1
5648#error "DeterministicESPAsyncWebServer: DETWS_COAP_MAX_PAYLOAD must be >= 1"
5649#endif
5650#if DETWS_COAP_MSG_BUF_SIZE < (DETWS_COAP_MAX_PAYLOAD + 16)
5651#error \
5652 "DeterministicESPAsyncWebServer: DETWS_COAP_MSG_BUF_SIZE must be >= DETWS_COAP_MAX_PAYLOAD + 16 (header + token + Content-Format option + payload marker)"
5653#endif
5654#endif
5655
5656#if DETWS_ENABLE_AUTH && MAX_AUTH_LEN < 2
5657#error "DeterministicESPAsyncWebServer: MAX_AUTH_LEN must be >= 2 when DETWS_ENABLE_AUTH is set"
5658#endif
5659
5660#if DETWS_ENABLE_PER_IP_THROTTLE
5661#if DETWS_PER_IP_THROTTLE_SLOTS < 1
5662#error \
5663 "DeterministicESPAsyncWebServer: DETWS_PER_IP_THROTTLE_SLOTS must be >= 1 when DETWS_ENABLE_PER_IP_THROTTLE is set"
5664#endif
5665#if DETWS_PER_IP_THROTTLE_MAX < 1
5666#error "DeterministicESPAsyncWebServer: DETWS_PER_IP_THROTTLE_MAX must be >= 1 when DETWS_ENABLE_PER_IP_THROTTLE is set"
5667#endif
5668#endif
5669
5670#if DETWS_ENABLE_IP_ALLOWLIST && DETWS_IP_ALLOWLIST_SLOTS < 1
5671#error "DeterministicESPAsyncWebServer: DETWS_IP_ALLOWLIST_SLOTS must be >= 1 when DETWS_ENABLE_IP_ALLOWLIST is set"
5672#endif
5673
5674#if DETWS_ENABLE_AUTH_LOCKOUT
5675#if !DETWS_ENABLE_AUTH
5676#error "DeterministicESPAsyncWebServer: DETWS_ENABLE_AUTH_LOCKOUT requires DETWS_ENABLE_AUTH"
5677#endif
5678#if DETWS_AUTH_LOCKOUT_SLOTS < 1
5679#error "DeterministicESPAsyncWebServer: DETWS_AUTH_LOCKOUT_SLOTS must be >= 1 when DETWS_ENABLE_AUTH_LOCKOUT is set"
5680#endif
5681#if DETWS_AUTH_LOCKOUT_THRESHOLD < 1
5682#error "DeterministicESPAsyncWebServer: DETWS_AUTH_LOCKOUT_THRESHOLD must be >= 1 when DETWS_ENABLE_AUTH_LOCKOUT is set"
5683#endif
5684#if DETWS_AUTH_LOCKOUT_BASE_MS < 1 || DETWS_AUTH_LOCKOUT_MAX_MS < DETWS_AUTH_LOCKOUT_BASE_MS
5685#error "DeterministicESPAsyncWebServer: need 1 <= DETWS_AUTH_LOCKOUT_BASE_MS <= DETWS_AUTH_LOCKOUT_MAX_MS"
5686#endif
5687// The backoff doubles a uint32 capped at MAX_MS, so MAX_MS must leave headroom for
5688// one more shift (cap <= 0x80000000 => cap<<1 fits in uint32 without overflow).
5689#if DETWS_AUTH_LOCKOUT_MAX_MS > 0x80000000
5690#error "DeterministicESPAsyncWebServer: DETWS_AUTH_LOCKOUT_MAX_MS must be <= 0x80000000 (2147483648)"
5691#endif
5692#endif
5693
5694#if DETWS_ENABLE_WEBDAV
5695#if !DETWS_ENABLE_FILE_SERVING
5696#error "DeterministicESPAsyncWebServer: DETWS_ENABLE_WEBDAV requires DETWS_ENABLE_FILE_SERVING"
5697#endif
5698#if DETWS_WEBDAV_BUF_SIZE < 256
5699#error "DeterministicESPAsyncWebServer: DETWS_WEBDAV_BUF_SIZE must be >= 256"
5700#endif
5701#if DETWS_METHOD_BUF_SIZE < 10
5702#error "DeterministicESPAsyncWebServer: DETWS_METHOD_BUF_SIZE must be >= 10 when DETWS_ENABLE_WEBDAV is set (PROPPATCH)"
5703#endif
5704#endif
5705
5706#if DETWS_ENABLE_MODBUS
5707#if DETWS_MODBUS_COILS < 1 || DETWS_MODBUS_DISCRETE_INPUTS < 1 || DETWS_MODBUS_HOLDING_REGS < 1 || \
5708 DETWS_MODBUS_INPUT_REGS < 1
5709#error "DeterministicESPAsyncWebServer: each DETWS_MODBUS_* table size must be >= 1 when DETWS_ENABLE_MODBUS is set"
5710#endif
5711#endif
5712
5713#if DETWS_ENABLE_KEEPALIVE && DETWS_KEEPALIVE_MAX_REQUESTS < 1
5714#error "DeterministicESPAsyncWebServer: DETWS_KEEPALIVE_MAX_REQUESTS must be >= 1 when DETWS_ENABLE_KEEPALIVE is set"
5715#endif
5716
5717#if DETWS_ENABLE_RANGE && !DETWS_ENABLE_FILE_SERVING && !DETWS_ENABLE_EDGE_CACHE
5718#error \
5719 "DeterministicESPAsyncWebServer: DETWS_ENABLE_RANGE requires DETWS_ENABLE_FILE_SERVING or DETWS_ENABLE_EDGE_CACHE"
5720#endif
5721
5722#if DETWS_ENABLE_MTLS && !DETWS_ENABLE_TLS
5723#error "DeterministicESPAsyncWebServer: DETWS_ENABLE_MTLS requires DETWS_ENABLE_TLS"
5724#endif
5725
5726#if DETWS_ENABLE_TLS_RESUMPTION && !DETWS_ENABLE_TLS
5727#error "DeterministicESPAsyncWebServer: DETWS_ENABLE_TLS_RESUMPTION requires DETWS_ENABLE_TLS"
5728#endif
5729
5730#if DETWS_ENABLE_METRICS && !DETWS_ENABLE_STATS
5731#error "DeterministicESPAsyncWebServer: DETWS_ENABLE_METRICS requires DETWS_ENABLE_STATS"
5732#endif
5733
5734#if DETWS_ENABLE_HTTP_CLIENT_TLS && !DETWS_ENABLE_TLS
5735#error "DeterministicESPAsyncWebServer: DETWS_ENABLE_HTTP_CLIENT_TLS requires DETWS_ENABLE_TLS"
5736#endif
5737
5738#if DETWS_ENABLE_MQTT_TLS && !DETWS_ENABLE_TLS
5739#error "DeterministicESPAsyncWebServer: DETWS_ENABLE_MQTT_TLS requires DETWS_ENABLE_TLS"
5740#endif
5741
5742#if DETWS_ENABLE_WS_CLIENT_TLS && !DETWS_ENABLE_TLS
5743#error "DeterministicESPAsyncWebServer: DETWS_ENABLE_WS_CLIENT_TLS requires DETWS_ENABLE_TLS"
5744#endif
5745
5746#if DETWS_ENABLE_SNMP_TRAP && !DETWS_ENABLE_SNMP
5747#error "DeterministicESPAsyncWebServer: DETWS_ENABLE_SNMP_TRAP requires DETWS_ENABLE_SNMP"
5748#endif
5749
5750#if DETWS_ENABLE_COAP_OBSERVE && !DETWS_ENABLE_COAP
5751#error "DeterministicESPAsyncWebServer: DETWS_ENABLE_COAP_OBSERVE requires DETWS_ENABLE_COAP"
5752#endif
5753
5754#if DETWS_ENABLE_COAP_BLOCK
5755#if !DETWS_ENABLE_COAP
5756#error "DeterministicESPAsyncWebServer: DETWS_ENABLE_COAP_BLOCK requires DETWS_ENABLE_COAP"
5757#endif
5758#if DETWS_COAP_BLOCK_SZX_MAX > 6
5759#error "DeterministicESPAsyncWebServer: DETWS_COAP_BLOCK_SZX_MAX must be <= 6 (block size 2^(SZX+4); SZX 7 is reserved)"
5760#endif
5761#if DETWS_COAP_MSG_BUF_SIZE < ((1 << (DETWS_COAP_BLOCK_SZX_MAX + 4)) + 16)
5762#error \
5763 "DeterministicESPAsyncWebServer: DETWS_COAP_MSG_BUF_SIZE must hold one full block (2^(DETWS_COAP_BLOCK_SZX_MAX+4)) + 16 header/option bytes"
5764#endif
5765#if DETWS_COAP_BLOCK1_MAX < (1 << (DETWS_COAP_BLOCK_SZX_MAX + 4))
5766#error "DeterministicESPAsyncWebServer: DETWS_COAP_BLOCK1_MAX must be >= one block (2^(DETWS_COAP_BLOCK_SZX_MAX+4))"
5767#endif
5768#endif
5769
5770// --- feature dependency guards (centralized; see the BUILD-FLAG DEPENDENCY TREE
5771// near the top of this file). A child feature requires its parent(s). ---
5772
5773#if DETWS_ENABLE_WS_DEFLATE && !DETWS_ENABLE_WEBSOCKET
5774#error "DeterministicESPAsyncWebServer: DETWS_ENABLE_WS_DEFLATE requires DETWS_ENABLE_WEBSOCKET"
5775#endif
5776
5777#if DETWS_ENABLE_CIA402 && !DETWS_ENABLE_CANOPEN
5778#error "DeterministicESPAsyncWebServer: DETWS_ENABLE_CIA402 requires DETWS_ENABLE_CANOPEN"
5779#endif
5780
5781#if DETWS_ENABLE_SSH_ZLIB && !DETWS_ENABLE_SSH
5782#error "DeterministicESPAsyncWebServer: DETWS_ENABLE_SSH_ZLIB requires DETWS_ENABLE_SSH"
5783#endif
5784
5785#if DETWS_ENABLE_SSH_ZLIB
5786// Window must be a power of two in [256, 32768] (32 KB is zlib's max; the client's inflate window).
5787#if (DETWS_SSH_ZLIB_WINDOW & (DETWS_SSH_ZLIB_WINDOW - 1)) != 0 || DETWS_SSH_ZLIB_WINDOW < 256 || \
5788 DETWS_SSH_ZLIB_WINDOW > 32768
5789#error "DeterministicESPAsyncWebServer: DETWS_SSH_ZLIB_WINDOW must be a power of two in [256, 32768]"
5790#endif
5791// Positions index a uint16 hash chain, so window + max-input must stay under 65535.
5792#if (DETWS_SSH_ZLIB_WINDOW + DETWS_SSH_ZLIB_MAX_IN) > 65534
5793#error "DeterministicESPAsyncWebServer: DETWS_SSH_ZLIB_WINDOW + DETWS_SSH_ZLIB_MAX_IN must be <= 65534"
5794#endif
5795// The compressor must accept a full packet payload, else a max-size outbound packet would fail to
5796// compress and drop, desyncing the stateful stream.
5797#if DETWS_SSH_ZLIB_MAX_IN < SSH_PKT_BUF_SIZE
5798#error "DeterministicESPAsyncWebServer: DETWS_SSH_ZLIB_MAX_IN must be >= SSH_PKT_BUF_SIZE"
5799#endif
5800// The per-connection compressor (window work buffer + hash chain) is ~48 KB. On ARDUINO pick a path:
5801// offload it to PSRAM (DETWS_SSH_ZLIB_IN_PSRAM, a PSRAM board built with the BSS-in-PSRAM core), or
5802// acknowledge the internal-DRAM cost (DETWS_SSH_ZLIB_ACK_DRAM, fine for MAX_SSH_CONNS=1 without TLS on
5803// a roomy S3 / P4). Fail fast with guidance instead of a raw linker overflow.
5804#if defined(ARDUINO) && !DETWS_SSH_ZLIB_IN_PSRAM && !DETWS_SSH_ZLIB_ACK_DRAM
5805#error \
5806 "DeterministicESPAsyncWebServer: DETWS_ENABLE_SSH_ZLIB - the per-connection compressor is ~48 KB. Set DETWS_SSH_ZLIB_IN_PSRAM=1 on a PSRAM board (S3 / P4 / WROVER, core built with CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY=y, tools/psram/README.md), OR set DETWS_SSH_ZLIB_ACK_DRAM=1 to accept the internal-DRAM cost (fits MAX_SSH_CONNS=1 without TLS on a roomy chip)."
5807#endif
5808#endif
5809
5810#if DETWS_ENABLE_WEB_TERMINAL && !DETWS_ENABLE_WEBSOCKET
5811#error "DeterministicESPAsyncWebServer: DETWS_ENABLE_WEB_TERMINAL requires DETWS_ENABLE_WEBSOCKET"
5812#endif
5813
5814#if DETWS_ENABLE_DASHBOARD && !DETWS_ENABLE_SSE
5815#error "DeterministicESPAsyncWebServer: DETWS_ENABLE_DASHBOARD requires DETWS_ENABLE_SSE"
5816#endif
5817
5818#if DETWS_ENABLE_SNMP_V3 && !DETWS_ENABLE_SNMP
5819#error "DeterministicESPAsyncWebServer: DETWS_ENABLE_SNMP_V3 requires DETWS_ENABLE_SNMP"
5820#endif
5821
5822#if DETWS_ENABLE_OPCUA_CLIENT && !DETWS_ENABLE_OPCUA
5823#error "DeterministicESPAsyncWebServer: DETWS_ENABLE_OPCUA_CLIENT requires DETWS_ENABLE_OPCUA (the shared OPC UA codec)"
5824#endif
5825
5826#if DETWS_ENABLE_UMATI && !DETWS_ENABLE_OPCUA
5827#error "DeterministicESPAsyncWebServer: DETWS_ENABLE_UMATI requires DETWS_ENABLE_OPCUA (it builds on the OPC UA server)"
5828#endif
5829
5830#if DETWS_ENABLE_CONFIG_IO && !DETWS_ENABLE_CONFIG_STORE
5831#error "DeterministicESPAsyncWebServer: DETWS_ENABLE_CONFIG_IO requires DETWS_ENABLE_CONFIG_STORE"
5832#endif
5833
5834#if DETWS_ENABLE_HTTP_CLIENT_TLS && !DETWS_ENABLE_HTTP_CLIENT
5835#error "DeterministicESPAsyncWebServer: DETWS_ENABLE_HTTP_CLIENT_TLS requires DETWS_ENABLE_HTTP_CLIENT"
5836#endif
5837
5838#if DETWS_ENABLE_MQTT_TLS && !DETWS_ENABLE_MQTT
5839#error "DeterministicESPAsyncWebServer: DETWS_ENABLE_MQTT_TLS requires DETWS_ENABLE_MQTT"
5840#endif
5841
5842#if DETWS_ENABLE_WS_CLIENT_TLS && !DETWS_ENABLE_WS_CLIENT
5843#error "DeterministicESPAsyncWebServer: DETWS_ENABLE_WS_CLIENT_TLS requires DETWS_ENABLE_WS_CLIENT"
5844#endif
5845
5846#if DETWS_ENABLE_WEBSOCKET && WS_FRAME_SIZE < 2
5847#error "DeterministicESPAsyncWebServer: WS_FRAME_SIZE must be >= 2 when DETWS_ENABLE_WEBSOCKET is set"
5848#endif
5849
5850#if DETWS_ENABLE_SSE && SSE_BUF_SIZE < 8
5851#error "DeterministicESPAsyncWebServer: SSE_BUF_SIZE must be >= 8 when DETWS_ENABLE_SSE is set"
5852#endif
5853
5854#if DETWS_ENABLE_MULTIPART && MAX_MULTIPART_PARTS < 1
5855#error "DeterministicESPAsyncWebServer: MAX_MULTIPART_PARTS must be >= 1 when DETWS_ENABLE_MULTIPART is set"
5856#endif
5857
5858#if RESP_HDR_BUF_SIZE < 128
5859#error "DeterministicESPAsyncWebServer: RESP_HDR_BUF_SIZE must be >= 128 (status line + headers + CORS block)"
5860#endif
5861
5862#if DETWS_ENABLE_WEBSOCKET && WS_HDR_BUF_SIZE < 128
5863#error "DeterministicESPAsyncWebServer: WS_HDR_BUF_SIZE must be >= 128 when DETWS_ENABLE_WEBSOCKET is set"
5864#endif
5865
5866#if CORS_HDR_BUF_SIZE < 64
5867#error "DeterministicESPAsyncWebServer: CORS_HDR_BUF_SIZE must be >= 64"
5868#endif
5869
5870#if RESP_HDR_BUF_SIZE < (CORS_HDR_BUF_SIZE + EXTRA_HDR_BUF_SIZE + 96)
5871#error \
5872 "DeterministicESPAsyncWebServer: RESP_HDR_BUF_SIZE must be >= CORS_HDR_BUF_SIZE + EXTRA_HDR_BUF_SIZE + 96 (status line + CORS block + custom-header block are injected into response headers)"
5873#endif
5874
5875// ===========================================================================
5876// Feature tuning knobs (grouped and gated by feature)
5877// ===========================================================================
5878//
5879// One place to turn every tunable knob - buffer sizes, table depths, limits, thresholds -
5880// so you never have to open a feature header. Each is an override-able default (set it in
5881// your build flags to change it); the owning module includes this header and uses the
5882// value. An optional feature's group is wrapped in that feature's DETWS_ENABLE_* flag
5883// (resolved above), so a knob only exists when its feature is built.
5884//
5885// What is NOT here: protocol- and algorithm-fixed constants (wire opcodes, magic bytes,
5886// crypto digest / block sizes, spec-mandated PDU / field widths, the deflate/inflate
5887// scratch sizes a static_assert pins to the table layout). Those are not knobs - changing
5888// them breaks conformance - so they stay in their feature file next to the code they bind.
5889
5890// -- Core: protocol dispatch + shared outbound transport (always built) --
5891/** @brief Size of the protocol-handler dispatch table; must exceed the largest ConnProto id. */
5892#ifndef DETWS_PROTO_MAX
5893#define DETWS_PROTO_MAX 10
5894#endif
5895// proto_register / proto_get index this table by ConnProto id, so it must be wide enough for every id.
5896static_assert((unsigned)ConnProto::PROTO_NTRIP_CASTER < DETWS_PROTO_MAX,
5897 "DETWS_PROTO_MAX must exceed the largest ConnProto id");
5898/** @brief Number of simultaneous outbound client connections (BSS pool size). */
5899#ifndef DETWS_CLIENT_CONNS
5900#define DETWS_CLIENT_CONNS 2
5901#endif
5902/**
5903 * @brief Per-connection wire receive ring size (bytes).
5904 *
5905 * Holds plaintext (plain) or ciphertext (TLS). The transport ACKs on consume
5906 * (det_client_read reopens the window), so for a large inbound transfer to never
5907 * stall the ring must hold a full TCP receive window: keep DETWS_CLIENT_RX_BUF >=
5908 * TCP_WND (~5.7 KB). The 8192 default clears that and a multi-KB TLS handshake
5909 * flight; a ring below TCP_WND can deadlock a sustained download (the peer would be
5910 * allowed to send more than the ring holds). Must exceed one TCP segment (TCP_MSS).
5911 */
5912#ifndef DETWS_CLIENT_RX_BUF
5913#define DETWS_CLIENT_RX_BUF 8192
5914#endif
5915
5916// -- SSH (network_drivers/presentation/ssh; the codec compiles when the SSH sources are
5917// built, so its knobs are always defined) --
5918/** @brief Initial receive window the SSH server advertises (RFC 4254 §5.1). */
5919#ifndef SSH_CHAN_WINDOW
5920#define SSH_CHAN_WINDOW 32768u
5921#endif
5922/** @brief Maximum SSH channel data payload the server accepts per message. */
5923#ifndef SSH_CHAN_MAX_PACKET
5924#define SSH_CHAN_MAX_PACKET 32768u
5925#endif
5926/**
5927 * @brief Re-key when either packet sequence number reaches this value.
5928 *
5929 * RFC 4253 §9 recommends re-keying after ~1 GB or one hour. A packet-count proxy is used
5930 * here; the default is well below SSH_SEQ_CLOSE_THRESHOLD so a re-key always happens before
5931 * the sequence number can wrap.
5932 */
5933#ifndef SSH_REKEY_PACKET_THRESHOLD
5934#define SSH_REKEY_PACKET_THRESHOLD 0x40000000u
5935#endif
5936/**
5937 * @brief Elapsed-time re-key trigger in milliseconds (RFC 4253 §9: "after each hour"). Default 1 hour.
5938 *
5939 * A server-initiated re-key fires when either this much time or SSH_REKEY_PACKET_THRESHOLD packets have
5940 * passed since the last KEX, whichever comes first. Set to 0 to disable the time trigger (packet-count
5941 * only). Measured with the pluggable clock (detws_millis()).
5942 */
5943#ifndef SSH_REKEY_TIME_MS
5944#define SSH_REKEY_TIME_MS 3600000u
5945#endif
5946/** @brief Max stored user name (RFC 4252 imposes no limit; we cap for BSS). */
5947#ifndef SSH_AUTH_USER_MAX
5948#define SSH_AUTH_USER_MAX 32
5949#endif
5950/** @brief Max stored password length. */
5951#ifndef SSH_AUTH_PASS_MAX
5952#define SSH_AUTH_PASS_MAX 64
5953#endif
5954/**
5955 * @brief Max stored size of the CLIENT KEXINIT payload (I_C, for the exchange hash).
5956 *
5957 * A modern OpenSSH client's KEXINIT (post-quantum KEX names + cert host-key algs + EtM
5958 * MACs + ext-info-c) runs well past 1 KB, so this must be large enough to hold it - a
5959 * smaller bound silently rejects real clients at key exchange. The packet layer already
5960 * caps any single packet at SSH_PKT_BUF_SIZE.
5961 */
5962#ifndef SSH_KEXINIT_MAX
5963#define SSH_KEXINIT_MAX 2048
5964#endif
5965
5966#if DETWS_ENABLE_AUDIT_LOG
5967// -- Audit log (services/audit_log) --
5968#ifndef DETWS_AUDIT_LOG_ENTRIES
5969#define DETWS_AUDIT_LOG_ENTRIES 32 ///< RAM ring depth (records retained for query/verify).
5970#endif
5971#ifndef DETWS_AUDIT_MSG_LEN
5972#define DETWS_AUDIT_MSG_LEN 48 ///< Max message bytes per record (truncated to fit).
5973#endif
5974#endif // DETWS_ENABLE_AUDIT_LOG
5975
5976#if DETWS_ENABLE_DEVICENET
5977// -- DeviceNet (services/devicenet) --
5978#ifndef DETWS_DEVICENET_MSG_MAX
5979#define DETWS_DEVICENET_MSG_MAX 256 ///< max reassembled fragmented message
5980#endif
5981#endif // DETWS_ENABLE_DEVICENET
5982
5983#if DETWS_ENABLE_ESPNOW
5984// -- ESP-NOW (services/espnow) --
5985#ifndef DETWS_ESPNOW_MAX_PEERS
5986#define DETWS_ESPNOW_MAX_PEERS 8 ///< Bounded peer registry size.
5987#endif
5988#endif // DETWS_ENABLE_ESPNOW
5989
5990#if DETWS_ENABLE_GRAPHQL
5991// -- GraphQL (services/graphql) --
5992#ifndef DETWS_GQL_MAX_NODES
5993#define DETWS_GQL_MAX_NODES 48 ///< Max fields across the whole query.
5994#endif
5995#ifndef DETWS_GQL_MAX_ARGS
5996#define DETWS_GQL_MAX_ARGS 24 ///< Max arguments across the whole query.
5997#endif
5998#ifndef DETWS_GQL_MAX_DEPTH
5999#define DETWS_GQL_MAX_DEPTH 6 ///< Max selection-set nesting depth.
6000#endif
6001#ifndef DETWS_GQL_NAME_MAX
6002#define DETWS_GQL_NAME_MAX 32 ///< Max field / argument name length.
6003#endif
6004#ifndef DETWS_GQL_PATH_MAX
6005#define DETWS_GQL_PATH_MAX 96 ///< Max dotted path length passed to the resolver.
6006#endif
6007#ifndef DETWS_GQL_STRBUF
6008#define DETWS_GQL_STRBUF 256 ///< Pool for decoded string-argument bytes.
6009#endif
6010#endif // DETWS_ENABLE_GRAPHQL
6011
6012#if DETWS_ENABLE_J1939
6013// -- J1939 (services/j1939; also built when NMEA 2000 is enabled) --
6014#ifndef DETWS_J1939_TP_MAX
6015#define DETWS_J1939_TP_MAX 256 ///< max reassembled TP message (spec allows up to 1785); sized down for RAM
6016#endif
6017#endif // DETWS_ENABLE_J1939
6018
6019#if DETWS_ENABLE_NMEA0183
6020// -- NMEA 0183 (services/nmea0183) --
6021#ifndef DETWS_NMEA0183_MAX_FIELDS
6022#define DETWS_NMEA0183_MAX_FIELDS 26 ///< max comma-separated fields (incl. the address field)
6023#endif
6024#endif // DETWS_ENABLE_NMEA0183
6025
6026#if DETWS_ENABLE_NMEA2000
6027// -- NMEA 2000 (services/nmea2000) --
6028#ifndef DETWS_N2K_FP_MAX
6029#define DETWS_N2K_FP_MAX 223 ///< Fast Packet max payload (6 in frame 0 + 31 x 7)
6030#endif
6031#endif // DETWS_ENABLE_NMEA2000
6032
6033#if DETWS_ENABLE_OAUTH2
6034// -- OAuth2 (services/oauth2) --
6035#ifndef DETWS_OAUTH2_TOKEN_LEN
6036#define DETWS_OAUTH2_TOKEN_LEN 768 ///< access_token / id_token buffer (JWTs are large).
6037#endif
6038#ifndef DETWS_OAUTH2_RT_LEN
6039#define DETWS_OAUTH2_RT_LEN 256 ///< refresh_token buffer.
6040#endif
6041#ifndef DETWS_OAUTH2_BODY_BUF
6042#define DETWS_OAUTH2_BODY_BUF 1024 ///< token-request body buffer.
6043#endif
6044#ifndef DETWS_OAUTH2_RESP_BUF
6045#define DETWS_OAUTH2_RESP_BUF 2048 ///< token-endpoint response buffer.
6046#endif
6047#endif // DETWS_ENABLE_OAUTH2
6048
6049#if DETWS_ENABLE_OIDC
6050// -- OIDC (services/oidc) --
6051#ifndef DETWS_OIDC_MAX_LEN
6052#define DETWS_OIDC_MAX_LEN 1600 ///< Max accepted ID-token length.
6053#endif
6054#ifndef DETWS_OIDC_SUB_LEN
6055#define DETWS_OIDC_SUB_LEN 64 ///< Captured `sub` claim buffer.
6056#endif
6057#ifndef DETWS_OIDC_EMAIL_LEN
6058#define DETWS_OIDC_EMAIL_LEN 96 ///< Captured `email` claim buffer.
6059#endif
6060#ifndef DETWS_OIDC_KID_LEN
6061#define DETWS_OIDC_KID_LEN 80 ///< Max `kid` length.
6062#endif
6063#ifndef DETWS_OIDC_JWKS_MAX
6064#define DETWS_OIDC_JWKS_MAX 16384 ///< Max JWKS document scanned; exceeds any real multi-key set, bounds the parse.
6065#endif
6066#endif // DETWS_ENABLE_OIDC
6067
6068#if DETWS_ENABLE_OPCUA
6069// -- OPC UA (services/opcua) --
6070#ifndef DETWS_OPCUA_BUF
6071#define DETWS_OPCUA_BUF 8192 ///< Server's advertised buffer / max-message size for the handshake.
6072#endif
6073#ifndef DETWS_OPCUA_READ_MAX
6074#define DETWS_OPCUA_READ_MAX 8 ///< max NodesToRead handled per ReadRequest.
6075#endif
6076#ifndef DETWS_OPCUA_BROWSE_MAX
6077#define DETWS_OPCUA_BROWSE_MAX 4 ///< max NodesToBrowse handled per BrowseRequest.
6078#endif
6079#ifndef DETWS_OPCUA_REF_MAX
6080#define DETWS_OPCUA_REF_MAX 8 ///< max references returned per browsed node.
6081#endif
6082#ifndef DETWS_OPCUA_WRITE_MAX
6083#define DETWS_OPCUA_WRITE_MAX 8 ///< max NodesToWrite handled per WriteRequest.
6084#endif
6085// Advertised server identity (endpoint descriptions), overridable per deployment; the app may
6086// also set these at runtime via opcua_set_endpoint_url() / the OpcUaServerInfo it passes.
6087#ifndef DETWS_OPCUA_DEFAULT_ENDPOINT
6088#define DETWS_OPCUA_DEFAULT_ENDPOINT "opc.tcp://localhost:4840" ///< default endpoint URL.
6089#endif
6090#ifndef DETWS_OPCUA_DEFAULT_APP_URI
6091#define DETWS_OPCUA_DEFAULT_APP_URI "urn:det:opcua:server" ///< default ApplicationUri.
6092#endif
6093#ifndef DETWS_OPCUA_DEFAULT_APP_NAME
6094#define DETWS_OPCUA_DEFAULT_APP_NAME "DetOpcUaServer" ///< default ApplicationName.
6095#endif
6096#endif // DETWS_ENABLE_OPCUA
6097
6098#if DETWS_ENABLE_PROVISIONING
6099// -- Wi-Fi provisioning credential store (services/provisioning_service) --
6100// The NVS namespace and its keys, overridable per deployment (e.g. to avoid an NVS-namespace
6101// collision with the application's own store).
6102#ifndef DETWS_PROV_NVS_NAMESPACE
6103#define DETWS_PROV_NVS_NAMESPACE "wifi_prov" ///< NVS namespace holding the saved credentials.
6104#endif
6105#ifndef DETWS_PROV_KEY_SSID
6106#define DETWS_PROV_KEY_SSID "ssid" ///< NVS key + HTML form field for the SSID.
6107#endif
6108#ifndef DETWS_PROV_KEY_PSK
6109#define DETWS_PROV_KEY_PSK "psk" ///< NVS key + HTML form field for the pre-shared key.
6110#endif
6111#endif // DETWS_ENABLE_PROVISIONING
6112
6113#if DETWS_ENABLE_VFS
6114// -- Virtual filesystem (services/vfs) --
6115#ifndef DETWS_VFS_RAM_FILES
6116#define DETWS_VFS_RAM_FILES 4 ///< RAM backend: number of files.
6117#endif
6118#ifndef DETWS_VFS_RAM_FILE_SIZE
6119#define DETWS_VFS_RAM_FILE_SIZE 1024 ///< RAM backend: max bytes per file.
6120#endif
6121#ifndef DETWS_VFS_MAX_OPEN
6122#define DETWS_VFS_MAX_OPEN 4 ///< Concurrent open handles (per backend).
6123#endif
6124#ifndef DETWS_VFS_NAME_MAX
6125#define DETWS_VFS_NAME_MAX 48 ///< Max path length (RAM backend).
6126#endif
6127#endif // DETWS_ENABLE_VFS
6128
6129#endif
DetIface
Network interface a connection arrived on (for per-route filtering).
@ DETIFACE_ETH
Ethernet interface (wired PHY).
@ DETIFACE_STA
Station interface (joined to an AP / your LAN).
@ DETIFACE_ANY
Unknown / no filter (matches any interface).
@ DETIFACE_AP
softAP interface (clients joined to the device).
ConnProto
Application protocol spoken on a listener port or connection slot.
@ PROTO_NONE
Unassigned slot.
@ PROTO_HTTP
HTTP/1.1 with optional WS and SSE upgrades.
@ PROTO_OPCUA
OPC UA Binary (UA-TCP) server.
@ PROTO_TELNET
Telnet (RFC 854).
@ PROTO_MODBUS
Modbus TCP slave (Modbus Application Protocol).
@ PROTO_BRIDGE
address:port -> hardware bus (DETWS_ENABLE_IFACE_BRIDGE): UART/SPI/I2C device server.
@ PROTO_SSH_RFWD
SSH remote-forward listener (ssh -R): accepts bridge to a forwarded-tcpip channel.
@ PROTO_RELAY
TCP relay / DNAT (DETWS_ENABLE_RELAY): bridge to an origin det_client connection.
@ PROTO_SSH
SSH (RFC 4253/4252/4254).
@ PROTO_NTRIP_CASTER
NTRIP caster (DETWS_ENABLE_NTRIP_CASTER): serves RTCM3 corrections to rovers.
#define DETWS_PROTO_MAX
Size of the protocol-handler dispatch table; must exceed the largest ConnProto id.
Runtime-tunable server parameters.
uint32_t conn_timeout_ms