25#ifndef PC_QUIC_SERVER_IN_PSRAM
26#define PC_QUIC_SERVER_IN_PSRAM 0
28#ifndef PC_QUIC_SERVER_ACK_DRAM
29#define PC_QUIC_SERVER_ACK_DRAM 0
34#if defined(ARDUINO) && !PC_QUIC_SERVER_IN_PSRAM && !PC_QUIC_SERVER_ACK_DRAM
36 "ProtoCore: PC_ENABLE_HTTP3 - the pc_quic_server QuicConn+H3Conn pool + ingest ring are tens of KB. Set PC_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 PC_QUIC_SERVER_ACK_DRAM=1 to accept the internal-DRAM cost (fits a small pool on a roomy chip)."
38#if PC_QUIC_SERVER_IN_PSRAM && defined(ARDUINO)
40#if defined(EXT_RAM_BSS_ATTR)
41#define PC_QUIC_POOL_ATTR EXT_RAM_BSS_ATTR
42#elif defined(EXT_RAM_ATTR)
43#define PC_QUIC_POOL_ATTR EXT_RAM_ATTR
45#define PC_QUIC_POOL_ATTR
48#define PC_QUIC_POOL_ATTR
56 uint8_t data[PC_QUIC_MAX_DATAGRAM];
77struct QuicServerPoolCtx
79 QuicSlot pool[PC_QUIC_MAX_CONNS];
80 QuicIngest ring[PC_QUIC_INGEST_RING];
82PC_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)
109 while (src[n] && n + 1 < cap)
118bool cid_eq(
const uint8_t *a, uint8_t alen,
const uint8_t *b, uint8_t blen)
120 return alen == blen && memcmp(a, b, alen) == 0;
123void server_send(
const char *ip, uint16_t port,
const uint8_t *data,
size_t len)
130 s_quic.out_sink(s_quic.out_ctx, data, len, ip, port);
136bool ring_push(
const uint8_t *dg,
size_t len,
const char *ip, uint16_t port)
138 if (len == 0 || len > PC_QUIC_MAX_DATAGRAM)
142 size_t head = s_quic.ring_head;
143 size_t next = (head + 1) % PC_QUIC_INGEST_RING;
144 if (next == (
size_t)s_quic.ring_tail)
148 QuicIngest *e = &s_qpool.ring[head];
149 memcpy(e->data, dg, len);
150 e->len = (uint16_t)len;
151 copy_str(e->ip,
sizeof e->ip, ip);
153 s_quic.ring_head = next;
157bool ring_pop(QuicIngest *out)
159 size_t tail = s_quic.ring_tail;
160 if (tail == (
size_t)s_quic.ring_head)
164 *out = s_qpool.ring[tail];
165 s_quic.ring_tail = (tail + 1) % PC_QUIC_INGEST_RING;
170QuicSlot *slot_by_id(uint32_t
id)
172 for (uint8_t i = 0; i < PC_QUIC_MAX_CONNS; i++)
174 if (s_qpool.pool[i].used && s_qpool.pool[i].id ==
id)
176 return &s_qpool.pool[i];
182QuicSlot *alloc_slot()
184 for (uint8_t i = 0; i < PC_QUIC_MAX_CONNS; i++)
186 if (!s_qpool.pool[i].used)
188 QuicSlot *s = &s_qpool.pool[i];
189 memset(s, 0,
sizeof *s);
191 s->id = s_quic.next_id++;
192 if (s_quic.next_id == 0)
206void pc_h3_on_request(
void *app, H3Conn * , uint64_t stream_id,
const char *method,
const char *path,
207 const char *authority,
const uint8_t *body,
size_t body_len)
209 QuicSlot *s = (QuicSlot *)app;
210 if (s_quic.on_request)
212 s_quic.on_request(s_quic.app, s->id, stream_id, method, path, authority, body, body_len);
217QuicSlot *open_conn(
const QuicLongHeader *lh,
const char *ip, uint16_t port)
219 QuicSlot *s = alloc_slot();
226 memset(&tc, 0,
sizeof tc);
227 tc.cert_der = s_quic.cfg.cert_der;
228 tc.cert_len = s_quic.cfg.cert_len;
229 memcpy(tc.ed25519_seed, s_quic.cfg.ed25519_seed,
sizeof tc.ed25519_seed);
230 pc_quic_tp_defaults(&tc.params);
233 tc.params.initial_max_data = 1048576;
234 tc.params.initial_max_sd_bidi_remote = 262144;
235 tc.params.initial_max_sd_uni = 262144;
238 tc.params.max_idle_timeout = PC_QUIC_IDLE_MS;
239 s_quic.cfg.rng(tc.ephemeral_priv,
sizeof tc.ephemeral_priv);
240 s_quic.cfg.rng(tc.random,
sizeof tc.random);
242 s_quic.cfg.rng(tc.mlkem_m,
sizeof tc.mlkem_m);
245 uint8_t our_scid[PC_QUIC_SCID_LEN];
246 s_quic.cfg.rng(our_scid,
sizeof our_scid);
248 QuicConnCallbacks cb;
249 memset(&cb, 0,
sizeof cb);
250 pc_quic_conn_init(&s->qc, &tc, lh->dcid, lh->dcid_len, lh->scid, lh->scid_len, our_scid, PC_QUIC_SCID_LEN, &cb);
251 pc_h3_conn_init(&s->h3, &s->qc, pc_h3_on_request, s);
253 copy_str(s->peer_ip,
sizeof s->peer_ip, ip);
260QuicSlot *route(
const uint8_t *dg,
size_t len,
bool *is_initial, QuicLongHeader *lh_out)
270 if (pc_quic_is_long_header(dg[0]))
272 if (!pc_quic_parse_long_header(dg, len, lh_out))
276 for (uint8_t i = 0; i < PC_QUIC_MAX_CONNS; i++)
278 QuicSlot *s = &s_qpool.pool[i];
283 if (cid_eq(lh_out->dcid, lh_out->dcid_len, s->qc.scid, s->qc.scid_len) ||
284 cid_eq(lh_out->dcid, lh_out->dcid_len, s->qc.odcid, s->qc.odcid_len))
289 if (lh_out->version == QUIC_VERSION_1 && lh_out->type == QuicLongPacket::QUIC_LP_INITIAL)
296 if (len < (
size_t)1 + PC_QUIC_SCID_LEN)
300 for (uint8_t i = 0; i < PC_QUIC_MAX_CONNS; i++)
302 QuicSlot *s = &s_qpool.pool[i];
306 if (s->used && s->qc.scid_len == PC_QUIC_SCID_LEN && memcmp(dg + 1, s->qc.scid, PC_QUIC_SCID_LEN) == 0)
315void flush_and_reap(uint32_t now_ms)
317 uint8_t out[PC_QUIC_MAX_DATAGRAM];
318 for (uint8_t i = 0; i < PC_QUIC_MAX_CONNS; i++)
320 QuicSlot *s = &s_qpool.pool[i];
325 pc_quic_conn_on_timeout(&s->qc, now_ms);
327 while ((n = pc_quic_conn_send(&s->qc, out,
sizeof out)) > 0)
329 server_send(s->peer_ip, s->peer_port, out, n);
333 if (pc_quic_conn_is_closed(&s->qc) || (uint32_t)(now_ms - s->last_ms) >= PC_QUIC_IDLE_MS)
341void udp_ingest_cb(
const uint8_t *data,
size_t len,
const pc_udp_peer *peer,
void * )
349 ring_push(data, len, ip, port);
354bool pc_quic_server_begin(uint16_t port,
const QuicServerConfig *cfg, QuicServerRequestFn on_request,
void *app)
356 if (!cfg || !cfg->rng)
361 s_quic.on_request = on_request;
364 for (uint8_t i = 0; i < PC_QUIC_MAX_CONNS; i++)
366 s_qpool.pool[i].used =
false;
368 s_quic.ring_head = 0;
369 s_quic.ring_tail = 0;
371 s_quic.running =
true;
379void pc_quic_server_poll(uint32_t now_ms)
386 while (ring_pop(&ig))
388 bool is_initial =
false;
390 QuicSlot *s = route(ig.data, ig.len, &is_initial, &lh);
391 if (!s && is_initial)
393 s = open_conn(&lh, ig.ip, ig.port);
400 pc_quic_conn_recv(&s->qc, ig.data, ig.len);
402 flush_and_reap(now_ms);
405bool pc_quic_server_respond(uint32_t conn_id, uint64_t stream_id,
int status,
const char *content_type,
406 const uint8_t *body,
size_t body_len)
408 QuicSlot *s = slot_by_id(conn_id);
413 return pc_h3_conn_respond(&s->h3, stream_id, status, content_type, body, body_len);
416uint8_t pc_quic_server_active_conns(
void)
419 for (uint8_t i = 0; i < PC_QUIC_MAX_CONNS; i++)
421 if (s_qpool.pool[i].used)
429void pc_quic_server_stop(
void)
431 s_quic.running =
false;
432 for (uint8_t i = 0; i < PC_QUIC_MAX_CONNS; i++)
434 s_qpool.pool[i].used =
false;
436 s_quic.ring_head = 0;
437 s_quic.ring_tail = 0;
441void pc_quic_server_set_out_sink_cb(QuicServerOutFn fn,
void *ctx)
443 s_quic.out_sink = fn;
444 s_quic.out_ctx = ctx;
447bool pc_quic_server_ingest(
const uint8_t *datagram,
size_t len,
const char *ip, uint16_t port)
449 return ring_push(datagram, len, ip, port);
#define PC_H3_MAX_STREAMS
#define PC_HTTP3_PORT
UDP port the HTTP/3 (QUIC) server binds by default (used by PC::pc_h3_cert).
Shared single-producer / single-consumer byte-ring primitive.
bool pc_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.
bool pc_udp_listen(uint16_t port, pc_udp_handler handler, void *ctx)
Bind a UDP port and route incoming datagrams to handler.
bool pc_udp_peer_addr(const struct pc_udp_peer *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.
Layer 4 (Transport) - connectionless UDP datagram service.