ProtoCore v0.0.1
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
tcp.cpp File Reference

Layer 4 (Transport) - TCP connection management implementation. More...

#include "tcp.h"
#include "diffserv.h"
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
#include "freertos/task.h"
#include "lwip/tcp.h"
#include "services/system/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  pc_tcp_call
 
struct  ConnPoolCtx
 

Enumerations

enum class  pc_tcp_op : uint8_t {
  PC_OP_SEND , PC_OP_OUTPUT , PC_OP_CLOSE , PC_OP_ABORT ,
  PC_OP_DETACH , PC_OP_RAWSEND , PC_OP_CLOSE_CHECK , PC_OP_RECVED
}
 

Functions

void pc_conn_set_state (uint8_t slot, ConnState st)
 The single conn_pool[slot].state write path. Writes the state (release) and keeps the free-slot bitmask consistent so allocation is one ctz. ALL state transitions must go through this - a raw conn_pool[i].state = ... would desync the mask.
 
int32_t pc_conn_alloc_free ()
 First CONN_FREE slot via a ctz on the bitmask (replaces the MAX_CONNS scan); -1 if the pool is full. tcpip_thread (accept).
 
bool pc_conn_send (uint8_t slot, const void *data, u16_t len)
 Send len bytes on connection slot (copies data; TLS-aware).
 
bool pc_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 pc_conn_sndbuf (uint8_t slot)
 Bytes that can currently be queued for sending on slot.
 
void pc_conn_flush (uint8_t slot)
 Flush queued bytes / finish the send on slot (TLS-aware).
 
void pc_conn_ack_consumed (uint8_t slot)
 Reopen the TCP receive window by however much slot has drained.
 
bool pc_conn_raw_send (struct tcp_pcb *pcb, const void *data, u16_t len)
 Write raw bytes straight to pcb (no TLS), context-safe.
 
void pc_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 pc_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 pc_conn_detach (struct tcp_pcb *pcb)
 Detach pcb from its slot's lwIP callbacks before the slot is freed.
 
void pc_conn_abort (struct tcp_pcb *pcb)
 Hard-abort pcb (RST) for a fatal condition; no graceful FIN.
 
void pc_conn_begin_close (uint8_t slot_id)
 Begin a graceful close that dwells in ConnState::CONN_CLOSING until the peer ACKs.
 
uint8_t pc_conn_active_count ()
 Number of server connection slots currently in the CONN_ACTIVE state.
 
uint32_t pc_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 pc_lwip_to_ip (const ip_addr_t *ra, pc_ip *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 pc_conn_remote_addr (uint8_t slot, pc_ip *out)
 The connected peer's address as a family-tagged pc_ip (IPv4 or IPv6).
 
void pc_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 pc_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 pc_atomic (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

◆ pc_tcp_op

enum class pc_tcp_op : uint8_t
strong
Enumerator
PC_OP_SEND 
PC_OP_OUTPUT 
PC_OP_CLOSE 
PC_OP_ABORT 
PC_OP_DETACH 
PC_OP_RAWSEND 
PC_OP_CLOSE_CHECK 
PC_OP_RECVED 

Definition at line 191 of file tcp.cpp.

Function Documentation

◆ pc_conn_set_state()

void pc_conn_set_state ( uint8_t  slot,
ConnState  st 
)

The single conn_pool[slot].state write path. Writes the state (release) and keeps the free-slot bitmask consistent so allocation is one ctz. ALL state transitions must go through this - a raw conn_pool[i].state = ... would desync the mask.

Definition at line 446 of file tcp.cpp.

References CONN_FREE, conn_pool, CONN_POOL_SLOTS, ConnPoolCtx::free_mask, MAX_CONNS, and TcpConn::state.

Referenced by DeterministicAsyncTCP::check_timeouts(), listener_accept_cb(), lowlevel_err_cb(), lowlevel_recv_cb(), pc_conn_abort_slot(), pc_conn_begin_close(), pc_conn_close(), DeterministicAsyncTCP::pool_init(), and DeterministicAsyncTCP::stop().

◆ pc_conn_alloc_free()

int32_t pc_conn_alloc_free ( )

First CONN_FREE slot via a ctz on the bitmask (replaces the MAX_CONNS scan); -1 if the pool is full. tcpip_thread (accept).

Definition at line 480 of file tcp.cpp.

References ConnPoolCtx::free_mask, and MAX_CONNS.

Referenced by listener_accept_cb().

◆ pc_conn_send()

bool pc_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 pc_conn_sndbuf() and resume on a later loop; existing fixed-size senders may ignore the result.

Definition at line 500 of file tcp.cpp.

References conn_pool, and PC_OP_SEND.

Referenced by pc_sse_write(), pc_ssh_conn_accept(), pc_ssh_conn_close_channel(), pc_ssh_conn_open_forwarded(), pc_ssh_conn_send(), PC::send(), PC::send_chunked(), and PC::send_template().

◆ pc_conn_send_flush()

bool pc_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 pc_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 pc_conn_send()+pc_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 pc_conn_send() + a single trailing pc_conn_flush(). TLS-identical to pc_conn_send (the record BIO already outputs per record). Same return contract as pc_conn_send.

Definition at line 518 of file tcp.cpp.

References conn_pool, and PC_OP_SEND.

Referenced by PC::redirect(), PC::send(), and PC::send_empty().

◆ pc_conn_sndbuf()

u16_t pc_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 542 of file tcp.cpp.

References conn_pool, and TcpConn::pcb.

◆ pc_conn_flush()

void pc_conn_flush ( uint8_t  slot)

Flush queued bytes / finish the send on slot (TLS-aware).

Definition at line 561 of file tcp.cpp.

References conn_pool, and PC_OP_OUTPUT.

Referenced by pc_ssh_conn_accept(), pc_ssh_conn_close_channel(), pc_ssh_conn_open_forwarded(), pc_ssh_conn_send(), and ws_close().

◆ pc_conn_ack_consumed()

void pc_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 595 of file tcp.cpp.

References CONN_ACTIVE, conn_pool, MAX_CONNS, PC_OP_RECVED, TcpConn::pcb, TcpConn::rx_acked, RX_BUF_SIZE, TcpConn::rx_tail, and TcpConn::state.

Referenced by PC::service_once().

◆ pc_conn_raw_send()

bool pc_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 623 of file tcp.cpp.

References PC_OP_RAWSEND.

◆ pc_conn_close()

void pc_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 645 of file tcp.cpp.

References CONN_ACTIVE, CONN_FREE, conn_pool, TcpConn::id, MAX_CONNS, pc_conn_detach(), pc_conn_set_state(), PC_OBS_TRANSITION, PC_OP_CLOSE, TcpConn::pcb, and TcpConn::tls.

Referenced by pc_ssh_conn_accept().

◆ pc_conn_abort_slot()

void pc_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 682 of file tcp.cpp.

References CONN_ACTIVE, CONN_FREE, conn_pool, TcpConn::id, MAX_CONNS, pc_conn_abort(), pc_conn_detach(), pc_conn_set_state(), PC_OBS_TRANSITION, TcpConn::pcb, and TcpConn::tls.

Referenced by PC::http_poll_slot().

◆ pc_conn_detach()

void pc_conn_detach ( struct tcp_pcb *  pcb)

Detach pcb from its slot's lwIP callbacks before the slot is freed.

Definition at line 708 of file tcp.cpp.

References PC_OP_DETACH.

Referenced by DeterministicAsyncTCP::check_timeouts(), pc_conn_abort_slot(), pc_conn_close(), and DeterministicAsyncTCP::stop().

◆ pc_conn_abort()

void pc_conn_abort ( struct tcp_pcb *  pcb)

Hard-abort pcb (RST) for a fatal condition; no graceful FIN.

Definition at line 719 of file tcp.cpp.

References PC_OP_ABORT.

Referenced by DeterministicAsyncTCP::check_timeouts(), pc_conn_abort_slot(), and DeterministicAsyncTCP::stop().

◆ pc_conn_begin_close()

void pc_conn_begin_close ( uint8_t  slot_id)

Begin a graceful close that dwells in ConnState::CONN_CLOSING until the peer ACKs.

Unlike pc_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 PC_CLOSING_TIMEOUT_MS if the peer never ACKs. The caller must already have queued (pc_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 778 of file tcp.cpp.

References CONN_ACTIVE, CONN_CLOSING, conn_pool, TcpConn::id, TcpConn::last_activity_ms, MAX_CONNS, pc_conn_set_state(), pc_millis(), PC_OBS_TRANSITION, PC_OP_CLOSE_CHECK, TcpConn::pcb, and TcpConn::state.

Referenced by PC::http_poll_slot().

◆ pc_conn_active_count()

uint8_t pc_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 859 of file tcp.cpp.

References CONN_ACTIVE, conn_pool, and MAX_CONNS.

◆ pc_conn_remote_ip()

uint32_t pc_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 872 of file tcp.cpp.

References CONN_ACTIVE, conn_pool, MAX_CONNS, TcpConn::pcb, and TcpConn::state.

◆ pc_lwip_to_ip()

void pc_lwip_to_ip ( const ip_addr_t *  ra,
pc_ip 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 pc_ip - for the accept callback, which has the pcb but no connection slot yet. ESP32 only (the parameter is an lwIP type).

Definition at line 894 of file tcp.cpp.

References pc_ip_from_v4_octets(), and pc_ip_from_v6_bytes().

Referenced by listener_accept_cb(), and pc_conn_remote_addr().

◆ pc_conn_remote_addr()

bool pc_conn_remote_addr ( uint8_t  slot,
pc_ip out 
)

The connected peer's address as a family-tagged pc_ip (IPv4 or IPv6).

Unlike pc_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 (PC_ENABLE_IPV6). Format it with pc_ip_format() or classify it with pc_ip_classify().

Returns
true if slot has an active connection whose address was written to out.

Definition at line 912 of file tcp.cpp.

References CONN_ACTIVE, conn_pool, pc_ip::family, MAX_CONNS, PC_IP_NONE, pc_lwip_to_ip(), TcpConn::pcb, and TcpConn::state.

◆ pc_conn_touch_active()

void pc_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 945 of file tcp.cpp.

References CONN_ACTIVE, conn_pool, TcpConn::last_activity_ms, MAX_CONNS, pc_millis(), 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 pc_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 1033 of file tcp.cpp.

References CONN_ACTIVE, CONN_CLOSING, CONN_FREE, EVT_DATA, EVT_DISCONNECT, TcpConn::id, TcpConn::last_activity_ms, pc_conn_set_state(), pc_millis(), PC_OBS_NOTICE, PC_OBS_TRANSITION, TcpConn::pcb, TcpConn::req_start_ms, RX_BUF_SIZE, TcpConn::rx_buffer, TcpConn::rx_head, TcpConn::rx_tail, and TcpConn::state.

Referenced by listener_accept_cb().

◆ 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 1147 of file tcp.cpp.

References CONN_CLOSING, TcpConn::id, TcpConn::last_activity_ms, TcpConn::owner, pc_millis(), pc_worker_wake(), and TcpConn::state.

Referenced by listener_accept_cb().

◆ 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 1177 of file tcp.cpp.

References CONN_ACTIVE, CONN_CLOSING, CONN_FREE, EVT_ERROR, TcpConn::id, pc_conn_set_state(), PC_OBS_TRANSITION, TcpConn::pcb, and TcpConn::state.

Referenced by listener_accept_cb().

Variable Documentation

◆ conn_pool

◆ pc_ap_ip

uint32_t pc_ap_ip = 0

softAP IPv4 address (network byte order) for STA/AP interface tagging.

Zero when no softAP is configured. Set via PC::set_ap_ip(); the accept callback tags each connection pc_iface::PC_IFACE_AP when its local IP equals this, else pc_iface::PC_IFACE_STA. Used by per-route interface filters.

Definition at line 487 of file tcp.cpp.

Referenced by listener_accept_cb(), and PC::set_ap_ip().