ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
feature_en_error.h
Go to the documentation of this file.
1// ProtoCore v1.0.16 - Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file feature_en_error.h
6 * @brief What the enabled features require of each other and of the sizing, as compile-time refusals.
7 *
8 * Reached from protocore_config.h, which is the single entry point and states the feature flags
9 * every block here is gated on. Including this file on its own would read those flags before they
10 * are settled.
11 *
12 * @author Douglas Quigg (dstroy0)
13 * @date 2026
14 */
15
16#ifndef PROTOCORE_FEATURE_EN_ERROR_H
17#define PROTOCORE_FEATURE_EN_ERROR_H
18
19#ifndef PROTOCORE_CONFIG_H
20#error "include protocore_config.h instead of this file - it is the entry point that states the feature flags"
21#endif
22
23// These produce a clear #error in the compiler output rather than a cryptic linker failure
24// or silent misbehavior.
25
26#if PROTOCORE_WORKER_COUNT < 1
27#error "ProtoCore: PROTOCORE_WORKER_COUNT must be >= 1"
28#endif
29#if PROTOCORE_WORKER_COUNT > MAX_CONNS
30#error "ProtoCore: PROTOCORE_WORKER_COUNT must be <= MAX_CONNS"
31#endif
32
33#if PROTOCORE_ENABLE_PREEMPT_QUEUE && (PROTOCORE_PQ_DEPTH < 1 || PROTOCORE_PQ_ITEM_SIZE < 1)
34#error "ProtoCore: PROTOCORE_PQ_DEPTH and PROTOCORE_PQ_ITEM_SIZE must be >= 1"
35#endif
36
37#if PROTOCORE_ENABLE_DMA && (PROTOCORE_DMA_CHANNELS < 1 || PROTOCORE_DMA_BUF_SIZE < 1)
38#error "ProtoCore: PROTOCORE_DMA_CHANNELS and PROTOCORE_DMA_BUF_SIZE must be >= 1"
39#endif
40
41#if PROTOCORE_ENABLE_TRACE_CAPTURE && PROTOCORE_TC_MAX_WINDOW_SAMPLES < 1
42#error "ProtoCore: PROTOCORE_TC_MAX_WINDOW_SAMPLES must be >= 1"
43#endif
44
45#if PROTOCORE_ENABLE_FORWARD && \
46 (PROTOCORE_PHY_MAX_IFACES < 1 || PROTOCORE_FWD_MAX_RULES < 1 || PROTOCORE_FWD_ACL_PATLEN < 1)
47#error "ProtoCore: PROTOCORE_PHY_MAX_IFACES / PROTOCORE_FWD_MAX_RULES / PROTOCORE_FWD_ACL_PATLEN must be >= 1"
48#endif
49
50#if PROTOCORE_ENABLE_GATEWAY && (PROTOCORE_GW_MAX_PORTS < 1)
51#error "ProtoCore: PROTOCORE_GW_MAX_PORTS must be >= 1"
52#endif
53
54#if PROTOCORE_ENABLE_LORA && (PROTOCORE_LORA_MAX_PAYLOAD < 1 || PROTOCORE_LORA_MAX_PAYLOAD > 251)
55#error "ProtoCore: PROTOCORE_LORA_MAX_PAYLOAD must be 1..251"
56#endif
57
58#if PROTOCORE_ENABLE_NRF24 && (PROTOCORE_NRF24_PAYLOAD < 1 || PROTOCORE_NRF24_PAYLOAD > 32)
59#error "ProtoCore: PROTOCORE_NRF24_PAYLOAD must be 1..32"
60#endif
61
62#if PROTOCORE_ENABLE_ENOCEAN && (PROTOCORE_ENOCEAN_MAX_DATA < 1)
63#error "ProtoCore: PROTOCORE_ENOCEAN_MAX_DATA must be >= 1"
64#endif
65
66#if PROTOCORE_ENABLE_PN532 && (PROTOCORE_PN532_MAX_DATA < 1 || PROTOCORE_PN532_MAX_DATA > 254)
67#error "ProtoCore: PROTOCORE_PN532_MAX_DATA must be 1..254"
68#endif
69
70#if PROTOCORE_ENABLE_SIGFOX && (PROTOCORE_SIGFOX_MAX_PAYLOAD < 1 || PROTOCORE_SIGFOX_MAX_PAYLOAD > 12)
71#error "ProtoCore: PROTOCORE_SIGFOX_MAX_PAYLOAD must be 1..12"
72#endif
73
74#if PROTOCORE_ENABLE_ZWAVE && (PROTOCORE_ZWAVE_MAX_DATA < 1)
75#error "ProtoCore: PROTOCORE_ZWAVE_MAX_DATA must be >= 1"
76#endif
77
78#if PROTOCORE_ENABLE_ZIGBEE && (PROTOCORE_ZIGBEE_MAX_DATA < 1)
79#error "ProtoCore: PROTOCORE_ZIGBEE_MAX_DATA must be >= 1"
80#endif
81
82#if PROTOCORE_ENABLE_THREAD && (PROTOCORE_THREAD_MAX_DATA < 1)
83#error "ProtoCore: PROTOCORE_THREAD_MAX_DATA must be >= 1"
84#endif
85
86// ---------------------------------------------------------------------------
87// Feature flags
88// ---------------------------------------------------------------------------
89// Set any of these to 0 in your sketch BEFORE including this library to strip
90// the feature from the build entirely (no code, no RAM, no flash cost).
91//
92// #define PROTOCORE_ENABLE_WEBSOCKET 0
93// #include <protocore.h>
94//
95// ---------------------------------------------------------------------------
96// BUILD-FLAG DEPENDENCY TREE
97// ---------------------------------------------------------------------------
98// Most features are independent. A few build on another feature and cannot
99// compile without it; those HARD dependencies are enforced near the bottom of
100// this file with a clear #error, so an illegal combination fails fast at
101// compile time instead of producing a cryptic linker error. Enable a child
102// only together with its parent(s).
103//
104// Hard dependencies (child needs parent) are DECLARED, not described here. Each one is a symbol:
105//
106// #define PROTOCORE_ENABLE_WEBDAV_NEEDS_FILE_SERVING PROTOCORE_ENABLE_FILE_SERVING
107// #if PROTOCORE_ENABLE_WEBDAV && !PROTOCORE_ENABLE_WEBDAV_NEEDS_FILE_SERVING
108// #error "ProtoCore: PROTOCORE_ENABLE_WEBDAV needs PROTOCORE_ENABLE_FILE_SERVING"
109// #endif
110//
111// so the whole graph is `grep -oE '_NEEDS_[A-Z_0-9]+' protocore_config.h` and both sides come out
112// of the name. The guard tests the symbol rather than restating the condition, so what is enforced
113// and what is declared are the same text.
114//
115// Optional integrations (these build fine on their own; the named feature is
116// simply inert or reduced until you also enable the other flag):
117//
118// WEBHOOK + HTTP_CLIENT : without it, Webhook.post() leaves Webhook.i32 at -1
119// OAUTH2 + HTTP_CLIENT : the token-endpoint POST helpers compile only with it
120// DASHBOARD + WEBSOCKET : adds live control widgets; the SSE value stream works alone
121//
122// Auto-derived (do NOT set these yourself; the library computes them):
123//
124// STREAM_BODY = OTA || UPLOAD
125// CLIENT_TLS = HTTP_CLIENT_TLS || MQTT_TLS || WS_CLIENT_TLS
126// CAPTURE_AUTH_HEADER = AUTH || JWT || OIDC
127//
128// The same tree appears in README.md and examples/Foundation/Configuration.
129// ---------------------------------------------------------------------------
130
131#if PROTOCORE_ENABLE_POWER_MGMT && (PROTOCORE_POWER_TEMP_COOL_C >= PROTOCORE_POWER_TEMP_HOT_C)
132#error "ProtoCore: PROTOCORE_POWER_TEMP_COOL_C must be below PROTOCORE_POWER_TEMP_HOT_C (hysteresis)"
133#endif
134
135#if PROTOCORE_ENABLE_POWER_MGMT && (PROTOCORE_POWER_MHZ_MIN > PROTOCORE_POWER_MHZ_MAX)
136#error "ProtoCore: PROTOCORE_POWER_MHZ_MIN must not exceed PROTOCORE_POWER_MHZ_MAX"
137#endif
138
139#if PROTOCORE_ENABLE_POWER_MGMT && (PROTOCORE_POWER_BUSY_PCT > 100)
140#error "ProtoCore: PROTOCORE_POWER_BUSY_PCT must be 0..100"
141#endif
142
143#if PROTOCORE_ENABLE_HOTSWAP && (PROTOCORE_HOTSWAP_FAIL_THRESHOLD < 1 || PROTOCORE_HOTSWAP_FAIL_THRESHOLD > 255)
144#error "ProtoCore: PROTOCORE_HOTSWAP_FAIL_THRESHOLD must be in [1, 255]"
145#endif
146
147#if PROTOCORE_ENABLE_FTP_SESSION && (PROTOCORE_FTP_REPLY_BUF < 128)
148#error "ProtoCore: PROTOCORE_FTP_REPLY_BUF must be >= 128 (a multiline greeting needs room)"
149#endif
150
151#if PROTOCORE_ENABLE_FTP_SESSION && (PROTOCORE_FTP_CHUNK < 64)
152#error "ProtoCore: PROTOCORE_FTP_CHUNK must be >= 64"
153#endif
154
155#if PROTOCORE_ENABLE_EXC_DECODER && (PROTOCORE_EXC_COREDUMP_CHUNK < 64)
156#error "ProtoCore: PROTOCORE_EXC_COREDUMP_CHUNK must be >= 64"
157#endif
158
159#if PROTOCORE_ENABLE_HTTP_DELIVERY && (PROTOCORE_DELIVERY_PRECACHE_MAX < 1)
160#error "ProtoCore: PROTOCORE_DELIVERY_PRECACHE_MAX must be >= 1"
161#endif
162#if PROTOCORE_ENABLE_HTTP_DELIVERY && (PROTOCORE_DELIVERY_MANIFEST_BUF < 64)
163#error "ProtoCore: PROTOCORE_DELIVERY_MANIFEST_BUF must be >= 64"
164#endif
165
166#if PROTOCORE_ENABLE_WIFI_SNIFFER && \
167 ((PROTOCORE_WIFI_SNIFFER_MAX_CHANNELS < 1) || (PROTOCORE_WIFI_SNIFFER_MAX_CHANNELS > 14))
168#error "ProtoCore: PROTOCORE_WIFI_SNIFFER_MAX_CHANNELS must be 1..14"
169#endif
170
171#if (PROTOCORE_LOG_LEVEL < PROTOCORE_LOG_LEVEL_DEBUG) || (PROTOCORE_LOG_LEVEL > PROTOCORE_LOG_LEVEL_NONE)
172#error "ProtoCore: PROTOCORE_LOG_LEVEL must be one of the PROTOCORE_LOG_LEVEL_* constants"
173#endif
174
175/**
176 * @brief The HPACK dynamic table one connection tracks for its peer's encoder.
177 *
178 * The entry descriptors and the byte ring they index into. The connection owns these bytes; the
179 * exact width is hpack.c's, and the static_assert there is what proves this covers it.
180 */
181#ifndef PROTOCORE_HPACK_BORROW
182#define PROTOCORE_HPACK_BORROW ((size_t)PROTOCORE_HPACK_MAX_ENTRIES * 8 + PROTOCORE_HPACK_TABLE_BYTES + 32)
183#endif
184
185// The ring indexes with `% cap` (mmgr/ring.h). A power-of-two capacity makes that an AND; any other
186// value emits a divide, which on a target without one is a call into a software routine per access.
187#if (PROTOCORE_UDP_RX_RING & (PROTOCORE_UDP_RX_RING - 1)) != 0
188#error "ProtoCore: PROTOCORE_UDP_RX_RING must be a power of two"
189#endif
190
191// A ring holds a frame header plus a payload, and keeps one slot free to tell full from empty, so a
192// ring that cannot take one largest datagram would drop every receive.
193#if PROTOCORE_UDP_RX_RING < (PROTOCORE_UDP_RX_BUF_SIZE + 64)
194#error "ProtoCore: PROTOCORE_UDP_RX_RING must exceed PROTOCORE_UDP_RX_BUF_SIZE by at least one frame header"
195#endif
196
197/**
198 * @brief Maximum concurrent SSH channels per connection (RFC 4254 multiplexing).
199 *
200 * Default 1 - one "session" channel per connection, byte-for-byte the original
201 * single-channel behavior. Raise it to multiplex several channels (e.g. several
202 * concurrent shells/exec, or - with the forwarding build flags - tunnels) over one
203 * SSH connection; each channel gets its own id, window, and peer state. Fixed BSS
204 * (ssh_chan[MAX_SSH_CONNS][PROTOCORE_SSH_MAX_CHANNELS]), no heap.
205 */
206#ifndef PROTOCORE_SSH_MAX_CHANNELS
207#define PROTOCORE_SSH_MAX_CHANNELS 1
208#endif
209#if PROTOCORE_SSH_MAX_CHANNELS < 1
210#error "ProtoCore: PROTOCORE_SSH_MAX_CHANNELS must be >= 1"
211#endif
212
213// One borrow per HTTP/2 connection from the plaintext pool's PERSISTENT end, split by offset into
214// the frame payload buffer, the header block, the HPACK emit scratch and the 9-octet frame header,
215// which gets 16 of its own so the three power-of-two regions keep their alignment. Proved against
216// the real PROTOCORE_H2_CONN_BORROW by a static_assert in h2_conn.c.
217#ifndef PROTOCORE_WORK_H2_CONN
218#define PROTOCORE_WORK_H2_CONN \
219 ((size_t)MAX_CONNS * ((size_t)PROTOCORE_H2_MAX_FRAME + 2u * (size_t)PROTOCORE_H2_HDR_BLOCK + 16u))
220#endif
221
222// One borrow per HTTP/3 connection from the same persistent end, grouped by field rather than by
223// stream: every reassembly buffer, then every :path, every :authority and every :method. Grouped,
224// each stride is a power of two and stream i reaches its bytes with a shift; strided by stream, the
225// stride would be their sum and the reach a multiply. Proved by a static_assert in h3_conn.c.
226// The context leads the borrow, as the digest context leads sha256's: it carries no key material, so
227// it belongs with the stream bytes rather than in the secure arena. Proved against sizeof(H3ConnCtx)
228// by a static_assert in h3_conn.c.
229#ifndef PROTOCORE_H3_CONN_CTX
230#define PROTOCORE_H3_CONN_CTX 672
231#endif
232// After the per-stream regions come four a dispatch reads one at a time and no stream owns: the
233// body it hands the handler, the QPACK scratch and the encoded block a header set decodes through,
234// and the buffer a response is built in. H3_OFF_BODY through H3_OFF_OUT in h3_conn.c.
235#ifndef PROTOCORE_H3_CONN_BORROW
236#define PROTOCORE_H3_CONN_BORROW \
237 ((size_t)PROTOCORE_H3_CONN_CTX + \
238 (size_t)PROTOCORE_H3_MAX_STREAMS * ((size_t)PROTOCORE_H3_STREAM_BUF + PROTOCORE_H3_PATH_LEN + \
239 PROTOCORE_H3_AUTHORITY_LEN + PROTOCORE_H3_METHOD_LEN) + \
240 2u * (size_t)PROTOCORE_H3_STREAM_BUF + (size_t)PROTOCORE_H3_QPACK_SCRATCH + (size_t)PROTOCORE_H3_QPACK_BLOCK)
241#endif
242#ifndef PROTOCORE_WORK_H3_CONN
243#define PROTOCORE_WORK_H3_CONN ((size_t)PROTOCORE_QUIC_MAX_CONNS * PROTOCORE_H3_CONN_BORROW)
244#endif
245
246// The QUIC/HTTP3 server, whole: its control state, the connection pool and the ingest ring, all
247// carved out of the one span protocore.c hands it. 96 bytes of control state, 56 per pool slot, and
248// one buffered datagram plus its peer per ring entry - measured, and the static_assert in
249// quic_server.c is what proves the three regions fit. 11168 bytes for the default
250// PROTOCORE_QUIC_MAX_CONNS of 2 and PROTOCORE_QUIC_INGEST_RING of 8, and scales with both and with
251// PROTOCORE_QUIC_MAX_DATAGRAM. Slot bookkeeping and datagrams, no key material of its own, so the
252// plaintext end.
253#ifndef PROTOCORE_QUIC_SERVER_BORROW
254#define PROTOCORE_QUIC_SERVER_BORROW \
255 (96u + (size_t)PROTOCORE_QUIC_MAX_CONNS * 56u + \
256 (size_t)PROTOCORE_QUIC_INGEST_RING * ((size_t)PROTOCORE_QUIC_MAX_DATAGRAM + 24u))
257#endif
258
259// The QUIC transport under it takes its own borrow from the same end: the bytes it owes each stream
260// and the CRYPTO window per packet-number space. Proved by a static_assert in quic_conn.c.
261// Only the byte buffers: the connection's context is key material and takes a secure borrow instead
262// (PROTOCORE_QUIC_CONN_CTX_BORROW). Proved against the real split by a static_assert in quic_conn.c.
263#ifndef PROTOCORE_QUIC_CONN_BORROW
264#define PROTOCORE_QUIC_CONN_BORROW \
265 (((size_t)PROTOCORE_QUIC_MAX_STREAMS * PROTOCORE_QUIC_STREAM_TX) + 3u * (size_t)PROTOCORE_QUIC_CRYPTO_RX)
266#endif
267#ifndef PROTOCORE_WORK_QUIC_CONN
268#define PROTOCORE_WORK_QUIC_CONN ((size_t)PROTOCORE_QUIC_MAX_CONNS * PROTOCORE_QUIC_CONN_BORROW)
269#endif
270
271// The edge cache's state, split across both pools the way a QUIC connection is. The L1 response
272// store, the route maps, the fetch slots and the per-connection scratch are cached origin bytes and
273// carry nothing secret, so they take the PLAINTEXT borrow. Measured at 18168 bytes with the default
274// widths and 26128 with the mesh, L2 and range arms all on, which is what this number covers; the
275// static_assert refuses a build it is short of. Only the TLS half is secure, below. A literal rather
276// than a formula because the terms are edge_cache.h's, which this file is included by rather than
277// includes; proved against sizeof(EdgeCacheProxyCtx) by a static_assert in edge_cache_proxy.c.
278#ifndef PROTOCORE_EDGE_PROXY_BORROW
279#define PROTOCORE_EDGE_PROXY_BORROW 28672
280#endif
281
282// The EUROMAP 77 model state: the pointer to the caller-owned EmImm the resolvers read out of.
283// Plaintext because a machine model carries no key material, and taken from the persistent end so
284// the bind done before begin() outlives every Read and Browse that follows it. A literal rather than
285// a formula because the term is euromap77.h's, which this file is included by rather than includes;
286// proved against sizeof(EuroMap77Ctx) by a static_assert in euromap77.c.
287#ifndef PROTOCORE_EUROMAP77_BORROW
288#define PROTOCORE_EUROMAP77_BORROW 16
289#endif
290
291// The umati model state: the pointer to the caller-owned UmatiMachineTool the resolvers read out of,
292// and the NamespaceIndex the OPC UA server gave this model's URI. Plaintext for the same reason as
293// EUROMAP 77 above, and taken from the persistent end so the bind done before begin() outlives every
294// Read and Browse. Proved against sizeof(UmatiCtx) by a static_assert in umati.c.
295#ifndef PROTOCORE_UMATI_BORROW
296#define PROTOCORE_UMATI_BORROW 16
297#endif
298
299// The robotics model state: as umati's, plus the per-axis BrowseName table the Browse hands out by
300// pointer - PROTOCORE_ROBOTICS_AXES names of "Axis_" plus up to two digits. Proved against
301// sizeof(RoboticsCtx) by a static_assert in robotics.c.
302#ifndef PROTOCORE_ROBOTICS_BORROW
303#define PROTOCORE_ROBOTICS_BORROW 128
304#endif
305
306// The SIMATIC helper operands: the block being walked with its length, position and BCC variant,
307// and on the receive side the caller's link plus the byte that arrived and when. No key material,
308// so the plaintext end. Proved against sizeof(SimaticCtx) by a static_assert in simatic.c.
309#ifndef PROTOCORE_SIMATIC_BORROW
310#define PROTOCORE_SIMATIC_BORROW 48
311#endif
312
313// The J1939 frame-builder operands: the CanFrame being filled, its PGN, priority, source and
314// destination addresses and its data length - the widest set any one entry hands its private
315// helper. No key material, so the plaintext end. Proved against sizeof(J1939Ctx) by a static_assert
316// in j1939.c.
317#ifndef PROTOCORE_J1939_BORROW
318#define PROTOCORE_J1939_BORROW 16
319#endif
320
321// The Modbus data model: one bit per coil and per discrete input, one 16-bit word per holding and
322// per input register, plus the write callback and the alignment between them. Scales with the four
323// table sizes above. No key material, so the plaintext end. Proved against sizeof(ModbusCtx) by a
324// static_assert in modbus.c.
325#ifndef PROTOCORE_MODBUS_BORROW
326#define PROTOCORE_MODBUS_BORROW \
327 ((size_t)((PROTOCORE_MODBUS_COILS + 7) / 8) + (size_t)((PROTOCORE_MODBUS_DISCRETE_INPUTS + 7) / 8) + \
328 (size_t)PROTOCORE_MODBUS_HOLDING_REGS * 2u + (size_t)PROTOCORE_MODBUS_INPUT_REGS * 2u + 32u)
329#endif
330
331// The ESP-NOW peer registry: a MAC and a used flag per peer, and on the vendor arm the receive
332// callback plus the channel it was bound on. No key material, so the plaintext end. Proved against
333// sizeof(EspnowCtx) by a static_assert in espnow.c.
334#ifndef PROTOCORE_ESPNOW_BORROW
335#define PROTOCORE_ESPNOW_BORROW ((size_t)PROTOCORE_ESPNOW_MAX_PEERS * 8u + 32u)
336#endif
337
338// The promiscuous-capture state: the frame sink the radio binding calls. No key material, so the
339// plaintext end. Proved against sizeof(PromiscCtx) by a static_assert in promisc.c.
340#ifndef PROTOCORE_PROMISC_BORROW
341#define PROTOCORE_PROMISC_BORROW 16
342#endif
343
344// The live sniff state: the packet-type tallies, the per-channel survey and the scan cursor, plus
345// the running flag. No key material, so the plaintext end. Proved against sizeof(WifiSnifferCtx)
346// by a static_assert in wifi_sniffer.c.
347#ifndef PROTOCORE_WIFI_SNIFFER_BORROW
348#define PROTOCORE_WIFI_SNIFFER_BORROW 256
349#endif
350
351// The radio keep-awake refcount: how many transfers are holding active mode. No key material, so
352// the plaintext end. Proved against sizeof(struct RadioStorage) by a static_assert in radio_power.c.
353#ifndef PROTOCORE_RADIO_POWER_BORROW
354#define PROTOCORE_RADIO_POWER_BORROW 16
355#endif
356
357// The authoritative A records this server answers from - the owner names, their addresses and the
358// count - plus the response staged for the last query. Scales with the record table. No key
359// material, so the plaintext end. Proved against sizeof(struct DnsServerStorage) by a static_assert
360// in dns_server.c.
361#ifndef PROTOCORE_DNS_SERVER_BORROW
362#define PROTOCORE_DNS_SERVER_BORROW \
363 ((size_t)PROTOCORE_DNS_SERVER_MAX_RECORDS * ((size_t)PROTOCORE_DNS_NAME_MAX + 4u) + \
364 (size_t)PROTOCORE_DNS_NAME_MAX + 96u)
365#endif
366
367// The forwarding plane's tables: the (src, dst) controls, the access list, the policy routes, the
368// default verdict, the counters and the inspection hook. Scales with the three table caps. No key
369// material, so the plaintext end. Proved against sizeof(struct ForwardStorage) by a static_assert
370// in forward.c.
371#ifndef PROTOCORE_FORWARD_BORROW
372#define PROTOCORE_FORWARD_BORROW \
373 ((size_t)PROTOCORE_FWD_MAX_RULES * 24u + \
374 (size_t)PROTOCORE_FWD_MAX_ACL * ((size_t)PROTOCORE_FWD_ACL_PATLEN * 2u + 24u) + \
375 (size_t)PROTOCORE_FWD_MAX_ROUTES * ((size_t)PROTOCORE_FWD_ACL_PATLEN * 2u + 32u) + 128u)
376#endif
377
378// The one installed log sink, the function every emitted line is handed to. A single pointer. No
379// key material, so the plaintext end. Proved against sizeof(struct LogStorage) by a static_assert
380// in log.c.
381#ifndef PROTOCORE_LOG_BORROW
382#define PROTOCORE_LOG_BORROW 16u
383#endif
384
385// The two server-wide DSCP defaults: the mark outbound TCP connections start from and the mark
386// outbound UDP datagrams take. Two bytes, fixed. No key material, so the plaintext end. Proved
387// against sizeof(struct DiffServStorage) by a static_assert in diffserv.c.
388#ifndef PROTOCORE_DIFFSERV_BORROW
389#define PROTOCORE_DIFFSERV_BORROW 8u
390#endif
391
392// The UDP sending side's one outbound control block, opened on first send. A single pointer. No key
393// material, so the plaintext end. Proved against sizeof(struct UdpClientStorage) by a static_assert
394// in udp/client/client.c.
395#ifndef PROTOCORE_UDP_CLIENT_BORROW
396#define PROTOCORE_UDP_CLIENT_BORROW 16u
397#endif
398
399// The UDP receiving side: one slot per bound port, each carrying its own receive ring, plus the
400// payload stage one delivery is handed out of, the two header stages at the ends of that ring, the
401// bitmap of bound slots, the reentrancy latch and the text a joined group is formatted into. Scales
402// with the listener count and the ring. No key material, so the plaintext end. Proved against
403// sizeof(struct UdpListenerStorage) by a static_assert in udp/server/server.c.
404#ifndef PROTOCORE_UDP_LISTENER_BORROW
405#define PROTOCORE_UDP_LISTENER_BORROW \
406 ((size_t)PROTOCORE_MAX_UDP_LISTENERS * ((size_t)PROTOCORE_UDP_RX_RING + 96u) + (size_t)PROTOCORE_UDP_RX_BUF_SIZE + \
407 256u)
408#endif
409
410// The TCP/lower-level seam: one marshal record per connection slot, plus the stack thread it
411// captured on its first op and the TTL it stamps outbound segments with. No key material - the
412// record carries a pointer to the caller's bytes, never a copy - so the plaintext end. Proved
413// against sizeof(struct TcpLowerStorage) by a static_assert in tcp/lower/lower.c.
414#ifndef PROTOCORE_TCP_LOWER_BORROW
415#define PROTOCORE_TCP_LOWER_BORROW ((size_t)CONN_POOL_SLOTS * 96u + 64u)
416#endif
417
418// The connection pool's own state: the zeroed slot template init resets a slot from (one whole
419// TcpConn, receive ring included), the nine close-reason counters, the idle deadline and the
420// installed observer. No key material - the slot payloads live in conn_pool, not here - so the
421// plaintext end. Proved against sizeof(struct ConnPoolStorage) by a static_assert in
422// tcp/protocol/protocol.c.
423#ifndef PROTOCORE_CONN_POOL_BORROW
424#define PROTOCORE_CONN_POOL_BORROW ((size_t)RX_BUF_SIZE + 512u)
425#endif
426
427// The accepting side's accept-time state: the global fixed-window throttle, the per-source-address
428// bucket table, the CIDR allowlist, and above one worker the per-worker event queues. Scales with
429// the two table caps and the worker count. No key material - a source address is not a secret - so
430// the plaintext end. Proved against sizeof(struct TcpListenerStorage) by a static_assert in
431// tcp/server/server.c.
432#ifndef PROTOCORE_TCP_LISTENER_BORROW
433#define PROTOCORE_TCP_LISTENER_BORROW \
434 ((size_t)PROTOCORE_PER_IP_THROTTLE_SLOTS * 32u + (size_t)PROTOCORE_IP_ALLOWLIST_SLOTS * 32u + \
435 (size_t)PROTOCORE_WORKER_COUNT * ((size_t)EVT_QUEUE_DEPTH * 24u + 128u) + 256u)
436#endif
437
438// The dialing side: one record per outbound connection, each carrying its own receive ring, plus
439// the record the private step every call runs first is pointing at. Scales with the connection
440// count and that ring. No key material - a TLS client's secrets live in the TLS context, not here -
441// so the plaintext end. Proved against sizeof(struct TcpClientStorage) by a static_assert in
442// tcp/client/client.c.
443#ifndef PROTOCORE_TCP_CLIENT_BORROW
444#define PROTOCORE_TCP_CLIENT_BORROW ((size_t)PROTOCORE_CLIENT_CONNS * ((size_t)PROTOCORE_CLIENT_RX_BUF + 96u) + 64u)
445#endif
446
447// The one address a Happy Eyeballs preference step scores. The sort compares a key it holds against
448// the element beside it, so the operand differs per call and rides the context rather than a
449// parameter. One pointer. No key material, so the plaintext end. Proved against
450// sizeof(HappyEyeballsCtx) by a static_assert in happy_eyeballs.c.
451#ifndef PROTOCORE_HAPPY_EYEBALLS_BORROW
452#define PROTOCORE_HAPPY_EYEBALLS_BORROW 16u
453#endif
454
455// The Layer 1 interface registry: one row per interface the application registered, each carrying
456// the callback that puts octets on it and the context that callback is handed back. Scales with
457// PROTOCORE_PHY_MAX_IFACES. No key material - a send callback is not a secret - so the plaintext
458// end. Proved against sizeof(struct PhysicalStorage) by a static_assert in physical.c.
459#ifndef PROTOCORE_PHYSICAL_BORROW
460#define PROTOCORE_PHYSICAL_BORROW ((size_t)PROTOCORE_PHY_MAX_IFACES * 32u + 32u)
461#endif
462
463/**
464 * @brief Worst-case bytes each module borrows from the secure pool in a single call.
465 *
466 * Declared here because PROTOCORE_SECURE_ARENA_SIZE below is derived from them and this is the one
467 * place that can see them all - a module header cannot host its own, since every module header
468 * includes this file. Each value is PROVED where the struct lives: the owning .cpp carries a
469 * static_assert(sizeof(X) <= PROTOCORE_WORK_X), so a working set that grows past its declaration fails
470 * the build naming itself, rather than exhausting the pool at run time.
471 *
472 * These are SIZES, not offsets. Nothing here couples one module to another: each is a term in a
473 * sum, order is irrelevant, and adding a module shifts no one. That is the difference from the
474 * crypto_work region map these replaced.
475 *
476 * Values are what the ESP32 toolchain reported for the real structs, rounded up.
477 */
478
479// The SHA-256 borrow, split by offset in sha256.c: the 64-byte block as it arrives, the padded last
480// one, and the 32-byte state copy finalizing compresses into so the running hash survives it. 64 + 64
481// + 32 = 160. The context is that module's own state and is no longer a region here. The schedule is a
482// register window, not storage.
483//
484// One figure, both arms. The accelerator compresses a block; it does not pad, buffer a partial block,
485// or hold a digest a caller can keep feeding, so the same regions are taken whether the compression
486// runs on the peripheral or in software. Proved against the real layout by a static_assert in sha256.c.
487#ifndef PROTOCORE_SHA256_BORROW
488#define PROTOCORE_SHA256_BORROW 256
489#endif
490
491// The SHA-1 borrow, split by offset in sha1.c: the running state (uint32_t h[5], 20 bytes) then the
492// two padded final blocks (128 bytes) a message whose tail reaches 56 bytes composes. 148 bytes,
493// rounded up to the next 32-byte multiple. One figure, both arms: the accelerated arm digests through
494// mbedtls and takes none of it. Proved against the real split by a static_assert in sha1.c.
495#ifndef PROTOCORE_SHA1_BORROW
496#define PROTOCORE_SHA1_BORROW 160
497#endif
498
499// The SSH key-exchange digest borrow, split by offset in ssh_kexhash.c: the region the bound hash
500// runs in - SHA-512's, the wider of the two - then the octet naming which hash that is. Proved by a
501// static_assert in ssh_kexhash.c.
502#ifndef PROTOCORE_SSH_KEXHASH_BORROW
503#define PROTOCORE_SSH_KEXHASH_BORROW (PROTOCORE_SHA512_BORROW + 8)
504#endif
505
506// A SHA-512 context works out of the same regions as SHA-256, at its own widths: the context itself,
507// the 128-byte block as it arrives, the padded last one, and the 64-byte state copy finalizing
508// compresses into. The schedule is a register window, not storage. One figure for both arms, for the
509// reason stated above SHA-256. Proved by a static_assert in sha512.c.
510#ifndef PROTOCORE_SHA512_BORROW
511#define PROTOCORE_SHA512_BORROW 448
512#endif
513
514// A SHA-384 context is a SHA-512 one: same block width, same state, same regions, and only the seed
515// and the digest length differ, so the figure is SHA-512's. Proved by a static_assert in sha384.c.
516#ifndef PROTOCORE_SHA384_BORROW
517#define PROTOCORE_SHA384_BORROW 448
518#endif
519
520// An HMAC-SHA512 context works out of two SHA-512 borrows - the inner hash it keeps across updates and
521// the outer one final runs - plus the two key blocks and the inner digest between them. Proved against
522// the real split by a static_assert in hmac_sha512.c.
523#ifndef PROTOCORE_HMAC_SHA512_BORROW
524#define PROTOCORE_HMAC_SHA512_BORROW (2 * PROTOCORE_SHA512_BORROW + 768)
525#endif
526
527// An HMAC-SHA384 context is the same split at the same widths: the block is SHA-512's 128 octets and
528// only the inner digest is shorter. Proved against the real split by a static_assert in hmac_sha384.c.
529#ifndef PROTOCORE_HMAC_SHA384_BORROW
530#define PROTOCORE_HMAC_SHA384_BORROW (2 * PROTOCORE_SHA384_BORROW + 768)
531#endif
532
533// An HMAC-SHA256 context works out of two SHA-256 borrows - the inner hash it keeps across updates and
534// the outer one final runs - plus the key blocks and digest between them. Proved against the real
535// split by a static_assert in hmac_sha256.c.
536#ifndef PROTOCORE_HMAC_SHA256_BORROW
537#define PROTOCORE_HMAC_SHA256_BORROW (2 * PROTOCORE_SHA256_BORROW + 384)
538#endif
539
540// The accelerated modexp backend: four 256-octet big-endian buffers handed to the vendor, then the
541// region the Bignum conversions that fill and drain them run in.
542#ifndef PROTOCORE_WORK_BIGNUM_HW
543#define PROTOCORE_WORK_BIGNUM_HW 1328
544#endif
545#ifndef PROTOCORE_WORK_BIGNUM_SW
546#define PROTOCORE_WORK_BIGNUM_SW 1408
547#endif
548// AES-256-GCM keyed context. Sized per vendor for the same reason as the bignum working set above: one
549// backend or the other is compiled, never both. It matters more here than it did as a transient
550// borrow - a consumer now embeds two of these per connection (send and receive) for the life of the
551// key, so one flat figure sized for the largest backend is RAM every target pays and only one uses.
552// The static_assert in each backend is what keeps these honest against a vendor header we do not own.
553#ifndef PROTOCORE_AESGCM_BORROW_HW
554#define PROTOCORE_AESGCM_BORROW_HW 416 // mbedtls_gcm_context measures 392 on the S3
555#endif
556#ifndef PROTOCORE_AESGCM_BORROW_SW
557#define PROTOCORE_AESGCM_BORROW_SW 640 // GcmWork is 608: AES-256 round keys + the 4-bit GHASH table
558#endif
559#ifndef PROTOCORE_AESGCM_BORROW
560#if PROTOCORE_HAS_HW_AESGCM
561#define PROTOCORE_AESGCM_BORROW PROTOCORE_AESGCM_BORROW_HW
562#else
563#define PROTOCORE_AESGCM_BORROW PROTOCORE_AESGCM_BORROW_SW
564#endif
565#endif
566#ifndef PROTOCORE_WORK_AESCCM
567#define PROTOCORE_WORK_AESCCM 448
568#endif
569// AES-128 single-block context (QUIC/DTLS header + sequence-number protection). Kept per key for the
570// same reason as the AEAD contexts, though the win is smaller: measured on an S3, rebuilding it per
571// record costs ~556 cycles plus a pool borrow and wipe. (The ECB block it protects is ~7,842 cycles on
572// its own - one HW-AES operation - which is now the larger per-packet cost in QUIC and DTLS.)
573#ifndef PROTOCORE_WORK_AES128_HW
574#define PROTOCORE_WORK_AES128_HW 288 // mbedtls_aes_context: nr + rk + buf[68]
575#endif
576#ifndef PROTOCORE_WORK_AES128_SW
577#define PROTOCORE_WORK_AES128_SW 176 // uint32_t rk[44]
578#endif
579#ifndef PROTOCORE_WORK_AES128
580#if PROTOCORE_HAS_HW_AESGCM
581#define PROTOCORE_WORK_AES128 PROTOCORE_WORK_AES128_HW
582#else
583#define PROTOCORE_WORK_AES128 PROTOCORE_WORK_AES128_SW
584#endif
585#endif
586
587// AES-128-GCM keyed context, sized per vendor exactly as PROTOCORE_AESGCM_BORROW above and for the same reason:
588// one backend is compiled, and a consumer holding a context per direction should not carry storage for
589// the backend it did not build.
590#ifndef PROTOCORE_WORK_AES128GCM_HW
591#define PROTOCORE_WORK_AES128GCM_HW 416 // mbedtls_gcm_context measures 392 on the S3
592#endif
593#ifndef PROTOCORE_WORK_AES128GCM_SW
594#define PROTOCORE_WORK_AES128GCM_SW 576 // Aes128GcmWork is 560: AES-128 round keys + the 4-bit GHASH table
595#endif
596#ifndef PROTOCORE_WORK_AES128GCM
597#if PROTOCORE_HAS_HW_AESGCM
598#define PROTOCORE_WORK_AES128GCM PROTOCORE_WORK_AES128GCM_HW
599#else
600#define PROTOCORE_WORK_AES128GCM PROTOCORE_WORK_AES128GCM_SW
601#endif
602#endif
603// The chacha20-poly1305 borrow, split by offset in chachapoly.c: the per-packet working set (the
604// nonce, the derived one-time Poly1305 key, the computed tag and the decrypted length word, 60
605// octets), then a region for the nested ChaCha20 and one for the nested Poly1305, each driven through
606// its own namespace. Proved by a static_assert in chachapoly.c.
607#ifndef PROTOCORE_CHACHAPOLY_BORROW
608#define PROTOCORE_CHACHAPOLY_BORROW (64 + PROTOCORE_CHACHA20_BORROW + PROTOCORE_POLY1305_BORROW)
609#endif
610// The ChaCha20 borrow: the 16-word input state and the 16-word round state, then one keystream block.
611// 192 octets. Proved by a static_assert in chacha20.c.
612#ifndef PROTOCORE_CHACHA20_BORROW
613#define PROTOCORE_CHACHA20_BORROW 192
614#endif
615// The AES-256-CTR borrow: the expanded key, then one keystream block. The accelerator's context is the
616// larger of the two arms at 304 octets. Proved by a static_assert in aes256ctr.c.
617#ifndef PROTOCORE_AES256CTR_BORROW
618#define PROTOCORE_AES256CTR_BORROW 384
619#endif
620// The Poly1305 borrow: the clamped key part, its reduction multipliers and the accumulator, five
621// 26-bit limbs each, then the padded final block. 76 octets. Proved by a static_assert in poly1305.c.
622#ifndef PROTOCORE_POLY1305_BORROW
623#define PROTOCORE_POLY1305_BORROW 96
624#endif
625// The AES-128-CMAC borrow: the block context, then the prepared last block, the CBC-MAC accumulator
626// and the XOR scratch. The accelerator's context is the larger of the two arms at 336 octets. Proved
627// by a static_assert in aes_cmac.c.
628#ifndef PROTOCORE_AES_CMAC_BORROW
629#define PROTOCORE_AES_CMAC_BORROW 384
630#endif
631// The MD-family borrow, split by offset in md.c: the running digest state, then the regions HMAC-MD5
632// needs - the key block, its two pads, the inner digest, and the state a key longer than the block is
633// hashed down in. All of it is NTLM password and session-key material, so none of it may sit on the
634// stack. Proved against the real split by a static_assert in md.c.
635#ifndef PROTOCORE_MD_BORROW
636#define PROTOCORE_MD_BORROW 448
637#endif
638// The Keccak borrow, split by offset in sha3.c: the sponge the streaming XOF carries across calls,
639// then the sponge the one-shot digests and XOFs run in. One KeccakCtx is 25 lanes plus the rate and
640// the squeeze position, 208 octets, and the split holds two of them. Proved against the real split by
641// a static_assert in sha3.c.
642#ifndef PROTOCORE_SHA3_BORROW
643#define PROTOCORE_SHA3_BORROW 224
644#endif
645// The SP800-108 KDF borrow, split by offset in kdf.c: the PRF's own bytes, then K(i) and the 32-bit
646// counter, 36 octets. Proved by a static_assert in kdf.c.
647#ifndef PROTOCORE_KDF_BORROW
648#define PROTOCORE_KDF_BORROW (PROTOCORE_HMAC_SHA256_BORROW + 64)
649#endif
650// The GHASH borrow: the 4-bit table built from the subkey H, 16 rows of 4 words. The table is the
651// state the module's own entries carry between them - key_init builds it, update and mul read it.
652// Proved by a static_assert in ghash.c.
653#ifndef PROTOCORE_GHASH_BORROW
654#define PROTOCORE_GHASH_BORROW 256
655#endif
656// The bignum borrow: the operands an entry stages for its helpers, then the value a conversion or a
657// compare runs over. The modexp's own scratch is not here - that lives in the backend the build
658// selects, sized by PROTOCORE_WORK_BIGNUM_HW / _SW above. Proved by a static_assert in bignum.c.
659#ifndef PROTOCORE_BIGNUM_BORROW
660#define PROTOCORE_BIGNUM_BORROW 304
661#endif
662// The AES-CCM borrow: the keyed context one record runs out of. The software arm holds the AES key
663// schedule, the round count, the CBC-MAC accumulator, the formatting block, the counter block and the
664// keystream, 308 octets; the accelerator's mbedtls_ccm_context is the larger arm at the figure this
665// file already carried for it. Proved by a static_assert in aesccm.c.
666#ifndef PROTOCORE_AESCCM_BORROW
667#define PROTOCORE_AESCCM_BORROW PROTOCORE_WORK_AESCCM
668#endif
669// The AES-128-GCM borrow, split by offset in aes128gcm.c: the keyed AEAD context, then the
670// single-block context the header protection uses. Both terms are already sized per vendor above, so
671// the borrow is their sum and follows the arm the build selects. Proved by a static_assert in
672// aes128gcm.c.
673#ifndef PROTOCORE_AES128GCM_BORROW
674#define PROTOCORE_AES128GCM_BORROW (PROTOCORE_WORK_AES128GCM + PROTOCORE_WORK_AES128)
675#endif
676
677// A TLS 1.3 record key holds one keyed AEAD context per direction, and which AEAD that is depends on
678// the suite the connection negotiated: AEAD_AES_128_GCM for 0x1301, AEAD_AES_256_GCM for 0x1302. The
679// slot is the wider of the two. AES-128-GCM's is not the smaller one despite the shorter key: that
680// borrow carries the single-block context aes128gcm.h also exposes, which AES-256-GCM has no
681// counterpart for. Proved against both by a static_assert in record.c.
682#ifndef PROTOCORE_TLS_RECORD_AEAD_BORROW
683#if PROTOCORE_AES128GCM_BORROW > PROTOCORE_AESGCM_BORROW
684#define PROTOCORE_TLS_RECORD_AEAD_BORROW PROTOCORE_AES128GCM_BORROW
685#else
686#define PROTOCORE_TLS_RECORD_AEAD_BORROW PROTOCORE_AESGCM_BORROW
687#endif
688#endif
689// The X25519 borrow: the clamped scalar, the base point, and the Montgomery ladder's running points
690// and per-bit intermediates. The radix-2^16 protocore_gf arm is the larger of the two at 960 octets,
691// seven 128-octet field elements over the two 32-octet scalars; the radix-2^32 fe arm is 576. Proved
692// by a static_assert in curve25519.c.
693#ifndef PROTOCORE_CURVE25519_BORROW
694#define PROTOCORE_CURVE25519_BORROW 960
695#endif
696// The Ed25519 borrow, split by offset in ed25519.c: the signature working set - the S accumulator,
697// the clamped hash of the seed, the two reduced scalars, the public point and the packed comparison
698// value, 768 octets - then the region SHA-512 runs in. Proved by a static_assert in ed25519.c.
699#ifndef PROTOCORE_ED25519_BORROW
700#define PROTOCORE_ED25519_BORROW (768 + PROTOCORE_SHA512_BORROW)
701#endif
702// The RSA modulus, and so the signature: RSA-2048. Stated here rather than in rsa.h because the
703// arena sizes X509_VERIFY_BORROW out of it, and a config header cannot reach into a module's own -
704// least of all one where the value sits inside `#if PROTOCORE_ENABLE_RSA` and is absent otherwise.
705#ifndef PROTOCORE_RSA_KEY_BYTES
706#define PROTOCORE_RSA_KEY_BYTES 256
707#endif
708// The RSA borrow, split by offset in rsa.c: the regions SHA-256, SHA-512 and the bignum conversions
709// run in, then the ladder's working set - the staged multiply, the digest, the encoded block, the
710// recovered block, the five 256-octet bignums and the double-width product, 2,432 octets. Proved by a
711// static_assert in rsa.c.
712#ifndef PROTOCORE_RSA_BORROW
713#define PROTOCORE_RSA_BORROW (PROTOCORE_SHA256_BORROW + PROTOCORE_SHA512_BORROW + PROTOCORE_BIGNUM_BORROW + 2432)
714#endif
715// The ML-KEM-768 borrow: the region SHA-3 runs in. The module carries nothing across its entries, so
716// the sponge is the whole working set. Proved by a static_assert in mlkem.c.
717#ifndef PROTOCORE_MLKEM_BORROW
718#define PROTOCORE_MLKEM_BORROW PROTOCORE_SHA3_BORROW
719#endif
720// The sntrup761 borrow: the region SHA-512 runs in. The module carries nothing across its entries.
721// Proved by a static_assert in sntrup761.c.
722#ifndef PROTOCORE_SNTRUP761_BORROW
723#define PROTOCORE_SNTRUP761_BORROW PROTOCORE_SHA512_BORROW
724#endif
725// The generator's borrow, split by offset in rng.c: the draw counter and the seeded flag, the seed,
726// the nonce, the ratchet's next seed, then the region ChaCha20 runs in. 88 octets over the cipher's
727// own borrow. This one is taken from the pool's PERSISTENT end and lives for the program, so the seed
728// survives across calls and a reseed lands in the same bytes. Proved by a static_assert in rng.c.
729#ifndef PROTOCORE_RNG_BORROW
730#define PROTOCORE_RNG_BORROW (88 + PROTOCORE_CHACHA20_BORROW)
731#endif
732
733// SSH frames every outbound packet in the secure pool: the payload it carries is the session's own
734// plaintext until the cipher runs over it. One transient payload plus one wire, live together while
735// protocore_ssh_conn_send or an open_forwarded builds a message and frames it.
736//
737// A connection's own bytes are not here. Every one of them - the two key epochs, the DH ephemeral,
738// the handshake constants, the wire, the packet MAC's and the handshake crypto's working bytes, and
739// the receive buffers - is a named offset in the connection's compile-time storage (ssh_conn.h).
740//
741// The wire bound is derived in ssh_packet.h from this same SSH_PKT_BUF_SIZE, so the figure is
742// stated here in units of it and proved against the real SSH_WIRE_CAP by a static_assert in
743// ssh_conn.c. Three units cover the wire's framing overhead over a full payload, compression's
744// expansion bound included, and the fourth is the payload.
745#ifndef PROTOCORE_WORK_SSH_CONN
746#define PROTOCORE_WORK_SSH_CONN (4u * (size_t)SSH_PKT_BUF_SIZE)
747#endif
748
749// The software TLS 1.3 handshake driver takes one borrow per connection from the secure pool's
750// PERSISTENT end and splits it by offset: TX at 0, where a message is built to send and where a
751// received record is opened; RX at PROTOCORE_TLS_CONN_MSG_CAP, which the worker fills with one record; and
752// the terms after it - the key share, the ECDHE secret, the transcript hash in hand, the Finished
753// MAC, and Transcript-Hash(CH..server Finished). Every offset is a multiple of 32, so the borrow
754// stays aligned end to end.
755#ifndef PROTOCORE_TLS_CONN_MSG_CAP
756#define PROTOCORE_TLS_CONN_MSG_CAP 1024
757#endif
758#ifndef PROTOCORE_TLS_CONN_REC_CAP
759#define PROTOCORE_TLS_CONN_REC_CAP 1024
760#endif
761// RFC 8446 sec 7.1 keys the schedule off the negotiated cipher suite's hash, so a term is 32 octets
762// under a SHA-256 suite and 48 under a SHA-384 one. The layout is stated at the wider of the two and
763// a connection reads back the length its suite bound (Tls13KsNs::len).
764#ifndef PROTOCORE_TLS13_SECRET_MAX
765#define PROTOCORE_TLS13_SECRET_MAX 48
766#endif
767// The key share, the ECDHE secret, the transcript hash in hand, the Finished MAC, and
768// Transcript-Hash(CH..server Finished). The peer's public key is NOT one of these: it can be an
769// RSA modulus, so it has its own region (PROTOCORE_TLS_CONN_PEERKEY_CAP) rather than a 32-byte term.
770#ifndef PROTOCORE_TLS_CONN_TERMS
771#define PROTOCORE_TLS_CONN_TERMS 5
772#endif
773#ifndef PROTOCORE_TLS_CONN_TERMS_CAP
774#define PROTOCORE_TLS_CONN_TERMS_CAP ((size_t)PROTOCORE_TLS_CONN_TERMS * PROTOCORE_TLS13_SECRET_MAX)
775#endif
776// The transcript's working bytes, the parsed ClientHello, the key schedule, and the SHA-512 an
777// Ed25519 signature runs through, which the driver reaches by pointer. Stated in bytes here and
778// proved against their real sizes by a static_assert in handshake.c:
779// 448 (TLS13_TRANSCRIPT_BORROW) + sizeof(Tls13ClientHello) + 2802 (TLS13_KS_BORROW) + 448
780// (SHA512_BORROW), which is 3842 with the PQC arm off and 3850 with it on. Rounded up to a multiple
781// of 32 so the offsets after it stay aligned.
782#ifndef PROTOCORE_TLS_CONN_STATE_CAP
783#define PROTOCORE_TLS_CONN_STATE_CAP 3872
784#endif
785// The peer's subjectPublicKey, kept from the Certificate that carried it to the CertificateVerify
786// checked under it: the message buffer the certificate arrived in is reused by the next handshake
787// message, so a view over it would dangle. An RSA-2048 RSAPublicKey SEQUENCE is a little over 256
788// octets, a P-256 point 65 and an Ed25519 key 32, so this covers the widest and the header naming
789// its algorithm and length.
790#ifndef PROTOCORE_TLS_CONN_PEERKEY_CAP
791#define PROTOCORE_TLS_CONN_PEERKEY_CAP 320
792#endif
793// The terms of one TLS 1.3 key schedule: early, handshake and master secrets; the four traffic
794// secrets; the empty hash, the derived salt, the finished key, the zero IKM, and the Finished
795// verify_data. The connection that runs the schedule owns the storage, so the extent is stated
796// here and spent there: PROTOCORE_TLS13_KS_CAP.
797#ifndef PROTOCORE_TLS13_KS_TERMS
798#define PROTOCORE_TLS13_KS_TERMS 12
799#endif
800// The schedule's terms, then the bytes its HKDF works out of. One borrow, split by offset in
801// key_schedule.h, taken by whichever connection runs the handshake. The HKDF figure is the SHA-384
802// one because it is the larger of the two and a connection binds either.
803#ifndef PROTOCORE_TLS13_KS_BORROW
804#define PROTOCORE_TLS13_KS_BORROW \
805 ((size_t)PROTOCORE_TLS13_KS_TERMS * PROTOCORE_TLS13_SECRET_MAX + PROTOCORE_HKDF_SHA384_BORROW)
806#endif
807// The bytes one running Transcript-Hash works out of, at the wider of the two suite hashes so the
808// region does not depend on what the connection binds. Taken by the connection that keeps the
809// transcript and passed to Tls13Ks.transcript_*.
810#ifndef PROTOCORE_TLS13_TRANSCRIPT_BORROW
811#if PROTOCORE_SHA384_BORROW > PROTOCORE_SHA256_BORROW
812#define PROTOCORE_TLS13_TRANSCRIPT_BORROW PROTOCORE_SHA384_BORROW
813#else
814#define PROTOCORE_TLS13_TRANSCRIPT_BORROW PROTOCORE_SHA256_BORROW
815#endif
816#endif
817#ifndef PROTOCORE_WORK_TLS_CONN
818#define PROTOCORE_WORK_TLS_CONN \
819 ((size_t)MAX_TLS_CONNS * \
820 ((size_t)PROTOCORE_TLS_CONN_MSG_CAP + (size_t)PROTOCORE_TLS_CONN_REC_CAP + (size_t)PROTOCORE_TLS_CONN_TERMS_CAP + \
821 (size_t)PROTOCORE_TLS_CONN_STATE_CAP + (size_t)PROTOCORE_TLS_CONN_PEERKEY_CAP))
822#endif
823
824/**
825 * @brief Size in bytes of the per-slot SECURE pool (see mmgr/secure.h), DERIVED.
826 *
827 * Not a chosen number. Every borrow from this pool is a working set some module declares - see the
828 * PROTOCORE_WORK_* constants, each proved against its struct's sizeof by a static_assert in the module that
829 * owns it. The floor is the sum of the ones a build actually compiles.
830 *
831 * A sum, not a deepest-nest figure. The sum is a strict upper bound - correct however those working
832 * sets nest under one another - whereas a nest depth is only correct while the call graph stays as it
833 * is. This value must not be wrong, so it buys certainty with a little slack.
834 *
835 * A module whose working set grows past its declaration fails the build, naming itself, instead of
836 * exhausting the pool at run time. Override PROTOCORE_SECURE_ARENA_SIZE to pin a size regardless.
837 */
838// Every module's borrow is declared here, above the arena guard: a static_assert in the
839// module names it in every build, and pinning PROTOCORE_SECURE_ARENA_SIZE overrides only
840// the sum below, never a declaration.
841// The SSE module's bytes: one subscribe handler per route and the buffer a write frames its record
842// in. Taken from the persistent end so it lasts the life of the program. Proved against the real
843// split by a static_assert in sse.c.
844#ifndef PROTOCORE_SSE_BORROW
845#define PROTOCORE_SSE_BORROW (MAX_ROUTES * 8 + SSE_BUF_SIZE + 32)
846#endif
847
848// The WebSocket module's bytes: one handler set per route, the scratch a slot's bytes are staged in
849// for the frame walk, and the outbound fragmentation size. Taken from the persistent end so it lasts
850// the life of the program. Proved against the real split by a static_assert in websocket.c.
851#ifndef PROTOCORE_WS_BORROW
852#define PROTOCORE_WS_BORROW (MAX_ROUTES * 24 + RX_BUF_SIZE + 32) // WsRoute is three handlers
853#endif
854
855// The CSRF issuer's bytes: its HMAC secret and nonce counter, then the region the nested
856// HMAC-SHA256 runs out of. Secure because the secret is key material, and taken from the persistent
857// end so it lasts the life of the program. Proved against the real split by a static_assert in csrf.c.
858#ifndef PROTOCORE_CSRF_BORROW
859#define PROTOCORE_CSRF_BORROW (64 + PROTOCORE_HMAC_SHA256_BORROW)
860#endif
861
862// The authentication lockout table: one bucket per recently-seen source address. Secure because a
863// bucket names who is being throttled, and taken from the persistent end so the table lasts the life
864// of the program. Proved against sizeof(LockoutCtx) by a static_assert in auth_lockout.c.
865#ifndef PROTOCORE_AUTH_LOCKOUT_BORROW
866#define PROTOCORE_AUTH_LOCKOUT_BORROW ((size_t)PROTOCORE_AUTH_LOCKOUT_SLOTS * 48u)
867#endif
868
869// The trusted-proxy table a forwarded header is honored against. Secure because it names which peers
870// may rewrite a client address, and taken from the persistent end so it lasts the life of the
871// program. Proved against the real table by a static_assert in forwarded_trust.c.
872#ifndef PROTOCORE_FORWARDED_TRUST_BORROW
873#define PROTOCORE_FORWARDED_TRUST_BORROW ((size_t)PROTOCORE_TRUSTED_PROXY_MAX * 32u)
874#endif
875
876// The audit ring: the retained records, their cursors, the chain anchor and the sink. Secure
877// because a record names who did what, and taken from the persistent end so the chain survives the
878// requests it spans. Proved against sizeof(AuditCtx) by a static_assert in audit_log.c.
879#ifndef PROTOCORE_AUDIT_LOG_BORROW
880#define PROTOCORE_AUDIT_LOG_BORROW \
881 ((size_t)PROTOCORE_AUDIT_LOG_ENTRIES * (PROTOCORE_AUDIT_MSG_LEN + PROTOCORE_AUDIT_HASH_LEN + 64u) + 128u)
882#endif
883
884// The web terminal's command callback, its WebSocket path and which slots are terminal browsers. Taken from the
885// persistent end so it outlives the connections it tracks. Proved by a static_assert in web_terminal.c.
886#ifndef PROTOCORE_WEB_TERMINAL_BORROW
887#define PROTOCORE_WEB_TERMINAL_BORROW (MAX_PATH_LEN + MAX_WS_CONNS + 32u)
888#endif
889
890// The config store's open namespace name. Secure because a namespace names where settings live, and taken from the
891// persistent end so it outlives the request that opened it. Proved by a static_assert in config_store.c.
892#ifndef PROTOCORE_CONFIG_STORE_BORROW
893#define PROTOCORE_CONFIG_STORE_BORROW (PROTOCORE_CONFIG_KEY_MAX + 32u)
894#endif
895
896// The relay listener's table: the published front-port binds, and one live bridge per relayed
897// connection - each carrying the relay engine's two PROTOCORE_RELAY_BUF carry buffers. Taken from
898// the persistent end so a bridge outlives the poll ticks it spans. Proved against
899// sizeof(RelayListenerCtx) by a static_assert in relay_listener.c.
900#ifndef PROTOCORE_RELAY_LISTENER_BORROW
901#define PROTOCORE_RELAY_LISTENER_BORROW \
902 ((size_t)PROTOCORE_RELAY_MAX_PUBLISH * (PROTOCORE_RELAY_HOST_MAX + 16u) + \
903 (size_t)PROTOCORE_RELAY_MAX_CONNS * (2u * PROTOCORE_RELAY_BUF + 128u) + 64u)
904#endif
905
906// The southbound gateway's table: one entry per published radio port with its rate-limit window,
907// plus the northbound sink, the topic prefix and the counters. Taken from the persistent end so a
908// published port outlives the frames it carries. Proved against sizeof(GatewayCtx) by a
909// static_assert in gateway.c.
910#ifndef PROTOCORE_GATEWAY_BORROW
911#define PROTOCORE_GATEWAY_BORROW ((size_t)PROTOCORE_GW_MAX_PORTS * 32u + 128u)
912#endif
913
914// The interface bridge's rule table: one address:port -> bus mapping per rule, each carrying a
915// bind address and the bus target it forwards to. Taken from the persistent end so a published
916// rule outlives the connections it serves. Proved against sizeof(BridgeCtx) by a static_assert in
917// iface_bridge.c.
918#ifndef PROTOCORE_IFACE_BRIDGE_BORROW
919#define PROTOCORE_IFACE_BRIDGE_BORROW ((size_t)PROTOCORE_BRIDGE_MAX_RULES * 64u + 64u)
920#endif
921
922// The bridge glue's state: which listener each rule is published on, whether the handler and the
923// shared SPI bus are up, and the one chunk a STREAM target moves per pump. Taken from the
924// persistent end so a published bind outlives the connections it serves. Proved against
925// sizeof(BridgeGlueCtx) by a static_assert in iface_bridge_hw.c.
926#ifndef PROTOCORE_IFACE_BRIDGE_HW_BORROW
927#define PROTOCORE_IFACE_BRIDGE_HW_BORROW \
928 ((size_t)PROTOCORE_BRIDGE_MAX_RULES * 16u + PROTOCORE_BRIDGE_STREAM_CHUNK + 64u)
929#endif
930
931// The WebDAV handler's state: the accessor root it resolves every path against, the 207 Multi-Status
932// build buffer (PROTOCORE_WEBDAV_BUF_SIZE), one directory entry's name for the Depth-1 listing
933// (PROTOCORE_FILESYSTEM_PATH_MAX), one streaming-PUT destination per connection slot, and the
934// server-global lock table (PROTOCORE_DAV_LOCK_MAX entries of path + token). Taken from the
935// persistent end so a lock outlives the request that took it. A literal rather than a formula
936// because the lock terms are webdav.h's, which this file is included by rather than includes;
937// proved against sizeof(DavCtx) by a static_assert in webdav_handler.c.
938#ifndef PROTOCORE_WEBDAV_BORROW
939#define PROTOCORE_WEBDAV_BORROW 4096
940#endif
941
942// The provisioning service's state: the softAP address the captive-portal DNS answers with, stamped
943// by begin() from the interface the radio actually came up on. Taken from the persistent end so it
944// outlives the DNS callbacks that read it. Proved against sizeof(ProvCtx) by a static_assert in
945// provisioning_service.c.
946#ifndef PROTOCORE_PROVISIONING_BORROW
947#define PROTOCORE_PROVISIONING_BORROW 32
948#endif
949
950// The hot-swap binding: the state machine (state, failure run and threshold, probe interval and
951// stamp, mount and fault counts) plus the four callbacks the application registered and the context
952// it passes back. Taken from the persistent end so a volume's state survives the polls that watch
953// it. A literal rather than a formula because the terms are hotswap.h's, which this file is
954// included by rather than includes; proved against sizeof(HotswapCtx) by a static_assert in
955// hotswap.c.
956#ifndef PROTOCORE_HOTSWAP_BORROW
957#define PROTOCORE_HOTSWAP_BORROW 128
958#endif
959
960// The SMBus transaction state: whether the Packet Error Code is on, and the frame a transaction is
961// composed in - two address bytes, a command, a count, a 32-octet block and the PEC, which is the
962// longest sequence any shape puts on the wire. Taken from the persistent end so the PEC setting
963// outlives the transactions that use it. A literal rather than a formula because the terms are
964// smbus.h's, which this file is included by rather than includes; proved against sizeof(SmbusCtx)
965// by a static_assert in smbus.c.
966#ifndef PROTOCORE_SMBUS_BORROW
967#define PROTOCORE_SMBUS_BORROW 64
968#endif
969
970// The MPR121's I2C binding: the device address, the two-octet register frame, and the bring-up
971// sequence (MPR121_INIT_MAX register/value pairs, written one pair at a time). Taken from the
972// persistent end so the address set at begin() outlives the reads that use it. A literal rather
973// than a formula because the terms are mpr121.h's, which this file is included by rather than
974// includes; proved against sizeof(Mpr121Ctx) by a static_assert in mpr121.c.
975#ifndef PROTOCORE_MPR121_BORROW
976#define PROTOCORE_MPR121_BORROW 128
977#endif
978
979// The HMMD radar's UART binding: the frame reassembler (a 45-octet frame plus its cursor), the last
980// decoded report (detect, distance and 16 gate energies), and the 64-octet chunk a poll reads into.
981// Taken from the persistent end so a frame split across polls reassembles. A literal rather than a
982// formula because the terms are hmmd.h's, which this file is included by rather than includes;
983// proved against sizeof(HmmdCtx) by a static_assert in hmmd.c.
984#ifndef PROTOCORE_HMMD_BORROW
985#define PROTOCORE_HMMD_BORROW 256
986#endif
987
988// The LD2410 radar's UART binding: the frame reassembler (a 72-octet frame plus its cursor), the
989// last decoded report with its 9 gate energies, the 64-octet receive chunk and the 16-octet command
990// frame. Taken from the persistent end so a frame split across polls reassembles. A literal rather
991// than a formula because the terms are ld2410's own; proved against sizeof(Ld2410Ctx) by a
992// static_assert in ld2410.c.
993#ifndef PROTOCORE_LD2410_BORROW
994#define PROTOCORE_LD2410_BORROW 320
995#endif
996
997// The dashboard's state: the widget table the application registered, the current value per widget,
998// the inbound-control callback, and the two route paths begin() composes - the SSE stream and the
999// control socket, one MAX_PATH_LEN each. Taken from the persistent end so the layout outlives the
1000// requests that render it. Proved against sizeof(DashboardCtx) by a static_assert in dashboard.c.
1001#ifndef PROTOCORE_DASHBOARD_BORROW
1002#define PROTOCORE_DASHBOARD_BORROW ((size_t)PROTOCORE_DASHBOARD_MAX_WIDGETS * 4u + 2u * MAX_PATH_LEN + 64u)
1003#endif
1004
1005// An I2C peripheral binding: the device address and the widest bus frame the part moves. Taken
1006// from the persistent end so the address set at begin() outlives the transfers that use it. One
1007// size covers them all - the widest frame here is a register byte plus a 16-bit value - and each
1008// module's own static_assert proves its context against it.
1009#ifndef PROTOCORE_I2C_DEVICE_BORROW
1010#define PROTOCORE_I2C_DEVICE_BORROW 32
1011#endif
1012
1013// The bus capture's binding: the frame sink a capture delivers to, and whether one is running.
1014// Taken from the persistent end so the binding outlives the poll ticks it spans. Proved against
1015// sizeof(BusCaptureCtx) by a static_assert in bus_capture.c.
1016#ifndef PROTOCORE_BUS_CAPTURE_BORROW
1017#define PROTOCORE_BUS_CAPTURE_BORROW 32u
1018#endif
1019
1020// A QUIC connection's context: the packet-number spaces, the stream table, and the TLS handshake
1021// with its traffic secrets. Secure rather than plaintext because of that last term - the bytes the
1022// connection owes its streams are the plaintext borrow beside it (PROTOCORE_QUIC_CONN_BORROW), and
1023// only these carry key material. One per connection, taken from the persistent end so it lasts the
1024// connection. Measured at 13904 bytes: the QuicTls inside it carries ks_store (TLS13_KS_BORROW) and
1025// two transcript contexts (TLS13_TRANSCRIPT_BORROW each), so it tracks both. Proved against
1026// sizeof(QuicConnCtx) by a static_assert in quic_conn.c.
1027#ifndef PROTOCORE_QUIC_CONN_CTX_BORROW
1028#define PROTOCORE_QUIC_CONN_CTX_BORROW 13952
1029#endif
1030
1031#ifndef PROTOCORE_SECURE_ARENA_SIZE
1032
1033// The modexp dominates: one backend or the other is compiled, never both. The software backend also
1034// walks the SSH host key in the pool and holds its private exponent there; the accelerated one hands
1035// the key to mbedtls instead.
1036#if PROTOCORE_HAS_HW_BIGNUM
1037#define PROTOCORE_SECURE_WORK_BIGNUM PROTOCORE_WORK_BIGNUM_HW
1038#else
1039#define PROTOCORE_SECURE_WORK_BIGNUM (PROTOCORE_WORK_BIGNUM_SW + PROTOCORE_WORK_SSH_HOST_KEY)
1040#endif
1041
1042// Feature-gated terms: a build pays only for the code it compiled.
1043#if PROTOCORE_ENABLE_SSH || PROTOCORE_ENABLE_SSH_CLIENT || PROTOCORE_ENABLE_TLS || PROTOCORE_ENABLE_HTTP3 || \
1044 PROTOCORE_ENABLE_DTLS
1045#define PROTOCORE_SECURE_WORK_AEAD \
1046 (PROTOCORE_AESGCM_BORROW + PROTOCORE_CHACHAPOLY_BORROW + PROTOCORE_CHACHA20_BORROW + PROTOCORE_POLY1305_BORROW)
1047#else
1048#define PROTOCORE_SECURE_WORK_AEAD 0
1049#endif
1050
1051// The SMB client's one sequential dialogue: the send and receive framing buffers, the NTLMv2
1052// response, the AUTHENTICATE blob, the second SESSION_SETUP body, the UTF-16 staging and the
1053// CHALLENGE target-info, plus the bytes this connection's crypto calls work out of. Measured at
1054// 6272 bytes for the default PROTOCORE_SMB_BUF of 1024, and scales with it. It holds the NTLM
1055// response and a keyed MAC context, so the secure end.
1056#ifndef PROTOCORE_SMB_CLIENT_BORROW
1057#define PROTOCORE_SMB_CLIENT_BORROW \
1058 (2u * PROTOCORE_SMB_BUF + 5u * (PROTOCORE_SMB_BUF / 2) + PROTOCORE_CRYPTO_BORROW_MAX)
1059#endif
1060
1061#if PROTOCORE_ENABLE_SMB
1062#define PROTOCORE_SECURE_WORK_SMBCLIENT PROTOCORE_SMB_CLIENT_BORROW
1063#else
1064#define PROTOCORE_SECURE_WORK_SMBCLIENT 0
1065#endif
1066
1067#if PROTOCORE_ENABLE_SMB
1068#define PROTOCORE_SECURE_WORK_SMB \
1069 (PROTOCORE_WORK_AESCCM + PROTOCORE_WORK_AES128GCM + PROTOCORE_MD_BORROW + PROTOCORE_KDF_BORROW)
1070#else
1071#define PROTOCORE_SECURE_WORK_SMB 0
1072#endif
1073
1074#if PROTOCORE_ENABLE_SSH || PROTOCORE_ENABLE_SSH_CLIENT
1075#define PROTOCORE_SECURE_WORK_SSHCIPHER PROTOCORE_AES256CTR_BORROW
1076#define PROTOCORE_SECURE_WORK_SSHCONN PROTOCORE_WORK_SSH_CONN
1077#else
1078#define PROTOCORE_SECURE_WORK_SSHCIPHER 0
1079#define PROTOCORE_SECURE_WORK_SSHCONN 0
1080#endif
1081
1082// The dialling role's session: the negotiated methods, the relay connection, the KEX private scalar,
1083// and the hybrid decapsulation key when one is built. Measured at 368 bytes classical, 2136 with
1084// sntrup761 and 2768 with ML-KEM-768, which share one union so the larger arm is the size. Carries
1085// the KEX private and the hybrid secret key, so the secure end.
1086#ifndef PROTOCORE_SSH_CLIENT_BORROW
1087#if PROTOCORE_ENABLE_PQC_KEX
1088#define PROTOCORE_SSH_CLIENT_BORROW 2816u
1089#elif PROTOCORE_ENABLE_SSH_SNTRUP761
1090#define PROTOCORE_SSH_CLIENT_BORROW 2176u
1091#else
1092#define PROTOCORE_SSH_CLIENT_BORROW 384u
1093#endif
1094#endif
1095
1096#if PROTOCORE_ENABLE_SSH_CLIENT
1097#define PROTOCORE_SECURE_WORK_SSHCLIENT PROTOCORE_SSH_CLIENT_BORROW
1098#else
1099#define PROTOCORE_SECURE_WORK_SSHCLIENT 0
1100#endif
1101
1102// Every SSH connection's span, which ssh.c hands out one slot at a time. This is the largest single
1103// borrow in the tree - 177472 bytes at the default sizing - and it is the bytes that used to sit in
1104// ssh.c's BSS, so the arena grows by exactly what BSS shed. Slot memory holds the session keys, so
1105// the secure end.
1106#if PROTOCORE_ENABLE_SSH || PROTOCORE_ENABLE_SSH_CLIENT
1107#define PROTOCORE_SECURE_WORK_SSHSLOTS PROTOCORE_SSH_BORROW
1108#else
1109#define PROTOCORE_SECURE_WORK_SSHSLOTS 0
1110#endif
1111
1112// The SSH RSA host key: the borrowed span holding the private exponent, and whether it has been
1113// parsed. Measured at 40 bytes; the key bytes themselves are a separate PROTOCORE_RSA_KEY_BYTES
1114// borrow this points at. A private exponent, so the secure end.
1115#ifndef PROTOCORE_SSH_RSA_BORROW
1116#define PROTOCORE_SSH_RSA_BORROW 64u
1117#endif
1118
1119#if PROTOCORE_ENABLE_SSH || PROTOCORE_ENABLE_SSH_CLIENT
1120#define PROTOCORE_SECURE_WORK_SSHRSA PROTOCORE_SSH_RSA_BORROW
1121#else
1122#define PROTOCORE_SECURE_WORK_SSHRSA 0
1123#endif
1124
1125// The SSH channel layer's remote-forward bindings (RFC 4254 sec 7.1), the application hooks it
1126// calls back into, and the local-forward admission policy. Measured at 176 bytes with every SSH
1127// capability on, and scales with PROTOCORE_SSH_RFWD_MAX. Carries the bound addresses a forward
1128// names, so the secure end alongside the rest of the SSH state.
1129#ifndef PROTOCORE_SSH_CONNECTION_BORROW
1130#define PROTOCORE_SSH_CONNECTION_BORROW ((size_t)PROTOCORE_SSH_RFWD_MAX * (PROTOCORE_SSH_FWD_HOST_MAX + 16u) + 128u)
1131#endif
1132
1133#if PROTOCORE_ENABLE_SSH || PROTOCORE_ENABLE_SSH_CLIENT
1134#define PROTOCORE_SECURE_WORK_SSHCONNECTION PROTOCORE_SSH_CONNECTION_BORROW
1135#else
1136#define PROTOCORE_SECURE_WORK_SSHCONNECTION 0
1137#endif
1138
1139// The SSH auth layer's per-slot state: failure counts, the sec 4 timeout stamps, the user and
1140// service each slot's state belongs to, the deferred password change, the armed keyboard-interactive
1141// exchange, and the three application verifiers. Measured at 112 bytes, 144 with
1142// PROTOCORE_ENABLE_SSH_KEYBOARD_INTERACTIVE, and scales with MAX_SSH_CONNS. Carries user names and
1143// a password change in flight, so the secure end.
1144#ifndef PROTOCORE_SSH_AUTH_BORROW
1145#define PROTOCORE_SSH_AUTH_BORROW ((size_t)MAX_SSH_CONNS * 160u + 32u)
1146#endif
1147
1148#if PROTOCORE_ENABLE_SSH || PROTOCORE_ENABLE_SSH_CLIENT
1149#define PROTOCORE_SECURE_WORK_SSHAUTH PROTOCORE_SSH_AUTH_BORROW
1150#else
1151#define PROTOCORE_SECURE_WORK_SSHAUTH 0
1152#endif
1153
1154// The SSH transport machine's host signing keys - the ed25519 seed and public half, the P-256
1155// scalar and point, whether each is loaded - and the runtime KEX preference. Measured at 164 bytes.
1156// Host private keys, so the secure end.
1157#ifndef PROTOCORE_SSH_TRANSPORT_BORROW
1158#define PROTOCORE_SSH_TRANSPORT_BORROW 192u
1159#endif
1160
1161#if PROTOCORE_ENABLE_SSH || PROTOCORE_ENABLE_SSH_CLIENT
1162#define PROTOCORE_SECURE_WORK_SSHTRANSPORT PROTOCORE_SSH_TRANSPORT_BORROW
1163#else
1164#define PROTOCORE_SECURE_WORK_SSHTRANSPORT 0
1165#endif
1166
1167#if PROTOCORE_ENABLE_AUTH
1168#define PROTOCORE_SECURE_WORK_AUTH PROTOCORE_HTTP_AUTH_BORROW
1169#else
1170#define PROTOCORE_SECURE_WORK_AUTH 0
1171#endif
1172
1173#if PROTOCORE_ENABLE_SSE
1174#define PROTOCORE_SECURE_WORK_SSE PROTOCORE_SSE_BORROW
1175#else
1176#define PROTOCORE_SECURE_WORK_SSE 0
1177#endif
1178
1179// The HTTP/2 engine's bytes: one connection record per transport slot and the per-slot
1180// header-block bits. Taken from the persistent end so it lasts the life of the program. Proved
1181// against the real split by a static_assert in h2_server.c.
1182// One H2Conn: the frame and header-block cursors, the HPACK decoder table, the peer settings and
1183// the stream table. The exact width is h2_conn.h's, which protocore_config.h cannot see, so the
1184// static_assert in h2_server.c is what proves this covers it.
1185#ifndef PROTOCORE_H2_CONN_RECORD
1186#define PROTOCORE_H2_CONN_RECORD 32768
1187#endif
1188
1189// HTTP/2 runs over TLS, so a connection's bytes are secure. The slot table holds the pointers;
1190// the connections hold the bytes.
1191#if PROTOCORE_ENABLE_HTTP2 && PROTOCORE_ENABLE_TLS
1192#define PROTOCORE_SECURE_WORK_H2_SERVER \
1193 (PROTOCORE_H2_SERVER_BORROW + \
1194 (size_t)MAX_CONNS * (PROTOCORE_H2_CONN_RECORD + PROTOCORE_H2_MAX_FRAME + 2 * PROTOCORE_H2_HDR_BLOCK + 16))
1195#else
1196#define PROTOCORE_SECURE_WORK_H2_SERVER 0
1197#endif
1198
1199#if PROTOCORE_ENABLE_WEBSOCKET
1200#define PROTOCORE_SECURE_WORK_WS PROTOCORE_WS_BORROW
1201#else
1202#define PROTOCORE_SECURE_WORK_WS 0
1203#endif
1204
1205#if PROTOCORE_ENABLE_CSRF
1206#define PROTOCORE_SECURE_WORK_CSRF PROTOCORE_CSRF_BORROW
1207#else
1208#define PROTOCORE_SECURE_WORK_CSRF 0
1209#endif
1210
1211#if PROTOCORE_ENABLE_AUTH_LOCKOUT
1212#define PROTOCORE_SECURE_WORK_LOCKOUT PROTOCORE_AUTH_LOCKOUT_BORROW
1213#else
1214#define PROTOCORE_SECURE_WORK_LOCKOUT 0
1215#endif
1216
1217#if PROTOCORE_ENABLE_FORWARDED_TRUST
1218#define PROTOCORE_SECURE_WORK_FWDTRUST PROTOCORE_FORWARDED_TRUST_BORROW
1219#else
1220#define PROTOCORE_SECURE_WORK_FWDTRUST 0
1221#endif
1222
1223#if PROTOCORE_ENABLE_AUDIT_LOG
1224#define PROTOCORE_SECURE_WORK_AUDIT PROTOCORE_AUDIT_LOG_BORROW
1225#else
1226#define PROTOCORE_SECURE_WORK_AUDIT 0
1227#endif
1228
1229#if PROTOCORE_ENABLE_WEB_TERMINAL
1230#define PROTOCORE_SECURE_WORK_WEBTERM PROTOCORE_WEB_TERMINAL_BORROW
1231#else
1232#define PROTOCORE_SECURE_WORK_WEBTERM 0
1233#endif
1234
1235#if PROTOCORE_ENABLE_CONFIG_STORE
1236#define PROTOCORE_SECURE_WORK_CFGSTORE PROTOCORE_CONFIG_STORE_BORROW
1237#else
1238#define PROTOCORE_SECURE_WORK_CFGSTORE 0
1239#endif
1240
1241#if PROTOCORE_ENABLE_RELAY
1242#define PROTOCORE_SECURE_WORK_RELAYLISTEN PROTOCORE_RELAY_LISTENER_BORROW
1243#else
1244#define PROTOCORE_SECURE_WORK_RELAYLISTEN 0
1245#endif
1246
1247#if PROTOCORE_ENABLE_GATEWAY
1248#define PROTOCORE_SECURE_WORK_GATEWAY PROTOCORE_GATEWAY_BORROW
1249#else
1250#define PROTOCORE_SECURE_WORK_GATEWAY 0
1251#endif
1252
1253#if PROTOCORE_ENABLE_IFACE_BRIDGE
1254#define PROTOCORE_SECURE_WORK_IFACEBRIDGE PROTOCORE_IFACE_BRIDGE_BORROW
1255#else
1256#define PROTOCORE_SECURE_WORK_IFACEBRIDGE 0
1257#endif
1258
1259#if PROTOCORE_ENABLE_IFACE_BRIDGE
1260#define PROTOCORE_SECURE_WORK_IFACEBRIDGEHW PROTOCORE_IFACE_BRIDGE_HW_BORROW
1261#else
1262#define PROTOCORE_SECURE_WORK_IFACEBRIDGEHW 0
1263#endif
1264
1265#if PROTOCORE_ENABLE_WEBDAV
1266#define PROTOCORE_SECURE_WORK_WEBDAV PROTOCORE_WEBDAV_BORROW
1267#else
1268#define PROTOCORE_SECURE_WORK_WEBDAV 0
1269#endif
1270
1271#if PROTOCORE_ENABLE_PROVISIONING
1272#define PROTOCORE_SECURE_WORK_PROVISIONING PROTOCORE_PROVISIONING_BORROW
1273#else
1274#define PROTOCORE_SECURE_WORK_PROVISIONING 0
1275#endif
1276
1277#if PROTOCORE_ENABLE_HOTSWAP
1278#define PROTOCORE_SECURE_WORK_HOTSWAP PROTOCORE_HOTSWAP_BORROW
1279#else
1280#define PROTOCORE_SECURE_WORK_HOTSWAP 0
1281#endif
1282
1283#if PROTOCORE_ENABLE_SMBUS
1284#define PROTOCORE_SECURE_WORK_SMBUS PROTOCORE_SMBUS_BORROW
1285#else
1286#define PROTOCORE_SECURE_WORK_SMBUS 0
1287#endif
1288
1289#if PROTOCORE_ENABLE_MPR121
1290#define PROTOCORE_SECURE_WORK_MPR121 PROTOCORE_MPR121_BORROW
1291#else
1292#define PROTOCORE_SECURE_WORK_MPR121 0
1293#endif
1294
1295#if PROTOCORE_ENABLE_HMMD
1296#define PROTOCORE_SECURE_WORK_HMMD PROTOCORE_HMMD_BORROW
1297#else
1298#define PROTOCORE_SECURE_WORK_HMMD 0
1299#endif
1300
1301#if PROTOCORE_ENABLE_LD2410
1302#define PROTOCORE_SECURE_WORK_LD2410 PROTOCORE_LD2410_BORROW
1303#else
1304#define PROTOCORE_SECURE_WORK_LD2410 0
1305#endif
1306
1307#if PROTOCORE_ENABLE_DASHBOARD
1308#define PROTOCORE_SECURE_WORK_DASHBOARD PROTOCORE_DASHBOARD_BORROW
1309#else
1310#define PROTOCORE_SECURE_WORK_DASHBOARD 0
1311#endif
1312
1313#if PROTOCORE_ENABLE_ADS1115
1314#define PROTOCORE_SECURE_WORK_ADS1115 PROTOCORE_I2C_DEVICE_BORROW
1315#else
1316#define PROTOCORE_SECURE_WORK_ADS1115 0
1317#endif
1318
1319#if PROTOCORE_ENABLE_INA219
1320#define PROTOCORE_SECURE_WORK_INA219 PROTOCORE_I2C_DEVICE_BORROW
1321#else
1322#define PROTOCORE_SECURE_WORK_INA219 0
1323#endif
1324
1325#if PROTOCORE_ENABLE_PCA9685
1326#define PROTOCORE_SECURE_WORK_PCA9685 PROTOCORE_I2C_DEVICE_BORROW
1327#else
1328#define PROTOCORE_SECURE_WORK_PCA9685 0
1329#endif
1330
1331#if PROTOCORE_ENABLE_FDC2214
1332#define PROTOCORE_SECURE_WORK_FDC2214 PROTOCORE_I2C_DEVICE_BORROW
1333#else
1334#define PROTOCORE_SECURE_WORK_FDC2214 0
1335#endif
1336
1337#if PROTOCORE_ENABLE_LDC1614
1338#define PROTOCORE_SECURE_WORK_LDC1614 PROTOCORE_I2C_DEVICE_BORROW
1339#else
1340#define PROTOCORE_SECURE_WORK_LDC1614 0
1341#endif
1342
1343#if PROTOCORE_ENABLE_VL53L0X
1344#define PROTOCORE_SECURE_WORK_VL53L0X PROTOCORE_I2C_DEVICE_BORROW
1345#else
1346#define PROTOCORE_SECURE_WORK_VL53L0X 0
1347#endif
1348
1349#if PROTOCORE_ENABLE_RTC
1350#define PROTOCORE_SECURE_WORK_RTC PROTOCORE_I2C_DEVICE_BORROW
1351#else
1352#define PROTOCORE_SECURE_WORK_RTC 0
1353#endif
1354
1355#if PROTOCORE_ENABLE_SHT3X
1356#define PROTOCORE_SECURE_WORK_SHT3X PROTOCORE_I2C_DEVICE_BORROW
1357#else
1358#define PROTOCORE_SECURE_WORK_SHT3X 0
1359#endif
1360
1361#if PROTOCORE_ENABLE_BUS_CAPTURE
1362#define PROTOCORE_SECURE_WORK_BUSCAPTURE PROTOCORE_BUS_CAPTURE_BORROW
1363#else
1364#define PROTOCORE_SECURE_WORK_BUSCAPTURE 0
1365#endif
1366
1367#if PROTOCORE_ENABLE_TLS
1368#define PROTOCORE_SECURE_WORK_TLSCONN PROTOCORE_WORK_TLS_CONN
1369#else
1370#define PROTOCORE_SECURE_WORK_TLSCONN 0
1371#endif
1372
1373// The HTTP/3 connection above it carries no key material, so its context sits in the plaintext
1374// borrow with the stream bytes and takes nothing from here.
1375#if PROTOCORE_ENABLE_HTTP3
1376#define PROTOCORE_SECURE_WORK_QUICCONN ((size_t)PROTOCORE_QUIC_MAX_CONNS * PROTOCORE_QUIC_CONN_CTX_BORROW)
1377#else
1378#define PROTOCORE_SECURE_WORK_QUICCONN 0
1379#endif
1380
1381#if PROTOCORE_ENABLE_SHA1
1382#define PROTOCORE_SECURE_WORK_SHA1 PROTOCORE_SHA1_BORROW
1383#else
1384#define PROTOCORE_SECURE_WORK_SHA1 0
1385#endif
1386
1387#if PROTOCORE_ENABLE_SHA3
1388#define PROTOCORE_SECURE_WORK_SHA3 PROTOCORE_SHA3_BORROW
1389#else
1390#define PROTOCORE_SECURE_WORK_SHA3 0
1391#endif
1392
1393#if PROTOCORE_ENABLE_SHA256
1394#define PROTOCORE_SECURE_WORK_SHA256 PROTOCORE_SHA256_BORROW
1395#else
1396#define PROTOCORE_SECURE_WORK_SHA256 0
1397#endif
1398
1399#if PROTOCORE_ENABLE_CURVE25519
1400#define PROTOCORE_SECURE_WORK_CURVE25519 PROTOCORE_CURVE25519_BORROW
1401#else
1402#define PROTOCORE_SECURE_WORK_CURVE25519 0
1403#endif
1404
1405#if PROTOCORE_ENABLE_ED25519
1406#define PROTOCORE_SECURE_WORK_ED25519 PROTOCORE_ED25519_BORROW
1407#else
1408#define PROTOCORE_SECURE_WORK_ED25519 0
1409#endif
1410
1411#if PROTOCORE_ENABLE_ECDSA
1412#define PROTOCORE_SECURE_WORK_ECDSA PROTOCORE_ECDSA_BORROW
1413#else
1414#define PROTOCORE_SECURE_WORK_ECDSA 0
1415#endif
1416
1417#if PROTOCORE_ENABLE_RSA
1418#define PROTOCORE_SECURE_WORK_RSA PROTOCORE_RSA_BORROW
1419#else
1420#define PROTOCORE_SECURE_WORK_RSA 0
1421#endif
1422
1423#if PROTOCORE_ENABLE_MLKEM
1424#define PROTOCORE_SECURE_WORK_MLKEM PROTOCORE_MLKEM_BORROW
1425#else
1426#define PROTOCORE_SECURE_WORK_MLKEM 0
1427#endif
1428
1429#if PROTOCORE_ENABLE_SNTRUP761
1430#define PROTOCORE_SECURE_WORK_SNTRUP761 PROTOCORE_SNTRUP761_BORROW
1431#else
1432#define PROTOCORE_SECURE_WORK_SNTRUP761 0
1433#endif
1434
1435// The record AEAD the QUIC, DTLS and TLS paths key per direction, one borrow carrying both the AEAD
1436// context and the header-protection block context. The SMB term below carries its own.
1437#if PROTOCORE_ENABLE_AES128GCM
1438#define PROTOCORE_SECURE_WORK_AES128GCM PROTOCORE_AES128GCM_BORROW
1439#else
1440#define PROTOCORE_SECURE_WORK_AES128GCM 0
1441#endif
1442
1443// The generator takes its borrow from the pool's PERSISTENT end, once, for the program's life.
1444#if PROTOCORE_ENABLE_RNG
1445#define PROTOCORE_SECURE_WORK_RNG PROTOCORE_RNG_BORROW
1446#else
1447#define PROTOCORE_SECURE_WORK_RNG 0
1448#endif
1449
1450// The resolver's own state: the span a record's owner name is walked into, the query in flight and
1451// the nameserver being asked, the query ID (RFC 1035 sec 4.1.1), the deadline it waits to, and the
1452// busy flag a second host waits on. The vendor arm holds the stack's reported address instead of
1453// the query. The spans it hands out are already taken from the secure end and its release wipes, so
1454// the handles to them are kept beside their bytes. Proved against sizeof(struct ResolverStorage) by
1455// a static_assert in dns_resolver.c.
1456#ifndef PROTOCORE_DNS_RESOLVER_BORROW
1457#define PROTOCORE_DNS_RESOLVER_BORROW 192u
1458#endif
1459
1460#if PROTOCORE_ENABLE_DNS_RESOLVER
1461#define PROTOCORE_SECURE_WORK_DNSRESOLVER PROTOCORE_DNS_RESOLVER_BORROW
1462#else
1463#define PROTOCORE_SECURE_WORK_DNSRESOLVER 0
1464#endif
1465
1466// What one pump of a handshake owes the wire: the server's whole flight fits in one build, and the
1467// seam sends it in one raw write. Sized by the largest flight this engine produces.
1468#ifndef PROTOCORE_TLS_SEAM_OUT_CAP
1469#define PROTOCORE_TLS_SEAM_OUT_CAP 2048
1470#endif
1471
1472// The slot-indexed TLS surface: one TlsConn per connection slot, the per-connection configuration
1473// carrying its own ephemeral key and Hello random, the pcb each writes through, the credential this
1474// end presents, and the flight buffer above. A TlsConn holds its four traffic key generations
1475// inline and the credential is a signing seed, so these are key material and take the end whose
1476// release wipes. Proved against sizeof(struct TlsStorage) by a static_assert in tls.c.
1477// Per slot: sizeof(TlsConn) 3312 + sizeof(TlsConnConfig) 64 + the pcb 8 + the ephemeral key and
1478// Hello random 64, measured on the host at the default widths; 3584 rounds that up.
1479#ifndef PROTOCORE_TLS_BORROW
1480#define PROTOCORE_TLS_BORROW ((size_t)MAX_CONNS * 3584u + (size_t)PROTOCORE_TLS_SEAM_OUT_CAP + 128u)
1481#endif
1482
1483#if PROTOCORE_ENABLE_TLS
1484#define PROTOCORE_SECURE_WORK_TLSSEAM PROTOCORE_TLS_BORROW
1485#else
1486#define PROTOCORE_SECURE_WORK_TLSSEAM 0
1487#endif
1488
1489// One certificate signature check: the RSA modulus and exponent left-padded into the fields the
1490// verifier takes, then the region the algorithm itself works in. RSA sizes the second - a
1491// verification runs over the modulus - and the ECDSA and Ed25519 paths use a fraction of it.
1492// Proved against sizeof(X509VerifyCtx) + PROTOCORE_RSA_BORROW by a static_assert in x509_verify.c.
1493#ifndef PROTOCORE_X509_VERIFY_BORROW
1494#define PROTOCORE_X509_VERIFY_BORROW ((size_t)PROTOCORE_RSA_KEY_BYTES + 8u + PROTOCORE_RSA_BORROW)
1495#endif
1496
1497// Either credential reaches it: PROTOCORE_ENABLE_X509 for the chain, and the portable TLS arm
1498// for CertificateVerify, which it checks through X509Verify.message whether the peer presented a
1499// certificate or an RFC 7250 raw public key.
1500#if PROTOCORE_ENABLE_X509 || PROTOCORE_ENABLE_TLS
1501#define PROTOCORE_SECURE_WORK_X509VERIFY PROTOCORE_X509_VERIFY_BORROW
1502#else
1503#define PROTOCORE_SECURE_WORK_X509VERIFY 0
1504#endif
1505
1506#define PROTOCORE_SECURE_ARENA_SIZE \
1507 (PROTOCORE_SECURE_WORK_BIGNUM + PROTOCORE_SECURE_WORK_AEAD + PROTOCORE_SECURE_WORK_SMB + \
1508 PROTOCORE_SECURE_WORK_SSHCIPHER + PROTOCORE_SECURE_WORK_SSHCONN + PROTOCORE_SECURE_WORK_TLSCONN + \
1509 PROTOCORE_SECURE_WORK_SHA1 + PROTOCORE_SECURE_WORK_SHA3 + PROTOCORE_SECURE_WORK_SHA256 + \
1510 PROTOCORE_SECURE_WORK_CURVE25519 + PROTOCORE_SECURE_WORK_ED25519 + PROTOCORE_SECURE_WORK_ECDSA + \
1511 PROTOCORE_SECURE_WORK_RSA + PROTOCORE_SECURE_WORK_MLKEM + PROTOCORE_SECURE_WORK_SNTRUP761 + \
1512 PROTOCORE_SECURE_WORK_AES128GCM + PROTOCORE_SECURE_WORK_ROUTETABLE + PROTOCORE_SECURE_WORK_AUTH + \
1513 PROTOCORE_SECURE_WORK_RNG + PROTOCORE_SECURE_WORK_QUICCONN + PROTOCORE_SECURE_WORK_WS + \
1514 PROTOCORE_SECURE_WORK_SSE + PROTOCORE_SECURE_WORK_H2_SERVER + PROTOCORE_SECURE_WORK_CSRF + \
1515 PROTOCORE_SECURE_WORK_LOCKOUT + PROTOCORE_SECURE_WORK_FWDTRUST + PROTOCORE_SECURE_WORK_AUDIT + \
1516 PROTOCORE_SECURE_WORK_WEBTERM + PROTOCORE_SECURE_WORK_CFGSTORE + PROTOCORE_SECURE_WORK_RELAYLISTEN + \
1517 PROTOCORE_SECURE_WORK_GATEWAY + PROTOCORE_SECURE_WORK_IFACEBRIDGE + PROTOCORE_SECURE_WORK_IFACEBRIDGEHW + \
1518 PROTOCORE_SECURE_WORK_BUSCAPTURE + PROTOCORE_SECURE_WORK_HMMD + PROTOCORE_SECURE_WORK_LD2410 + \
1519 PROTOCORE_SECURE_WORK_DASHBOARD + PROTOCORE_SECURE_WORK_ADS1115 + PROTOCORE_SECURE_WORK_RTC + \
1520 PROTOCORE_SECURE_WORK_SHT3X + PROTOCORE_SECURE_WORK_INA219 + PROTOCORE_SECURE_WORK_PCA9685 + \
1521 PROTOCORE_SECURE_WORK_MPR121 + PROTOCORE_SECURE_WORK_FDC2214 + PROTOCORE_SECURE_WORK_LDC1614 + \
1522 PROTOCORE_SECURE_WORK_VL53L0X + PROTOCORE_SECURE_WORK_SMBUS + PROTOCORE_SECURE_WORK_HOTSWAP + \
1523 PROTOCORE_SECURE_WORK_PROVISIONING + PROTOCORE_SECURE_WORK_WEBDAV + PROTOCORE_SECURE_WORK_DNSRESOLVER + \
1524 PROTOCORE_SECURE_WORK_TLSSEAM + PROTOCORE_SECURE_WORK_X509VERIFY + PROTOCORE_SECURE_WORK_COAPSSERVER + \
1525 PROTOCORE_SECURE_WORK_MQTT + PROTOCORE_SECURE_WORK_SNMPNOTIFY + PROTOCORE_SECURE_WORK_HTTPCLIENT + \
1526 PROTOCORE_SECURE_WORK_SMTP + PROTOCORE_SECURE_WORK_WSCLIENT + PROTOCORE_SECURE_WORK_SNMPV3 + \
1527 PROTOCORE_SECURE_WORK_SNMPAGENT + PROTOCORE_SECURE_WORK_OAUTH2 + PROTOCORE_SECURE_WORK_SSHCLIENT + \
1528 PROTOCORE_SECURE_WORK_SSHTRANSPORT + PROTOCORE_SECURE_WORK_SSHAUTH + PROTOCORE_SECURE_WORK_SSHCONNECTION + \
1529 PROTOCORE_SECURE_WORK_SSHRSA + PROTOCORE_SECURE_WORK_SSHSLOTS + PROTOCORE_SECURE_WORK_SMBCLIENT + \
1530 256) // + 256: alignment round-up across the individual borrows
1531#endif
1532
1533// Both of these are struct members (TcpConn::proto, TcpConn::iface, Listener::proto, and a route's
1534// interface gate), so their width is per-slot BSS rather than a detail of the type. Pinned here
1535// because the pool sizes in this file are what the footprint is computed from.
1536static_assert(sizeof(ProtoConn) == 1, "ProtoConn must stay one byte: it is a per-slot struct member");
1537static_assert(sizeof(protocore_if_kind) == 1, "protocore_if_kind must stay one byte: it is a per-slot struct member");
1538
1539// ---------------------------------------------------------------------------
1540// Compile-time sanity checks
1541// ---------------------------------------------------------------------------
1542// These produce a clear #error message in the compiler output rather than a
1543// cryptic linker failure or silent misbehavior.
1544
1545#if EVT_QUEUE_DEPTH < MAX_CONNS * 4
1546#error "ProtoCore: EVT_QUEUE_DEPTH must be >= MAX_CONNS * 4 to absorb event bursts without blocking lwIP"
1547#endif
1548
1549#if MAX_CONNS < 1
1550#error "ProtoCore: MAX_CONNS must be >= 1"
1551#endif
1552
1553#if MAX_CONNS > 255
1554#error "ProtoCore: MAX_CONNS must be <= 255 (slot IDs are uint8_t)"
1555#endif
1556
1557#if PROTOCORE_ENABLE_WEBSOCKET && PROTOCORE_ENABLE_SSE
1558#if MAX_WS_CONNS + MAX_SSE_CONNS > MAX_CONNS
1559#error "ProtoCore: MAX_WS_CONNS + MAX_SSE_CONNS must not exceed MAX_CONNS"
1560#endif
1561#elif PROTOCORE_ENABLE_WEBSOCKET
1562#if MAX_WS_CONNS > MAX_CONNS
1563#error "ProtoCore: MAX_WS_CONNS must not exceed MAX_CONNS"
1564#endif
1565#elif PROTOCORE_ENABLE_SSE
1566#if MAX_SSE_CONNS > MAX_CONNS
1567#error "ProtoCore: MAX_SSE_CONNS must not exceed MAX_CONNS"
1568#endif
1569#endif
1570
1571#if BODY_BUF_SIZE < 1
1572#error "ProtoCore: BODY_BUF_SIZE must be >= 1"
1573#endif
1574
1575#if BODY_BUF_SIZE > RX_BUF_SIZE
1576#error "ProtoCore: BODY_BUF_SIZE must not exceed RX_BUF_SIZE (parser reads from the ring buffer)"
1577#endif
1578
1579#if PROTOCORE_ENABLE_FILE_SERVING && FILE_CHUNK_SIZE > RX_BUF_SIZE
1580#error "ProtoCore: FILE_CHUNK_SIZE must not exceed RX_BUF_SIZE"
1581#endif
1582
1583#if MAX_KEY_LEN < 4
1584#error "ProtoCore: MAX_KEY_LEN must be >= 4 (minimum valid HTTP header name length)"
1585#endif
1586
1587#if MAX_VAL_LEN < 1
1588#error "ProtoCore: MAX_VAL_LEN must be >= 1"
1589#endif
1590
1591#if MAX_PATH_LEN < 2
1592#error "ProtoCore: MAX_PATH_LEN must be >= 2 (minimum: \"/\")"
1593#endif
1594
1595#if MAX_ROUTES < 1
1596#error "ProtoCore: MAX_ROUTES must be >= 1"
1597#endif
1598
1599#if MAX_MIDDLEWARE < 1
1600#error "ProtoCore: MAX_MIDDLEWARE must be >= 1"
1601#endif
1602
1603#if CHUNK_BUF_SIZE < 16
1604#error "ProtoCore: CHUNK_BUF_SIZE must be >= 16"
1605#endif
1606
1607#if JSON_MAX_DEPTH < 1
1608#error "ProtoCore: JSON_MAX_DEPTH must be >= 1"
1609#endif
1610
1611#if RE_MAX_STEPS < 64
1612#error "ProtoCore: RE_MAX_STEPS must be >= 64"
1613#endif
1614
1615// RSA-2048 verification (OIDC / SSH host key / JWKS) runs on a worker task and consumes
1616// ~7 KB of stack via the mbedTLS bignum modexp. Enforce the documented floor so a lowered
1617// worker stack is caught at build time instead of overflowing on the first verify.
1618#if PROTOCORE_ENABLE_OIDC && !PROTOCORE_ENABLE_SSH && (PROTOCORE_WORKER_TASK_STACK < PROTOCORE_WORKER_STACK_RSA_MIN)
1619#error \
1620 "ProtoCore: PROTOCORE_WORKER_TASK_STACK is below PROTOCORE_WORKER_STACK_RSA_MIN; RSA-2048 verification (OIDC) needs ~7 KB of worker stack - raise PROTOCORE_WORKER_TASK_STACK (>= 8192) or marshal RSA verifies onto a dedicated larger-stack task"
1621#endif
1622
1623// SSH additionally can negotiate curve25519-sha256 + ssh-ed25519, whose software field
1624// arithmetic peaks at ~10.5 KB of worker stack (deeper than the RSA path). Enforce the
1625// higher floor so a lowered stack is caught at build time instead of tripping the task
1626// stack canary on the first modern-crypto handshake.
1627#if (PROTOCORE_ENABLE_SSH || PROTOCORE_ENABLE_SSH_CLIENT || PROTOCORE_ENABLE_HTTP3) && \
1628 (PROTOCORE_WORKER_TASK_STACK < PROTOCORE_WORKER_STACK_CURVE_MIN)
1629#error \
1630 "ProtoCore: PROTOCORE_WORKER_TASK_STACK is below PROTOCORE_WORKER_STACK_CURVE_MIN; SSH (server or reverse-SSH client) and HTTP/3 (QUIC TLS-1.3) curve25519/ed25519 need ~10.5 KB of worker stack - raise PROTOCORE_WORKER_TASK_STACK (>= 12288) or marshal the handshake onto a dedicated larger-stack task"
1631#endif
1632
1633// The PQ/T hybrid KEX (PROTOCORE_ENABLE_PQC_KEX) runs ML-KEM-768 Encaps in the handshake path, whose NTT
1634// + sampling peak at ~7 KB of worker stack on top of the classical curve/ed25519 work. Enforce a
1635// higher floor so it is caught at build time rather than overflowing on the first hybrid handshake.
1636#ifndef PROTOCORE_WORKER_STACK_PQC_MIN
1637#define PROTOCORE_WORKER_STACK_PQC_MIN 16384
1638#endif
1639#if PROTOCORE_ENABLE_PQC_KEX && !PROTOCORE_ENABLE_SSH_SNTRUP761 && \
1640 (PROTOCORE_ENABLE_SSH || PROTOCORE_ENABLE_SSH_CLIENT || PROTOCORE_ENABLE_HTTP3) && \
1641 (PROTOCORE_WORKER_TASK_STACK < PROTOCORE_WORKER_STACK_PQC_MIN)
1642#error \
1643 "ProtoCore: PROTOCORE_WORKER_TASK_STACK is below PROTOCORE_WORKER_STACK_PQC_MIN; the ML-KEM-768 hybrid KEX (PROTOCORE_ENABLE_PQC_KEX) needs ~7 KB more worker stack - raise PROTOCORE_WORKER_TASK_STACK (>= 16384) or marshal the handshake onto a dedicated larger-stack task"
1644#endif
1645
1646// sntrup761x25519-sha512 (PROTOCORE_ENABLE_SSH_SNTRUP761, on by default with the hybrid) is heavier than
1647// ML-KEM: the server runs Encaps (~22 KB), the reverse-SSH client runs KeyGen+Decaps whose FO
1648// re-encrypt peaks ~32 KB. Enforce the matching floor so it is caught at build time.
1649#ifndef PROTOCORE_WORKER_STACK_SNTRUP_MIN
1650#if defined(PROTOCORE_ENABLE_SSH_CLIENT) && PROTOCORE_ENABLE_SSH_CLIENT
1651#define PROTOCORE_WORKER_STACK_SNTRUP_MIN 40960
1652#else
1653#define PROTOCORE_WORKER_STACK_SNTRUP_MIN 32768
1654#endif
1655#endif
1656// sntrup761x25519-sha512 is an SSH key exchange; only an SSH server / reverse-SSH client runs its heavy
1657// KeyGen/Encaps/Decaps on the worker stack. HTTP/3's PQC is the lighter ML-KEM hybrid, so an HTTP/3-only
1658// build (PROTOCORE_ENABLE_SSH_SNTRUP761 defaults on with PQC but is dormant without SSH) must not trip this.
1659#if PROTOCORE_ENABLE_SSH_SNTRUP761 && (PROTOCORE_ENABLE_SSH || PROTOCORE_ENABLE_SSH_CLIENT) && \
1660 (PROTOCORE_WORKER_TASK_STACK < PROTOCORE_WORKER_STACK_SNTRUP_MIN)
1661#error \
1662 "ProtoCore: PROTOCORE_WORKER_TASK_STACK is below PROTOCORE_WORKER_STACK_SNTRUP_MIN; sntrup761x25519-sha512 needs ~32 KB worker stack for the server Encaps and ~40 KB for the reverse-SSH client KeyGen+Decaps - raise PROTOCORE_WORKER_TASK_STACK (>= 32768 server / 40960 client), set PROTOCORE_ENABLE_SSH_SNTRUP761 0 to keep ML-KEM only, or marshal the handshake onto a dedicated larger-stack task"
1663#endif
1664
1665#if PROTOCORE_ENABLE_TLS
1666#if MAX_TLS_CONNS < 1 || MAX_TLS_CONNS > MAX_CONNS
1667#error "ProtoCore: MAX_TLS_CONNS must be between 1 and MAX_CONNS"
1668#endif
1669#if PROTOCORE_TLS_ARENA_SIZE < 8192
1670#error "ProtoCore: PROTOCORE_TLS_ARENA_SIZE is far too small for a TLS handshake"
1671#endif
1672// Concurrent TLS guard: the whole arena is static .bss and the ESP32 internal
1673// dram0_0_seg ceiling is only ~122 KB (ROM-reserved at both ends), so a 2nd
1674// connection's arena overflows the link. Reject MAX_TLS_CONNS > 1 with a clear
1675// message unless the arena is offloaded to PSRAM or the build was consciously sized -
1676// far friendlier than the raw "region `dram0_0_seg' overflowed" linker error.
1677#if PROTOCORE_HAS_BOUNDED_DRAM && (MAX_TLS_CONNS > 1) && !PROTOCORE_TLS_ARENA_IN_PSRAM && \
1678 !PROTOCORE_TLS_ACK_MULTI_CONN_DRAM
1679#error \
1680 "ProtoCore: 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 PROTOCORE_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 + PROTOCORE_TLS_MAX_FRAG_LEN), OR reclaim internal DRAM; then set PROTOCORE_TLS_ACK_MULTI_CONN_DRAM=1 to confirm."
1681#endif
1682#endif
1683
1684// HTTP/2's per-connection engine pool (~MAX_CONNS x 28 KB) cannot fit internal DRAM alongside
1685// TLS, so it must live in PSRAM. Fail fast with guidance instead of the raw linker overflow.
1686#if PROTOCORE_ENABLE_HTTP2 && PROTOCORE_HAS_BOUNDED_DRAM && !PROTOCORE_H2_POOL_IN_PSRAM
1687#error \
1688 "ProtoCore: PROTOCORE_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 PROTOCORE_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)."
1689#endif
1690
1691#if PROTOCORE_ENABLE_SNMP
1692#if SNMP_MAX_OID_LEN < 4
1693#error "ProtoCore: SNMP_MAX_OID_LEN must be >= 4"
1694#endif
1695#if SNMP_MAX_MIB_ENTRIES < 1
1696#error "ProtoCore: SNMP_MAX_MIB_ENTRIES must be >= 1"
1697#endif
1698#if SNMP_MAX_VARBINDS < 1
1699#error "ProtoCore: SNMP_MAX_VARBINDS must be >= 1"
1700#endif
1701#if SNMP_MSG_BUF_SIZE < 484
1702#error "ProtoCore: SNMP_MSG_BUF_SIZE must be >= 484 (RFC 1157 minimum)"
1703#endif
1704#endif
1705
1706#if PROTOCORE_ENABLE_COAP
1707#if PROTOCORE_COAP_MAX_RESOURCES < 1
1708#error "ProtoCore: PROTOCORE_COAP_MAX_RESOURCES must be >= 1"
1709#endif
1710#if PROTOCORE_COAP_MAX_PATH < 2
1711#error "ProtoCore: PROTOCORE_COAP_MAX_PATH must be >= 2 (minimum: \"/\")"
1712#endif
1713#if PROTOCORE_COAP_MAX_PAYLOAD < 1
1714#error "ProtoCore: PROTOCORE_COAP_MAX_PAYLOAD must be >= 1"
1715#endif
1716#if PROTOCORE_COAP_MSG_BUF_SIZE < (PROTOCORE_COAP_MAX_PAYLOAD + 16)
1717#error \
1718 "ProtoCore: PROTOCORE_COAP_MSG_BUF_SIZE must be >= PROTOCORE_COAP_MAX_PAYLOAD + 16 (header + token + Content-Format option + payload marker)"
1719#endif
1720#endif
1721
1722#if PROTOCORE_ENABLE_AUTH && MAX_AUTH_LEN < 2
1723#error "ProtoCore: MAX_AUTH_LEN must be >= 2 when PROTOCORE_ENABLE_AUTH is set"
1724#endif
1725
1726#if PROTOCORE_ENABLE_PER_IP_THROTTLE
1727#if PROTOCORE_PER_IP_THROTTLE_SLOTS < 1
1728#error "ProtoCore: PROTOCORE_PER_IP_THROTTLE_SLOTS must be >= 1 when PROTOCORE_ENABLE_PER_IP_THROTTLE is set"
1729#endif
1730#if PROTOCORE_PER_IP_THROTTLE_MAX < 1
1731#error "ProtoCore: PROTOCORE_PER_IP_THROTTLE_MAX must be >= 1 when PROTOCORE_ENABLE_PER_IP_THROTTLE is set"
1732#endif
1733#endif
1734
1735#if PROTOCORE_ENABLE_IP_ALLOWLIST && PROTOCORE_IP_ALLOWLIST_SLOTS < 1
1736#error "ProtoCore: PROTOCORE_IP_ALLOWLIST_SLOTS must be >= 1 when PROTOCORE_ENABLE_IP_ALLOWLIST is set"
1737#endif
1738
1739#if PROTOCORE_ENABLE_AUTH_LOCKOUT
1740#if !PROTOCORE_ENABLE_AUTH
1741#error "ProtoCore: PROTOCORE_ENABLE_AUTH_LOCKOUT requires PROTOCORE_ENABLE_AUTH"
1742#endif
1743#if PROTOCORE_AUTH_LOCKOUT_SLOTS < 1
1744#error "ProtoCore: PROTOCORE_AUTH_LOCKOUT_SLOTS must be >= 1 when PROTOCORE_ENABLE_AUTH_LOCKOUT is set"
1745#endif
1746#if PROTOCORE_AUTH_LOCKOUT_THRESHOLD < 1
1747#error "ProtoCore: PROTOCORE_AUTH_LOCKOUT_THRESHOLD must be >= 1 when PROTOCORE_ENABLE_AUTH_LOCKOUT is set"
1748#endif
1749#if PROTOCORE_AUTH_LOCKOUT_BASE_MS < 1 || PROTOCORE_AUTH_LOCKOUT_MAX_MS < PROTOCORE_AUTH_LOCKOUT_BASE_MS
1750#error "ProtoCore: need 1 <= PROTOCORE_AUTH_LOCKOUT_BASE_MS <= PROTOCORE_AUTH_LOCKOUT_MAX_MS"
1751#endif
1752// The backoff doubles a uint32 capped at MAX_MS, so MAX_MS must leave headroom for
1753// one more shift (cap <= 0x80000000 => cap<<1 fits in uint32 without overflow).
1754#if PROTOCORE_AUTH_LOCKOUT_MAX_MS > 0x80000000
1755#error "ProtoCore: PROTOCORE_AUTH_LOCKOUT_MAX_MS must be <= 0x80000000 (2147483648)"
1756#endif
1757#endif
1758
1759#if PROTOCORE_ENABLE_FORWARDED_TRUST
1760#if !PROTOCORE_ENABLE_AUTH_LOCKOUT
1761#error "ProtoCore: PROTOCORE_ENABLE_FORWARDED_TRUST requires PROTOCORE_ENABLE_AUTH_LOCKOUT"
1762#endif
1763#if PROTOCORE_TRUSTED_PROXY_MAX < 1
1764#error "ProtoCore: PROTOCORE_TRUSTED_PROXY_MAX must be >= 1 when PROTOCORE_ENABLE_FORWARDED_TRUST is set"
1765#endif
1766#endif
1767
1768#if PROTOCORE_ENABLE_WEBDAV
1769#if !PROTOCORE_ENABLE_FILE_SERVING
1770#error "ProtoCore: PROTOCORE_ENABLE_WEBDAV requires PROTOCORE_ENABLE_FILE_SERVING"
1771#endif
1772#if PROTOCORE_WEBDAV_BUF_SIZE < 256
1773#error "ProtoCore: PROTOCORE_WEBDAV_BUF_SIZE must be >= 256"
1774#endif
1775#if PROTOCORE_METHOD_BUF_SIZE < 10
1776#error "ProtoCore: PROTOCORE_METHOD_BUF_SIZE must be >= 10 when PROTOCORE_ENABLE_WEBDAV is set (PROPPATCH)"
1777#endif
1778#endif
1779
1780#if PROTOCORE_ENABLE_MODBUS
1781#if PROTOCORE_MODBUS_COILS < 1 || PROTOCORE_MODBUS_DISCRETE_INPUTS < 1 || PROTOCORE_MODBUS_HOLDING_REGS < 1 || \
1782 PROTOCORE_MODBUS_INPUT_REGS < 1
1783#error "ProtoCore: each PROTOCORE_MODBUS_* table size must be >= 1 when PROTOCORE_ENABLE_MODBUS is set"
1784#endif
1785#endif
1786
1787#if PROTOCORE_ENABLE_KEEPALIVE && PROTOCORE_KEEPALIVE_MAX_REQUESTS < 1
1788#error "ProtoCore: PROTOCORE_KEEPALIVE_MAX_REQUESTS must be >= 1 when PROTOCORE_ENABLE_KEEPALIVE is set"
1789#endif
1790
1791#if PROTOCORE_ENABLE_RANGE && !PROTOCORE_ENABLE_FILE_SERVING && !PROTOCORE_ENABLE_EDGE_CACHE
1792#error "ProtoCore: PROTOCORE_ENABLE_RANGE requires PROTOCORE_ENABLE_FILE_SERVING or PROTOCORE_ENABLE_EDGE_CACHE"
1793#endif
1794
1795// The portable TLS arm authenticates by raw public key and asserts on PROTOCORE_ENABLE_TLS_RPK, so it is
1796// the third thing that carries the extension, alongside DTLS and HTTP/3.
1797#if PROTOCORE_ENABLE_TLS_RPK && !(PROTOCORE_ENABLE_DTLS || PROTOCORE_ENABLE_HTTP3 || PROTOCORE_ENABLE_TLS)
1798#error \
1799 "ProtoCore: PROTOCORE_ENABLE_TLS_RPK requires PROTOCORE_ENABLE_DTLS, PROTOCORE_ENABLE_HTTP3 or the portable TLS arm (PROTOCORE_ENABLE_TLS without a vendor stack)"
1800#endif
1801
1802#if PROTOCORE_ENABLE_COAP_BLOCK
1803#if !PROTOCORE_ENABLE_COAP
1804#error "ProtoCore: PROTOCORE_ENABLE_COAP_BLOCK requires PROTOCORE_ENABLE_COAP"
1805#endif
1806#if PROTOCORE_COAP_BLOCK_SZX_MAX > 6
1807#error "ProtoCore: PROTOCORE_COAP_BLOCK_SZX_MAX must be <= 6 (block size 2^(SZX+4); SZX 7 is reserved)"
1808#endif
1809#if PROTOCORE_COAP_MSG_BUF_SIZE < ((1 << (PROTOCORE_COAP_BLOCK_SZX_MAX + 4)) + 16)
1810#error \
1811 "ProtoCore: PROTOCORE_COAP_MSG_BUF_SIZE must hold one full block (2^(PROTOCORE_COAP_BLOCK_SZX_MAX+4)) + 16 header/option bytes"
1812#endif
1813#if PROTOCORE_COAP_BLOCK1_MAX < (1 << (PROTOCORE_COAP_BLOCK_SZX_MAX + 4))
1814#error "ProtoCore: PROTOCORE_COAP_BLOCK1_MAX must be >= one block (2^(PROTOCORE_COAP_BLOCK_SZX_MAX+4))"
1815#endif
1816#endif
1817
1818#if PROTOCORE_ENABLE_SSH_ZLIB
1819// Window must be a power of two in [256, 32768] (32 KB is zlib's max; the client's inflate window).
1820#if (PROTOCORE_SSH_ZLIB_WINDOW & (PROTOCORE_SSH_ZLIB_WINDOW - 1)) != 0 || PROTOCORE_SSH_ZLIB_WINDOW < 256 || \
1821 PROTOCORE_SSH_ZLIB_WINDOW > 32768
1822#error "ProtoCore: PROTOCORE_SSH_ZLIB_WINDOW must be a power of two in [256, 32768]"
1823#endif
1824// Positions index a uint16 hash chain, so window + max-input must stay under 65535.
1825#if (PROTOCORE_SSH_ZLIB_WINDOW + PROTOCORE_SSH_ZLIB_MAX_IN) > 65534
1826#error "ProtoCore: PROTOCORE_SSH_ZLIB_WINDOW + PROTOCORE_SSH_ZLIB_MAX_IN must be <= 65534"
1827#endif
1828// The compressor must accept a full packet payload, else a max-size outbound packet would fail to
1829// compress and drop, desyncing the stateful stream.
1830#if PROTOCORE_SSH_ZLIB_MAX_IN < SSH_PKT_BUF_SIZE
1831#error "ProtoCore: PROTOCORE_SSH_ZLIB_MAX_IN must be >= SSH_PKT_BUF_SIZE"
1832#endif
1833// The per-connection compression pool is ~80 KB: the s2c deflate work buffer + hash chain (~48 KB)
1834// plus the c2s 32 KB context-takeover inflate window. On ARDUINO pick a path: offload it to PSRAM
1835// (PROTOCORE_SSH_ZLIB_IN_PSRAM, a PSRAM board built with the BSS-in-PSRAM core), or acknowledge the
1836// internal-DRAM cost (PROTOCORE_SSH_ZLIB_ACK_DRAM, fine for MAX_SSH_CONNS=1 without TLS on a roomy S3 / P4).
1837// Fail fast with guidance instead of a raw linker overflow.
1838#if PROTOCORE_HAS_BOUNDED_DRAM && !PROTOCORE_SSH_ZLIB_IN_PSRAM && !PROTOCORE_SSH_ZLIB_ACK_DRAM
1839#error \
1840 "ProtoCore: PROTOCORE_ENABLE_SSH_ZLIB - the per-connection compression pool is ~80 KB (s2c deflate ~48 KB + the c2s 32 KB inflate window). Set PROTOCORE_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 PROTOCORE_SSH_ZLIB_ACK_DRAM=1 to accept the internal-DRAM cost (fits MAX_SSH_CONNS=1 without TLS on a roomy chip)."
1841#endif
1842#endif
1843
1844#if PROTOCORE_ENABLE_WEBSOCKET && WS_FRAME_SIZE < 2
1845#error "ProtoCore: WS_FRAME_SIZE must be >= 2 when PROTOCORE_ENABLE_WEBSOCKET is set"
1846#endif
1847
1848#if PROTOCORE_ENABLE_SSE && SSE_BUF_SIZE < 8
1849#error "ProtoCore: SSE_BUF_SIZE must be >= 8 when PROTOCORE_ENABLE_SSE is set"
1850#endif
1851
1852#if PROTOCORE_ENABLE_MULTIPART && MAX_MULTIPART_PARTS < 1
1853#error "ProtoCore: MAX_MULTIPART_PARTS must be >= 1 when PROTOCORE_ENABLE_MULTIPART is set"
1854#endif
1855
1856#if RESP_HDR_BUF_SIZE < 128
1857#error "ProtoCore: RESP_HDR_BUF_SIZE must be >= 128 (status line + headers + CORS block)"
1858#endif
1859
1860#if PROTOCORE_ENABLE_WEBSOCKET && WS_HDR_BUF_SIZE < 128
1861#error "ProtoCore: WS_HDR_BUF_SIZE must be >= 128 when PROTOCORE_ENABLE_WEBSOCKET is set"
1862#endif
1863
1864#if CORS_HDR_BUF_SIZE < 64
1865#error "ProtoCore: CORS_HDR_BUF_SIZE must be >= 64"
1866#endif
1867
1868#if RESP_HDR_BUF_SIZE < (CORS_HDR_BUF_SIZE + EXTRA_HDR_BUF_SIZE + 96)
1869#error \
1870 "ProtoCore: 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)"
1871#endif
1872
1873// ===========================================================================
1874// Feature tuning knobs (grouped and gated by feature)
1875// ===========================================================================
1876//
1877// One place to turn every tunable knob - buffer sizes, table depths, limits, thresholds -
1878// so you never have to open a feature header. Each is an override-able default (set it in
1879// your build flags to change it); the owning module includes this header and uses the
1880// value. An optional feature's group is wrapped in that feature's PROTOCORE_ENABLE_* flag
1881// (resolved above), so a knob only exists when its feature is built.
1882//
1883// What is NOT here: protocol- and algorithm-fixed constants (wire opcodes, magic bytes,
1884// crypto digest / block sizes, spec-mandated PDU / field widths, the deflate/inflate
1885// scratch sizes a static_assert pins to the table layout). Those are not knobs - changing
1886// them breaks conformance - so they stay in their feature file next to the code they bind.
1887
1888// -- Core: protocol dispatch + shared outbound transport (always built) --
1889/** @brief Size of the protocol-handler dispatch table; must exceed the largest ProtoConn id. */
1890#ifndef PROTO_MAX_HANDLERS
1891#define PROTO_MAX_HANDLERS 12
1892#endif
1893// proto_register / proto_get index this table by ProtoConn id, so it must be wide enough for every id.
1894static_assert((unsigned)PROTO_UDP < PROTO_MAX_HANDLERS, "PROTO_MAX_HANDLERS must exceed the largest ProtoConn id");
1895/** @brief Reverse-SSH tunnel: max concurrent forwarded-tcpip channels bridged at once. A relay that
1896 * forwards to a web UI opens one channel per inbound TCP connection, so this bounds concurrency. */
1897#if PROTOCORE_ENABLE_SSH_CLIENT
1898#ifndef PROTOCORE_SSH_CLIENT_MAX_CHANNELS
1899#define PROTOCORE_SSH_CLIENT_MAX_CHANNELS 4
1900#endif
1901#if PROTOCORE_SSH_CLIENT_MAX_CHANNELS < 1
1902#error "ProtoCore: PROTOCORE_SSH_CLIENT_MAX_CHANNELS must be >= 1"
1903#endif
1904#endif
1905
1906// The FTP session driver holds a control connection and a data connection at the same time; with a
1907// smaller pool the data open would fail after login and every transfer would abort mid-session.
1908#if PROTOCORE_ENABLE_FTP_SESSION && (PROTOCORE_CLIENT_CONNS < 2)
1909#error "ProtoCore: PROTOCORE_ENABLE_FTP_SESSION needs PROTOCORE_CLIENT_CONNS >= 2 (control + data)"
1910#endif
1911
1912// The reverse-SSH tunnel needs the relay connection plus one local bridge per forwarded channel.
1913#if PROTOCORE_ENABLE_SSH_CLIENT && (PROTOCORE_CLIENT_CONNS < 1 + PROTOCORE_SSH_CLIENT_MAX_CHANNELS)
1914#error \
1915 "ProtoCore: PROTOCORE_ENABLE_SSH_CLIENT needs PROTOCORE_CLIENT_CONNS >= 1 + PROTOCORE_SSH_CLIENT_MAX_CHANNELS (relay + one local bridge per channel)"
1916#endif
1917
1918/**
1919 * @brief Max stored size of the exchange value the client sends (RFC 4253 sec 8 'e', RFC 8731 Q_C,
1920 * or a hybrid C_INIT), kept for the exchange hash.
1921 *
1922 * Worst case for the build: an ML-KEM-768 ek (1184) or sntrup761 pk (1158) followed by the 32-byte
1923 * X25519 public, else the 256-byte dh-group14 'e'. ssh_transport.c static_asserts this against
1924 * MLKEM768_EK_BYTES and PROTOCORE_SNTRUP761_PK_BYTES, which only that translation unit can see.
1925 */
1926#ifndef PROTOCORE_SSH_CPUB_MAX
1927#if PROTOCORE_ENABLE_PQC_KEX
1928#define PROTOCORE_SSH_CPUB_MAX 1216
1929#elif PROTOCORE_ENABLE_SSH_SNTRUP761
1930#define PROTOCORE_SSH_CPUB_MAX 1190
1931#else
1932#define PROTOCORE_SSH_CPUB_MAX 256
1933#endif
1934#endif
1935
1936#if PROTOCORE_ENABLE_OPCUA
1937// -- OPC UA (services/fieldbus/opcua) --
1938#ifndef PROTOCORE_OPCUA_BUF
1939#define PROTOCORE_OPCUA_BUF 8192 ///< Server's advertised buffer / max-message size for the handshake.
1940#endif
1941#ifndef PROTOCORE_OPCUA_READ_MAX
1942#define PROTOCORE_OPCUA_READ_MAX 8 ///< max NodesToRead handled per ReadRequest.
1943#endif
1944#ifndef PROTOCORE_OPCUA_BROWSE_MAX
1945#define PROTOCORE_OPCUA_BROWSE_MAX 4 ///< max NodesToBrowse handled per BrowseRequest.
1946#endif
1947#ifndef PROTOCORE_OPCUA_REF_MAX
1948#define PROTOCORE_OPCUA_REF_MAX 8 ///< max references returned per browsed node.
1949#endif
1950#ifndef PROTOCORE_OPCUA_WRITE_MAX
1951#define PROTOCORE_OPCUA_WRITE_MAX 8 ///< max NodesToWrite handled per WriteRequest.
1952#endif
1953
1954// An Axes Browse returns one reference per axis, so the axis count must fit a single Browse response.
1955#if PROTOCORE_ENABLE_ROBOTICS && (PROTOCORE_ROBOTICS_AXES > PROTOCORE_OPCUA_REF_MAX)
1956#error \
1957 "ProtoCore: PROTOCORE_ROBOTICS_AXES must be <= PROTOCORE_OPCUA_REF_MAX (an Axes Browse returns one reference per axis)"
1958#endif
1959// Advertised server identity (endpoint descriptions), overridable per deployment; the app may
1960// also set these at runtime via protocore_opcua_set_endpoint_url() / the OpcUaServerInfo it passes.
1961#ifndef PROTOCORE_OPCUA_DEFAULT_ENDPOINT
1962#define PROTOCORE_OPCUA_DEFAULT_ENDPOINT "opc.tcp://localhost:4840" ///< default endpoint URL.
1963#endif
1964#ifndef PROTOCORE_OPCUA_DEFAULT_APP_URI
1965#define PROTOCORE_OPCUA_DEFAULT_APP_URI "urn:det:opcua:server" ///< default ApplicationUri.
1966#endif
1967#ifndef PROTOCORE_OPCUA_DEFAULT_APP_NAME
1968#define PROTOCORE_OPCUA_DEFAULT_APP_NAME "protocore_opcua_server" ///< default ApplicationName.
1969#endif
1970#endif // PROTOCORE_ENABLE_OPCUA
1971
1972#endif // PROTOCORE_FEATURE_EN_ERROR_H
enum PROTO_ENUM_PACKED protocore_if_kind
What an interface is, and the filter that selects one.
@ PROTO_UDP
A bound datagram port. The slot carries the peer per entry, not per slot.
enum PROTO_ENUM_PACKED ProtoConn
Application protocol spoken on a listener port or connection slot.
#define PROTO_MAX_HANDLERS
Size of the protocol-handler dispatch table; must exceed the largest ProtoConn id.