ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
sse.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 sse.h
6 * @brief Layer 6 (Presentation) -- Server-Sent Events connection pool.
7 *
8 * SSE (WHATWG HTML Living Standard, Server-sent events; the W3C EventSource
9 * API) is a long-lived HTTP GET response with Content-Type: text/event-stream.
10 * After the initial headers the connection stays open indefinitely; the server
11 * pushes newline-delimited event records at any time.
12 *
13 * **Event record format** (WHATWG HTML, Server-sent events)
14 * ```
15 * [event: <name>\n]
16 * [id: <id>\n]
17 * data: <payload>\n
18 * \n
19 * ```
20 *
21 * Each SseConn occupies one TCP slot from conn_pool[] for the lifetime of
22 * the subscription. The total number of simultaneous SSE connections is
23 * capped at MAX_SSE_CONNS.
24 *
25 * @author Douglas Quigg (dstroy0)
26 * @date 2026
27 */
28
29#ifndef PROTOCORE_SSE_H
30#define PROTOCORE_SSE_H
31
32#include "protocore_config.h"
33
34#if PROTOCORE_ENABLE_SSE
35
37
38// ---------------------------------------------------------------------------
39// Per-connection SSE state
40// ---------------------------------------------------------------------------
41
42/**
43 * @brief SSE connection state stored in protocore_sse_pool[].
44 *
45 * Allocated when the SSE handshake (200 + headers) is sent. slot_id ties
46 * this entry back to conn_pool[] and the underlying TCP PCB.
47 */
48typedef struct
49{
50 uint8_t protocore_sse_id; ///< Index into protocore_sse_pool[] (set at init).
51 uint8_t slot_id; ///< Owning TCP slot in conn_pool[].
52 proto_bool active; ///< True when this entry is in use.
53
54 /** Path this client subscribed to (for protocore_sse_broadcast() matching). */
55 char path[MAX_PATH_LEN];
56} SseConn;
57
58/** @brief Pool of SSE connection state, one per MAX_SSE_CONNS. Defined in sse.c. */
59extern SseConn protocore_sse_pool[MAX_SSE_CONNS];
60
61/**
62 * @brief Callback fired when a new SSE client connects.
63 *
64 * Use protocore_sse_send() inside this callback to push an initial event if needed.
65 *
66 * @param protocore_sse_id Index into protocore_sse_pool[] for this connection.
67 */
68typedef void (*SseConnectHandler)(uint8_t protocore_sse_id);
69
70/** @brief One subscribe route: the path, and what an open on it runs. */
71typedef struct
72{
73 const char *path; ///< the path a client subscribed to
74 SseConnectHandler on_connect; ///< the subscribe handler a route records
75} SseRouteArgs;
76
77/** @brief The fields of one text/event-stream record. */
78typedef struct
79{
80 const char *data; ///< the event data; required
81 const char *event; ///< the event name; optional
82 const char *event_id; ///< the event id; optional
83} SseEventArgs;
84
85/** @brief Where a formatted record lands. */
86typedef struct
87{
88 char *buf; ///< where format writes
89 size_t cap; ///< how much room it has
90} SseOutArgs;
91
92// ---------------------------------------------------------------------------
93// SSE pool API
94// ---------------------------------------------------------------------------
95
96/** @brief The id a route carries when it serves no SSE stream. */
97#define PROTOCORE_SSE_NONE 0xFFu
98
99/**
100 * @brief The event streams this server holds open, and what one carries.
101 *
102 * A caller sets the members a call takes, invokes it through ::Sse, and reads the outcome off the
103 * same handle.
104 *
105 * @var SseNs::slot the TCP slot a call acts on
106 * @var SseNs::id the route id a lookup names
107 * @var SseNs::route what one subscribe route records
108 * @var SseNs::stream the stream a write goes to
109 * @var SseNs::event_args the fields of one event record
110 * @var SseNs::out where a format writes
111 * @var SseNs::ok a call's true/false outcome
112 * @var SseNs::u8 the route id an add reports, or ::PROTOCORE_SSE_NONE when full
113 * @var SseNs::n bytes format wrote, excluding the terminator
114 * @var SseNs::conn the stream an alloc or a find reports, or NULL
115 * @var SseNs::handler the subscribe handler a lookup reports, or NULL
116 * @var SseNs::route_add record one route's subscribe handler
117 * @var SseNs::route_reset empty the handler table; a route holds the id an add returned, so
118 * this empties with the routes
119 * @var SseNs::route_connect the subscribe handler an id names
120 * @var SseNs::init set every pool slot inactive; called once from begin()
121 * @var SseNs::alloc take a stream and bind it to a TCP slot
122 * @var SseNs::find the stream bound to a TCP slot
123 * @var SseNs::free release the stream bound to a TCP slot
124 * @var SseNs::format format one event record into out.buf, no transport
125 * @var SseNs::write format one event record and send it to the stream
126 *
127 * Every entry takes the module's borrow. How those bytes are carved is sse.c's and is never named
128 * here. ::protocore_sse_span is where a caller gets one.
129 *
130 * format emits `event: <event>\n` (if event), `id: <event_id>\n` (if event_id), then
131 * `data: <data>\n\n` per the WHATWG event-stream format. It touches no connection state, so it is
132 * unit-testable on its own; write wraps it with the send. A caller that needs immediate delivery
133 * flushes the connection itself afterwards.
134 */
135typedef struct
136{
137 uint8_t slot; ///< the TCP slot a call acts on
138 uint8_t id; ///< the route id a lookup names
139 SseConn *stream; ///< the stream a write goes to
140 SseRouteArgs route; ///< what one subscribe route records
141 SseEventArgs event_args; ///< the fields of one event record
142 SseOutArgs out; ///< where a format writes
143 proto_bool ok;
144 uint8_t u8;
145 int n;
146 SseConn *conn;
147 SseConnectHandler handler;
148} SseVars;
149
150/** @brief The operands and the outcome. */
151extern SseVars SseV;
152
153/** @brief The entries. */
154typedef struct
155{
156 void (*const route_add)(uint8_t *work);
157 void (*const route_reset)(uint8_t *work);
158 void (*const route_connect)(uint8_t *work);
159 void (*const init)(uint8_t *work);
160 void (*const alloc)(uint8_t *work);
161 void (*const find)(uint8_t *work);
162 void (*const free)(uint8_t *work);
163 void (*const format)(uint8_t *work);
164 void (*const write)(uint8_t *work);
165} SseNs;
166
167// What the table binds, defined once in the .c and taking one parameter each: everything
168// else an entry needs is an operand in SseV or a region of the borrow at a fixed offset.
169void protocore_sse_route_add(uint8_t *work);
170void protocore_sse_route_reset(uint8_t *work);
171void protocore_sse_route_connect(uint8_t *work);
172void protocore_sse_init(uint8_t *work);
173void protocore_sse_alloc(uint8_t *work);
174void protocore_sse_find(uint8_t *work);
175void protocore_sse_free(uint8_t *work);
176void protocore_sse_format(uint8_t *work);
177void protocore_sse_write(uint8_t *work);
178
179// `static const`, initialised HERE rather than `extern` against a definition in the .c: a
180// const object whose initializer every translation unit can see is a COMPILE-TIME FACT, so
181// `Sse.route_add(work)` resolves to a named function and becomes a DIRECT call. An extern table
182// leaves the call indirect and the symbol live at every level, -O2 -flto included.
183static const SseNs Sse __attribute__((unused)) = {
184 .route_add = protocore_sse_route_add,
185 .route_reset = protocore_sse_route_reset,
186 .route_connect = protocore_sse_route_connect,
187 .init = protocore_sse_init,
188 .alloc = protocore_sse_alloc,
189 .find = protocore_sse_find,
190 .free = protocore_sse_free,
191 .format = protocore_sse_format,
192 .write = protocore_sse_write,
193};
194
195/** @brief Not an entry: an entry takes a borrow and this is where that borrow comes from. */
196uint8_t *protocore_sse_span(void);
197
199
200#endif // PROTOCORE_ENABLE_SSE
201
202#endif
#define MAX_PATH_LEN
Maximum URL path length (including leading /).
#define MAX_SSE_CONNS
Maximum simultaneous SSE connections.
#define PROTOCORE_BEGIN_DECLS
Give a header's declarations C linkage, so their symbol names carry no parameter types.
Definition types.h:96
_Bool proto_bool
The truth value.
Definition types.h:64
#define PROTOCORE_END_DECLS
Definition types.h:97