ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
session.h
Go to the documentation of this file.
1// ProtoCore v1.0.16 - Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file session.h
6 * @brief Layer 5 (Session) - where a connection is opened, closed and controlled.
7 *
8 * A connection's life is decided here: the transport signals that one arrived, ended or faulted,
9 * and this layer turns that into an open, a close, or a dispatch to whichever protocol owns the
10 * slot. It sits above the server core and uses it rather than being part of it - the worker pool
11 * turns the crank (server/core/worker.h) and the protocol registry says who receives the event
12 * (server/core/proto_handler.h).
13 *
14 * One tick drains every pending event in a single bounded loop, so the worst case is
15 * O(queue_depth + MAX_CONNS) rather than unbounded in the arrival rate.
16 *
17 * @author Douglas Quigg (dstroy0)
18 * @date 2026
19 */
20
21#ifndef PROTOCORE_SESSION_H
22#define PROTOCORE_SESSION_H
23
24#include "network_drivers/transport/tcp/evt/evt.h" // EvtType, TcpEvt: the events this layer drains
25#include "server/core/proto_handler/proto_handler.h" // ProtoHandler: the record ::Protocols binds
26#include "server/core/worker/worker.h" // WorkerNs: carried below as Session.workers
27
28#include "protocore_config.h" // CONN_POOL_SLOTS, proto_bool: the tables below
29
30// --- the protocol registry -------------------------------------------------------------
31//
32// Declared HERE and not in server/core/proto_handler.h, which declares the ProtoHandler record
33// the application layer implements. Registering a handler is connection lifetime, which this
34// layer owns: the storage is proto_handlers[PROTO_MAX_HANDLERS] inside SessionCtx, every entry
35// reaches it through SESSION_CTX(work), and every caller passes protocore_session_span(). With
36// the declaration on the server side, the registry's own header had no .c to be defined in.
37
38/**
39 * @brief The protocol registry.
40 *
41 * A caller sets the members a call takes, invokes it through ::Protocols, and reads the outcome off
42 * ::ProtocolsV. The handlers themselves live in SessionCtx, reached only through the borrow.
43 *
44 * @var ProtoRegistryNs::proto the protocol a call names
45 * @var ProtoRegistryNs::h the handler an add binds to it
46 * @var ProtoRegistryNs::handler the handler a lookup reports, or NULL when none is bound
47 * @var ProtoRegistryNs::register_builtins install every handler the build compiled in
48 * @var ProtoRegistryNs::add bind one handler to one protocol
49 * @var ProtoRegistryNs::get the handler for a protocol, or null if none is bound
50 */
51typedef struct
52{
57
58/** @brief The operands and the outcome. */
60
61/** @brief The entries. */
62typedef struct
63{
64 void (*const register_builtins)(uint8_t *work);
65 void (*const add)(uint8_t *work);
66 void (*const get)(uint8_t *work);
68
69// What the table binds, defined once in the .c and taking one parameter each: everything
70// else an entry needs is an operand in ProtocolsV or a region of the borrow at a fixed offset.
72void protocore_protocols_add(uint8_t *work);
73void protocore_protocols_get(uint8_t *work);
74
75// `static const`, initialised HERE rather than `extern` against a definition in the .c: a
76// const object whose initializer every translation unit can see is a COMPILE-TIME FACT, so
77// `Protocols.register_builtins(work)` resolves to a named function and becomes a DIRECT call. An extern table
78// leaves the call indirect and the symbol live at every level, -O2 -flto included.
79static const ProtoRegistryNs Protocols __attribute__((unused)) = {
83};
84
85/**
86 * @brief Per-connection state, keyed on the transport slot index.
87 *
88 * A connection is opened, closed and controlled here, so what a connection carries between its
89 * requests is held here too rather than by whichever layer happens to read it. Sized
90 * CONN_POOL_SLOTS, not MAX_CONNS: the HTTP/3 dispatch slot is a reserved index above the TCP range.
91 * All BSS. Defined in session.c.
92 *
93 * @var http_req_start_ms protocore_millis() at the first byte of the in-progress request (0 = none).
94 * The request-header deadline (PROTOCORE_REQUEST_TIMEOUT_MS, slow-loris
95 * defense) measures against this; unlike the transport's idle timer a
96 * trickle byte cannot reset it.
97 * @var http_resp_sink where a response for the slot is written, per slot.
98 */
99typedef proto_bool (*protocore_resp_sink_fn)(uint8_t slot, int code, const char *content_type, const char *body,
100 size_t len);
101
102extern uint32_t http_req_start_ms[CONN_POOL_SLOTS];
104
105#if PROTOCORE_ENABLE_HTTP2
106/**
107 * @brief Whether the slot negotiated HTTP/2 (ALPN "h2"), whether that check has run, and the
108 * stream it is serving.
109 *
110 * RFC 9113 sec 5: a stream is "an independent, bidirectional sequence of frames exchanged between
111 * the client and server within an HTTP/2 connection", and "stream identifiers are assigned to
112 * streams by the endpoint initiating the stream" - so which stream a slot is answering on is a
113 * property of the connection, not of the request being parsed.
114 */
115extern uint8_t http_h2[CONN_POOL_SLOTS];
116extern uint8_t http_h2_checked[CONN_POOL_SLOTS];
117extern uint32_t http_h2_stream[CONN_POOL_SLOTS];
118#endif
119
120#if PROTOCORE_ENABLE_HTTP3
121/** @brief The reserved HTTP/3 dispatch slot, and the QUIC connection and stream a response routes back on. */
122extern uint8_t http_h3[CONN_POOL_SLOTS];
123extern uint32_t http_h3_conn_id[CONN_POOL_SLOTS];
124extern uint64_t http_h3_stream[CONN_POOL_SLOTS];
125#endif
126
127#if PROTOCORE_ENABLE_FILE_SERVING
128/**
129 * @brief A file transfer in progress on a slot: the open file and how much of it is left.
130 *
131 * A file larger than the send window cannot go out in one dispatch, so the transfer spans several
132 * loops and is therefore something the CONNECTION carries between them, not something the file
133 * server holds on the side. Session opens and closes the connection, so session holds it.
134 */
135typedef struct
136{
137 int fh; ///< accessor handle for the open source file, held across loops.
138 size_t off; ///< absolute file offset of the next byte to send.
139 size_t remaining; ///< body bytes still to send.
140 int status; ///< response status (200 / 206) for note_response.
141 int total; ///< total body length, for the access log.
142 proto_bool keep; ///< keep-alive vs close at completion.
143 proto_bool active; ///< a transfer is in progress on this slot.
144} FileSend;
145
146extern FileSend file_send[CONN_POOL_SLOTS];
147#endif
148
149#if PROTOCORE_ENABLE_SSH_SCP
150/**
151 * @brief One SCP transfer in progress on a slot: where it is in the rcp SINK exchange, the
152 * destination it is writing, and how much of the file is still to arrive.
153 *
154 * A transfer spans many channel messages, so it is what the CONNECTION carries between them.
155 * Session opens and closes the connection, so session holds it.
156 */
157typedef enum PROTO_ENUM_PACKED
158{
159 PROTOCORE_NONE,
160 WAIT_CLINE, ///< reading the C<mode> <size> <name> control line
161 RECV, ///< streaming file data to disk
162 WAIT_END ///< the file's bytes are in; awaiting the end-of-record byte
163} ScpSt;
164
165typedef struct
166{
167 proto_bool active;
168 uint8_t slot;
169 uint32_t channel;
170 ScpSt st;
171 char dest[PROTOCORE_FILESYSTEM_PATH_MAX]; ///< the -t target (a file, or a dir if it ends with '/')
172 proto_bool dest_is_dir;
173 int fh; ///< open file handle, or -1
174 uint64_t remaining; ///< data bytes still to receive
175 proto_bool err;
176 uint16_t cl_len; ///< control-line accumulator length
177 char cl[PROTOCORE_FILESYSTEM_PATH_MAX + 64];
178} ScpConn;
179
180extern ScpConn scp_conns[MAX_SSH_CONNS];
181#endif
182
183#if PROTOCORE_ENABLE_SSH_SFTP
184/**
185 * @brief One SFTP session on a slot: its open handles, a streaming write part way through, and
186 * the accumulator holding an incomplete request packet.
187 *
188 * All of it spans several channel messages, so it is what the CONNECTION carries between them.
189 * Session opens and closes the connection, so session holds it.
190 */
191typedef struct
192{
193 proto_bool is_dir;
194 int fh; ///< the accessor's handle (a dir cursor when is_dir)
195 char req[PROTOCORE_FILESYSTEM_PATH_MAX]; ///< the request path this was opened with; FSTAT stats it
196 proto_bool readdir_done; ///< the directory has been fully listed
197 proto_bool has_pending; ///< a READDIR entry that did not fit last time, emitted first next time
198 uint16_t pend_len;
199 uint8_t pend[PROTOCORE_SFTP_ENTRY_MAX];
200} SftpHandle;
201
202typedef struct
203{
204 proto_bool active;
205 uint8_t slot;
206 uint32_t channel;
207 // Which handles are open, one bit each. In-use state is a single bit, so the whole table's
208 // answer fits in a register: allocation is one bit scan instead of a walk, releasing them all
209 // touches only the ones actually open, and resetting the table is a store rather than a loop.
210 uint32_t open_mask;
211 uint16_t acc_len; ///< bytes accumulated toward the next request packet
212 uint8_t acc[PROTOCORE_SFTP_PKT_BUF];
213 // streaming write: a WRITE whose data payload arrives across CHANNEL_DATA calls
214 proto_bool writing;
215 int wr_handle;
216 uint64_t wr_off;
217 uint32_t wr_remaining;
218 uint32_t wr_id;
219 proto_bool wr_err;
220 SftpHandle handles[PROTOCORE_SFTP_MAX_HANDLES];
221} SftpSession;
222
223extern SftpSession sftp_sess[MAX_SSH_CONNS];
224#endif
225
226/**
227 * @brief The session tick, and the core modules it drives.
228 *
229 * A caller sets the members a call takes and invokes it through ::Session.
230 *
231 * @var SessionNs::worker_id which worker is turning: whose slots it sweeps and whose queue it drains
232 * @var SessionNs::conn_timeout_ms
233 * milliseconds of inactivity before a connection is closed, applied by
234 * the sweep this layer drives
235 * @var SessionNs::tick drive the layer for one loop iteration: sweep, drain, dispatch
236 * @var SessionNs::proto the protocol registry a connection is dispatched through
237 * @var SessionNs::workers the worker tasks that turn the pipeline, their deferred-callback
238 * path, and the queue they jump when one is compiled in
239 *
240 * A child is a pointer: a table in one translation unit is not a constant expression in another.
241 * A child behind a feature flag is declared under it, so the layer names only what the image
242 * contains.
243 */
250
251/** @brief The operands and the outcome. */
252extern SessionVars SessionV;
253
254/** @brief The entries. */
255typedef struct
256{
257 void (*const tick)(uint8_t *work);
258} SessionNs;
259
260// What the table binds, defined once in the .c and taking one parameter each: everything
261// else an entry needs is an operand in SessionV or a region of the borrow at a fixed offset.
262void protocore_session_tick(uint8_t *work);
263
264// `static const`, initialised HERE rather than `extern` against a definition in the .c: a
265// const object whose initializer every translation unit can see is a COMPILE-TIME FACT, so
266// `Session.tick(work)` resolves to a named function and becomes a DIRECT call. An extern table
267// leaves the call indirect and the symbol live at every level, -O2 -flto included.
268static const SessionNs Session __attribute__((unused)) = {
270};
271
272/**
273 * @brief The PROTOCORE_SESSION_BORROW bytes this module's state lives in.
274 *
275 * Stated beside the namespace rather than on it: an entry takes a borrow, and this is where
276 * that borrow comes from. Taken once from the end of the pool, which no mark and no release
277 * walks, so the state lasts the life of the program.
278 *
279 * @return the span.
280 */
282
283#endif
#define CONN_POOL_SLOTS
#define PROTOCORE_SFTP_ENTRY_MAX
Largest serialized SSH_FXP_NAME entry one READDIR emits: filename, ls -l longname and attributes....
#define PROTOCORE_SFTP_PKT_BUF
SFTP packet-assembly buffer per SFTP channel (bytes); bounds one non-streamed request/response.
#define PROTOCORE_SFTP_MAX_HANDLES
Max concurrent open SFTP handles (files + dirs) per SSH connection.
#define MAX_SSH_CONNS
Maximum simultaneous SSH connections.
PROTO_ENUM_PACKED
Application protocol spoken on a listener port or connection slot.
#define PROTOCORE_FILESYSTEM_PATH_MAX
Largest absolute path the SFTP/SCP server resolves (mount root + request path).
enum PROTO_ENUM_PACKED ProtoConn
Application protocol spoken on a listener port or connection slot.
What the stack callbacks post to a listener's queue: the event type and the record.
Core server - per-protocol connection handler dispatch table.
uint32_t http_req_start_ms[CONN_POOL_SLOTS]
proto_bool(* protocore_resp_sink_fn)(uint8_t slot, int code, const char *content_type, const char *body, size_t len)
Definition session.h:99
SessionVars SessionV
The operands and the outcome.
void protocore_protocols_add(uint8_t *work)
ProtocolsVars ProtocolsV
The operands and the outcome.
void protocore_protocols_register_builtins(uint8_t *work)
uint8_t * protocore_session_span(void)
The PROTOCORE_SESSION_BORROW bytes this module's state lives in.
protocore_resp_sink_fn http_resp_sink[CONN_POOL_SLOTS]
void protocore_protocols_get(uint8_t *work)
void protocore_session_tick(uint8_t *work)
Per-protocol connection event/poll callbacks (the server's dispatch vtable).
The entries.
Definition session.h:63
void(*const register_builtins)(uint8_t *work)
Definition session.h:64
ProtoConn proto
Definition session.h:53
const ProtoHandler * handler
Definition session.h:55
const ProtoHandler * h
Definition session.h:54
The entries.
Definition session.h:256
int worker_id
Definition session.h:246
WorkerNs * workers
Definition session.h:248
proto_u32 conn_timeout_ms
Definition session.h:247
The entries.
Definition worker.h:180
_Bool proto_bool
The truth value.
Definition types.h:64
uint32_t proto_u32
Definition types.h:42
Core server - server worker identity.