|
DeterministicESPAsyncWebServer v6.27.1
Zero-allocation, bounded-execution async HTTP server for ESP32
|
Layer 4 (Transport) - TCP connection management implementation. More...
#include "tcp.h"#include "freertos/FreeRTOS.h"#include "freertos/queue.h"#include "freertos/task.h"#include "lwip/tcp.h"#include "services/clock.h"#include "network_drivers/session/worker.h"#include "lwip/priv/tcpip_priv.h"#include <string.h>Go to the source code of this file.
Classes | |
| struct | TransportCtx |
| struct | DetTcpCall |
Enumerations | |
| enum class | DetTcpOp : uint8_t { DET_OP_SEND , DET_OP_OUTPUT , DET_OP_CLOSE , DET_OP_ABORT , DET_OP_DETACH , DET_OP_RAWSEND , DET_OP_CLOSE_CHECK , DET_OP_RECVED } |
Functions | |
| bool | det_conn_send (uint8_t slot, const void *data, u16_t len) |
Send len bytes on connection slot (copies data; TLS-aware). | |
| bool | det_conn_send_flush (uint8_t slot, const void *data, u16_t len) |
Send len bytes on slot and flush in a single tcpip_thread round-trip. | |
| u16_t | det_conn_sndbuf (uint8_t slot) |
Bytes that can currently be queued for sending on slot. | |
| void | det_conn_flush (uint8_t slot) |
Flush queued bytes / finish the send on slot (TLS-aware). | |
| void | det_conn_ack_consumed (uint8_t slot) |
Reopen the TCP receive window by however much slot has drained. | |
| bool | det_conn_raw_send (struct tcp_pcb *pcb, const void *data, u16_t len) |
Write raw bytes straight to pcb (no TLS), context-safe. | |
| void | det_conn_close (uint8_t slot) |
Close connection slot gracefully (tcp_close), aborting if the FIN cannot be queued. The transport owns the whole teardown: it detaches the pcb from its lwIP callbacks, frees the slot, and (TLS slot) emits close_notify + frees the per-connection TLS context - so callers pass only the slot and never touch the raw tcp_pcb. A no-op if the slot has no live pcb. | |
| void | det_conn_abort_slot (uint8_t slot) |
Hard-abort connection slot (RST) for a fatal condition. The transport owns the teardown order: free the per-connection TLS context (abrupt, no close_notify), detach the pcb from its callbacks, reset the slot, then abort - so callers pass only the slot and never touch the raw tcp_pcb. A no-op if the slot has no live pcb. | |
| void | det_conn_detach (struct tcp_pcb *pcb) |
Detach pcb from its slot's lwIP callbacks before the slot is freed. | |
| void | det_conn_abort (struct tcp_pcb *pcb) |
Hard-abort pcb (RST) for a fatal condition; no graceful FIN. | |
| void | det_conn_begin_close (uint8_t slot_id) |
| Begin a graceful close that dwells in ConnState::CONN_CLOSING until the peer ACKs. | |
| uint8_t | det_conn_active_count () |
| Number of server connection slots currently in the CONN_ACTIVE state. | |
| uint32_t | det_conn_remote_ip (uint8_t slot) |
Raw source IPv4 of the connection in slot, or 0 if the slot has no active pcb (or on host builds). Byte order is irrelevant: this is an identity key (e.g. for the auth lockout), not for display. Keeps the lwIP pcb access inside L4 so callers never reach into the pcb directly. | |
| void | det_lwip_to_detip (const ip_addr_t *ra, DetIp *out) |
A stable per-peer 32-bit identity key for slot (the v4 address, or an FNV-1a hash of a v6 address). For rate-limit / auth-lockout buckets, where a v6 peer must not silently share the all-zero v4 bucket. Returns 0 if the slot has no active connection. | |
| bool | det_conn_remote_addr (uint8_t slot, DetIp *out) |
| The connected peer's address as a family-tagged DetIp (IPv4 or IPv6). | |
| void | det_conn_touch_active (uint8_t slot_id) |
Refresh slot's idle-timeout timestamp while a response body is in flight. | |
| err_t | lowlevel_recv_cb (void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err) |
| lwIP receive callback - fires when data arrives on a connection. | |
| err_t | lowlevel_sent_cb (void *arg, struct tcp_pcb *tpcb, u16_t len) |
| lwIP sent callback - fires after the stack acknowledges sent bytes. | |
| void | lowlevel_err_cb (void *arg, err_t err) |
| lwIP error callback - fires when the stack detects a fatal error. | |
Variables | |
| TcpConn | conn_pool [CONN_POOL_SLOTS] |
| Static pool of connection contexts. Defined in tcp.cpp. Sized CONN_POOL_SLOTS: MAX_CONNS TCP slots plus any reserved internal dispatch slot(s) (HTTP/3); the TCP accept path only ever uses [0, MAX_CONNS). | |
| uint32_t | detws_ap_ip = 0 |
| softAP IPv4 address (network byte order) for STA/AP interface tagging. | |
Layer 4 (Transport) - TCP connection management implementation.
All lwIP raw-API callbacks run in the tcpip_thread FreeRTOS task context. They are NOT hardware ISRs, which is why we use xQueueSend() (non-ISR variant) with timeout=0 rather than xQueueSendFromISR().
Ring buffer write ordering The producer (recv callback) writes payload bytes into rx_buffer[] and then advances rx_head. The consumer (worker) reads from rx_buffer[] at rx_tail and advances rx_tail. rx_head/rx_tail are DetAtomic (acquire/release): the producer's buffer writes are published by the release store of rx_head and observed by the consumer's acquire load, correct on either core. The ring math itself is the shared ring.h primitive.
Listener coupling Each TcpConn carries listener_id (set at accept time by listener_accept_cb in listener.cpp). The enqueue() helper forwards events to listener_enqueue() - defined in listener.cpp - so tcp.cpp needs no direct knowledge of the Listener struct. listener_enqueue() is forward- declared in tcp.h to avoid a circular include.
Definition in file tcp.cpp.
|
strong |
| bool det_conn_send | ( | uint8_t | slot, |
| const void * | data, | ||
| u16_t | len | ||
| ) |
Send len bytes on connection slot (copies data; TLS-aware).
Definition at line 362 of file tcp.cpp.
References conn_pool, and DET_OP_SEND.
Referenced by DetWebServer::send(), DetWebServer::send_chunked(), DetWebServer::send_template(), sse_write(), ssh_conn_accept(), ssh_conn_close_channel(), ssh_conn_open_forwarded(), and ssh_conn_send().
| bool det_conn_send_flush | ( | uint8_t | slot, |
| const void * | data, | ||
| u16_t | len | ||
| ) |
Send len bytes on slot and flush in a single tcpip_thread round-trip.
The terminal-write analogue of det_conn_send(): the tcp_write and its tcp_output() run inside one marshaled op, so a small single-shot response costs one ~23 us on-device marshal instead of the det_conn_send()+det_conn_flush() pair (two). Use for the LAST write of a response whose body is already fully buffered (send / send_empty / redirect); a streaming producer that pages across loops must keep using det_conn_send() + a single trailing det_conn_flush(). TLS-identical to det_conn_send (the record BIO already outputs per record). Same return contract as det_conn_send.
Definition at line 378 of file tcp.cpp.
References conn_pool, and DET_OP_SEND.
Referenced by DetWebServer::redirect(), DetWebServer::send(), and DetWebServer::send_empty().
| u16_t det_conn_sndbuf | ( | uint8_t | slot | ) |
Bytes that can currently be queued for sending on slot.
Advisory free space in the TCP send buffer: a producer can send at most this many bytes per handle() loop and resume on the next loop as the window drains (the on_poll hook is the natural resume point). For a TLS slot the usable plaintext is somewhat less (TLS record + cipher overhead). Returns 0 when the slot has no pcb.
Definition at line 398 of file tcp.cpp.
References conn_pool, and TcpConn::pcb.
| void det_conn_flush | ( | uint8_t | slot | ) |
Flush queued bytes / finish the send on slot (TLS-aware).
Definition at line 413 of file tcp.cpp.
References conn_pool, and DET_OP_OUTPUT.
Referenced by ssh_conn_accept(), ssh_conn_close_channel(), ssh_conn_open_forwarded(), ssh_conn_send(), and ws_close().
| void det_conn_ack_consumed | ( | uint8_t | slot | ) |
Reopen the TCP receive window by however much slot has drained.
Ack-on-consume, owned entirely by the transport layer: recv_cb no longer ACKs on copy. Instead the window tracks how much the application has actually drained from the ring (rx_tail) since the last ACK, so it never advertises more than the ring can hold and the peer is paced to the consumer; a slow sink (e.g. flash writes during a streamed upload) can never overflow the ring and deadlock.
Other layers do not touch the ring indices to manage flow control - the worker just calls this once per owned slot per loop and transport does the rest (computes the delta vs rx_acked, marshals tcp_recved to tcpip_thread, advances rx_acked). A no-op when nothing was drained or slot is not active.
Definition at line 427 of file tcp.cpp.
References CONN_ACTIVE, conn_pool, DET_OP_RECVED, MAX_CONNS, TcpConn::pcb, TcpConn::rx_acked, RX_BUF_SIZE, TcpConn::rx_tail, and TcpConn::state.
Referenced by DetWebServer::service_once().
| bool det_conn_raw_send | ( | struct tcp_pcb * | pcb, |
| const void * | data, | ||
| u16_t | len | ||
| ) |
Write raw bytes straight to pcb (no TLS), context-safe.
This is the one safe path for the TLS engine's BIO to emit ciphertext: it writes directly when already running inside the lwIP thread (the marshaled app-data send path) and marshals a raw write when called from the main-loop task (the handshake / read pump), so a TLS handshake never does an unsynchronized tcp_write from the main loop. Calls tcp_output() on success.
Definition at line 449 of file tcp.cpp.
References DET_OP_RAWSEND.
| void det_conn_close | ( | uint8_t | slot | ) |
Close connection slot gracefully (tcp_close), aborting if the FIN cannot be queued. The transport owns the whole teardown: it detaches the pcb from its lwIP callbacks, frees the slot, and (TLS slot) emits close_notify + frees the per-connection TLS context - so callers pass only the slot and never touch the raw tcp_pcb. A no-op if the slot has no live pcb.
Definition at line 467 of file tcp.cpp.
References CONN_ACTIVE, CONN_FREE, conn_pool, det_conn_detach(), DET_OP_CLOSE, DETWS_OBS_TRANSITION, MAX_CONNS, TcpConn::pcb, TcpConn::state, and TcpConn::tls.
Referenced by ssh_conn_accept().
| void det_conn_abort_slot | ( | uint8_t | slot | ) |
Hard-abort connection slot (RST) for a fatal condition. The transport owns the teardown order: free the per-connection TLS context (abrupt, no close_notify), detach the pcb from its callbacks, reset the slot, then abort - so callers pass only the slot and never touch the raw tcp_pcb. A no-op if the slot has no live pcb.
Definition at line 496 of file tcp.cpp.
References CONN_ACTIVE, CONN_FREE, conn_pool, det_conn_abort(), det_conn_detach(), DETWS_OBS_TRANSITION, MAX_CONNS, TcpConn::pcb, TcpConn::state, and TcpConn::tls.
Referenced by DetWebServer::http_poll_slot().
| void det_conn_detach | ( | struct tcp_pcb * | pcb | ) |
Detach pcb from its slot's lwIP callbacks before the slot is freed.
Definition at line 516 of file tcp.cpp.
References DET_OP_DETACH.
Referenced by DeterministicAsyncTCP::check_timeouts(), det_conn_abort_slot(), det_conn_close(), and DeterministicAsyncTCP::stop().
| void det_conn_abort | ( | struct tcp_pcb * | pcb | ) |
Hard-abort pcb (RST) for a fatal condition; no graceful FIN.
Definition at line 527 of file tcp.cpp.
References DET_OP_ABORT.
Referenced by DeterministicAsyncTCP::check_timeouts(), det_conn_abort_slot(), and DeterministicAsyncTCP::stop().
| void det_conn_begin_close | ( | uint8_t | slot_id | ) |
Begin a graceful close that dwells in ConnState::CONN_CLOSING until the peer ACKs.
Unlike det_conn_close() (immediate teardown), this leaves the slot's PCB and callbacks live and moves it ACTIVE -> ConnState::CONN_CLOSING. The slot finalizes (PCB closed, slot freed) from the sent callback once the response has fully drained, or from the idle sweep after DETWS_CLOSING_TIMEOUT_MS if the peer never ACKs. The caller must already have queued (det_conn_send) + flushed the response. A no-op if the slot is not ConnState::CONN_ACTIVE (e.g. an error freed it mid-write).
Definition at line 572 of file tcp.cpp.
References CONN_ACTIVE, CONN_CLOSING, conn_pool, DET_OP_CLOSE_CHECK, detws_millis(), DETWS_OBS_TRANSITION, TcpConn::last_activity_ms, MAX_CONNS, TcpConn::pcb, and TcpConn::state.
Referenced by DetWebServer::http_poll_slot().
| uint8_t det_conn_active_count | ( | ) |
Number of server connection slots currently in the CONN_ACTIVE state.
The connection pool owns this aggregate: callers (stats / metrics) ask transport for the count instead of sweeping conn_pool[] and testing .state themselves.
Definition at line 646 of file tcp.cpp.
References CONN_ACTIVE, conn_pool, and MAX_CONNS.
| uint32_t det_conn_remote_ip | ( | uint8_t | slot | ) |
Raw source IPv4 of the connection in slot, or 0 if the slot has no active pcb (or on host builds). Byte order is irrelevant: this is an identity key (e.g. for the auth lockout), not for display. Keeps the lwIP pcb access inside L4 so callers never reach into the pcb directly.
Definition at line 655 of file tcp.cpp.
References CONN_ACTIVE, conn_pool, MAX_CONNS, TcpConn::pcb, and TcpConn::state.
| void det_lwip_to_detip | ( | const ip_addr_t * | ra, |
| DetIp * | out | ||
| ) |
A stable per-peer 32-bit identity key for slot (the v4 address, or an FNV-1a hash of a v6 address). For rate-limit / auth-lockout buckets, where a v6 peer must not silently share the all-zero v4 bucket. Returns 0 if the slot has no active connection.
Convert a raw lwIP address to the portable family-tagged DetIp - for the accept callback, which has the pcb but no connection slot yet. ESP32 only (the parameter is an lwIP type).
Definition at line 673 of file tcp.cpp.
References det_ip_from_v4_octets(), and det_ip_from_v6_bytes().
Referenced by det_conn_remote_addr().
| bool det_conn_remote_addr | ( | uint8_t | slot, |
| DetIp * | out | ||
| ) |
The connected peer's address as a family-tagged DetIp (IPv4 or IPv6).
Unlike det_conn_remote_ip() (which flattens to a v4 uint32 and cannot represent a v6 peer), this reports the real address for a dual-stack build (DETWS_ENABLE_IPV6). Format it with det_ip_format() or classify it with det_ip_classify().
slot has an active connection whose address was written to out. Definition at line 691 of file tcp.cpp.
References CONN_ACTIVE, conn_pool, DET_IP_NONE, det_lwip_to_detip(), DetIp::family, MAX_CONNS, TcpConn::pcb, and TcpConn::state.
| void det_conn_touch_active | ( | uint8_t | slot | ) |
Refresh slot's idle-timeout timestamp while a response body is in flight.
The file/chunk send pumps call this each poll they run: a slot still paging out a body is actively streaming (or briefly blocked on a full window / a transient link stall), not idle, so the CONN_TIMEOUT_MS idle sweep must not reap it mid-transfer - that truncates any body larger than one TCP window. A genuinely dead peer is still reclaimed by lwIP's retransmission timers (err callback), not this sweep.
Definition at line 718 of file tcp.cpp.
References CONN_ACTIVE, conn_pool, detws_millis(), TcpConn::last_activity_ms, MAX_CONNS, and TcpConn::state.
| err_t lowlevel_recv_cb | ( | void * | arg, |
| struct tcp_pcb * | tpcb, | ||
| struct pbuf * | p, | ||
| err_t | err | ||
| ) |
lwIP receive callback - fires when data arrives on a connection.
lwIP receive callback - wired to each new connection by listener_accept_cb.
Copies pbuf chain bytes into the ring buffer; the window is reopened later by det_conn_ack_consumed() as the worker drains (ack-on-consume), not here. If the whole segment will not fit it is refused (ERR_MEM) for lossless backpressure. A null pbuf signals graceful remote close (FIN received).
Definition at line 795 of file tcp.cpp.
References CONN_ACTIVE, CONN_CLOSING, CONN_FREE, detws_millis(), DETWS_OBS_NOTICE, DETWS_OBS_TRANSITION, EVT_DATA, EVT_DISCONNECT, TcpConn::id, TcpConn::last_activity_ms, TcpConn::pcb, RX_BUF_SIZE, TcpConn::rx_buffer, TcpConn::rx_head, TcpConn::rx_tail, and TcpConn::state.
| err_t lowlevel_sent_cb | ( | void * | arg, |
| struct tcp_pcb * | tpcb, | ||
| u16_t | len | ||
| ) |
lwIP sent callback - fires after the stack acknowledges sent bytes.
lwIP sent callback - refreshes the idle-timeout timestamp.
Refreshes the idle-timeout timestamp so an active sender is not reaped while its responses are in flight, and - for a slot dwelling in ConnState::CONN_CLOSING - finalizes the close once the response has fully drained (the peer ACKed everything).
Definition at line 896 of file tcp.cpp.
References CONN_CLOSING, detws_millis(), detws_worker_wake(), TcpConn::id, TcpConn::last_activity_ms, TcpConn::owner, and TcpConn::state.
| void lowlevel_err_cb | ( | void * | arg, |
| err_t | err | ||
| ) |
lwIP error callback - fires when the stack detects a fatal error.
By the time this fires the PCB is already gone internally, so we must NOT call tcp_close() or tcp_abort(). Null out the slot's pcb pointer and post EvtType::EVT_ERROR so the session layer resets the protocol state.
Definition at line 922 of file tcp.cpp.
References CONN_ACTIVE, CONN_CLOSING, CONN_FREE, DETWS_OBS_TRANSITION, EVT_ERROR, TcpConn::id, TcpConn::pcb, and TcpConn::state.
| TcpConn conn_pool[CONN_POOL_SLOTS] |
Static pool of connection contexts. Defined in tcp.cpp. Sized CONN_POOL_SLOTS: MAX_CONNS TCP slots plus any reserved internal dispatch slot(s) (HTTP/3); the TCP accept path only ever uses [0, MAX_CONNS).
Definition at line 347 of file tcp.cpp.
Referenced by DeterministicAsyncTCP::check_timeouts(), DetWebServer::defer(), det_conn_abort_slot(), det_conn_ack_consumed(), det_conn_active_count(), det_conn_begin_close(), det_conn_close(), det_conn_flush(), det_conn_remote_addr(), det_conn_remote_ip(), det_conn_send(), det_conn_send_flush(), det_conn_sndbuf(), det_conn_touch_active(), DetWebServer::http_poll_slot(), listener_enqueue(), DeterministicAsyncTCP::pool_init(), DetWebServer::redirect(), DetWebServer::send(), DetWebServer::send_empty(), DetWebServer::service_once(), ssh_conn_accept(), ssh_conn_close(), ssh_conn_close_channel(), ssh_conn_open_forwarded(), ssh_conn_poll(), ssh_conn_rx(), ssh_conn_send(), DeterministicAsyncTCP::stop(), ws_feed_byte(), and ws_send_frame().
| uint32_t detws_ap_ip = 0 |
softAP IPv4 address (network byte order) for STA/AP interface tagging.
Zero when no softAP is configured. Set via DetWebServer::set_ap_ip(); the accept callback tags each connection DetIface::DETIFACE_AP when its local IP equals this, else DetIface::DETIFACE_STA. Used by per-route interface filters.
Definition at line 349 of file tcp.cpp.
Referenced by DetWebServer::set_ap_ip().