DeterministicESPAsyncWebServer 1.2.0
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
transport.h
Go to the documentation of this file.
1// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file transport.h
6 * @brief Layer 4 (Transport) — TCP connection pool, ring buffers, and lwIP integration.
7 *
8 * Defines the static connection pool and the FreeRTOS event queue used to
9 * pass connection events from lwIP callbacks (running in `tcpip_thread`) to
10 * the Arduino main-loop task.
11 *
12 * **Concurrency model**
13 * | Context | Reads | Writes |
14 * |------------------|------------------------|-------------------------|
15 * | lwIP callbacks | rx_head (to check full)| rx_buffer[], rx_head |
16 * | Arduino loop | rx_buffer[], rx_tail | rx_tail |
17 *
18 * `rx_head` and `rx_tail` are `volatile` to prevent the compiler from
19 * caching them across the inter-task boundary. The single-producer /
20 * single-consumer ring buffer design guarantees correctness without a mutex
21 * as long as writes to each index are atomic (true for 32-bit Xtensa).
22 *
23 * **Backpressure**
24 * When the ring buffer is full, `tcp_recved()` is called with only the
25 * bytes actually copied — not `p->tot_len`. This shrinks the TCP receive
26 * window and applies backpressure to the sender rather than silently
27 * dropping data.
28 *
29 * @author Douglas Quigg (dstroy0)
30 * @date 2026
31 */
32
33#ifndef DETERMINISTICESPASYNCWEBSERVER_TRANSPORT_H
34#define DETERMINISTICESPASYNCWEBSERVER_TRANSPORT_H
35
36#include "DetWebServerConfig.h"
37#include "freertos/FreeRTOS.h"
38#include "freertos/queue.h"
39#include "lwip/tcp.h"
40#include <Arduino.h>
41
42// ---------------------------------------------------------------------------
43// Connection state
44// ---------------------------------------------------------------------------
45
46/**
47 * @brief Lifecycle state of a connection pool slot.
48 *
49 * Transitions:
50 * - `CONN_FREE → CONN_ACTIVE` : accept callback fires.
51 * - `CONN_ACTIVE → CONN_FREE` : graceful close, error, or timeout.
52 * - `CONN_ACTIVE → CONN_CLOSING` : (reserved for future half-close support).
53 */
55{
56 CONN_FREE, ///< Slot is available; no PCB is attached.
57 CONN_ACTIVE, ///< Live connection; PCB is valid.
58 CONN_CLOSING ///< FIN sent; waiting for final ACK (reserved).
59};
60
61/**
62 * @brief A single TCP connection context.
63 *
64 * Sized so that `MAX_CONNS` instances fit in a static array without
65 * fragmentation. All fields except the volatile ring-buffer indices may
66 * only be accessed from the Arduino main-loop task.
67 */
68struct TcpConn
69{
70 uint8_t id; ///< Fixed slot index (0 … MAX_CONNS-1).
71 volatile ConnState state; ///< Lifecycle state; volatile for inter-task visibility.
72 struct tcp_pcb *pcb; ///< lwIP PCB; null when slot is free.
73 uint32_t last_activity_ms; ///< `millis()` timestamp of last TX/RX event.
74
75 uint8_t rx_buffer[RX_BUF_SIZE]; ///< Ring buffer storage.
76 volatile size_t rx_head; ///< Producer write index (lwIP context).
77 volatile size_t rx_tail; ///< Consumer read index (main-loop context).
78};
79
80/** @brief Static pool of connection contexts. Defined in transport.cpp. */
82
83// ---------------------------------------------------------------------------
84// Event queue
85// ---------------------------------------------------------------------------
86
87/**
88 * @brief Type of connection event posted to the FreeRTOS event queue.
89 */
91{
92 EVT_CONNECT, ///< New connection accepted.
93 EVT_DATA, ///< Data received; bytes are already in the ring buffer.
94 EVT_DISCONNECT, ///< Remote peer closed the connection gracefully.
95 EVT_ERROR ///< lwIP reported an error (PCB may already be freed).
96};
97
98/**
99 * @brief Event record posted from lwIP callbacks to the main-loop task.
100 *
101 * Small enough (8 bytes on 32-bit) that the FreeRTOS queue copies it by
102 * value — no pointer lifetime issues.
103 */
104struct TcpEvt
105{
106 EvtType type; ///< What happened.
107 uint8_t slot_id; ///< Which connection slot is affected.
108 size_t data_len; ///< Bytes copied (EVT_DATA only); 0 for other types.
109};
110
111// ---------------------------------------------------------------------------
112// DeterministicAsyncTCP
113// ---------------------------------------------------------------------------
114
115/**
116 * @class DeterministicAsyncTCP
117 * @brief Static-only facade over the raw lwIP TCP API.
118 *
119 * All state is class-level (no instances). Initialises the connection pool,
120 * the FreeRTOS event queue, and the lwIP listening PCB once per boot via
121 * init(). The lwIP callbacks (lowlevel_accept_cb, lowlevel_recv_cb, …) are
122 * private implementation details in transport.cpp.
123 */
125{
126 public:
127 /**
128 * @brief Initialise the TCP stack, create the event queue, and begin listening.
129 *
130 * Checks whether enough heap is available before attempting queue creation.
131 * Zeroes the connection pool and stores the runtime config.
132 *
133 * @param port TCP port to bind and listen on.
134 * @param cfg Optional runtime config. Pass nullptr to use defaults.
135 * @return Positive value on success; negative value whose absolute value is
136 * the number of heap bytes needed when initialisation fails.
137 */
138 static int32_t init(uint16_t port, const WebServerConfig *cfg = nullptr);
139
140 /**
141 * @brief Stop the server: abort all connections, close the listener, free the queue.
142 *
143 * Safe to call from the main-loop task. After stop() returns,
144 * init() may be called again to restart.
145 */
146 static void stop();
147
148 /**
149 * @brief Scan the pool and force-close connections idle for > conn_timeout_ms.
150 *
151 * Called at the start of every server_tick() call, before the event queue
152 * is drained. Uses `tcp_abort()` (not `tcp_close()`) because the
153 * connection has already timed out and a graceful FIN is not warranted.
154 *
155 * A timed-out slot has its state set to `CONN_FREE` and `pcb` cleared
156 * *before* `tcp_abort()` is called, so any in-flight lwIP callback for
157 * that PCB will see `slot->state != CONN_ACTIVE` and exit without
158 * touching the slot.
159 */
160 static void check_timeouts();
161
162 /**
163 * @brief FreeRTOS queue handle shared between lwIP callbacks and server_tick().
164 *
165 * Events are posted from `tcpip_thread` context via `xQueueSend()` (not
166 * the ISR variant) because lwIP callbacks run in a task, not a hardware ISR.
167 * Queue depth is EVT_QUEUE_DEPTH, sized for MAX_CONNS * 4 event bursts.
168 * Backed by a static BSS array; no heap is allocated.
169 */
170 static QueueHandle_t queue;
171
172 /**
173 * @brief Runtime connection-idle timeout in milliseconds.
174 *
175 * Loaded from WebServerConfig::conn_timeout_ms at init() time.
176 * Defaults to CONN_TIMEOUT_MS if no config is supplied.
177 */
178 static uint32_t conn_timeout_ms;
179
180 /**
181 * @brief Always returns 0 — the library makes no heap allocations.
182 *
183 * The event queue is backed by statically-allocated BSS storage
184 * (_queue_struct + _queue_storage in transport.cpp). Retained for
185 * API compatibility with code that calls it before begin().
186 */
187 static size_t heap_needed();
188
189 /**
190 * @brief Always returns true — no heap allocation means no pre-flight needed.
191 *
192 * Retained for API compatibility. Safe to call or omit.
193 */
194 static bool heap_available();
195};
196
197#endif
User-facing configuration for DeterministicESPAsyncWebServer.
#define RX_BUF_SIZE
Ring-buffer capacity in bytes per connection slot.
#define MAX_CONNS
Maximum simultaneous TCP connections.
Static-only facade over the raw lwIP TCP API.
Definition transport.h:125
static bool heap_available()
Always returns true — no heap allocation means no pre-flight needed.
Definition transport.cpp:57
static void check_timeouts()
Scan the pool and force-close connections idle for > conn_timeout_ms.
static int32_t init(uint16_t port, const WebServerConfig *cfg=nullptr)
Initialise the TCP stack, create the event queue, and begin listening.
Definition transport.cpp:62
static size_t heap_needed()
Always returns 0 — the library makes no heap allocations.
Definition transport.cpp:52
static uint32_t conn_timeout_ms
Runtime connection-idle timeout in milliseconds.
Definition transport.h:178
static void stop()
Stop the server: abort all connections, close the listener, free the queue.
static QueueHandle_t queue
FreeRTOS queue handle shared between lwIP callbacks and server_tick().
Definition transport.h:170
A single TCP connection context.
Definition transport.h:69
volatile ConnState state
Lifecycle state; volatile for inter-task visibility.
Definition transport.h:71
struct tcp_pcb * pcb
lwIP PCB; null when slot is free.
Definition transport.h:72
volatile size_t rx_tail
Consumer read index (main-loop context).
Definition transport.h:77
uint32_t last_activity_ms
millis() timestamp of last TX/RX event.
Definition transport.h:73
uint8_t id
Fixed slot index (0 … MAX_CONNS-1).
Definition transport.h:70
uint8_t rx_buffer[RX_BUF_SIZE]
Ring buffer storage.
Definition transport.h:75
volatile size_t rx_head
Producer write index (lwIP context).
Definition transport.h:76
Event record posted from lwIP callbacks to the main-loop task.
Definition transport.h:105
EvtType type
What happened.
Definition transport.h:106
size_t data_len
Bytes copied (EVT_DATA only); 0 for other types.
Definition transport.h:108
uint8_t slot_id
Which connection slot is affected.
Definition transport.h:107
Runtime-tunable server parameters.
ConnState
Lifecycle state of a connection pool slot.
Definition transport.h:55
@ CONN_CLOSING
FIN sent; waiting for final ACK (reserved).
Definition transport.h:58
@ CONN_FREE
Slot is available; no PCB is attached.
Definition transport.h:56
@ CONN_ACTIVE
Live connection; PCB is valid.
Definition transport.h:57
EvtType
Type of connection event posted to the FreeRTOS event queue.
Definition transport.h:91
@ EVT_CONNECT
New connection accepted.
Definition transport.h:92
@ EVT_DISCONNECT
Remote peer closed the connection gracefully.
Definition transport.h:94
@ EVT_ERROR
lwIP reported an error (PCB may already be freed).
Definition transport.h:95
@ EVT_DATA
Data received; bytes are already in the ring buffer.
Definition transport.h:93
TcpConn conn_pool[MAX_CONNS]
Static pool of connection contexts. Defined in transport.cpp.
Definition transport.cpp:25