DeterministicESPAsyncWebServer v6.27.1
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
tcp.cpp File Reference

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.
 

Detailed Description

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.

Enumeration Type Documentation

◆ DetTcpOp

enum class DetTcpOp : uint8_t
strong
Enumerator
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 

Definition at line 174 of file tcp.cpp.

Function Documentation

◆ det_conn_send()

bool det_conn_send ( uint8_t  slot,
const void *  data,
u16_t  len 
)

Send len bytes on connection slot (copies data; TLS-aware).

Returns
true if the bytes were queued; false if the send buffer was full and the write was refused. A streaming producer should pace with det_conn_sndbuf() and resume on a later loop; existing fixed-size senders may ignore the result.

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().

◆ det_conn_send_flush()

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().

◆ det_conn_sndbuf()

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.

◆ det_conn_flush()

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().

◆ det_conn_ack_consumed()

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().

◆ det_conn_raw_send()

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.

Returns
true if the bytes were queued; false on a full send buffer (ERR_MEM).

Definition at line 449 of file tcp.cpp.

References DET_OP_RAWSEND.

◆ det_conn_close()

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().

◆ det_conn_abort_slot()

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().

◆ det_conn_detach()

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().

◆ det_conn_abort()

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().

◆ det_conn_begin_close()

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().

◆ det_conn_active_count()

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.

◆ det_conn_remote_ip()

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.

◆ det_lwip_to_detip()

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().

◆ 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().

Returns
true if 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.

◆ det_conn_touch_active()

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.

◆ lowlevel_recv_cb()

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.

◆ lowlevel_sent_cb()

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.

◆ lowlevel_err_cb()

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.

Variable Documentation

◆ conn_pool

◆ detws_ap_ip

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().