30#include "freertos/FreeRTOS.h"
31#include "freertos/queue.h"
32#include "freertos/task.h"
51#include "lwip/priv/tcpip_priv.h"
54#if PC_ENABLE_OBSERVABILITY
63 pc_conn_event_cb conn_event_cb =
nullptr;
64 std::atomic<uint32_t> ctr[8];
68void pc_conn_on_event(pc_conn_event_cb cb)
70 s_obs.conn_event_cb = cb;
73pc_conn_counters pc_conn_counters_get()
76 c.accepts = s_obs.ctr[0].load(std::memory_order_relaxed);
77 c.closes_remote = s_obs.ctr[1].load(std::memory_order_relaxed);
78 c.closes_local = s_obs.ctr[2].load(std::memory_order_relaxed);
79 c.closes_error = s_obs.ctr[3].load(std::memory_order_relaxed);
80 c.closes_timeout = s_obs.ctr[4].load(std::memory_order_relaxed);
81 c.closes_abort = s_obs.ctr[5].load(std::memory_order_relaxed);
82 c.backpressure = s_obs.ctr[6].load(std::memory_order_relaxed);
83 c.defer_drops = s_obs.ctr[7].load(std::memory_order_relaxed);
96void pc_conn_counters_reset()
98 for (
int i = 0; i < 8; i++)
100 s_obs.ctr[i].store(0, std::memory_order_relaxed);
104static void obs_bump(pc_conn_reason reason)
111 case pc_conn_reason::PC_CONN_R_ACCEPT:
114 case pc_conn_reason::PC_CONN_R_CLOSE_REMOTE:
117 case pc_conn_reason::PC_CONN_R_CLOSE_LOCAL:
120 case pc_conn_reason::PC_CONN_R_ERROR:
123 case pc_conn_reason::PC_CONN_R_TIMEOUT:
126 case pc_conn_reason::PC_CONN_R_ABORT:
129 case pc_conn_reason::PC_CONN_R_BACKPRESSURE:
132 case pc_conn_reason::PC_CONN_R_DEFER_DROP:
135 case pc_conn_reason::PC_CONN_R_DRAINED:
141 s_obs.ctr[idx].fetch_add(1, std::memory_order_relaxed);
149void pc_obs_transition(uint8_t slot,
ConnState olds,
ConnState news, pc_conn_reason reason)
152 if (s_obs.conn_event_cb)
154 s_obs.conn_event_cb(slot, olds, news, reason);
159void pc_obs_notice(uint8_t slot,
ConnState st, pc_conn_reason reason)
162 if (s_obs.conn_event_cb)
164 s_obs.conn_event_cb(slot, st, st, reason);
187static void closing_check(uint8_t slot,
struct tcp_pcb *pcb);
201#if PC_ENABLE_DIFFSERV
221static inline bool on_tcpip_thread()
223#if defined(LWIP_TCPIP_CORE_LOCKING) && LWIP_TCPIP_CORE_LOCKING
231 return sys_thread_tcpip(LWIP_CORE_LOCK_QUERY_HOLDER);
242 struct tcpip_api_call_data
base;
260static bool pcb_still_bound(
const struct tcp_pcb *pcb)
279static err_t pc_tcp_do(
struct tcpip_api_call_data *c)
285 s_tp.
tcpip_task = xTaskGetCurrentTaskHandle();
293 if (!pcb_still_bound(k->
pcb))
347 pc_tls_conn_end(k->
slot);
350 if (tcp_close(k->
pcb) != ERR_OK)
359 tcp_arg(k->
pcb,
nullptr);
362 closing_check(k->
slot, k->
pcb);
373 tcp_recved(k->
pcb, k->
len);
380#if PC_ENABLE_DIFFSERV
381 case pc_tcp_op::PC_OP_SET_TOS:
387 k->
pcb->tos = (uint8_t)k->
len;
399static inline err_t pc_tcp_marshal(
pc_tcp_op op, uint8_t slot,
struct tcp_pcb *pcb,
const void *data, u16_t len,
403 memset(&k, 0,
sizeof(k));
413 if (on_tcpip_thread())
419 tcpip_api_call(pc_tcp_do, &k.
base);
438static_assert(
MAX_CONNS <= 32,
"the free-slot bitmask (s_pool.free_mask) is a uint32; raise it to uint64_t "
439 "or fall back to a scan if MAX_CONNS ever exceeds 32");
455#if PC_INTERNAL_SLOTS > 0
465 const uint32_t bit = 1u << slot;
469 s_pool.
free_mask.fetch_or(bit, std::memory_order_release);
473 s_pool.
free_mask.fetch_and(~bit, std::memory_order_release);
483 uint32_t m = s_pool.
free_mask.load(std::memory_order_acquire) & valid;
484 return m ? (int32_t)__builtin_ctz(m) : -1;
511 return pc_tls_write(slot, data, len) >= 0;
514 return tcp_write(
conn_pool[slot].pcb, data, len, TCP_WRITE_FLAG_COPY) == ERR_OK;
530 return pc_tls_write(slot, data, len) >= 0;
533 if (tcp_write(
conn_pool[slot].pcb, data, len, TCP_WRITE_FLAG_COPY) != ERR_OK)
549 u16_t avail = tcp_sndbuf(pcb);
555 avail = (avail > 64) ? (u16_t)(avail - 64) : 0;
577#if PC_ENABLE_DIFFSERV
578bool pc_conn_set_dscp(uint8_t slot, uint8_t dscp)
587 return pc_tcp_marshal(pc_tcp_op::PC_OP_SET_TOS, slot,
conn_pool[slot].pcb,
nullptr, pc_dscp_to_tos(dscp)) == ERR_OK;
619 tcp_recved(c->
pcb, (u16_t)consumed);
636 err_t e = tcp_write(pcb, data, len, TCP_WRITE_FLAG_COPY);
652 struct tcp_pcb *pcb = c->
pcb;
672 pc_tls_conn_end(slot);
675 if (tcp_close(pcb) != ERR_OK)
689 struct tcp_pcb *pcb = c->
pcb;
698 pc_tls_conn_free(slot);
715 tcp_arg(pcb,
nullptr);
736static void closing_finalize(uint8_t slot,
struct tcp_pcb *pcb)
742 pc_tls_conn_end(slot);
749 tcp_arg(pcb,
nullptr);
750 if (tcp_close(pcb) != ERR_OK)
766static void closing_check(uint8_t slot,
struct tcp_pcb *pcb)
772 if (!pcb || pcb->snd_queuelen == 0)
774 closing_finalize(slot, pcb);
789 struct tcp_pcb *pcb = c->
pcb;
799 closing_check(slot_id, pcb);
829 static const TcpConn blank = {};
882 return ip4_addr_get_u32(ip_2_ip4(&conn->
pcb->remote_ip));
900 memcpy(b, ip_2_ip6(ra)->addr, 16);
907 uint32_t be = ip4_addr_get_u32(ip_2_ip4(ra));
908 *out =
pc_ip_from_v4_octets((uint8_t)be, (uint8_t)(be >> 8), (uint8_t)(be >> 16), (uint8_t)(be >> 24));
964 if (slot->
owner != worker_id)
979 struct tcp_pcb *cpcb = slot->
pcb;
988 pc_conn_reason::PC_CONN_R_DRAINED);
1001 struct tcp_pcb *pcb = slot->
pcb;
1008 slot->
pcb =
nullptr;
1049 tcp_recved(tpcb, p->tot_len);
1068 slot->
pcb =
nullptr;
1069 tcp_arg(tpcb,
nullptr);
1070 if (tcp_close(tpcb) != ERR_OK)
1075 pc_conn_reason::PC_CONN_R_CLOSE_REMOTE);
1117 for (
struct pbuf *q = p; q !=
nullptr; q = q->next)
1119 head = pc_ring_write_span(slot->
rx_buffer,
RX_BUF_SIZE, head, (
const uint8_t *)q->payload, q->len);
1122 size_t bytes_copied = p->tot_len;
1131 if (bytes_copied > 0)
1155 closing_check(slot->
id, tpcb);
1192 slot->
pcb =
nullptr;
static void check_timeouts(int worker_id=0)
Scan the pool and force-close connections idle for > conn_timeout_ms.
static void pool_init(const WebServerConfig *cfg=nullptr)
Initialize the connection pool and store the runtime config.
static uint32_t conn_timeout_ms
Runtime connection-idle timeout in milliseconds.
static void stop()
Abort all active connections and reset the pool to ConnState::CONN_FREE.
Pluggable monotonic clock for all library timing.
uint32_t pc_millis(void)
The library's monotonic time at 1000 Hz (milliseconds).
Layer 4 (Transport) - DiffServ QoS marking (RFC 2474) for outbound traffic.
pc_ip pc_ip_from_v4_octets(uint8_t a, uint8_t b, uint8_t c, uint8_t d)
Build a v4 pc_ip from four octets (a.b.c.d).
pc_ip pc_ip_from_v6_bytes(const uint8_t bytes[16])
Build a v6 pc_ip from 16 address bytes in network (big-endian) order.
@ PC_IP_NONE
empty / unparsed
bool listener_enqueue(uint8_t listener_id, const TcpEvt *evt)
Post evt to the queue owned by listener listener_id.
#define CONN_TIMEOUT_MS
Compile-time default for connection idle timeout in milliseconds.
#define PC_CLOSING_TIMEOUT_MS
Upper bound (ms) a slot may dwell in ConnState::CONN_CLOSING after a graceful close before the idle s...
std::atomic< uint32_t > free_mask
A single TCP connection context.
struct tcp_pcb * pcb
lwIP PCB; null when slot is free.
uint32_t last_activity_ms
millis() timestamp of last TX/RX event.
uint8_t id
Fixed slot index (0 … MAX_CONNS-1).
uint8_t rx_buffer[RX_BUF_SIZE]
Ring buffer storage.
uint8_t owner
Worker that owns this slot (round-robin at accept). Always 0 at N=1.
pc_atomic< size_t > rx_tail
Consumer read index (worker context).
uint8_t listener_id
Index into listener_pool[]; set at accept time.
uint8_t tls
Non-zero when this connection is TLS (set at accept time).
pc_atomic< ConnState > state
Lifecycle state; acquire/release for inter-task visibility.
pc_atomic< size_t > rx_head
Producer write index (lwIP/tcpip context).
Event record posted from lwIP callbacks to the session layer.
Runtime-tunable server parameters.
A v4 or v6 address in network (big-endian) byte order.
pc_ip_family family
address family tag
err_t result
outcome of the op (pc_tcp_op::PC_OP_SEND: whether the write was queued)
bool flush
pc_tcp_op::PC_OP_SEND: also tcp_output() after a successful write (coalesced write+flush)
struct tcpip_api_call_data base
void pc_conn_touch_active(uint8_t slot_id)
Refresh slot's idle-timeout timestamp while a response body is in flight.
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 bitma...
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....
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_slot(uint8_t slot)
Hard-abort connection slot (RST) for a fatal condition. The transport owns the teardown order: free t...
TcpConn conn_pool[CONN_POOL_SLOTS]
Static pool of connection contexts. Defined in tcp.cpp. Sized CONN_POOL_SLOTS: MAX_CONNS TCP slots pl...
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)....
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.
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_flush(uint8_t slot)
Flush queued bytes / finish the send on slot (TLS-aware).
uint32_t pc_ap_ip
softAP IPv4 address (network byte order) for STA/AP interface tagging.
u16_t pc_conn_sndbuf(uint8_t slot)
Bytes that can currently be queued for sending on slot.
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)....
void pc_conn_ack_consumed(uint8_t slot)
Reopen the TCP receive window by however much slot has drained.
void pc_conn_close(uint8_t slot)
Close connection slot gracefully (tcp_close), aborting if the FIN cannot be queued....
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.
bool pc_conn_send(uint8_t slot, const void *data, u16_t len)
Send len bytes on connection slot (copies data; TLS-aware).
uint8_t pc_conn_active_count()
Number of server connection slots currently in the CONN_ACTIVE state.
void pc_conn_begin_close(uint8_t slot_id)
Begin a graceful close that dwells in ConnState::CONN_CLOSING until the peer ACKs.
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).
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.
void pc_conn_abort(struct tcp_pcb *pcb)
Hard-abort pcb (RST) for a fatal condition; no graceful FIN.
Layer 4 (Transport) - TCP connection pool, ring buffers, and lwIP integration.
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 bitma...
void pc_conn_detach(struct tcp_pcb *pcb)
Detach pcb from its slot's lwIP callbacks before the slot is freed.
TcpConn conn_pool[CONN_POOL_SLOTS]
Static pool of connection contexts. Defined in tcp.cpp. Sized CONN_POOL_SLOTS: MAX_CONNS TCP slots pl...
#define PC_OBS_NOTICE(slot, st, reason)
@ EVT_DATA
Data received; bytes are already in the ring buffer.
@ EVT_DISCONNECT
Remote peer closed the connection gracefully.
@ EVT_ERROR
lwIP reported an error (PCB may already be freed).
#define PC_OBS_TRANSITION(slot, olds, news, reason)
ConnState
Lifecycle state of a connection pool slot.
@ CONN_CLOSING
FIN sent; waiting for final ACK (reserved).
@ CONN_ACTIVE
Live connection; PCB is valid.
@ CONN_FREE
Slot is available; no PCB is attached.
void pc_conn_abort(struct tcp_pcb *pcb)
Hard-abort pcb (RST) for a fatal condition; no graceful FIN.
Deterministic TLS engine: mbedTLS over a static memory pool (PC_ENABLE_TLS).
void pc_worker_wake(int worker_id)
Wake worker worker_id so it services a freshly-queued event now.
Layer 5 (Session) - server worker identity.