ProtoCore v0.0.1
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
session.cpp
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 session.cpp
6 * @brief Layer 5 (Session) - event queue processor implementation.
7 *
8 * server_tick() is the only function here. Its bounded loop drains every
9 * active listener's FreeRTOS queue in one call so that the application layer
10 * always sees the most up-to-date state before checking http_pool[].
11 *
12 * Events are routed to the correct protocol handler via TcpConn::proto.
13 * A slot must carry an explicit protocol (assigned from its listener on
14 * accept); ConnProto::PROTO_NONE and any unregistered protocol resolve to no handler
15 * and the event is dropped.
16 */
17
18#include "session.h"
19#include "../transport/listener.h"
20#include "proto_handler.h"
21#include "server/mmgr/scratch.h"
22
23// This layer is protocol-agnostic: it owns the dispatch mechanism only (register / look up /
24// route / drain) and names no protocol. Each protocol's handler lives in its own module and is
25// installed through proto_register_builtins() (proto_builtins.cpp, the policy list).
26
27// ---------------------------------------------------------------------------
28// Protocol-handler dispatch table (see proto_handler.h)
29// ---------------------------------------------------------------------------
30// Protocol-handler dispatch table, owned by one instance (internal linkage): the per-protocol
31// ProtoHandler pointers. One named owner, unreachable from any other translation unit.
36static SessionCtx s_session;
37
39{
40 if ((unsigned)proto < PC_PROTO_MAX)
41 {
42 s_session.proto_handlers[(unsigned)proto] = h;
43 }
44}
45
47{
48 // Install the built-ins on first lookup so dispatch works before begin() (the native test
49 // harness drives server_tick() directly). The list itself lives in proto_builtins.cpp -
50 // this dispatcher names no protocol; it just knows ConnProto::PROTO_HTTP is always registered, and
51 // uses that as the "already bootstrapped" sentinel.
52 if (!s_session.proto_handlers[(unsigned)ConnProto::PROTO_HTTP])
53 {
55 }
56 // No implicit fallback: a slot must carry an explicit, registered protocol.
57 // ConnProto::PROTO_NONE and any unregistered protocol resolve to nullptr (event dropped).
58 return ((unsigned)proto < PC_PROTO_MAX) ? s_session.proto_handlers[(unsigned)proto] : nullptr;
59}
60
61// Dispatch one drained event to its slot's protocol handler. Shared by the
62// single-queue (N=1) and per-worker-queue (N>1) drain paths below.
63static inline void dispatch_event(const TcpEvt &evt)
64{
65 // Per-dispatch reset of the calling worker's scratch arena: every handler
66 // runs with the whole arena available, and any scratch it borrows is
67 // reclaimed before the next event - the backstop that stops a forgotten
68 // release from accumulating across events.
70
71 // Route to the slot's protocol handler. ConnProto::PROTO_NONE and any unregistered
72 // protocol have no handler, so the event is dropped.
74 if (!h)
75 {
76 return;
77 }
78
79 switch (evt.type)
80 {
82 if (h->on_accept)
83 {
84 h->on_accept(evt.slot_id);
85 }
86 break;
88 if (h->on_data)
89 {
90 h->on_data(evt.slot_id);
91 }
92 break;
95 if (h->on_close)
96 {
97 h->on_close(evt.slot_id);
98 }
99 break;
100 }
101}
102
103void server_tick(int worker_id)
104{
105 /*
106 * Check timeouts BEFORE draining events. This ensures that a slot
107 * freed by a timeout is already in ConnState::CONN_FREE state if a coincident
108 * EvtType::EVT_DISCONNECT or EvtType::EVT_ERROR is dequeued in the same tick - the
109 * http_reset() call for that event is then a clean no-op. Each worker
110 * sweeps only the slots it owns.
111 */
113
114#if PC_WORKER_COUNT > 1
115 // Drain only this worker's queue: it is the sole consumer of its slots.
116 QueueHandle_t q = listener_worker_queue(worker_id);
117 if (!q)
118 {
119 return;
120 }
121 TcpEvt evt;
122 while (xQueueReceive(q, &evt, 0) == pdTRUE)
123 {
124 dispatch_event(evt);
125 }
126#else
127 (void)worker_id; // single worker owns all slots; drain every listener queue
128 for (uint8_t li = 0; li < MAX_LISTENERS; li++)
129 {
130 Listener *lst = &listener_pool[li];
131 if (!lst->active || !lst->queue)
132 {
133 continue;
134 }
135
136 TcpEvt evt;
137 while (xQueueReceive(lst->queue, &evt, 0) == pdTRUE)
138 {
139 dispatch_event(evt);
140 }
141 }
142#endif
143}
static void check_timeouts(int worker_id=0)
Scan the pool and force-close connections idle for > conn_timeout_ms.
Definition tcp.cpp:958
Listener listener_pool[MAX_LISTENERS]
Static pool of listener contexts. Defined in listener.cpp.
Definition listener.cpp:35
void proto_register_builtins(void)
Register every built-in protocol's handler (the policy list).
Layer 5 (Session) - per-protocol connection handler dispatch table.
#define MAX_LISTENERS
Maximum number of simultaneously active listener ports.
ConnProto
Application protocol spoken on a listener port or connection slot.
@ PROTO_HTTP
HTTP/1.1 with optional WS and SSE upgrades.
#define PC_PROTO_MAX
Size of the protocol-handler dispatch table; must exceed the largest ConnProto id.
void scratch_reset(void)
Reclaim the whole arena (empties it).
Definition scratch.cpp:154
Shared per-dispatch scratch arena (Layer 5, session-scoped memory).
void proto_register(ConnProto proto, const ProtoHandler *h)
Register h for protocol proto (replaces any previous handler).
Definition session.cpp:38
const ProtoHandler * proto_get(ConnProto proto)
Look up the handler for proto.
Definition session.cpp:46
void server_tick(int worker_id)
Drive the session layer for one Arduino loop iteration.
Definition session.cpp:103
Layer 5 (Session) - event queue dispatcher and session lifecycle.
State for one TCP listening port.
Definition listener.h:55
QueueHandle_t queue
Handle returned by xQueueCreateStatic().
Definition listener.h:61
bool active
True after listener_add(), false after listener_stop().
Definition listener.h:62
Per-protocol connection event/poll callbacks (Layer 5 dispatch vtable).
void(* on_close)(uint8_t slot)
EvtType::EVT_DISCONNECT / EvtType::EVT_ERROR: tear down slot state.
void(* on_data)(uint8_t slot)
EvtType::EVT_DATA: bytes are available in the slot's rx ring.
void(* on_accept)(uint8_t slot)
EvtType::EVT_CONNECT: a new connection was accepted.
const ProtoHandler * proto_handlers[PC_PROTO_MAX]
Definition session.cpp:34
ConnProto proto
Application protocol for this connection.
Definition tcp.h:90
Event record posted from lwIP callbacks to the session layer.
Definition tcp.h:161
EvtType type
What happened.
Definition tcp.h:162
uint8_t slot_id
Which connection slot is affected.
Definition tcp.h:163
TcpConn conn_pool[CONN_POOL_SLOTS]
Static pool of connection contexts. Defined in tcp.cpp. Sized CONN_POOL_SLOTS: MAX_CONNS TCP slots pl...
Definition tcp.cpp:425
@ EVT_DATA
Data received; bytes are already in the ring buffer.
@ EVT_CONNECT
New connection accepted.
@ EVT_DISCONNECT
Remote peer closed the connection gracefully.
@ EVT_ERROR
lwIP reported an error (PCB may already be freed).