ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
h2_server.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 pc_h2_server.cpp
6 * @brief HTTP/2 engine <-> request-pipeline bridge - implementation. See pc_h2_server.h.
7 */
8
10
11#if PC_ENABLE_HTTP2 && PC_ENABLE_TLS
12
17#include <stdint.h>
18#include <string.h>
19
20// The per-slot engines are large (~28 KB each), so the pool does not fit internal DRAM alongside
21// TLS - it lives in PSRAM (PC_H2_POOL_IN_PSRAM). Same mechanism/caveat as the TLS arena: it
22// needs a framework built with CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY=y (the stock
23// arduino-esp32 core ships it OFF, so EXT_RAM_BSS_ATTR would no-op); see tools/psram/README.md.
24#if PC_H2_POOL_IN_PSRAM && defined(ARDUINO)
25#include <esp_attr.h> // pulls in sdkconfig.h -> CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY
26#if !defined(CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY)
27#error \
28 "PC_H2_POOL_IN_PSRAM needs a framework built with CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY=y. The stock arduino-esp32 core ships it OFF, so EXT_RAM_BSS_ATTR silently no-ops and the pool would overflow internal DRAM. Rebuild the core (tools/psram/README.md) or unset PC_H2_POOL_IN_PSRAM."
29#endif
30#if defined(EXT_RAM_BSS_ATTR)
31#define PC_H2_POOL_ATTR EXT_RAM_BSS_ATTR // IDF v5 / arduino-esp32 3.x
32#elif defined(EXT_RAM_ATTR)
33#define PC_H2_POOL_ATTR EXT_RAM_ATTR // IDF v4 / arduino-esp32 2.x
34#else
35#define PC_H2_POOL_ATTR
36#endif
37#else
38#define PC_H2_POOL_ATTR
39#endif
40
41// HTTP/2 connection pool, owned by one instance (internal linkage): the per-slot H2 connection
42// state. One named owner, unreachable from any other translation unit.
43struct H2ServerCtx
44{
45 H2Conn pool[MAX_CONNS];
46};
47static PC_H2_POOL_ATTR H2ServerCtx s_h2;
48
49namespace
50{
51void set_field(char *dst, size_t cap, const char *src, size_t n)
52{
53 if (n >= cap)
54 {
55 n = cap - 1;
56 }
57 memcpy(dst, src, n);
58 dst[n] = 0;
59}
60
61// --- engine callbacks (io / app carry the slot index) ---------------------------------------
62
63void cb_write(void *io, const uint8_t *data, size_t len)
64{
65 uint8_t slot = (uint8_t)(uintptr_t)io;
66 size_t off = 0;
67 while (off < len)
68 {
69 int w = pc_tls_write(slot, data + off, len - off);
70 if (w <= 0)
71 {
72 break; // error / would-block: best-effort for this path
73 }
74 off += (size_t)w;
75 }
76}
77
78void cb_header(void *app, uint32_t, const char *n, size_t nl, const char *v, size_t vl)
79{
80 HttpReq *r = &http_pool[(uint8_t)(uintptr_t)app];
81 if (nl == 7 && memcmp(n, ":method", 7) == 0)
82 {
83 set_field(r->method, sizeof r->method, v, vl);
84 }
85 else if (nl == 5 && memcmp(n, ":path", 5) == 0)
86 {
87 const char *q = (const char *)memchr(v, '?', vl);
88 size_t plen = q ? (size_t)(q - v) : vl;
89 set_field(r->path, sizeof r->path, v, plen);
90 r->path_idx = strnlen(r->path, sizeof r->path);
91 if (q)
92 {
93 set_field(r->query, sizeof r->query, q + 1, vl - plen - 1);
94 r->query_idx = strnlen(r->query, sizeof r->query);
95 }
96 }
97 else if (nl == 10 && memcmp(n, ":authority", 10) == 0)
98 {
100 {
101 Header *h = &r->headers[r->header_count++];
102 set_field(h->key, sizeof h->key, "host", 4);
103 set_field(h->val, sizeof h->val, v, vl);
104 }
105 }
106 else if (nl >= 1 && n[0] == ':')
107 {
108 // other pseudo-header (:scheme, :protocol) - not needed by the dispatcher
109 }
110 else
111 {
112 if (r->header_count < MAX_HEADERS)
113 {
114 Header *h = &r->headers[r->header_count++];
115 set_field(h->key, sizeof h->key, n, nl);
116 set_field(h->val, sizeof h->val, v, vl);
117 }
118 if (nl == 14 && memcmp(n, "content-length", 14) == 0)
119 {
120 size_t cl = 0;
121 for (size_t i = 0; i < vl && v[i] >= '0' && v[i] <= '9'; i++)
122 {
123 cl = cl * 10 + (size_t)(v[i] - '0');
124 }
125 r->content_length = cl;
126 }
127 }
128}
129
130void cb_headers_end(void *app, uint32_t sid, bool)
131{
132 uint8_t slot = (uint8_t)(uintptr_t)app;
133 conn_pool[slot].pc_h2_stream = sid;
134 http_pool[slot].parse_state = ParseState::PARSE_COMPLETE; // the worker's handle() loop dispatches it
135}
136
137void cb_data(void *app, uint32_t, const uint8_t *data, size_t len, bool)
138{
139 HttpReq *r = &http_pool[(uint8_t)(uintptr_t)app];
140 for (size_t i = 0; i < len && r->body_len < BODY_BUF_SIZE; i++)
141 {
142 r->body[r->body_len++] = data[i];
143 }
144 r->body[r->body_len] = 0;
145 r->body_bytes_read += len;
146}
147} // namespace
148
149void pc_h2_server_open(uint8_t slot)
150{
151 H2Callbacks cb;
152 memset(&cb, 0, sizeof cb);
153 cb.write = cb_write;
154 cb.on_header = cb_header;
155 cb.on_headers_end = cb_headers_end;
156 cb.on_data = cb_data;
157 cb.io = (void *)(uintptr_t)slot;
158 cb.app = (void *)(uintptr_t)slot;
159 pc_h2_conn_init(&s_h2.pool[slot], &cb); // emits our SETTINGS through cb_write
161}
162
163void pc_h2_server_data(uint8_t slot)
164{
165 uint8_t buf[512];
166 int n;
167 while ((n = pc_tls_read(slot, buf, sizeof buf)) > 0)
168 {
169 if (!pc_h2_conn_recv(&s_h2.pool[slot], buf, (size_t)n))
170 {
171 pc_h2_conn_goaway(&s_h2.pool[slot], 1 /* PROTOCOL_ERROR */);
172 return;
173 }
174 }
175}
176
177bool pc_h2_server_respond(uint8_t slot, int code, const char *content_type, const char *body, size_t len)
178{
179 bool ok = pc_h2_conn_respond(&s_h2.pool[slot], conn_pool[slot].pc_h2_stream, code, content_type, body, len);
180 http_parser_reset(&http_pool[slot]); // ready for the next stream; keep the connection open
181 return ok;
182}
183
184void pc_h2_server_close(uint8_t slot)
185{
186 conn_pool[slot].h2 = 0;
187 conn_pool[slot].pc_h2_checked = 0;
188 conn_pool[slot].pc_resp_sink = nullptr;
189}
190
191#endif // PC_ENABLE_HTTP2 && PC_ENABLE_TLS
#define BODY_BUF_SIZE
Definition c2_defaults.h:67
#define MAX_HEADERS
Definition c2_defaults.h:64
#define MAX_CONNS
Definition c2_defaults.h:47
void http_parser_reset(HttpReq *req)
Reset a parser context to the initial (ParseState::PARSE_METHOD) state.
HttpReq http_pool[CONN_POOL_SLOTS]
Pool of parser contexts, one per connection-pool slot (incl. reserved dispatch slots).
Standalone HTTP/1.1 request parser - no transport dependency.
@ PARSE_COMPLETE
Full request parsed; ready for dispatch.
A single HTTP header field (key: value).
Definition http_parser.h:90
char val[MAX_VAL_LEN]
Field value, null-terminated.
Definition http_parser.h:92
char key[MAX_KEY_LEN]
Field name, null-terminated.
Definition http_parser.h:91
Fully-parsed HTTP/1.1 request.
Header headers[MAX_HEADERS]
Captured header fields.
char query[MAX_QUERY_LEN]
Raw query string (after ?).
uint8_t body[BODY_BUF_SIZE+1]
Stored body bytes, always null-terminated.
uint8_t header_count
Valid entries in headers[].
ParseState parse_state
Current parser state.
size_t path_idx
Write cursor into path[].
size_t content_length
Value of Content-Length header (0 if absent).
size_t body_len
Bytes stored in body[] (≤ BODY_BUF_SIZE).
char path[MAX_PATH_LEN]
URL path, null-terminated; no query string.
char method[PC_METHOD_BUF_SIZE]
HTTP method, null-terminated (OPTIONS, or WebDAV methods when enabled).
size_t body_bytes_read
Body bytes received (may exceed BODY_BUF_SIZE).
size_t query_idx
Write cursor into query[].
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
Layer 4 (Transport) - TCP connection pool, ring buffers, and lwIP integration.
Deterministic TLS engine: mbedTLS over a static memory pool (PC_ENABLE_TLS).