25#ifndef DETWS_QUIC_SERVER_IN_PSRAM
26#define DETWS_QUIC_SERVER_IN_PSRAM 0
28#ifndef DETWS_QUIC_SERVER_ACK_DRAM
29#define DETWS_QUIC_SERVER_ACK_DRAM 0
34#if defined(ARDUINO) && !DETWS_QUIC_SERVER_IN_PSRAM && !DETWS_QUIC_SERVER_ACK_DRAM
36 "DeterministicESPAsyncWebServer: DETWS_ENABLE_HTTP3 - the quic_server QuicConn+H3Conn pool + ingest ring are tens of KB. Set DETWS_QUIC_SERVER_IN_PSRAM=1 on a PSRAM board (S3 / P4 / WROVER built with CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY=y, tools/psram/README.md), OR set DETWS_QUIC_SERVER_ACK_DRAM=1 to accept the internal-DRAM cost (fits a small pool on a roomy chip)."
38#if DETWS_QUIC_SERVER_IN_PSRAM && defined(ARDUINO)
40#if defined(EXT_RAM_BSS_ATTR)
41#define DETWS_QUIC_POOL_ATTR EXT_RAM_BSS_ATTR
42#elif defined(EXT_RAM_ATTR)
43#define DETWS_QUIC_POOL_ATTR EXT_RAM_ATTR
45#define DETWS_QUIC_POOL_ATTR
48#define DETWS_QUIC_POOL_ATTR
56 uint8_t data[DETWS_QUIC_MAX_DATAGRAM];
77struct QuicServerPoolCtx
79 QuicSlot pool[DETWS_QUIC_MAX_CONNS];
80 QuicIngest ring[DETWS_QUIC_INGEST_RING];
82DETWS_QUIC_POOL_ATTR QuicServerPoolCtx s_qpool;
92 QuicServerRequestFn on_request =
nullptr;
98 QuicServerOutFn out_sink =
nullptr;
99 void *out_ctx =
nullptr;
104void copy_str(
char *dst,
size_t cap,
const char *src)
108 while (src[n] && n + 1 < cap)
116bool cid_eq(
const uint8_t *a, uint8_t alen,
const uint8_t *b, uint8_t blen)
118 return alen == blen && memcmp(a, b, alen) == 0;
121void server_send(
const char *ip, uint16_t port,
const uint8_t *data,
size_t len)
127 s_quic.out_sink(s_quic.out_ctx, data, len, ip, port);
132bool ring_push(
const uint8_t *dg,
size_t len,
const char *ip, uint16_t port)
134 if (len == 0 || len > DETWS_QUIC_MAX_DATAGRAM)
136 size_t head = s_quic.ring_head;
137 size_t next = (head + 1) % DETWS_QUIC_INGEST_RING;
138 if (next == (
size_t)s_quic.ring_tail)
140 QuicIngest *e = &s_qpool.ring[head];
141 memcpy(e->data, dg, len);
142 e->len = (uint16_t)len;
143 copy_str(e->ip,
sizeof e->ip, ip);
145 s_quic.ring_head = next;
149bool ring_pop(QuicIngest *out)
151 size_t tail = s_quic.ring_tail;
152 if (tail == (
size_t)s_quic.ring_head)
154 *out = s_qpool.ring[tail];
155 s_quic.ring_tail = (tail + 1) % DETWS_QUIC_INGEST_RING;
160QuicSlot *slot_by_id(uint32_t
id)
162 for (uint8_t i = 0; i < DETWS_QUIC_MAX_CONNS; i++)
163 if (s_qpool.pool[i].used && s_qpool.pool[i].id ==
id)
164 return &s_qpool.pool[i];
168QuicSlot *alloc_slot()
170 for (uint8_t i = 0; i < DETWS_QUIC_MAX_CONNS; i++)
171 if (!s_qpool.pool[i].used)
173 QuicSlot *s = &s_qpool.pool[i];
174 memset(s, 0,
sizeof *s);
176 s->id = s_quic.next_id++;
177 if (s_quic.next_id == 0)
185void h3_on_request(
void *app, H3Conn * , uint64_t stream_id,
const char *method,
const char *path,
186 const char *authority,
const uint8_t *body,
size_t body_len)
188 QuicSlot *s = (QuicSlot *)app;
189 if (s_quic.on_request)
190 s_quic.on_request(s_quic.app, s->id, stream_id, method, path, authority, body, body_len);
194QuicSlot *open_conn(
const QuicLongHeader *lh,
const char *ip, uint16_t port)
196 QuicSlot *s = alloc_slot();
201 memset(&tc, 0,
sizeof tc);
202 tc.cert_der = s_quic.cfg.cert_der;
203 tc.cert_len = s_quic.cfg.cert_len;
204 memcpy(tc.ed25519_seed, s_quic.cfg.ed25519_seed,
sizeof tc.ed25519_seed);
205 quic_tp_defaults(&tc.params);
208 tc.params.initial_max_data = 1048576;
209 tc.params.initial_max_sd_bidi_remote = 262144;
210 tc.params.initial_max_sd_uni = 262144;
213 tc.params.max_idle_timeout = DETWS_QUIC_IDLE_MS;
214 s_quic.cfg.rng(tc.ephemeral_priv,
sizeof tc.ephemeral_priv);
215 s_quic.cfg.rng(tc.random,
sizeof tc.random);
216#if DETWS_ENABLE_PQC_KEX
217 s_quic.cfg.rng(tc.mlkem_m,
sizeof tc.mlkem_m);
220 uint8_t our_scid[DETWS_QUIC_SCID_LEN];
221 s_quic.cfg.rng(our_scid,
sizeof our_scid);
223 QuicConnCallbacks cb;
224 memset(&cb, 0,
sizeof cb);
225 quic_conn_init(&s->qc, &tc, lh->dcid, lh->dcid_len, lh->scid, lh->scid_len, our_scid, DETWS_QUIC_SCID_LEN, &cb);
226 h3_conn_init(&s->h3, &s->qc, h3_on_request, s);
228 copy_str(s->peer_ip,
sizeof s->peer_ip, ip);
235QuicSlot *route(
const uint8_t *dg,
size_t len,
bool *is_initial, QuicLongHeader *lh_out)
240 if (quic_is_long_header(dg[0]))
242 if (!quic_parse_long_header(dg, len, lh_out))
244 for (uint8_t i = 0; i < DETWS_QUIC_MAX_CONNS; i++)
246 QuicSlot *s = &s_qpool.pool[i];
249 if (cid_eq(lh_out->dcid, lh_out->dcid_len, s->qc.scid, s->qc.scid_len) ||
250 cid_eq(lh_out->dcid, lh_out->dcid_len, s->qc.odcid, s->qc.odcid_len))
253 if (lh_out->version == QUIC_VERSION_1 && lh_out->type == QuicLongPacket::QUIC_LP_INITIAL)
258 if (len < (
size_t)1 + DETWS_QUIC_SCID_LEN)
260 for (uint8_t i = 0; i < DETWS_QUIC_MAX_CONNS; i++)
262 QuicSlot *s = &s_qpool.pool[i];
263 if (s->used && s->qc.scid_len == DETWS_QUIC_SCID_LEN && memcmp(dg + 1, s->qc.scid, DETWS_QUIC_SCID_LEN) == 0)
269void flush_and_reap(uint32_t now_ms)
271 uint8_t out[DETWS_QUIC_MAX_DATAGRAM];
272 for (uint8_t i = 0; i < DETWS_QUIC_MAX_CONNS; i++)
274 QuicSlot *s = &s_qpool.pool[i];
277 quic_conn_on_timeout(&s->qc, now_ms);
279 while ((n = quic_conn_send(&s->qc, out,
sizeof out)) > 0)
280 server_send(s->peer_ip, s->peer_port, out, n);
283 if (quic_conn_is_closed(&s->qc) || (uint32_t)(now_ms - s->last_ms) >= DETWS_QUIC_IDLE_MS)
289void udp_ingest_cb(
const uint8_t *data,
size_t len,
DetUdpPeer *peer,
void * )
295 ring_push(data, len, ip, port);
300bool quic_server_begin(uint16_t port,
const QuicServerConfig *cfg, QuicServerRequestFn on_request,
void *app)
302 if (!cfg || !cfg->rng)
305 s_quic.on_request = on_request;
308 for (uint8_t i = 0; i < DETWS_QUIC_MAX_CONNS; i++)
309 s_qpool.pool[i].used =
false;
310 s_quic.ring_head = 0;
311 s_quic.ring_tail = 0;
313 s_quic.running =
true;
321void quic_server_poll(uint32_t now_ms)
326 while (ring_pop(&ig))
328 bool is_initial =
false;
330 QuicSlot *s = route(ig.data, ig.len, &is_initial, &lh);
331 if (!s && is_initial)
332 s = open_conn(&lh, ig.ip, ig.port);
336 quic_conn_recv(&s->qc, ig.data, ig.len);
338 flush_and_reap(now_ms);
341bool quic_server_respond(uint32_t conn_id, uint64_t stream_id,
int status,
const char *content_type,
342 const uint8_t *body,
size_t body_len)
344 QuicSlot *s = slot_by_id(conn_id);
347 return h3_conn_respond(&s->h3, stream_id, status, content_type, body, body_len);
350uint8_t quic_server_active_conns(
void)
353 for (uint8_t i = 0; i < DETWS_QUIC_MAX_CONNS; i++)
354 if (s_qpool.pool[i].used)
359void quic_server_stop(
void)
361 s_quic.running =
false;
362 for (uint8_t i = 0; i < DETWS_QUIC_MAX_CONNS; i++)
363 s_qpool.pool[i].used =
false;
364 s_quic.ring_head = 0;
365 s_quic.ring_tail = 0;
369void quic_server_set_out_sink(QuicServerOutFn fn,
void *ctx)
371 s_quic.out_sink = fn;
372 s_quic.out_ctx = ctx;
375bool quic_server_ingest(
const uint8_t *datagram,
size_t len,
const char *ip, uint16_t port)
377 return ring_push(datagram, len, ip, port);
#define DETWS_HTTP3_PORT
UDP port the HTTP/3 (QUIC) server binds by default (used by DetWebServer::h3_cert).
#define DETWS_H3_MAX_STREAMS
Maximum concurrent request streams per HTTP/3 connection.
QUIC packet headers and packet-number coding (RFC 9000 sec 17).
HTTP/3 server glue - binds UDP to a pool of QUIC + HTTP/3 connections (RFC 9000/9114).
QUIC transport parameters (RFC 9000 sec 18) carried in the TLS quic_transport_parameters extension (R...
Shared single-producer / single-consumer byte-ring primitive.
bool det_udp_peer_addr(const struct DetUdpPeer *peer, char *ip_out, size_t ip_cap, uint16_t *port_out)
Copy a received peer's source IPv4 address (dotted-quad) and port out.
bool det_udp_listen(uint16_t port, DetUdpHandler handler, void *ctx)
Bind a UDP port and route incoming datagrams to handler.
bool det_udp_listener_sendto(uint16_t listen_port, const char *dst_ip, uint16_t dst_port, const uint8_t *data, size_t len)
Send a datagram from the listener bound on listen_port to an address.
Layer 4 (Transport) - connectionless UDP datagram service.