cwist
Modern C web framework with libttak runtime
Loading...
Searching...
No Matches
http3.c
Go to the documentation of this file.
1
10#if defined(__APPLE__)
11#ifndef _DARWIN_C_SOURCE
12#define _DARWIN_C_SOURCE
13#endif
14#elif !defined(__FreeBSD__) && !defined(__NetBSD__) && !defined(__OpenBSD__) && \
15 !defined(__DragonFly__)
16#define _POSIX_C_SOURCE 200809L
17#endif
20#include <cwist/core/log.h>
22#include <cwist/core/seq/seq.h>
25#include <ttak/timing/timing.h>
26#include <ttak/async/sched.h>
27#include "tls_chain.h"
28#include "h3_hdr_out.h"
29
30#include <stdio.h>
31#include <stdlib.h>
32#include <string.h>
33#include <unistd.h>
34#include <errno.h>
35#include <fcntl.h>
36#include <arpa/inet.h>
37#include <netinet/in.h>
38#include <sys/socket.h>
39#include <sys/uio.h>
40#include <sys/stat.h>
41#include <poll.h>
42#include <pthread.h>
43#ifdef __linux__
44#include <sys/epoll.h>
45#endif
46#include <ctype.h>
47#include <strings.h>
48#include <inttypes.h>
49
50#include <openssl/ssl.h>
51#include <openssl/x509.h>
52#include <openssl/evp.h>
53#include <openssl/rsa.h>
54#include <openssl/bn.h>
55#include <openssl/rand.h>
56#include <openssl/hmac.h>
57
58#include <lsquic.h>
59/* WebTransport server backends (CWIST_WEBTRANSPORT=server, see Makefile):
60 * - CWIST_WT_SESSION_API: lsquic's WebTransport session API (lsquic_wt.h,
61 * LSQUIC PR #629). Sessions, peer and local uni/bidi streams, datagrams.
62 * - CWIST_WT_STOCK_LSQUIC: the client-bidi-stream support stock lsquic
63 * ships (LSQUIC_WEBTRANSPORT_SERVER_SUPPORT, draft-02 SETTINGS). Sessions,
64 * peer-opened bidi streams and datagrams; stock lsquic cannot open server
65 * streams and drops peer uni streams, so those calls return -1. */
66#ifdef CWIST_WEBTRANSPORT_SERVER
67#ifdef CWIST_HAVE_LSQUIC_WT
68#include <lsquic_wt.h>
69#define CWIST_WT_SESSION_API 1
70#elif LSQUIC_WEBTRANSPORT_SERVER_SUPPORT
71#define CWIST_WT_STOCK_LSQUIC 1
72#else
73#error "CWIST_WEBTRANSPORT_SERVER needs lsquic_wt.h or LSQUIC_WEBTRANSPORT_SERVER_SUPPORT"
74#endif
75#endif
76#include <lsxpack_header.h>
77
78/* BSD sockets do not universally provide MSG_DONTWAIT. The UDP socket is
79 * configured non-blocking before lsquic can emit packets, so no flag is
80 * needed on platforms that omit it. */
81#ifndef MSG_DONTWAIT
82#define MSG_DONTWAIT 0
83#endif
84
85/* ECN ancillary data is optional across supported socket implementations. */
86#if defined(CMSG_SPACE) && defined(CMSG_FIRSTHDR) && defined(CMSG_NXTHDR) && defined(IP_TOS)
87#define CWIST_H3_HAVE_ECN_CMSG 1
88#endif
89
90/* ------------------------------------------------------------------ */
91/* Globals */
92/* ------------------------------------------------------------------ */
93
94static pthread_mutex_t g_h3_global_mtx = PTHREAD_MUTEX_INITIALIZER;
95static int g_h3_global_ref = 0;
96
102static void h3_global_init(void) {
103 pthread_mutex_lock(&g_h3_global_mtx);
104 if (g_h3_global_ref == 0) {
106 }
107 g_h3_global_ref++;
108 pthread_mutex_unlock(&g_h3_global_mtx);
109}
110
116static void h3_global_cleanup(void) {
117 pthread_mutex_lock(&g_h3_global_mtx);
118 if (g_h3_global_ref > 0) {
119 g_h3_global_ref--;
120 if (g_h3_global_ref == 0) {
122 }
123 }
124 pthread_mutex_unlock(&g_h3_global_mtx);
125}
126
127/* ------------------------------------------------------------------ */
128/* Internal stream context */
129/* ------------------------------------------------------------------ */
130
131/* RFC 9114 Section 4.3.1 request pseudo-header tracking (bitmask) */
132#define H3_PSEUDO_METHOD 0x01u
133#define H3_PSEUDO_SCHEME 0x02u
134#define H3_PSEUDO_PATH 0x04u
135#define H3_PSEUDO_AUTHORITY 0x08u
136#define H3_PSEUDO_PROTOCOL 0x10u
137
138typedef struct h3_stream_ctx {
139 lsquic_stream_t *stream;
142 char *body;
143 size_t body_len;
144 size_t body_cap;
145 int headers_done;
146 int response_ready;
147 int write_state; /* 0=headers, 1=body, 2=done */
148 size_t body_sent;
149 uint8_t recv_xor; /* running XOR of received body bytes */
150 uint8_t send_xor; /* running XOR of sent body bytes */
151 int sequenced_data; /* X-CWIST-Sequenced-Data: 1 request body */
152 unsigned char *seq_buf;
153 size_t seq_len;
154 size_t seq_cap;
155 cwist_seq_assembler_t *body_assembler;
156 unsigned pseudo_seen; /* H3_PSEUDO_* bits seen so far */
157 int seen_regular_header; /* a non-pseudo header already arrived */
158 int is_connect; /* :method CONNECT */
159 int saw_empty_path; /* :path arrived with a zero-length value */
160 int malformed; /* RFC 9114 Section 4.3.1 violation */
161#ifdef CWIST_WEBTRANSPORT_SERVER
162 int is_webtransport; /* extended CONNECT with :protocol webtransport */
163 int wt_taken;
164#endif
165#ifdef CWIST_WT_STOCK_LSQUIC
166 int wt_dispatched; /* session handler already ran */
167 struct cwist_wt_handle *wt_session; /* CONNECT stream: accepted session */
168 struct cwist_wt_handle *wt_stream; /* stream is a peer-opened WT bidi */
169#endif
171
172/* ------------------------------------------------------------------ */
173/* Per-connection context */
174/* ------------------------------------------------------------------ */
175
176/* One queued outgoing datagram (see h3_conn_ctx_t::dgram_head below). */
177typedef struct h3_dgram_node {
178 struct h3_dgram_node *next;
179 char *data;
180 size_t len;
182
183/* lsquic_conn_set_ctx()'s payload for every connection this stream_if
184 * services. Previously every connection shared the single, unlocked
185 * cwist_http3_context pointer directly, which put datagram state
186 * (g_h3_dgram) in a process-wide global: one connection's pending
187 * datagram could be freed/overwritten by a concurrent send on a
188 * different connection (double-free/UAF), with no lock protecting any
189 * of it. This wrapper keeps the shared, read-mostly server config
190 * (h3_ctx) reachable exactly as before while giving each connection its
191 * own mutex-guarded outgoing-datagram queue. */
192typedef struct h3_conn_ctx {
193 cwist_http3_context *h3_ctx; /* shared server config, not owned here */
194 pthread_mutex_t dgram_lock;
195 h3_dgram_node_t *dgram_head;
196 h3_dgram_node_t *dgram_tail;
197#ifdef CWIST_WT_STOCK_LSQUIC
198 struct cwist_wt_handle *wt_sessions; /* accepted sessions, engine thread only */
199#endif
201
202/* Every lsquic_conn_get_ctx(conn) callsite below reads this wrapper's
203 * ->h3_ctx instead of casting the raw ctx pointer directly. */
210static inline cwist_http3_context *h3_shared_ctx(lsquic_conn_t *conn) {
212 return cc ? cc->h3_ctx : NULL;
213}
214
215#ifdef CWIST_WEBTRANSPORT_SERVER
216
217typedef enum cwist_wt_handle_kind {
221
222typedef struct cwist_wt_handle {
224 uint16_t version;
226 uint32_t flags;
227 void *ptr;
228 struct cwist_wt_handle *parent;
231#ifdef CWIST_WT_STOCK_LSQUIC
232 lsquic_conn_t *conn; /* session: owning connection */
233 uint64_t session_id; /* session: CONNECT stream id */
234 struct cwist_wt_handle *next_session; /* session: h3_conn_ctx_t list */
235#endif
237
238#define CWIST_WT_HANDLE_MAGIC UINT64_C(0x4357495354575431)
239#define CWIST_WT_HANDLE_VERSION 1
240
248 if (!ptr) return NULL;
249 cwist_wt_handle_t *handle = calloc(1, sizeof(*handle));
250 if (!handle) return NULL;
251 handle->magic = CWIST_WT_HANDLE_MAGIC;
252 handle->version = CWIST_WT_HANDLE_VERSION;
253 handle->kind = (uint16_t)kind;
254 handle->ptr = ptr;
255 return handle;
256}
257
264static void cwist_wt_handle_free(cwist_wt_handle_t *handle) {
265 if (!handle) return;
266 while (handle->first_child) {
267 cwist_wt_handle_t *next = handle->first_child;
268 handle->first_child = next->next_sibling;
269 next->magic = 0;
270 next->ptr = NULL;
271 next->parent = NULL;
272 next->first_child = NULL;
273 next->next_sibling = NULL;
274 free(next);
275 }
276 if (handle->parent) {
277 cwist_wt_handle_t **link = &handle->parent->first_child;
278 while (*link) {
279 if (*link == handle) {
280 *link = handle->next_sibling;
281 break;
282 }
283 link = &(*link)->next_sibling;
284 }
285 }
286 handle->magic = 0;
287 handle->ptr = NULL;
288 handle->parent = NULL;
289 handle->first_child = NULL;
290 handle->next_sibling = NULL;
291 free(handle);
292}
293
294#ifdef CWIST_WT_SESSION_API
300 if (!parent || !child) return;
301 child->parent = parent;
302 child->next_sibling = parent->first_child;
303 parent->first_child = child;
304}
305#endif
306
316 if (!h || h->magic != CWIST_WT_HANDLE_MAGIC || h->version != CWIST_WT_HANDLE_VERSION ||
317 h->kind != (uint16_t)kind || !h->ptr) {
318 return NULL;
319 }
320 return h;
321}
322
328static lsquic_stream_t *cwist_wt_handle_stream(void *handle) {
330 return h ? (lsquic_stream_t *)h->ptr : NULL;
331}
332
333#ifdef CWIST_WT_SESSION_API
339static lsquic_wt_session_t *cwist_wt_handle_session(void *handle) {
341 return h ? (lsquic_wt_session_t *)h->ptr : NULL;
342}
343#endif
344
345#endif /* CWIST_WEBTRANSPORT_SERVER */
346
347/* ------------------------------------------------------------------ */
348/* Header-set interface for lsquic (QPACK decode) */
349/* ------------------------------------------------------------------ */
350
351/* Browsers routinely send more than 64 HTTP/3 headers once Client Hints,
352 * security metadata and cookies are included. Keep a firm per-stream cap,
353 * but leave enough headroom that a late Cookie or :path is never discarded. */
354#define H3_MAX_HEADERS 256
355#define H3_DECODE_BUF_SIZE 131072
356#define H3_MAX_RESPONSE_HEADERS 256
357#define H3_RESPONSE_HEADER_BUF_SIZE 32768
358
359typedef struct cwist_h3_hset {
360 lsquic_stream_t *stream;
361 struct cwist_h3_hset *next; /* intrusive list of live hsets per ctx */
362 struct cwist_h3_hset **prev; /* link to the pointer that points at us */
363 cwist_http3_context *owner; /* tracking context, NULL if untracked */
364 struct lsxpack_header headers[H3_MAX_HEADERS];
365 size_t count;
366 char decode_buf[H3_DECODE_BUF_SIZE];
367 size_t decode_off;
369
377static void cwist_h3_hset_track(cwist_http3_context *ctx, cwist_h3_hset_t *hset) {
378 if (!ctx || !hset) return;
379 hset->owner = ctx;
380 hset->next = (cwist_h3_hset_t *)ctx->hsets;
381 hset->prev = (cwist_h3_hset_t **)&ctx->hsets;
382 if (hset->next) hset->next->prev = &hset->next;
383 ctx->hsets = hset;
384}
385
390static void cwist_h3_hset_untrack(cwist_h3_hset_t *hset) {
391 if (!hset || !hset->owner) return;
392 *hset->prev = hset->next;
393 if (hset->next) hset->next->prev = hset->prev;
394 hset->next = NULL;
395 hset->prev = NULL;
396 hset->owner = NULL;
397}
398
407static void cwist_h3_hset_sweep(cwist_http3_context *ctx) {
408 if (!ctx) return;
409 while (ctx->hsets) {
411 cwist_h3_hset_untrack(hset);
412 free(hset);
413 }
414}
415
423static void *cwist_h3_hsi_create(void *hsi_ctx, lsquic_stream_t *stream, int is_push_promise) {
425 cwist_h3_hset_t *hset = calloc(1, sizeof(*hset));
426 if (!hset) return NULL;
427 hset->stream = stream;
428 cwist_h3_hset_track((cwist_http3_context *)hsi_ctx, hset);
429 return hset;
430}
431
445static struct lsxpack_header *cwist_h3_hsi_prepare(void *hset_p, struct lsxpack_header *xhdr,
446 size_t req_space) {
448 if (!hset) return NULL;
449
450 if (xhdr) {
451 if (req_space > LSXPACK_MAX_STRLEN || xhdr->name_offset < 0 ||
452 (size_t)xhdr->name_offset >= sizeof(hset->decode_buf) ||
453 req_space > sizeof(hset->decode_buf) - (size_t)xhdr->name_offset) {
455 "[HTTP/3] Rejecting oversized QPACK header resize (space=%zu, offset=%d)\n",
456 req_space, (int)xhdr->name_offset);
457 return NULL;
458 }
459 xhdr->val_len = (lsxpack_strlen_t)req_space;
460 return xhdr;
461 }
462
463 if (hset->count >= H3_MAX_HEADERS) return NULL;
464 if (req_space > sizeof(hset->decode_buf) - hset->decode_off) return NULL;
465 lsxpack_header_prepare_decode(&hset->headers[hset->count], hset->decode_buf, hset->decode_off,
466 sizeof(hset->decode_buf) - hset->decode_off);
467 return &hset->headers[hset->count];
468}
469
478static int cwist_h3_hsi_process_header(void *hset_p, struct lsxpack_header *xhdr) {
480 /* A NULL header marks the end of a header block. */
481 if (!hset || !xhdr) return 0;
482
483 /* The QPACK decoder exposes the exact storage used by this completed
484 * header. */
485 size_t total = lsxpack_header_get_dec_size(xhdr);
486 if (total > sizeof(hset->decode_buf) - hset->decode_off) {
487 fprintf(stderr, "[HTTP/3] Rejecting oversized QPACK header (size=%zu, used=%zu)\n", total,
488 hset->decode_off);
489 return -1;
490 }
491 hset->decode_off += total;
492 hset->count++;
493 return 0;
494}
495
500static void cwist_h3_hsi_discard(void *hset_p) {
501 cwist_h3_hset_untrack((cwist_h3_hset_t *)hset_p);
502 free(hset_p);
503}
504
505static const struct lsquic_hset_if cwist_h3_hset_if = {
506 .hsi_create_header_set = cwist_h3_hsi_create,
507 .hsi_prepare_decode = cwist_h3_hsi_prepare,
508 .hsi_process_header = cwist_h3_hsi_process_header,
509 .hsi_discard_header_set = cwist_h3_hsi_discard,
510};
511
512#ifdef CWIST_WT_SESSION_API
513static const struct lsquic_webtransport_if cwist_h3_wt_if;
514#endif
515
516/* ------------------------------------------------------------------ */
517/* SSL context callback */
518/* ------------------------------------------------------------------ */
519
533static SSL_CTX *cwist_h3_get_ssl_ctx(void *peer_ctx, const struct sockaddr *local) {
534 (void)local;
536 if (!h3_ctx) return NULL;
538 SSL_CTX *ssl_ctx = h3_ctx->ssl_ctx;
539 if (ssl_ctx) SSL_CTX_up_ref(ssl_ctx);
540 SSL_CTX *prev = h3_ctx->ssl_ctx_handout;
541 h3_ctx->ssl_ctx_handout = ssl_ctx;
543 if (prev) SSL_CTX_free(prev);
544 return ssl_ctx;
545}
546
547/* ------------------------------------------------------------------ */
548/* Packet-out callback */
549/* ------------------------------------------------------------------ */
550
564static void h3_setup_cmsg(struct msghdr *msg, char *cbuf, size_t cbuf_sz,
565 const struct lsquic_out_spec *spec, uint16_t gso_seg) {
566 msg->msg_control = cbuf;
567 msg->msg_controllen = cbuf_sz;
568 memset(cbuf, 0, cbuf_sz);
569 size_t ctl_len = 0;
570
571#if defined(__linux__) && defined(UDP_SEGMENT)
572 if (gso_seg > 0) {
573 struct cmsghdr *cmsg = (struct cmsghdr *)(cbuf + ctl_len);
574 cmsg->cmsg_level = SOL_UDP;
575 cmsg->cmsg_type = UDP_SEGMENT;
576 cmsg->cmsg_len = CMSG_LEN(sizeof(uint16_t));
577 memcpy(CMSG_DATA(cmsg), &gso_seg, sizeof(uint16_t));
578 ctl_len += CMSG_SPACE(sizeof(uint16_t));
579 }
580#else
581 (void)gso_seg;
582#endif
583
584 if (spec->local_sa && spec->dest_sa) {
585 if (spec->dest_sa->sa_family == AF_INET && spec->local_sa->sa_family == AF_INET) {
586 struct in_addr addr = ((const struct sockaddr_in *)spec->local_sa)->sin_addr;
587 if (addr.s_addr != INADDR_ANY) {
588#if defined(__linux__) && defined(IP_PKTINFO)
589 struct cmsghdr *cmsg = (struct cmsghdr *)(cbuf + ctl_len);
590 cmsg->cmsg_level = IPPROTO_IP;
591 cmsg->cmsg_type = IP_PKTINFO;
592 cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo));
593 struct in_pktinfo info = {0};
594 info.ipi_spec_dst = addr;
595 memcpy(CMSG_DATA(cmsg), &info, sizeof(info));
596 ctl_len += CMSG_SPACE(sizeof(struct in_pktinfo));
597#elif defined(IP_SENDSRCADDR)
598 struct cmsghdr *cmsg = (struct cmsghdr *)(cbuf + ctl_len);
599 cmsg->cmsg_level = IPPROTO_IP;
600 cmsg->cmsg_type = IP_SENDSRCADDR;
601 cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_addr));
602 memcpy(CMSG_DATA(cmsg), &addr, sizeof(addr));
603 ctl_len += CMSG_SPACE(sizeof(struct in_addr));
604#endif
605 }
606 } else if (spec->dest_sa->sa_family == AF_INET6 && spec->local_sa->sa_family == AF_INET6) {
607 const struct in6_addr *addr6 =
608 &((const struct sockaddr_in6 *)spec->local_sa)->sin6_addr;
609 if (memcmp(addr6, &in6addr_any, sizeof(struct in6_addr)) != 0) {
610#if defined(IPV6_PKTINFO)
611 struct cmsghdr *cmsg = (struct cmsghdr *)(cbuf + ctl_len);
612 cmsg->cmsg_level = IPPROTO_IPV6;
613 cmsg->cmsg_type = IPV6_PKTINFO;
614 cmsg->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo));
615 struct in6_pktinfo info6 = {0};
616 info6.ipi6_addr = *addr6;
617 memcpy(CMSG_DATA(cmsg), &info6, sizeof(info6));
618 ctl_len += CMSG_SPACE(sizeof(struct in6_pktinfo));
619#endif
620 }
621 }
622 }
623
624#if defined(CWIST_H3_HAVE_ECN_CMSG)
625 if (spec->ecn && spec->dest_sa) {
626 if (spec->dest_sa->sa_family == AF_INET) {
627 struct cmsghdr *cmsg = (struct cmsghdr *)(cbuf + ctl_len);
628 cmsg->cmsg_level = IPPROTO_IP;
629 cmsg->cmsg_type = IP_TOS;
630 int tos = spec->ecn;
631 cmsg->cmsg_len = CMSG_LEN(sizeof(tos));
632 memcpy(CMSG_DATA(cmsg), &tos, sizeof(tos));
633 ctl_len += CMSG_SPACE(sizeof(tos));
634 }
635#if defined(IPV6_TCLASS)
636 else if (spec->dest_sa->sa_family == AF_INET6) {
637 struct cmsghdr *cmsg = (struct cmsghdr *)(cbuf + ctl_len);
638 cmsg->cmsg_level = IPPROTO_IPV6;
639 cmsg->cmsg_type = IPV6_TCLASS;
640 int tos = spec->ecn;
641 cmsg->cmsg_len = CMSG_LEN(sizeof(tos));
642 memcpy(CMSG_DATA(cmsg), &tos, sizeof(tos));
643 ctl_len += CMSG_SPACE(sizeof(tos));
644 }
645#endif
646 }
647#endif
648
649 msg->msg_controllen = ctl_len;
650 if (ctl_len == 0) {
651 msg->msg_control = NULL;
652 }
653}
654
662static int h3_send_one(int udp_fd, const struct lsquic_out_spec *spec) {
663 char ctrl[256];
664 struct msghdr msg = {0};
665 msg.msg_name = (void *)spec->dest_sa;
666 msg.msg_namelen = (spec->dest_sa && spec->dest_sa->sa_family == AF_INET)
667 ? sizeof(struct sockaddr_in)
668 : sizeof(struct sockaddr_in6);
669 msg.msg_iov = (struct iovec *)spec->iov;
670 msg.msg_iovlen = spec->iovlen;
671 h3_setup_cmsg(&msg, ctrl, sizeof(ctrl), spec, 0);
672 return (int)sendmsg(udp_fd, &msg, MSG_DONTWAIT);
673}
674
675#if defined(__linux__)
676#ifndef UDP_SEGMENT
677#define UDP_SEGMENT 103
678#endif
679#ifndef SOL_UDP
680#define SOL_UDP 17
681#endif
682
686static size_t h3_spec_len(const struct lsquic_out_spec *spec) {
687 size_t n = 0;
688 for (unsigned k = 0; k < spec->iovlen; k++) n += spec->iov[k].iov_len;
689 return n;
690}
691
698static bool h3_same_dest(const struct lsquic_out_spec *a, const struct lsquic_out_spec *b) {
699 if (!a->dest_sa || !b->dest_sa) return false;
700 if (a->dest_sa->sa_family != b->dest_sa->sa_family) return false;
701 if (a->local_sa != b->local_sa) {
702 if (!a->local_sa || !b->local_sa) return false;
703 if (a->local_sa->sa_family != b->local_sa->sa_family) return false;
704 if (a->local_sa->sa_family == AF_INET) {
705 const struct sockaddr_in *x = (const struct sockaddr_in *)a->local_sa;
706 const struct sockaddr_in *y = (const struct sockaddr_in *)b->local_sa;
707 if (x->sin_port != y->sin_port || x->sin_addr.s_addr != y->sin_addr.s_addr)
708 return false;
709 } else {
710 const struct sockaddr_in6 *x = (const struct sockaddr_in6 *)a->local_sa;
711 const struct sockaddr_in6 *y = (const struct sockaddr_in6 *)b->local_sa;
712 if (x->sin6_port != y->sin6_port ||
713 memcmp(&x->sin6_addr, &y->sin6_addr, sizeof(x->sin6_addr)) != 0)
714 return false;
715 }
716 }
717 if (a->dest_sa->sa_family == AF_INET) {
718 const struct sockaddr_in *x = (const struct sockaddr_in *)a->dest_sa;
719 const struct sockaddr_in *y = (const struct sockaddr_in *)b->dest_sa;
720 return x->sin_port == y->sin_port && x->sin_addr.s_addr == y->sin_addr.s_addr;
721 }
722 const struct sockaddr_in6 *x = (const struct sockaddr_in6 *)a->dest_sa;
723 const struct sockaddr_in6 *y = (const struct sockaddr_in6 *)b->dest_sa;
724 return x->sin6_port == y->sin6_port &&
725 memcmp(&x->sin6_addr, &y->sin6_addr, sizeof(x->sin6_addr)) == 0;
726}
727#endif
728
729#if defined(__linux__)
730/* Whether UDP GSO has been disabled at runtime. Starts at -1 (unknown),
731 * set to 1 if CWIST_H3_NO_GSO=1 env or if GSO sendmsg fails with a
732 * hard error (ENOPROTOOPT, EIO, EMSGSIZE on a coalesced send).
733 * 0 means GSO is confirmed working. */
734static int h3_gso_state = -1; /* -1 = unknown, 0 = enabled, 1 = disabled */
735
748static int h3_sendmmsg_batch(int udp_fd, const struct lsquic_out_spec *specs, unsigned i,
749 unsigned n_specs) {
750 struct mmsghdr msgs[64];
751 char ctrl_bufs[64][256];
752
753 while (i < n_specs) {
754 unsigned batch = n_specs - i;
755 if (batch > 64) batch = 64;
756
757 for (unsigned k = 0; k < batch; k++) {
758 const struct lsquic_out_spec *spec = &specs[i + k];
759 struct msghdr *msg = &msgs[k].msg_hdr;
760 memset(msg, 0, sizeof(*msg));
761 msg->msg_name = (void *)spec->dest_sa;
762 msg->msg_namelen = (spec->dest_sa && spec->dest_sa->sa_family == AF_INET)
763 ? sizeof(struct sockaddr_in)
764 : sizeof(struct sockaddr_in6);
765 msg->msg_iov = (struct iovec *)spec->iov;
766 msg->msg_iovlen = spec->iovlen;
767 h3_setup_cmsg(msg, ctrl_bufs[k], sizeof(ctrl_bufs[k]), spec, 0);
768 msgs[k].msg_len = 0;
769 }
770
771 int res = sendmmsg(udp_fd, msgs, batch, MSG_DONTWAIT);
772 if (res > 0) {
773 i += (unsigned)res;
774 if ((unsigned)res < batch) break;
775 continue;
776 }
777 if (res < 0) {
778 if (errno == EAGAIN || errno == EWOULDBLOCK) break;
779 if (i > 0) return (int)i;
780 int nw = h3_send_one(udp_fd, &specs[i]);
781 if (nw >= 0) {
782 i++;
783 continue;
784 }
785 if (errno == EAGAIN || errno == EWOULDBLOCK) break;
786 return -1;
787 }
788 break;
789 }
790 return (int)i;
791}
792#endif
793
809static int cwist_h3_packets_out(void *ctx, const struct lsquic_out_spec *specs, unsigned n_specs) {
810 int udp_fd = *(int *)ctx;
811#if defined(__linux__)
812 /* One-time GSO probe: check env var on first call. */
813 if (__builtin_expect(h3_gso_state < 0, 0)) {
814 const char *no_gso = getenv("CWIST_H3_NO_GSO");
815 h3_gso_state = (no_gso && no_gso[0] == '1') ? 1 : 0;
816 }
817
818 if (!h3_gso_state) {
819 /* GSO fast path: coalesce runs of equal-size datagrams to the same
820 * peer into one sendmsg with UDP_SEGMENT. */
821 unsigned i = 0;
822 while (i < n_specs) {
823 size_t seg = h3_spec_len(&specs[i]);
824 unsigned run = 1;
825 unsigned niov = specs[i].iovlen;
826 if (seg > 0 && seg <= 65535) {
827 /* Total payload must fit in one UDP datagram (65535 bytes);
828 * 65536/seg could allow an exactly-64KiB super-packet whose
829 * sendmsg fails with EMSGSIZE and would disable GSO. */
830 unsigned max_segs = (unsigned)(65535 / seg);
831 if (max_segs > 48) max_segs = 48;
832 while (i + run < n_specs && run < max_segs && specs[i + run].ecn == specs[i].ecn &&
833 h3_same_dest(&specs[i], &specs[i + run]) &&
834 h3_spec_len(&specs[i + run]) == seg && niov + specs[i + run].iovlen <= 128) {
835 niov += specs[i + run].iovlen;
836 run++;
837 }
838 }
839 if (run >= 2) {
840 struct iovec iov[128];
841 unsigned n = 0;
842 for (unsigned k = 0; k < run; k++)
843 for (unsigned m = 0; m < specs[i + k].iovlen; m++)
844 iov[n++] = specs[i + k].iov[m];
845
846 char ctrl[512];
847 struct msghdr msg = {0};
848 msg.msg_name = (void *)specs[i].dest_sa;
849 msg.msg_namelen = (specs[i].dest_sa && specs[i].dest_sa->sa_family == AF_INET)
850 ? sizeof(struct sockaddr_in)
851 : sizeof(struct sockaddr_in6);
852 msg.msg_iov = iov;
853 msg.msg_iovlen = n;
854 h3_setup_cmsg(&msg, ctrl, sizeof(ctrl), &specs[i], (uint16_t)seg);
855
856 ssize_t nw = sendmsg(udp_fd, &msg, MSG_DONTWAIT);
857 if (nw >= 0) {
858 i += run;
859 continue;
860 }
861 if (errno == EAGAIN || errno == EWOULDBLOCK) break;
862 /* GSO rejected: permanently disable and fall back to sendmmsg. */
863 h3_gso_state = 1;
864 return h3_sendmmsg_batch(udp_fd, specs, i, n_specs);
865 }
866 /* Single packet: send directly. */
867 int nw = h3_send_one(udp_fd, &specs[i]);
868 if (nw < 0) {
869 if (errno == EAGAIN || errno == EWOULDBLOCK) return (int)i;
871 errno == EMSGSIZE) {
872 i++;
873 continue;
874 }
875 return (i > 0) ? (int)i : -1;
876 }
877 i++;
878 }
879 return (int)i;
880 }
881
882 /* sendmmsg fallback path (GSO disabled). */
883 return h3_sendmmsg_batch(udp_fd, specs, 0, n_specs);
884#else
885 unsigned i = 0;
886 for (; i < n_specs; ++i) {
887 int nw = h3_send_one(udp_fd, &specs[i]);
888 if (nw < 0) {
889 if (errno == EAGAIN || errno == EWOULDBLOCK) return (int)i;
891 errno == EMSGSIZE)
892 continue;
893 return (i > 0) ? (int)i : -1;
894 }
895 }
896 return (int)i;
897#endif
898}
899
900/* ------------------------------------------------------------------ */
901/* ALPN selection callback (BoringSSL) */
902/* ------------------------------------------------------------------ */
903
911static int cwist_h3_alpn_select_cb(SSL *ssl, const uint8_t **out, uint8_t *outlen,
912 const uint8_t *in, unsigned inlen, void *arg) {
913 (void)ssl;
914 (void)arg;
915 static const char *const protos[] = {"h3", "h3-29"};
916 for (size_t p = 0; p < sizeof(protos) / sizeof(protos[0]); ++p) {
917 const char *proto = protos[p];
918 size_t plen = strlen(proto);
919 const uint8_t *ptr = in;
920 const uint8_t *end = in + inlen;
921 while (ptr < end) {
922 uint8_t len = *ptr++;
923 if (ptr + len > end) break;
924 if (len == plen && memcmp(ptr, proto, plen) == 0) {
925 *out = ptr;
926 *outlen = (uint8_t)plen;
927 return SSL_TLSEXT_ERR_OK;
928 }
929 ptr += len;
930 }
931 }
933}
934
935/* ------------------------------------------------------------------ */
936/* Stream callbacks */
937/* ------------------------------------------------------------------ */
938
943static int cwist_h3_log_stderr(void *ctx, const char *buf, size_t len) {
944 (void)ctx;
945 return (int)fwrite(buf, 1, len, stderr);
946}
947
958static lsquic_conn_ctx_t *cwist_h3_on_new_conn(void *stream_if_ctx, lsquic_conn_t *conn) {
960 h3_conn_ctx_t *cc = (h3_conn_ctx_t *)calloc(1, sizeof(*cc));
961 if (!cc) {
962 /* OOM at connection-accept time: proceed without a ctx rather than
963 * risk type confusion by storing a bare cwist_http3_context* here
964 * on some paths and a h3_conn_ctx_t* on others. Every consumer
965 * below already tolerates lsquic_conn_get_ctx() returning NULL
966 * (h3_shared_ctx(), the datagram callbacks); this connection just
967 * won't get request dispatch or datagram support. */
968 CWIST_LOG_ERROR("[HTTP/3] OOM allocating per-connection context");
969 return NULL;
970 }
971 cc->h3_ctx = h3_ctx;
972 pthread_mutex_init(&cc->dgram_lock, NULL);
974 return (lsquic_conn_ctx_t *)cc;
975}
976
985static void cwist_h3_on_conn_closed(lsquic_conn_t *conn) {
986 if (!conn) return;
987 char errbuf[256] = {0};
988 enum LSQUIC_CONN_STATUS status = lsquic_conn_status(conn, errbuf, sizeof(errbuf));
989 fprintf(stderr, "[HTTP/3] Conn close status=%d msg=%s\n", (int)status,
990 errbuf[0] ? errbuf : "(none)");
991 struct lsquic_conn_info info;
992 if (lsquic_conn_get_info(conn, &info) == 0) {
994 "[HTTP/3] Conn closed rtt=%u rttvar=%u "
995 "pkts_sent=%" PRIu64 " pkts_lost=%" PRIu64 " "
996 "pkts_retx=%" PRIu64 " cwnd=%u\n",
997 info.lci_rtt, info.lci_rttvar, info.lci_pkts_sent, info.lci_pkts_lost,
998 info.lci_pkts_retx, info.lci_cwnd);
999 }
1000
1002 if (cc) {
1003 pthread_mutex_lock(&cc->dgram_lock);
1004 h3_dgram_node_t *n = cc->dgram_head;
1005 cc->dgram_head = cc->dgram_tail = NULL;
1006 pthread_mutex_unlock(&cc->dgram_lock);
1007 while (n) {
1008 h3_dgram_node_t *next = n->next;
1009 free(n->data);
1010 free(n);
1011 n = next;
1012 }
1013 pthread_mutex_destroy(&cc->dgram_lock);
1014 free(cc);
1015 }
1016}
1017
1018static lsquic_stream_ctx_t *cwist_h3_on_new_stream(void *stream_if_ctx, lsquic_stream_t *stream) {
1020 h3_stream_ctx_t *st = calloc(1, sizeof(*st));
1021 if (!st) return NULL;
1022 st->stream = stream;
1024 if (!st->req) {
1025 free(st);
1026 return NULL;
1027 }
1028 cwist_sstring_assign(st->req->version, "HTTP/3");
1029 st->req->private_data = stream;
1030#ifdef CWIST_WEBTRANSPORT_SERVER
1031 st->is_webtransport = 0;
1032#endif
1033
1034 /* Handle server-pushed streams */
1035 if (h3_ctx && h3_ctx->push_enabled && lsquic_stream_is_pushed(stream)) {
1036 /* Pushed streams have their request headers already included
1037 * in the PUSH_PROMISE. We process them the same way. */
1038 lsquic_stream_wantread(stream, 1);
1039 return (lsquic_stream_ctx_t *)st;
1040 }
1041
1042 /* RFC 9218 Section 6: a server MUST NOT send a PRIORITY_UPDATE frame for
1043 * a request stream. lsquic_stream_set_priority() emits exactly that
1044 * frame when ext priorities are negotiated, and strict clients (Firefox/
1045 * neqo) close the connection with H3_FRAME_UNEXPECTED. Keep the default
1046 * scheduling priority instead of touching the stream. */
1047 lsquic_stream_wantread(stream, 1);
1048
1049 return (lsquic_stream_ctx_t *)st;
1050}
1051
1058static void h3_parse_path(cwist_http_request *req, const char *path) {
1059 const char *q = strchr(path, '?');
1060 if (q) {
1061 cwist_sstring_assign_len(req->path, (char *)path, (size_t)(q - path));
1062 cwist_sstring_assign(req->query, (char *)(q + 1));
1063 if (req->query_params) {
1065 } else {
1067 }
1068 if (req->query_params && req->query->size > 0) {
1070 }
1071 } else {
1072 cwist_sstring_assign(req->path, (char *)path);
1073 }
1074}
1075
1080static uint8_t h3_xor_bytes(const unsigned char *buf, size_t len) {
1081 uint8_t x = 0;
1082 for (size_t i = 0; i < len; i++) x ^= buf[i];
1083 return x;
1084}
1085
1090static int h3_header_name_char_is_valid(unsigned char c) {
1091 return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '!' ||
1092 c == '#' || c == '$' || c == '%' || c == '&' || c == '\'' || c == '*' || c == '+' ||
1093 c == '-' || c == '.' || c == '^' || c == '_' || c == '`' || c == '|' || c == '~';
1094}
1095
1105int cwist_http3_normalize_response_header_name(const char *name, char *out, size_t out_len) {
1106 if (!name || !out || out_len == 0) return -1;
1107
1108 size_t len = strlen(name);
1109 if (len == 0 || len >= out_len || name[0] == ':') return -1;
1110
1111 for (size_t i = 0; i < len; i++) {
1112 unsigned char c = (unsigned char)name[i];
1113 if (!h3_header_name_char_is_valid(c)) return -1;
1114 out[i] = (char)tolower(c);
1115 }
1116 out[len] = '\0';
1117 return 0;
1118}
1119
1128 if (!value) return 0;
1129 for (const unsigned char *p = (const unsigned char *)value; *p; p++) {
1130 /* RFC 9113/9114 field-value grammar: no CTLs (incl. CR/LF) or DEL;
1131 * clients reject such responses as malformed. */
1132 if (*p < 0x20 || *p == 0x7f) return 0;
1133 }
1134 return 1;
1135}
1136
1141static void h3_json_error(cwist_http_response *res, int status, const char *body_json) {
1142 res->status_code = status;
1143 if (res->body) cwist_sstring_assign(res->body, body_json);
1144 cwist_http_header_add(&res->headers, "Content-Type", "application/json");
1145}
1146
1156static void h3_apply_header(cwist_http_request *req, const char *name, const char *value) {
1157 if (strcmp(name, ":method") == 0) {
1158 req->method = cwist_http_string_to_method(value);
1159 } else if (strcmp(name, ":path") == 0) {
1160 h3_parse_path(req, value);
1161 } else if (strcmp(name, ":authority") == 0 || strcmp(name, "host") == 0) {
1162 if (!cwist_http_header_get(req->headers, "host")) {
1163 cwist_http_header_add(&req->headers, "host", value);
1164 }
1165 } else if (strcmp(name, ":scheme") == 0) {
1166 /* RFC 9114: silently ignore pseudo-headers we don't need to expose */
1167 } else if (strcmp(name, "content-length") == 0) {
1168 char *endptr = NULL;
1169 unsigned long long cl = strtoull(value, &endptr, 10);
1170 if (endptr && *endptr == '\0') {
1171 req->content_length = (size_t)cl;
1172 }
1173 cwist_http_header_add(&req->headers, name, value);
1174 } else if (strcmp(name, "content-type") == 0) {
1175 cwist_http_header_add(&req->headers, name, value);
1176 } else if (strcmp(name, "cookie") == 0) {
1177 cwist_http_header_add(&req->headers, name, value);
1178 } else if (strcmp(name, "authorization") == 0) {
1179 cwist_http_header_add(&req->headers, name, value);
1180 } else if (strcmp(name, "accept") == 0) {
1181 cwist_http_header_add(&req->headers, name, value);
1182 } else if (strcmp(name, "user-agent") == 0) {
1183 cwist_http_header_add(&req->headers, name, value);
1184 } else if (strcmp(name, "accept-encoding") == 0) {
1185 cwist_http_header_add(&req->headers, name, value);
1186 } else if (strcmp(name, "accept-language") == 0) {
1187 cwist_http_header_add(&req->headers, name, value);
1188 } else if (strcmp(name, "referer") == 0 || strcmp(name, "referrer") == 0) {
1189 cwist_http_header_add(&req->headers, "referer", value);
1190 } else if (strcmp(name, "origin") == 0) {
1191 cwist_http_header_add(&req->headers, name, value);
1192 } else if (strcmp(name, "x-requested-with") == 0) {
1193 cwist_http_header_add(&req->headers, name, value);
1194 } else if (strcmp(name, "priority") == 0) {
1195 /* RFC 9218 Extensible Priorities: u=urgency, i=incremental.
1196 * Record the header only: calling lsquic_stream_set_priority() here
1197 * would emit a PRIORITY_UPDATE frame for a request stream, which a
1198 * server MUST NOT send (strict clients abort with
1199 * H3_FRAME_UNEXPECTED). */
1200 cwist_http_header_add(&req->headers, name, value);
1201 } else if (name[0] != ':') {
1202 /* Any other non-pseudo header */
1203 cwist_http_header_add(&req->headers, name, value);
1204 }
1205}
1206
1219static int h3_seq_append_and_feed(h3_stream_ctx_t *st, const unsigned char *data, size_t len) {
1220 if (len > SIZE_MAX - st->seq_len) return -1;
1221 size_t need = st->seq_len + len;
1222 if (need > st->seq_cap) {
1223 size_t cap = st->seq_cap ? st->seq_cap : 4096;
1224 while (cap < need) {
1225 if (cap > (CWIST_SEQ_HEADER_SIZE + UINT16_MAX) / 2) {
1227 break;
1228 }
1229 cap *= 2;
1230 }
1232 unsigned char *tmp = realloc(st->seq_buf, cap);
1233 if (!tmp) return -1;
1234 st->seq_buf = tmp;
1235 st->seq_cap = cap;
1236 }
1237 memcpy(st->seq_buf + st->seq_len, data, len);
1238 st->seq_len += len;
1239
1240 while (st->seq_len >= CWIST_SEQ_HEADER_SIZE) {
1241 size_t payload_len = ((size_t)st->seq_buf[4] << 8) | st->seq_buf[5];
1242 size_t chunk_len = CWIST_SEQ_HEADER_SIZE + payload_len;
1243 if (payload_len == 0 || st->seq_len < chunk_len) break;
1245 if (!cwist_seq_chunk_parse(st->seq_buf, chunk_len, &chunk)) return -1;
1246 if (!st->body_assembler) {
1248 if (!st->body_assembler) return -1;
1249 }
1250 if (!cwist_seq_assembler_feed(st->body_assembler, &chunk)) return -1;
1251 memmove(st->seq_buf, st->seq_buf + chunk_len, st->seq_len - chunk_len);
1252 st->seq_len -= chunk_len;
1253 }
1254 return 0;
1255}
1256
1264static void h3_reject_malformed_request(lsquic_stream_t *stream, h3_stream_ctx_t *st) {
1265 st->malformed = 1;
1266 st->headers_done = 1;
1267 if (!st->res) st->res = cwist_http_response_create();
1268 if (st->res)
1269 h3_json_error(st->res, CWIST_HTTP_BAD_REQUEST, "{\"error\":\"malformed request\"}");
1270 st->response_ready = 1;
1271 lsquic_stream_wantread(stream, 0);
1272 lsquic_stream_shutdown(stream, 0);
1273 lsquic_stream_wantwrite(stream, 1);
1274}
1275
1288static bool h3_process_stream_headers(lsquic_stream_t *stream, h3_stream_ctx_t *st) {
1289 if (st->headers_done) return true;
1290 void *hset = lsquic_stream_get_hset(stream);
1291 if (!hset) return false;
1293 /* lsxpack exposes counted slices, not C strings, so each header needs a
1294 * NUL-terminated scratch copy to hand to strcmp()/h3_apply_header().
1295 * These used to be a malloc(name_len+1)/malloc(value_len+1) pair freed
1296 * at the bottom of every loop iteration - up to two heap round trips
1297 * per header, tens of them on a real request. The bounds below
1298 * (name_len <= 1024, value_len <= H3_DECODE_BUF_SIZE - 1) are already
1299 * enforced before anything touches these buffers, so a single
1300 * reusable pair sized to those same bounds, declared once outside the
1301 * loop, replaces all of that: h3_apply_header() copies out of them
1302 * before the next iteration overwrites them (see below), so reuse is
1303 * safe. */
1304 char name_scratch[1024 + 1];
1306 for (size_t i = 0; i < hs->count; ++i) {
1307 const struct lsxpack_header *xhdr = &hs->headers[i];
1308 const char *raw_name = lsxpack_header_get_name(xhdr);
1310 size_t name_len = xhdr->name_len;
1311 size_t value_len = xhdr->val_len;
1312 if (raw_name && raw_value && name_len > 0 && name_len <= 1024 &&
1314 char *name = name_scratch;
1315 char *value = value_scratch;
1316 memcpy(name, raw_name, name_len);
1317 name[name_len] = '\0';
1318 memcpy(value, raw_value, value_len);
1319 value[value_len] = '\0';
1320 if (name[0] == ':') {
1321 /* RFC 9114 Section 4.3.1: pseudo-headers precede regular
1322 * headers, appear at most once, and only the defined set is
1323 * valid in requests. */
1324 unsigned bit = 0;
1325 if (strcmp(name, ":method") == 0)
1327 else if (strcmp(name, ":scheme") == 0)
1329 else if (strcmp(name, ":path") == 0)
1331 else if (strcmp(name, ":authority") == 0)
1333 else if (strcmp(name, ":protocol") == 0)
1335 if (bit == 0 || st->seen_regular_header || (st->pseudo_seen & bit)) {
1336 h3_reject_malformed_request(stream, st);
1337 return false;
1338 }
1339 st->pseudo_seen |= bit;
1340 if (bit == H3_PSEUDO_METHOD && strcmp(value, "CONNECT") == 0) {
1341 st->is_connect = 1;
1342 }
1343 if (bit == H3_PSEUDO_PATH && value_len == 0) {
1344 st->saw_empty_path = 1;
1345 }
1346 } else {
1347 st->seen_regular_header = 1;
1348 }
1349 h3_apply_header(st->req, name, value);
1350#ifdef CWIST_WEBTRANSPORT_SERVER
1351 /* :protocol may precede :method; is_connect is checked once the
1352 * whole block is in (below). */
1353 if (strcmp(name, ":protocol") == 0 && strcmp(value, "webtransport") == 0) {
1354 st->is_webtransport = 1;
1355 }
1356#endif
1357 }
1358 }
1359 /* RFC 9114 Section 4.3.1 completeness rules, evaluated once the whole
1360 * header block has been seen. */
1361 if (st->is_connect && !(st->pseudo_seen & H3_PSEUDO_PROTOCOL)) {
1362 /* Plain CONNECT: :scheme and :path MUST be omitted, :authority is
1363 * required. */
1364 if ((st->pseudo_seen & (H3_PSEUDO_SCHEME | H3_PSEUDO_PATH)) ||
1365 !(st->pseudo_seen & H3_PSEUDO_AUTHORITY)) {
1366 h3_reject_malformed_request(stream, st);
1367 return false;
1368 }
1369 } else if (st->is_connect) {
1370 /* Extended CONNECT (RFC 9220, e.g. WebTransport): :scheme, :path and
1371 * :authority are all required. */
1372 if (!(st->pseudo_seen & H3_PSEUDO_SCHEME) || !(st->pseudo_seen & H3_PSEUDO_PATH) ||
1373 !(st->pseudo_seen & H3_PSEUDO_AUTHORITY)) {
1374 h3_reject_malformed_request(stream, st);
1375 return false;
1376 }
1377 } else {
1378 /* Normal request: :method, :scheme and :path are mandatory,
1379 * :authority (or an equivalent Host header) is required for https,
1380 * and :protocol is only legal on extended CONNECT. */
1381 if (!(st->pseudo_seen & H3_PSEUDO_METHOD) || !(st->pseudo_seen & H3_PSEUDO_SCHEME) ||
1382 !(st->pseudo_seen & H3_PSEUDO_PATH) || (st->pseudo_seen & H3_PSEUDO_PROTOCOL) ||
1383 (!(st->pseudo_seen & H3_PSEUDO_AUTHORITY) &&
1384 !cwist_http_header_get(st->req->headers, "host"))) {
1385 h3_reject_malformed_request(stream, st);
1386 return false;
1387 }
1388 /* An empty :path is only legal for OPTIONS (asterisk-form). */
1389 if (st->saw_empty_path && (!st->req || st->req->method != CWIST_HTTP_OPTIONS)) {
1390 h3_reject_malformed_request(stream, st);
1391 return false;
1392 }
1393 }
1394#ifdef CWIST_WEBTRANSPORT_SERVER
1395 if (!st->is_connect) st->is_webtransport = 0;
1396#endif
1397 st->headers_done = 1;
1398 char *seq_header = cwist_http_header_get(st->req->headers, "x-cwist-sequenced-data");
1399 st->sequenced_data =
1400 seq_header && (strcmp(seq_header, "1") == 0 || strcasecmp(seq_header, "true") == 0);
1401 return true;
1402}
1403
1408static bool h3_method_is_idempotent(cwist_http_method_t method) {
1409 switch (method) {
1410 case CWIST_HTTP_GET:
1411 case CWIST_HTTP_HEAD:
1412 case CWIST_HTTP_PUT:
1413 case CWIST_HTTP_DELETE:
1414 case CWIST_HTTP_OPTIONS: return true;
1415 default: return false;
1416 }
1417}
1418
1419#ifdef CWIST_WT_STOCK_LSQUIC
1420/* ------------------------------------------------------------------ */
1421/* WebTransport on stock lsquic */
1422/* ------------------------------------------------------------------ */
1423
1424/* Stock lsquic (LSQUIC_WEBTRANSPORT_SERVER_SUPPORT) advertises the draft-02
1425 * SETTINGS and turns a peer bidi stream that opens with the 0x41 signal into
1426 * a raw byte stream tagged with its session id; the rest lives here. The
1427 * session is the extended-CONNECT stream, kept open after a 2xx response.
1428 * Datagrams carry the session's quarter stream id (RFC 9297 Section 2.1).
1429 * Closing writes a CLOSE_WEBTRANSPORT_SESSION capsule and FIN on the CONNECT
1430 * stream. Everything runs on the engine thread, so the per-connection
1431 * session list needs no lock. */
1432
1433#define H3_WT_CLOSE_SESSION_CAPSULE UINT64_C(0x2843)
1434#define H3_WT_MAX_CLOSE_REASON 1024
1435/* lsquic (this baseline) has no "largest DATAGRAM payload" query: assume the
1436 * 1200-byte QUIC minimum minus short-header, AEAD and frame overhead. */
1437#define H3_WT_DATAGRAM_BUDGET (1200 - 64)
1438
1440static size_t h3_wt_varint_len(uint64_t v) {
1441 return v < 64 ? 1 : v < 16384 ? 2 : v < 1073741824 ? 4 : 8;
1442}
1443
1448static size_t h3_wt_varint_write(unsigned char *p, uint64_t v) {
1449 size_t n = h3_wt_varint_len(v);
1450 for (size_t i = 0; i < n; i++) p[n - 1 - i] = (unsigned char)(v >> (8 * i));
1451 p[0] |= (unsigned char)((n == 1 ? 0 : n == 2 ? 1 : n == 4 ? 2 : 3) << 6);
1452 return n;
1453}
1454
1459static size_t h3_wt_varint_read(const unsigned char *p, size_t len, uint64_t *out) {
1460 if (len == 0) return 0;
1461 size_t n = (size_t)1 << (p[0] >> 6);
1462 if (len < n) return 0;
1463 uint64_t v = p[0] & 0x3f;
1464 for (size_t i = 1; i < n; i++) v = (v << 8) | p[i];
1465 *out = v;
1466 return n;
1467}
1468
1472 for (cwist_wt_handle_t *h = cc ? cc->wt_sessions : NULL; h; h = h->next_session) {
1473 if (h->session_id == id) return h;
1474 }
1475 return NULL;
1476}
1477
1479static void h3_wt_unlink_session(cwist_wt_handle_t *session) {
1481 if (!cc) return;
1482 for (cwist_wt_handle_t **link = &cc->wt_sessions; *link; link = &(*link)->next_session) {
1483 if (*link == session) {
1484 *link = session->next_session;
1485 break;
1486 }
1487 }
1488 session->next_session = NULL;
1489}
1490
1499 cwist_http3_context *h3_ctx) {
1500 st->wt_dispatched = 1;
1501 lsquic_conn_t *conn = lsquic_stream_conn(stream);
1503 if (!st->res) st->res = cwist_http_response_create();
1504 cwist_wt_handle_t *session =
1506 if (!session) {
1507 lsquic_stream_close(stream);
1508 return;
1509 }
1510 session->conn = conn;
1511 session->session_id = lsquic_stream_id(stream);
1512 session->next_session = cc->wt_sessions;
1513 cc->wt_sessions = session;
1514
1515 h3_ctx->wt_handler(st->req, st->res, session);
1516
1517 if (st->res->status_code >= 200 && st->res->status_code < 300) {
1518 st->wt_session = session;
1519 if (st->res->body && st->res->body->size > 0) {
1520 CWIST_LOG_WARN("[HTTP/3] WebTransport session response body dropped");
1521 cwist_sstring_assign(st->res->body, "");
1522 }
1523 cwist_http_header_add(&st->res->headers, "sec-webtransport-http3-draft", "draft02");
1524 st->response_ready = 1;
1525 lsquic_stream_wantwrite(stream, 1);
1526 return;
1527 }
1528 h3_wt_unlink_session(session);
1529 cwist_wt_handle_free(session);
1530 st->response_ready = 1;
1531 lsquic_stream_wantread(stream, 0);
1532 lsquic_stream_shutdown(stream, 0);
1533 lsquic_stream_wantwrite(stream, 1);
1534}
1535
1543static void h3_wt_session_on_read(lsquic_stream_t *stream) {
1544 unsigned char buf[1024];
1545 ssize_t n;
1546 while ((n = lsquic_stream_read(stream, buf, sizeof(buf))) > 0) {
1547 }
1548 if (n == 0 || (errno != EAGAIN && errno != EWOULDBLOCK)) {
1549 lsquic_stream_close(stream);
1550 }
1551}
1552
1565 if (st->wt_stream) return true;
1566 if (!lsquic_stream_is_webtransport_client_bidi_stream(stream)) return false;
1568 cwist_wt_handle_t *session =
1570 cwist_wt_handle_t *handle =
1571 session ? cwist_wt_handle_new(CWIST_WT_HANDLE_STREAM, stream) : NULL;
1572 if (!handle) {
1573 lsquic_stream_close(stream);
1574 return true;
1575 }
1576 st->wt_stream = handle;
1577 return true;
1578}
1579
1586 cwist_http3_context *ctx = h3_shared_ctx(lsquic_stream_conn(stream));
1587 if (ctx && ctx->wt_new_stream_handler) {
1588 ctx->wt_new_stream_handler(st->wt_stream, ctx->wt_new_stream_ctx);
1589 return;
1590 }
1591 unsigned char buf[4096];
1592 ssize_t n;
1593 while ((n = lsquic_stream_read(stream, buf, sizeof(buf))) > 0) {
1594 }
1595 if (n == 0 || (errno != EAGAIN && errno != EWOULDBLOCK)) {
1596 lsquic_stream_close(stream);
1597 }
1598}
1599
1605static bool h3_wt_datagram_payload(lsquic_conn_t *conn, const void *buf, size_t len,
1606 const void **payload, size_t *payload_len) {
1608 if (!cc || !cc->wt_sessions) return false;
1609 uint64_t quarter = 0;
1610 size_t n = h3_wt_varint_read((const unsigned char *)buf, len, &quarter);
1611 if (n == 0 || quarter > (UINT64_MAX >> 2) || !h3_wt_find_session(conn, quarter << 2)) {
1612 return false;
1613 }
1614 *payload = (const unsigned char *)buf + n;
1615 *payload_len = len - n;
1616 return true;
1617}
1618#endif /* CWIST_WT_STOCK_LSQUIC */
1619
1631static void cwist_h3_on_read(lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h) {
1633 if (!st) return;
1634
1635 unsigned char buf[8192];
1636 ssize_t nread;
1637
1638#ifdef CWIST_WT_STOCK_LSQUIC
1639 if (h3_wt_adopt_stream(stream, st)) {
1640 if (st->wt_stream) h3_wt_stream_on_read(stream, st);
1641 return;
1642 }
1643 if (st->wt_session) {
1644 h3_wt_session_on_read(stream);
1645 return;
1646 }
1647#endif
1648 h3_process_stream_headers(stream, st);
1649 if (st->malformed) return;
1650#ifdef CWIST_WT_STOCK_LSQUIC
1651 /* An extended CONNECT never sends FIN, so the session is opened as soon
1652 * as its header block is in rather than at end of stream. */
1653 if (st->headers_done && st->is_webtransport && !st->wt_dispatched) {
1654 cwist_http3_context *h3_ctx = h3_shared_ctx(lsquic_stream_conn(stream));
1655 if (h3_ctx && h3_ctx->wt_handler) {
1656 h3_wt_open_session(stream, st, h3_ctx);
1657 return;
1658 }
1659 }
1660#endif
1661
1662 while ((nread = lsquic_stream_read(stream, buf, sizeof(buf))) > 0) {
1663 if (st->sequenced_data) {
1664 if (h3_seq_append_and_feed(st, buf, (size_t)nread) != 0) {
1665 lsquic_stream_close(stream);
1666 return;
1667 }
1668 continue;
1669 }
1670 size_t need = st->body_len + (size_t)nread;
1672 /* Body too large: answer 413 with a small JSON body instead of a
1673 * bare stream close, so the client sees an explicit status
1674 * (RFC 9110 Section 15.5.14). */
1675 if (!st->res) st->res = cwist_http_response_create();
1676 if (st->res) {
1677 h3_json_error(st->res, 413 /* Payload Too Large */,
1678 "{\"error\":\"payload too large\"}");
1679 st->response_ready = 1;
1680 lsquic_stream_wantread(stream, 0);
1681 lsquic_stream_shutdown(stream, 0);
1682 lsquic_stream_wantwrite(stream, 1);
1683 } else {
1684 lsquic_stream_close(stream);
1685 }
1686 return;
1687 }
1688 if (need > st->body_cap) {
1689 size_t new_cap = st->body_cap ? st->body_cap * 2 : 4096;
1690 while (new_cap < need) new_cap *= 2;
1691 char *tmp = realloc(st->body, new_cap);
1692 if (!tmp) {
1693 lsquic_stream_close(stream);
1694 return;
1695 }
1696 st->body = tmp;
1697 st->body_cap = new_cap;
1698 }
1699 memcpy(st->body + st->body_len, buf, (size_t)nread);
1700 st->recv_xor ^= h3_xor_bytes((const unsigned char *)buf, (size_t)nread);
1701 st->body_len += (size_t)nread;
1702 }
1703
1704 if (nread == 0) {
1705 /* End of stream (FIN received) */
1706 h3_process_stream_headers(stream, st);
1707
1708 if (st->malformed) return;
1709
1710 if (!st->headers_done) {
1711 /* Malformed request: no headers before FIN */
1713 if (st->res) {
1714 h3_json_error(st->res, CWIST_HTTP_BAD_REQUEST, "{\"error\":\"malformed request\"}");
1715 }
1716 st->response_ready = 1;
1717 lsquic_stream_wantread(stream, 0);
1718 lsquic_stream_shutdown(stream, 0);
1719 lsquic_stream_wantwrite(stream, 1);
1720 return;
1721 }
1722
1723 if (st->sequenced_data) {
1724 const uint8_t *assembled = NULL;
1725 size_t assembled_len = 0;
1726 if (st->seq_len != 0 || !st->body_assembler ||
1728 /* Do not dispatch a partial request. */
1730 if (st->res) {
1731 cwist_http_header_add(&st->res->headers, "x-cwist-retry", "1");
1732 h3_json_error(st->res, CWIST_HTTP_BAD_REQUEST,
1733 "{\"error\":\"incomplete sequenced body, retry\"}");
1734 }
1735 st->response_ready = 1;
1736 lsquic_stream_wantread(stream, 0);
1737 lsquic_stream_shutdown(stream, 0);
1738 lsquic_stream_wantwrite(stream, 1);
1739 return;
1740 }
1741 if (assembled_len == 0) {
1742 /* realloc(p, 0) may legally return NULL; do not mistake an
1743 * empty assembled body for an allocation failure (which used
1744 * to reset the stream instead of dispatching the request). */
1745 free(st->body);
1746 st->body = NULL;
1747 st->body_len = 0;
1748 st->body_cap = 0;
1749 } else {
1750 st->body = realloc(st->body, assembled_len);
1751 if (!st->body) {
1752 lsquic_stream_close(stream);
1753 return;
1754 }
1756 st->body_len = assembled_len;
1757 st->body_cap = assembled_len;
1758 }
1759 }
1760
1761 if (st->body_len > 0 && st->req && st->req->body) {
1762 cwist_sstring_assign_len(st->req->body, st->body, st->body_len);
1763 }
1764
1766 if (st->res && st->req) {
1767 cwist_http3_context *h3_ctx = h3_shared_ctx(lsquic_stream_conn(stream));
1768#ifdef CWIST_WT_SESSION_API
1769 if (st->is_webtransport && h3_ctx && h3_ctx->wt_handler) {
1770 char *host = cwist_http_header_get(st->req->headers, "host");
1771 char *origin = cwist_http_header_get(st->req->headers, "origin");
1772 struct lsquic_wt_connect_info info = {
1773 .wtci_authority = host,
1774 .wtci_path = st->req->path ? st->req->path->data : NULL,
1775 .wtci_origin = origin,
1776 .wtci_protocol = NULL,
1777 .wtci_draft = lsquic_wt_peer_draft(lsquic_stream_conn(stream)),
1778 };
1780 .wtap_status = LSQUIC_WTAP_STATUS_DEFAULT,
1781 .wtap_wt_if = &cwist_h3_wt_if,
1782 .wtap_wt_if_ctx = st,
1783 .wtap_connect_info = &info,
1784 .wtap_datagram_send_mode = LSQUIC_HTTP_DG_SEND_DEFAULT,
1785 };
1786 if (lsquic_wt_accept(stream, &params) == 0) {
1787 st->wt_taken = 1;
1788 lsquic_stream_wantread(stream, 0);
1789 lsquic_stream_shutdown(stream, 0);
1790 lsquic_stream_wantwrite(stream, 0);
1791 return;
1792 }
1793 /* Accept failed: falling through with the default 200/empty
1794 * response used to send the client a silent 0-byte body. */
1795 CWIST_LOG_WARN("[HTTP/3] WebTransport accept failed; answering 500");
1796 st->res->status_code = CWIST_HTTP_INTERNAL_ERROR;
1797 if (st->res->body && st->res->body->size == 0) {
1798 cwist_sstring_assign(st->res->body,
1799 "{\"error\":\"webtransport accept failed\"}");
1800 }
1801 if (st->res->headers) {
1802 cwist_http_header_add(&st->res->headers, "Content-Type", "application/json");
1803 }
1804 } else
1805#endif
1806 if (h3_ctx && h3_ctx->handler) {
1807 /* 0-RTT replay guard: requests delivered while the QUIC
1808 * handshake is still in progress arrived as early data.
1809 * lsquic (this baseline) exposes no
1810 * lsquic_conn_is_early_data_accepted()-style query, so the
1811 * guard uses the public lsquic_conn_status() instead. */
1812 bool is_early_data = h3_ctx->early_data_enabled && h3_ctx->early_data_guard &&
1815 if (is_early_data && st->req && !h3_method_is_idempotent(st->req->method)) {
1816 /* RFC 8470: refuse replayable non-idempotent early data
1817 * so the client retries after the handshake. Attach a
1818 * body: without one the response goes out as HEADERS+FIN
1819 * with no content-length, which some clients surface as
1820 * an empty (0-byte) reply instead of status 425. */
1821 h3_json_error(st->res, 425 /* Too Early */,
1822 "{\"error\":\"too early, retry after handshake\"}");
1823 } else {
1824 h3_ctx->handler(h3_ctx->user_ctx, st->req, st->res);
1825 }
1826 }
1827 }
1828 st->response_ready = 1;
1829 lsquic_stream_wantread(stream, 0);
1830 lsquic_stream_shutdown(stream, 0);
1831 lsquic_stream_wantwrite(stream, 1);
1832 } else if (nread < 0 && errno != EAGAIN && errno != EWOULDBLOCK) {
1833#ifdef CWIST_WT_STOCK_LSQUIC
1834 /* The read that runs lsquic's frame filter over a WebTransport
1835 * stream's signal fails; the stream is tagged by then. */
1836 if (h3_wt_adopt_stream(stream, st)) {
1837 if (st->wt_stream) h3_wt_stream_on_read(stream, st);
1838 return;
1839 }
1840#endif
1841 lsquic_stream_close(stream);
1842 }
1843}
1844
1851static bool h3_content_length_is_valid(const char *cl) {
1852 if (!cl || cl[0] < '0' || cl[0] > '9') return false;
1853 for (const char *p = cl + 1; *p; ++p) {
1854 if (*p < '0' || *p > '9') return false;
1855 }
1856 return true;
1857}
1858
1864static bool h3_status_forbids_body(int status_code) {
1865 return (status_code >= 100 && status_code < 200) || status_code == 204 || status_code == 304;
1866}
1867
1881static void cwist_h3_on_write(lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h) {
1883 if (!st || !st->response_ready) return;
1884 if (!st->res) {
1885 /* Response allocation failed upstream; reset instead of dereferencing
1886 * NULL (which used to take down every stream on the connection). */
1887 lsquic_stream_close(stream);
1888 return;
1889 }
1890
1891 if (st->write_state == 0) {
1894 h3_hdr_out hdrs = {
1895 .arr = headers_arr, .max = H3_MAX_RESPONSE_HEADERS, .buf = hbuf, .cap = sizeof(hbuf)};
1896
1897 bool bodyless = h3_status_forbids_body(st->res->status_code);
1898 bool is_head = st->req && st->req->method == CWIST_HTTP_HEAD;
1899
1900 /* Handler attached a body source that cannot produce bytes
1901 * (NULL pointer body / invalid file fd with a non-zero length).
1902 * Sending HEADERS would announce (or imply) a body we cannot
1903 * deliver; close the stream so the client sees an explicit error
1904 * instead of a silent empty 200. */
1905 if (!bodyless && !is_head &&
1906 ((st->res->is_ptr_body && !st->res->ptr_body && st->res->ptr_body_len > 0) ||
1907 (st->res->use_file_stream && st->res->file_stream_fd < 0 &&
1908 st->res->file_stream_len > 0))) {
1909 CWIST_LOG_WARN("[HTTP/3] unusable response body source; resetting stream");
1910 lsquic_stream_close(stream);
1911 return;
1912 }
1913
1914 /* :status */
1915 if (st->res->status_code < 200 || st->res->status_code > 999) {
1916 /* RFC 9110 Section 15: a final response needs a status in
1917 * 200-999 (RFC 9114 Section 4.1.2 requires three digits).
1918 * A handler that leaves the status unset, picks 1xx (an
1919 * interim response, illegal as the complete answer), or an
1920 * out-of-range value gets coerced to 500 instead of putting
1921 * a malformed :status on the wire. */
1922 CWIST_LOG_WARN("[HTTP/3] invalid status %d; coercing to 500", st->res->status_code);
1923 st->res->status_code = CWIST_HTTP_INTERNAL_ERROR;
1924 bodyless = false;
1925 if (!is_head && st->res->body && st->res->body->size == 0 && !st->res->is_ptr_body &&
1926 !st->res->use_file_stream) {
1927 cwist_sstring_assign(st->res->body, "{\"error\":\"internal server error\"}");
1928 }
1929 }
1930 int status_code = st->res->status_code;
1931 char status_str[16];
1932 snprintf(status_str, sizeof(status_str), "%d", status_code);
1933 if (status_str[0] != '\0') h3_hdr_out_add_str(&hdrs, ":status", status_str);
1934
1935 /* content-length */
1936 size_t body_len = 0;
1937 if (st->res->use_file_stream) {
1938 if (st->res->file_stream_fd >= 0) body_len = st->res->file_stream_len;
1939 } else if (st->res->is_ptr_body)
1940 body_len = st->res->ptr_body ? st->res->ptr_body_len : 0;
1941 else if (st->res->body)
1942 body_len = st->res->body->size;
1943
1944 const char *user_cl =
1945 st->res->headers ? cwist_http_header_get(st->res->headers, "content-length") : NULL;
1946
1947 if (!bodyless && (body_len > 0 || is_head) && hdrs.count < hdrs.max) {
1948 /* For HEAD, content-length describes the would-be GET body: keep
1949 * the handler-provided value when present (e.g. static file size
1950 * with an empty body), otherwise compute it like a GET. For all
1951 * other methods the computed body length is authoritative: it is
1952 * exactly what the write path below will send, so trusting a
1953 * divergent handler value would announce bytes never delivered. */
1954 char cl_str[32];
1955 const char *cl_val = NULL;
1956 if (is_head && user_cl) {
1957 if (h3_content_length_is_valid(user_cl)) {
1958 cl_val = user_cl;
1959 } else {
1960 /* Malformed handler content-length must not reach the
1961 * wire; fall back to the computed body length. */
1962 CWIST_LOG_WARN("[HTTP/3] dropping invalid HEAD content-length '%s'", user_cl);
1963 }
1964 }
1965 if (!cl_val) {
1966 snprintf(cl_str, sizeof(cl_str), "%zu", body_len);
1967 cl_val = cl_str;
1968 }
1969 if (cl_val && cl_val[0] != '\0') h3_hdr_out_add_str(&hdrs, "content-length", cl_val);
1970 }
1971
1972 /* content-type (if body is present or HEAD request) */
1973 if (st->res->headers && (body_len > 0 || is_head)) {
1974 char *ct = cwist_http_header_get(st->res->headers, "content-type");
1975 if (ct && ct[0] != '\0') h3_hdr_out_add_str(&hdrs, "content-type", ct);
1976 }
1977
1978 /* user headers (skip content-length/content-type already handled) */
1979 cwist_http_header_node *node = st->res->headers;
1980 while (node && hdrs.count < hdrs.max) {
1981 if (node->key && node->key->data && node->key->size > 0 && node->value &&
1982 node->value->data) {
1983 char h3_name[256];
1985 sizeof(h3_name)) != 0 ||
1986 h3_name[0] == '\0' || strlen(node->value->data) != node->value->size ||
1988 node = node->next;
1989 continue;
1990 }
1991
1992 if (strcmp(h3_name, "content-length") == 0 ||
1993 strcmp(h3_name, "content-type") == 0) {
1994 node = node->next;
1995 continue;
1996 }
1997 /* RFC 9114 section 4.2: connection-specific fields are malformed in
1998 * HTTP/3; te is only allowed with the value "trailers". */
1999 if (strcmp(h3_name, "connection") == 0 || strcmp(h3_name, "keep-alive") == 0 ||
2000 strcmp(h3_name, "proxy-connection") == 0 ||
2001 strcmp(h3_name, "transfer-encoding") == 0 || strcmp(h3_name, "upgrade") == 0 ||
2002 (strcmp(h3_name, "te") == 0 &&
2003 strcasecmp(node->value->data, "trailers") != 0)) {
2004 node = node->next;
2005 continue;
2006 }
2007
2008 h3_hdr_out_add(&hdrs, h3_name, strlen(h3_name), node->value->data,
2009 node->value->size);
2010 }
2011 node = node->next;
2012 }
2013
2014 bool empty_ok = false;
2015#ifdef CWIST_WT_STOCK_LSQUIC
2016 empty_ok = st->wt_session != NULL;
2017#endif
2018 if (status_code == CWIST_HTTP_OK && !bodyless && !is_head && body_len == 0 && !empty_ok) {
2019 /* Legal per RFC 9110, but a 200 with no body and no
2020 * content-length usually means a handler forgot to assign one;
2021 * make it diagnosable in the logs. */
2022 CWIST_LOG_WARN("[HTTP/3] handler produced 200 with empty body");
2023 }
2024
2025 size_t initial_body_len = 0;
2026 if (st->res->use_file_stream && st->res->file_stream_fd >= 0) {
2027 initial_body_len = st->res->file_stream_len;
2028 } else if (st->res->is_ptr_body && st->res->ptr_body) {
2029 initial_body_len = st->res->ptr_body_len;
2030 } else if (st->res->body) {
2031 initial_body_len = st->res->body->size;
2032 }
2033 int eos = (bodyless || is_head || initial_body_len == 0) ? 1 : 0;
2034#ifdef CWIST_WT_STOCK_LSQUIC
2035 if (st->wt_session) eos = 0;
2036#endif
2037
2038 lsquic_http_headers_t headers = {
2039 .count = (unsigned)hdrs.count,
2040 .headers = headers_arr,
2041 };
2042 if (lsquic_stream_send_headers(stream, &headers, eos) != 0) {
2043 if (errno == EAGAIN || errno == EWOULDBLOCK) {
2044 lsquic_stream_wantwrite(stream, 1);
2045 return;
2046 }
2047 lsquic_stream_close(stream);
2048 return;
2049 }
2050#ifdef CWIST_WT_STOCK_LSQUIC
2051 if (st->wt_session) {
2052 /* Session accepted: the stream stays open until either side
2053 * closes it (write_state 3). */
2054 st->write_state = 3;
2055 lsquic_stream_wantwrite(stream, 0);
2056 lsquic_stream_flush(stream);
2057 return;
2058 }
2059#endif
2060 if (eos) {
2061 /* NB: the eos argument of lsquic_stream_send_headers is ignored
2062 * for IETF QUIC, so an empty body must be finished with an
2063 * explicit shutdown(1) here. Relying on eos leaves every
2064 * empty-body response (204s, 304s, HEAD, error statuses) without
2065 * FIN, which browsers surface as a protocol error — timing- and
2066 * RTT-dependent because of header-block stashing under low cwnd. */
2067 lsquic_stream_shutdown(stream, 1);
2068 st->write_state = 2;
2069 lsquic_stream_wantread(stream, 1);
2070 return;
2071 }
2072 st->write_state = 1;
2073 lsquic_stream_wantwrite(stream, 1);
2074 return;
2075 }
2076
2077 if (st->write_state == 1 && st->res) {
2078 bool bodyless = h3_status_forbids_body(st->res->status_code);
2079 bool is_head = st->req && st->req->method == CWIST_HTTP_HEAD;
2080 size_t body_len = 0;
2081 const char *body_data = NULL;
2082
2083 if (st->res->use_file_stream) {
2084 /* Fill the congestion window on each callback: loop chunk writes
2085 * until the stream says EAGAIN instead of one 64 KiB chunk per
2086 * tick - but cap how many synchronous pread()s a single callback
2087 * does. This thread also drives lsquic_engine_process_conns()
2088 * and packet I/O for every other connection; with an unbounded
2089 * loop, one big response on a wide-open congestion window turns
2090 * into dozens of blocking pread()s back to back (worse under
2091 * disk contention/page faults than the SSD-idle case), starving
2092 * every other connection's ACKs/packets for that whole stretch.
2093 * Re-arming wantwrite and returning after H3_FILE_CHUNKS_PER_TICK
2094 * chunks gives the event loop a chance to service other
2095 * connections between bursts; lsquic calls this back again on
2096 * the next tick to keep going from where body_sent left off. */
2097#define H3_FILE_CHUNKS_PER_TICK 4
2098 int chunks_this_tick = 0;
2099 while (st->res->file_stream_fd >= 0 && st->body_sent < st->res->file_stream_len) {
2101 lsquic_stream_wantwrite(stream, 1);
2102 return;
2103 }
2104 static __thread char file_buf[65536];
2105 off_t offset = st->res->file_stream_offset + (off_t)st->body_sent;
2106 size_t to_read = st->res->file_stream_len - st->body_sent;
2107 if (to_read > sizeof(file_buf)) to_read = sizeof(file_buf);
2108 ssize_t nr = pread(st->res->file_stream_fd, file_buf, to_read, offset);
2109 if (nr > 0) {
2111 ssize_t nw = lsquic_stream_write(stream, file_buf, (size_t)nr);
2112 if (nw < 0) {
2113 if (errno == EAGAIN || errno == EWOULDBLOCK) {
2114 lsquic_stream_wantwrite(stream, 1);
2115 return;
2116 }
2117 lsquic_stream_close(stream);
2118 return;
2119 }
2120 if (nw == 0) {
2121 lsquic_stream_wantwrite(stream, 1);
2122 return;
2123 }
2124 st->send_xor ^= h3_xor_bytes((const unsigned char *)file_buf, (size_t)nw);
2125 st->body_sent += (size_t)nw;
2126 if (nw < nr) {
2127 /* Stream buffer full for now; come back when writable. */
2128 lsquic_stream_wantwrite(stream, 1);
2129 return;
2130 }
2131 continue;
2132 } else if (nr == 0) {
2133 /* Short EOF (file truncated after fstat): the announced
2134 * content-length can no longer be honoured, and finishing
2135 * anyway would be a malformed-message error at the
2136 * client. Reset the stream instead. */
2137 lsquic_stream_close(stream);
2138 return;
2139 } else {
2140 if (errno == EAGAIN || errno == EWOULDBLOCK) {
2141 /* Retry next tick */
2142 lsquic_stream_wantwrite(stream, 1);
2143 return;
2144 }
2145 lsquic_stream_close(stream);
2146 return;
2147 }
2148 }
2149#undef H3_FILE_CHUNKS_PER_TICK
2150 if (st->res->file_stream_fd >= 0) body_len = st->res->file_stream_len;
2151 } else if (st->res->is_ptr_body) {
2152 if (!st->res->ptr_body && st->res->ptr_body_len > 0) {
2153 /* Announced a body we cannot produce; spinning here with
2154 * wantwrite armed would hang the stream forever. */
2155 lsquic_stream_close(stream);
2156 return;
2157 }
2158 body_len = st->res->ptr_body_len;
2159 body_data = (const char *)st->res->ptr_body;
2160 } else if (st->res->body) {
2161 body_len = st->res->body->size;
2162 body_data = st->res->body->data;
2163 }
2164
2165 /* Skip payload writing entirely if body is empty, forbidden, or already fully sent */
2166 if (bodyless || is_head || body_len == 0 || st->body_sent >= body_len) {
2167 st->write_state = 2;
2168 lsquic_stream_shutdown(stream, 1);
2169 lsquic_stream_wantread(stream, 1);
2170 return;
2171 }
2172
2173 while (body_data && body_len > 0 && st->body_sent < body_len) {
2174 ssize_t n =
2175 lsquic_stream_write(stream, body_data + st->body_sent, body_len - st->body_sent);
2176 if (n > 0) {
2177 st->send_xor ^=
2178 h3_xor_bytes((const unsigned char *)(body_data + st->body_sent), (size_t)n);
2179 st->body_sent += (size_t)n;
2180 if (st->body_sent < body_len) {
2181 lsquic_stream_wantwrite(stream, 1);
2182 }
2183 continue;
2184 }
2185 if (n < 0) {
2186 if (errno == EAGAIN || errno == EWOULDBLOCK) {
2187 lsquic_stream_wantwrite(stream, 1);
2188 return;
2189 }
2190 lsquic_stream_close(stream);
2191 return;
2192 }
2193 /* n == 0: stream buffer full for now; re-arm wantwrite and come back */
2194 lsquic_stream_wantwrite(stream, 1);
2195 return;
2196 }
2197
2198 if (st->body_sent >= body_len) {
2199 st->write_state = 2;
2200 lsquic_stream_shutdown(stream, 1);
2201 lsquic_stream_wantread(stream, 1);
2202 } else {
2203 lsquic_stream_wantwrite(stream, 1);
2204 }
2205 }
2206}
2207
2212static void cwist_h3_on_close(lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h) {
2214 if (st) {
2215#ifdef CWIST_WT_STOCK_LSQUIC
2216 if (st->wt_session) {
2217 h3_wt_unlink_session(st->wt_session);
2218 cwist_wt_handle_free(st->wt_session);
2219 }
2220 cwist_wt_handle_free(st->wt_stream);
2221#endif
2224 free(st->body);
2225 cwist_seq_assembler_destroy(st->body_assembler);
2226 free(st->seq_buf);
2227 free(st);
2228 }
2229 (void)stream;
2230}
2231
2243static ssize_t cwist_h3_on_dg_write(lsquic_conn_t *conn, void *buf, size_t len) {
2245 if (!cc) return 0;
2246
2247 pthread_mutex_lock(&cc->dgram_lock);
2248 h3_dgram_node_t *n = cc->dgram_head;
2249 if (!n) {
2250 pthread_mutex_unlock(&cc->dgram_lock);
2251 return 0;
2252 }
2253 size_t to_copy = n->len < len ? n->len : len;
2254 memcpy(buf, n->data, to_copy);
2255 cc->dgram_head = n->next;
2256 if (!cc->dgram_head) cc->dgram_tail = NULL;
2257 bool more_queued = cc->dgram_head != NULL;
2258 pthread_mutex_unlock(&cc->dgram_lock);
2259
2260 free(n->data);
2261 free(n);
2262 /* Keep want_datagram_write armed while the queue is non-empty so
2263 * lsquic calls back for the next entry. */
2265 return (ssize_t)to_copy;
2266}
2267
2272static void cwist_h3_on_datagram(lsquic_conn_t *conn, const void *buf, size_t len) {
2273 cwist_http3_context *ctx = h3_shared_ctx(conn);
2274 if (ctx && ctx->datagram_cb) {
2275#ifdef CWIST_WT_STOCK_LSQUIC
2276 /* A WebTransport session's datagram reaches the app without its
2277 * quarter stream id, as with the session API backend. */
2278 const void *payload = NULL;
2279 size_t payload_len = 0;
2280 if (h3_wt_datagram_payload(conn, buf, len, &payload, &payload_len)) {
2281 ctx->datagram_cb(payload, payload_len, ctx->datagram_user_ctx);
2282 return;
2283 }
2284#endif
2285 ctx->datagram_cb(buf, len, ctx->datagram_user_ctx);
2286 }
2287}
2288
2289#ifdef CWIST_WT_SESSION_API
2290
2302 const struct lsquic_wt_connect_info *info) {
2303 (void)info;
2305 if (st && st->req && st->res) {
2307 cwist_http3_context *h3_ctx = h3_shared_ctx(conn);
2309 if (!session_handle) return NULL;
2310 if (h3_ctx && h3_ctx->wt_handler) {
2311 h3_ctx->wt_handler(st->req, st->res, session_handle);
2312 }
2314 }
2315 return NULL;
2316}
2317
2322static void cwist_h3_wt_on_session_rejected(void *ctx, const struct lsquic_wt_connect_info *info,
2323 unsigned status, const char *reason,
2324 size_t reason_len) {
2325 (void)ctx;
2326 (void)info;
2327 (void)status;
2328 (void)reason;
2330}
2331
2332/* WebTransport application error codes are 32-bit on the wire. The lsquic
2333 * WebTransport branch declared this callback and wti_on_stream_ss_code with
2334 * uint64_t when this code was written; the branch head
2335 * (litespeedtech/lsquic#629) narrows both to uint32_t. */
2338 const char *reason, size_t reason_len) {
2339 (void)sess;
2340 (void)code;
2341 (void)reason;
2344}
2345
2356 lsquic_stream_t *stream) {
2358 if (sess) {
2360 if (conn) {
2361 ctx = h3_shared_ctx(conn);
2362 }
2363 }
2364 if (ctx && ctx->wt_new_stream_handler) {
2366 if (!stream_handle) return NULL;
2368 lsquic_stream_wantread(stream, 1);
2370 }
2371 lsquic_stream_wantread(stream, 1);
2373}
2374
2380 lsquic_stream_t *stream) {
2381 return cwist_h3_wt_on_stream(sess, stream);
2382}
2383
2389 lsquic_stream_t *stream) {
2390 return cwist_h3_wt_on_stream(sess, stream);
2391}
2392
2400 if (!sess) return;
2402 cwist_http3_context *ctx = h3_shared_ctx(conn);
2403 if (ctx && ctx->wt_new_stream_handler) {
2405 }
2406}
2407
2413 (void)stream;
2414 (void)st_h;
2415}
2416
2421 (void)stream;
2423}
2424
2426 (void)stream;
2427 (void)st_h;
2428 return 0;
2429}
2430
2435static void cwist_h3_wt_on_datagram_read(lsquic_wt_session_t *sess, const void *buf, size_t len) {
2437 cwist_http3_context *ctx = h3_shared_ctx(conn);
2438 if (ctx && ctx->datagram_cb) {
2439 ctx->datagram_cb(buf, len, ctx->datagram_user_ctx);
2440 }
2441}
2442
2448 (void)sess;
2450 return 0;
2451}
2452
2453static const struct lsquic_webtransport_if cwist_h3_wt_if = {
2454 .wti_on_session_open = cwist_h3_wt_on_session_open,
2455 .wti_on_session_rejected = cwist_h3_wt_on_session_rejected,
2456 .wti_on_session_close = cwist_h3_wt_on_session_close,
2457 .wti_on_uni_stream = cwist_h3_wt_on_uni_stream,
2458 .wti_on_bidi_stream = cwist_h3_wt_on_bidi_stream,
2459 .wti_on_stream_read = cwist_h3_wt_on_stream_read,
2460 .wti_on_stream_write = cwist_h3_wt_on_stream_write,
2461 .wti_on_stream_close = cwist_h3_wt_on_stream_close,
2462 .wti_on_stream_ss_code = cwist_h3_wt_on_stream_ss_code,
2463 .wti_on_datagram_read = cwist_h3_wt_on_datagram_read,
2464 .wti_on_datagram_write = cwist_h3_wt_on_datagram_write,
2465};
2466
2467#endif /* CWIST_WT_SESSION_API */
2468
2469static const struct lsquic_stream_if cwist_h3_stream_if = {
2470 .on_new_conn = cwist_h3_on_new_conn,
2471 .on_conn_closed = cwist_h3_on_conn_closed,
2472 .on_new_stream = cwist_h3_on_new_stream,
2473 .on_read = cwist_h3_on_read,
2474 .on_write = cwist_h3_on_write,
2475 .on_close = cwist_h3_on_close,
2476 .on_dg_write = cwist_h3_on_dg_write,
2477 .on_datagram = cwist_h3_on_datagram,
2478};
2479
2480/* ------------------------------------------------------------------ */
2481/* Context management */
2482/* ------------------------------------------------------------------ */
2483
2484/* --- Shared session ticket key -------------------------------------------
2485 * Session resumption (and therefore 0-RTT early data) requires the server to
2486 * issue resumable tickets. Mirrors the prefork-safe pattern used by the
2487 * HTTPS stack (src/net/http/https.c): one random key generated at context
2488 * creation and installed via an explicit ticket-key callback, so tickets
2489 * issued by one worker resume on any other (SO_REUSEPORT scatters
2490 * connections across workers). The key lives for the process lifetime.
2491 * ------------------------------------------------------------------------- */
2492typedef struct cwist_h3_ticket_key {
2493 unsigned char name[16]; /* key_name sent in the ticket */
2494 unsigned char aes_key[32]; /* AES-256-CBC encryption key */
2495 unsigned char hmac_key[32]; /* HMAC-SHA-256 MAC key */
2497
2498static int g_h3_ticket_key_ex_data_idx = -1;
2499static pthread_once_t g_h3_ticket_key_ex_data_once = PTHREAD_ONCE_INIT;
2500
2505static void cwist_h3_ticket_key_ex_data_init(void) {
2506 g_h3_ticket_key_ex_data_idx = SSL_CTX_get_ex_new_index(0, NULL, NULL, NULL, NULL);
2507}
2508
2519static int cwist_h3_ticket_key_cb(SSL *ssl, uint8_t *key_name, uint8_t *iv, EVP_CIPHER_CTX *ectx,
2520 HMAC_CTX *hctx, int encrypt) {
2521 SSL_CTX *ssl_ctx = ssl ? SSL_get_SSL_CTX(ssl) : NULL;
2522 const cwist_h3_ticket_key *key = NULL;
2523 if (ssl_ctx && g_h3_ticket_key_ex_data_idx >= 0) {
2524 key =
2525 (const cwist_h3_ticket_key *)SSL_CTX_get_ex_data(ssl_ctx, g_h3_ticket_key_ex_data_idx);
2526 }
2527 if (!key) return 0;
2528
2529 if (encrypt) {
2530 memcpy(key_name, key->name, sizeof(key->name));
2531 if (RAND_bytes(iv, 16) != 1) return 0;
2532 if (EVP_EncryptInit_ex(ectx, EVP_aes_256_cbc(), NULL, key->aes_key, iv) != 1) return 0;
2533 if (HMAC_Init_ex(hctx, key->hmac_key, sizeof(key->hmac_key), EVP_sha256(), NULL) != 1)
2534 return 0;
2535 return 1;
2536 }
2537
2538 if (memcmp(key_name, key->name, sizeof(key->name)) != 0) {
2539 return 0; /* Unknown key: decline the ticket, do a full handshake. */
2540 }
2541 if (EVP_DecryptInit_ex(ectx, EVP_aes_256_cbc(), NULL, key->aes_key, iv) != 1) return 0;
2542 if (HMAC_Init_ex(hctx, key->hmac_key, sizeof(key->hmac_key), EVP_sha256(), NULL) != 1) return 0;
2543 return 1;
2544}
2545
2555static void cwist_h3_setup_session_tickets(SSL_CTX *ssl_ctx, cwist_h3_ticket_key **key_io) {
2558
2559 pthread_once(&g_h3_ticket_key_ex_data_once, cwist_h3_ticket_key_ex_data_init);
2560 if (g_h3_ticket_key_ex_data_idx < 0) return;
2561
2562 if (!*key_io) {
2563 cwist_h3_ticket_key *key = (cwist_h3_ticket_key *)cwist_alloc(sizeof(*key));
2564 if (!key) return;
2565 if (RAND_bytes((uint8_t *)key, sizeof(*key)) != 1) {
2566 cwist_free(key);
2567 return; /* Fall back to the library-internal (per-worker) keys. */
2568 }
2569 *key_io = key;
2570 }
2571 SSL_CTX_set_ex_data(ssl_ctx, g_h3_ticket_key_ex_data_idx, *key_io);
2572 SSL_CTX_set_tlsext_ticket_key_cb(ssl_ctx, cwist_h3_ticket_key_cb);
2573}
2574
2586static int cwist_h3_ssl_ctx_init(SSL_CTX *ssl_ctx, int early_data, cwist_h3_ticket_key **key_io) {
2590 SSL_CTX_set_alpn_select_cb(ssl_ctx, cwist_h3_alpn_select_cb, NULL);
2591 cwist_h3_setup_session_tickets(ssl_ctx, key_io);
2592
2593 if (early_data) {
2595 }
2596
2597 /* Server-side QUIC transport parameters will be supplied by lsquic */
2598 return 0;
2599}
2600
2605static void cwist_h3_free_ticket_key(cwist_h3_ticket_key *key) {
2606 if (key) {
2607 OPENSSL_cleanse(key, sizeof(*key));
2608 cwist_free(key);
2609 }
2610}
2611
2623static cwist_error_t cwist_h3_build_ssl_ctx(SSL_CTX **out, const char *cert_path,
2624 const char *key_path, int early_data,
2625 cwist_h3_ticket_key **key_io) {
2627
2628 const SSL_METHOD *method = TLS_method();
2629 SSL_CTX *ssl_ctx = SSL_CTX_new(method);
2630 if (!ssl_ctx) {
2631 err.error.err_i16 = -1;
2632 return err;
2633 }
2634
2635 cwist_h3_ssl_ctx_init(ssl_ctx, early_data, key_io);
2636
2637 if (SSL_CTX_use_certificate_chain_file(ssl_ctx, cert_path) <= 0 ||
2638 SSL_CTX_use_PrivateKey_file(ssl_ctx, key_path, SSL_FILETYPE_PEM) <= 0) {
2639 SSL_CTX_free(ssl_ctx);
2640 err.error.err_i16 = -1;
2641 return err;
2642 }
2643
2644 if (cwist_tls_autoload_intermediates(ssl_ctx) < 0) {
2645 SSL_CTX_free(ssl_ctx);
2646 err.error.err_i16 = -1;
2647 return err;
2648 }
2649
2650 if (SSL_CTX_check_private_key(ssl_ctx) != 1) {
2651 SSL_CTX_free(ssl_ctx);
2652 err.error.err_i16 = -1;
2653 return err;
2654 }
2655
2656 *out = ssl_ctx;
2657 err.error.err_i16 = 0;
2658 return err;
2659}
2660
2674 const char *key_path) {
2676 if (!ctx || !cert_path || !key_path) {
2677 err.error.err_i16 = -1;
2678 return err;
2679 }
2680
2681 h3_global_init();
2682
2684 SSL_CTX *ssl_ctx = NULL;
2685 err = cwist_h3_build_ssl_ctx(&ssl_ctx, cert_path, key_path, 0, &tkey);
2686 if (!cwist_error_is_ok(&err)) {
2687 cwist_h3_free_ticket_key(tkey);
2688 h3_global_cleanup();
2689 return err;
2690 }
2691
2693 if (!*ctx) {
2694 SSL_CTX_free(ssl_ctx);
2695 cwist_h3_free_ticket_key(tkey);
2696 h3_global_cleanup();
2697 err.error.err_i16 = -1;
2698 return err;
2699 }
2700
2701 memset(*ctx, 0, sizeof(cwist_http3_context));
2702 (*ctx)->ssl_ctx = ssl_ctx;
2703 (*ctx)->udp_fd = -1;
2704 (*ctx)->ticket_key = tkey;
2705 pthread_rwlock_init(&(*ctx)->ssl_ctx_lock, NULL);
2706 err.error.err_i16 = 0;
2707 return err;
2708}
2709
2723 if (!ctx) {
2724 err.error.err_i16 = -1;
2725 return err;
2726 }
2727
2728 h3_global_init();
2729
2731 const SSL_METHOD *method = TLS_method();
2732 SSL_CTX *ssl_ctx = SSL_CTX_new(method);
2733 if (!ssl_ctx) {
2734 h3_global_cleanup();
2735 err.error.err_i16 = -1;
2736 return err;
2737 }
2738
2739 cwist_h3_ssl_ctx_init(ssl_ctx, 0, &tkey);
2740
2742 if (!pctx) {
2743 cwist_h3_free_ticket_key(tkey);
2744 SSL_CTX_free(ssl_ctx);
2745 h3_global_cleanup();
2746 err.error.err_i16 = -1;
2747 return err;
2748 }
2751 cwist_h3_free_ticket_key(tkey);
2752 SSL_CTX_free(ssl_ctx);
2753 h3_global_cleanup();
2754 err.error.err_i16 = -1;
2755 return err;
2756 }
2757 EVP_PKEY *pkey = NULL;
2758 if (EVP_PKEY_keygen(pctx, &pkey) <= 0 || !pkey) {
2760 cwist_h3_free_ticket_key(tkey);
2761 SSL_CTX_free(ssl_ctx);
2762 h3_global_cleanup();
2763 err.error.err_i16 = -1;
2764 return err;
2765 }
2767
2768 X509 *x509 = X509_new();
2769 if (!x509) {
2770 EVP_PKEY_free(pkey);
2771 cwist_h3_free_ticket_key(tkey);
2772 SSL_CTX_free(ssl_ctx);
2773 h3_global_cleanup();
2774 err.error.err_i16 = -1;
2775 return err;
2776 }
2780 X509_set_pubkey(x509, pkey);
2781
2782 /* Self-signed subject */
2784 X509_NAME_add_entry_by_txt(subj, "CN", MBSTRING_ASC, (const unsigned char *)"localhost", -1, -1,
2785 0);
2787 X509_sign(x509, pkey, EVP_sha256());
2788
2789 if (SSL_CTX_use_certificate(ssl_ctx, x509) != 1 || SSL_CTX_use_PrivateKey(ssl_ctx, pkey) != 1) {
2790 X509_free(x509);
2791 EVP_PKEY_free(pkey);
2792 cwist_h3_free_ticket_key(tkey);
2793 SSL_CTX_free(ssl_ctx);
2794 h3_global_cleanup();
2795 err.error.err_i16 = -1;
2796 return err;
2797 }
2798
2799 X509_free(x509);
2800 EVP_PKEY_free(pkey);
2801
2803 if (!*ctx) {
2804 cwist_h3_free_ticket_key(tkey);
2805 SSL_CTX_free(ssl_ctx);
2806 h3_global_cleanup();
2807 err.error.err_i16 = -1;
2808 return err;
2809 }
2810
2811 memset(*ctx, 0, sizeof(cwist_http3_context));
2812 (*ctx)->ssl_ctx = ssl_ctx;
2813 (*ctx)->udp_fd = -1;
2814 (*ctx)->ticket_key = tkey;
2815 pthread_rwlock_init(&(*ctx)->ssl_ctx_lock, NULL);
2816 err.error.err_i16 = 0;
2817 return err;
2818}
2819
2830static void cwist_h3_engine_graceful_stop(cwist_http3_context *ctx) {
2831 lsquic_engine_t *engine = (lsquic_engine_t *)ctx->engine;
2832 if (!engine) return;
2833 lsquic_engine_cooldown(engine);
2835 if (ctx->udp_fd >= 0 && lsquic_engine_has_unsent_packets(engine)) {
2837 }
2838}
2839
2849 if (ctx) {
2850 if (ctx->engine) {
2851 cwist_h3_engine_graceful_stop(ctx);
2853 ctx->engine = NULL;
2854 }
2855 cwist_h3_hset_sweep(ctx);
2856 if (ctx->ssl_ctx) {
2857 SSL_CTX_free(ctx->ssl_ctx);
2858 ctx->ssl_ctx = NULL;
2859 }
2860 if (ctx->ssl_ctx_handout) {
2862 ctx->ssl_ctx_handout = NULL;
2863 }
2865 /* The shared ticket key outlives every SSL_CTX it was installed on:
2866 * retired reload predecessors die with their last connection, all
2867 * closed by the engine destroy above. */
2868 cwist_h3_free_ticket_key((cwist_h3_ticket_key *)ctx->ticket_key);
2869 ctx->ticket_key = NULL;
2870 cwist_free(ctx);
2871 h3_global_cleanup();
2872 }
2873}
2874
2880 const char *key_path) {
2882 if (!ctx || !cert_path || !key_path) {
2883 err.error.err_i16 = -1;
2884 return err;
2885 }
2886
2887 /* The wrapper owns the shared ticket key; the new SSL_CTX reuses it, so
2888 * tickets issued before the reload still resume afterwards. */
2889 SSL_CTX *new_ctx = NULL;
2890 err = cwist_h3_build_ssl_ctx(&new_ctx, cert_path, key_path, ctx->early_data_enabled,
2892 if (!cwist_error_is_ok(&err)) {
2893 return err;
2894 }
2895
2897 SSL_CTX *old_ctx = ctx->ssl_ctx;
2898 ctx->ssl_ctx = new_ctx;
2900
2901 /* Drops only the listener's reference; the handout pin and live
2902 * connections hold their own. */
2904
2905 err.error.err_i16 = 0;
2906 return err;
2907}
2908
2909/* ------------------------------------------------------------------ */
2910/* Server loop */
2911/* ------------------------------------------------------------------ */
2912
2933 cwist_http3_request_handler_func handler, void *user_ctx) {
2935 if (udp_fd < 0 || !ctx || !ctx->ssl_ctx || !handler) {
2936 err.error.err_i16 = -1;
2937 return err;
2938 }
2939
2942 settings.es_support_push = ctx->push_enabled;
2943 settings.es_allow_migration = ctx->allow_migration ? ctx->allow_migration : 1;
2944 settings.es_max_delayed_0rtt_packets = 32;
2945 settings.es_datagrams = ctx->datagram_enabled;
2946#ifdef CWIST_WT_STOCK_LSQUIC
2947 if (ctx->wt_handler) {
2948 /* Stock lsquic sends SETTINGS_ENABLE_WEBTRANSPORT, the session limit,
2949 * H3_DATAGRAM and SETTINGS_ENABLE_CONNECT_PROTOCOL when this is on;
2950 * the DATAGRAM transport parameter needs es_datagrams. */
2951 settings.es_webtransport_server = 1;
2952 settings.es_max_webtransport_server_streams = 1;
2953 settings.es_datagrams = 1;
2954 }
2955#elif defined(CWIST_WT_SESSION_API)
2956 if (ctx->wt_handler) {
2957 /* Required lsquic settings for WebTransport (see DEV_LSQUIC.md).
2958 * RFC 9297 Section 3.1: H3_DATAGRAM is only meaningful when the
2959 * QUIC DATAGRAM transport parameter is also sent, so enabling
2960 * WebTransport (or H3 datagrams) must force es_datagrams on. */
2961 settings.es_webtransport = 1;
2962 settings.es_http_datagrams = 1;
2963 settings.es_datagrams = 1;
2964 settings.es_max_webtransport_sessions = 1;
2965 settings.es_reset_stream_at = 1;
2966 } else {
2967 settings.es_http_datagrams = ctx->datagram_enabled;
2968 }
2969#endif
2970 settings.es_max_cfcw = 16 * 1024 * 1024;
2971 settings.es_max_sfcw = 8 * 1024 * 1024;
2972 settings.es_init_max_data = 16 * 1024 * 1024;
2973 settings.es_init_max_stream_data_bidi_remote = 8 * 1024 * 1024;
2974 settings.es_init_max_stream_data_bidi_local = 8 * 1024 * 1024;
2975 settings.es_init_max_stream_data_uni = 8 * 1024 * 1024;
2976 settings.es_init_max_streams_bidi = 256;
2977 settings.es_ecn = 1;
2978 settings.es_pace_packets = 1;
2979 settings.es_optimistic_nat = 1;
2980
2981 if (ctx->idle_timeout_ms > 0)
2982 settings.es_idle_timeout = (unsigned)(ctx->idle_timeout_ms / 1000);
2983 if (ctx->handshake_timeout_ms > 0)
2984 settings.es_handshake_to = (unsigned long)ctx->handshake_timeout_ms * 1000UL;
2985 if (ctx->ping_period_ms > 0) settings.es_ping_period = (unsigned)(ctx->ping_period_ms / 1000);
2986 if (ctx->noprogress_timeout_ms > 0)
2987 settings.es_noprogress_timeout = (unsigned)(ctx->noprogress_timeout_ms / 1000);
2988
2989 char err_buf[256];
2991 fprintf(stderr, "[HTTP/3] Invalid engine settings: %s\n", err_buf);
2992 err.error.err_i16 = -1;
2993 return err;
2994 }
2995
2996 /* ea_packets_out_ctx points at ctx->udp_fd (not the udp_fd local) so the
2997 * pointer stays valid for the graceful-shutdown flush in
2998 * cwist_http3_destroy_context(), which may run after this function's
2999 * frame is gone. */
3000 ctx->udp_fd = udp_fd;
3001
3002 struct lsquic_engine_api api = {
3003 .ea_stream_if = &cwist_h3_stream_if,
3004 .ea_stream_if_ctx = ctx,
3005 .ea_packets_out = cwist_h3_packets_out,
3006 .ea_packets_out_ctx = &ctx->udp_fd,
3007 .ea_get_ssl_ctx = cwist_h3_get_ssl_ctx,
3008 .ea_hsi_if = &cwist_h3_hset_if,
3009 .ea_hsi_ctx = ctx,
3010 .ea_settings = &settings,
3011 .ea_alpn = "h3",
3012 };
3013
3015 if (!engine) {
3016 err.error.err_i16 = -1;
3017 return err;
3018 }
3019
3020 /* Diagnostic hook: CWIST_H3_DEBUG=1 routes lsquic's internal logger to
3021 * stderr so CONNECTION_CLOSE error codes become visible in the journal. */
3022 if (getenv("CWIST_H3_DEBUG")) {
3023 static const struct lsquic_logger_if h3_log_if = {
3024 .log_buf = cwist_h3_log_stderr,
3025 };
3027 lsquic_logger_lopt("engine=debug,conn=debug,event=debug");
3028 }
3029
3030 ctx->engine = engine;
3031 ctx->handler = handler;
3032 ctx->user_ctx = user_ctx;
3034
3035 /* The receive loop relies on MSG_DONTWAIT, which some platforms lack
3036 * (it degrades to 0). Guarantee non-blocking semantics at the socket
3037 * level instead; idempotent if the caller already set O_NONBLOCK. */
3038 int fl = fcntl(udp_fd, F_GETFL, 0);
3039 if (fl >= 0 && !(fl & O_NONBLOCK)) {
3040 fcntl(udp_fd, F_SETFL, fl | O_NONBLOCK);
3041 }
3042
3043 struct sockaddr_storage local_addr;
3044 socklen_t local_addr_len = sizeof(local_addr);
3045 if (getsockname(udp_fd, (struct sockaddr *)&local_addr, &local_addr_len) != 0) {
3046 local_addr_len = 0;
3047 }
3048
3049#ifdef IP_PKTINFO
3050 int opt_pktinfo = 1;
3052#endif
3053#ifdef IP_RECVTOS
3054 int opt_tos = 1;
3055 setsockopt(udp_fd, IPPROTO_IP, IP_RECVTOS, &opt_tos, sizeof(opt_tos));
3056#endif
3057#ifdef IPV6_RECVPKTINFO
3058 int opt_pktinfo6 = 1;
3060#endif
3061#ifdef IPV6_RECVTCLASS
3062 int opt_tclass = 1;
3064#endif
3065
3066 unsigned char *pkt_buf = malloc(65535);
3067 if (!pkt_buf) {
3068 lsquic_engine_destroy(engine);
3069 ctx->engine = NULL;
3070 err.error.err_i16 = -1;
3071 return err;
3072 }
3073
3074#ifdef __linux__
3075 int epoll_fd = epoll_create1(0);
3076 if (epoll_fd < 0) {
3077 free(pkt_buf);
3078 err.error.err_i16 = -1;
3079 lsquic_engine_destroy(engine);
3080 ctx->engine = NULL;
3081 return err;
3082 }
3083 struct epoll_event ev;
3084 ev.events = EPOLLIN;
3085 ev.data.fd = udp_fd;
3086 if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, udp_fd, &ev) < 0) {
3087 free(pkt_buf);
3088 close(epoll_fd);
3089 err.error.err_i16 = -1;
3090 lsquic_engine_destroy(engine);
3091 ctx->engine = NULL;
3092 return err;
3093 }
3094#endif
3095
3096#if defined(__linux__) && defined(_GNU_SOURCE)
3097#define H3_RECV_BATCH 32
3098 /* recvmmsg() batch scratch space: was 5 fixed-size arrays declared on
3099 * the stack inside the loop below (2.1MB total, dominated by
3100 * batch_bufs[32][65535]). That only worked because this loop is known
3101 * to run on a dedicated thread with the default ~8MB stack - the
3102 * comment used to live right above the arrays explaining that TLS
3103 * (.tbss) had been tried and rejected for the same reason (it forces
3104 * every explicit pthread stack size below ~2.2MB to fail). Heap
3105 * allocation removes that "must have a big-stack thread" requirement
3106 * entirely without reintroducing the TLS problem (this was never
3107 * about a thread-local instance, just about not using the stack),
3108 * paying one extra pointer indirection per packet in exchange. Sized
3109 * once here, freed once after the loop below (same place pkt_buf is),
3110 * since this whole function's body has exactly one path out of the
3111 * loop (break/continue only, no returns inside it). */
3112 unsigned char(*batch_bufs)[65535] = malloc(sizeof(*batch_bufs) * H3_RECV_BATCH);
3114 char(*batch_cmsgs)[512] = malloc(sizeof(*batch_cmsgs) * H3_RECV_BATCH);
3115 struct iovec *batch_iovs = malloc(sizeof(*batch_iovs) * H3_RECV_BATCH);
3116 struct mmsghdr *batch_msgs = malloc(sizeof(*batch_msgs) * H3_RECV_BATCH);
3123 free(pkt_buf);
3124 close(epoll_fd);
3125 err.error.err_i16 = -1;
3126 lsquic_engine_destroy(engine);
3127 ctx->engine = NULL;
3128 return err;
3129 }
3130#endif
3131
3132 /* running is a public volatile int another thread clears to stop the
3133 * loop; access it atomically (#351). */
3134 while (ctx && __atomic_load_n(&ctx->running, __ATOMIC_ACQUIRE) &&
3136 int diff = 100000;
3137 int timeout_ms = 50;
3139 if (has_tick) {
3140 if (diff <= 0)
3141 timeout_ms = 0;
3142 else if (diff < 1000)
3143 timeout_ms = 0;
3144 else if (diff > 1000000)
3145 timeout_ms = 1000;
3146 else
3147 timeout_ms = (diff + 999) / 1000;
3148 }
3150 timeout_ms = 0;
3151 }
3152
3153#ifdef __linux__
3154 struct epoll_event events[1];
3155 int pret = epoll_wait(epoll_fd, events, 1, timeout_ms);
3156 if (pret < 0) {
3157 if (errno == EINTR) continue;
3158 break;
3159 }
3160 if (pret > 0 && (events[0].events & (EPOLLERR | EPOLLHUP))) {
3161 fprintf(stderr, "[HTTP/3] UDP socket error, exiting loop.\n");
3162 break;
3163 }
3164 bool can_read = (pret > 0 && (events[0].events & EPOLLIN));
3165#else
3166 struct pollfd pfd = {.fd = udp_fd, .events = POLLIN};
3167 int pret = poll(&pfd, 1, timeout_ms);
3168
3169 if (pret < 0) {
3170 if (errno == EINTR) continue;
3171 if (errno == EBADF) {
3172 fprintf(stderr, "[HTTP/3] UDP socket closed, exiting loop.\n");
3173 break;
3174 }
3175 break;
3176 }
3177
3178 if (pret > 0 && (pfd.revents & (POLLERR | POLLNVAL))) {
3179 fprintf(stderr, "[HTTP/3] UDP socket error, exiting loop.\n");
3180 break;
3181 }
3182 bool can_read = (pret > 0 && (pfd.revents & POLLIN));
3183#endif
3184
3185#if defined(__linux__) && defined(_GNU_SOURCE)
3186 if (can_read) {
3187 /* batch_bufs/batch_peers/batch_cmsgs/batch_iovs/batch_msgs are
3188 * heap-allocated once above the outer loop now - see the
3189 * comment by their malloc()s. */
3190 while (1) {
3191 for (int b = 0; b < H3_RECV_BATCH; b++) {
3192 batch_iovs[b].iov_base = batch_bufs[b];
3193 batch_iovs[b].iov_len = sizeof(batch_bufs[b]);
3194 memset(&batch_msgs[b], 0, sizeof(batch_msgs[b]));
3195 batch_msgs[b].msg_hdr.msg_name = &batch_peers[b];
3196 batch_msgs[b].msg_hdr.msg_namelen = sizeof(batch_peers[b]);
3197 batch_msgs[b].msg_hdr.msg_iov = &batch_iovs[b];
3198 batch_msgs[b].msg_hdr.msg_iovlen = 1;
3199 batch_msgs[b].msg_hdr.msg_control = batch_cmsgs[b];
3200 batch_msgs[b].msg_hdr.msg_controllen = sizeof(batch_cmsgs[b]);
3201 }
3202
3204 if (nmsgs <= 0) {
3205 if (nmsgs < 0 && errno != EAGAIN && errno != EWOULDBLOCK) {
3207 continue;
3208 if (errno == EBADF) break;
3209 }
3210 break;
3211 }
3212
3213 for (int b = 0; b < nmsgs; b++) {
3214 size_t nr = (size_t)batch_msgs[b].msg_len;
3215 if (nr == 0) continue;
3216
3218 socklen_t cur_local_len = local_addr_len;
3219 if (local_addr_len) memcpy(&cur_local_addr, &local_addr, local_addr_len);
3220
3221 int ecn = 0;
3222 for (struct cmsghdr *cmsg = CMSG_FIRSTHDR(&batch_msgs[b].msg_hdr); cmsg != NULL;
3224 if (cmsg->cmsg_level == IPPROTO_IP) {
3225#ifdef IP_PKTINFO
3226 if (cmsg->cmsg_type == IP_PKTINFO) {
3227 struct in_pktinfo *pi = (struct in_pktinfo *)CMSG_DATA(cmsg);
3228 if (pi->ipi_addr.s_addr != INADDR_ANY) {
3229 struct sockaddr_in *sin = (struct sockaddr_in *)&cur_local_addr;
3230 sin->sin_family = AF_INET;
3231 sin->sin_addr = pi->ipi_addr;
3232 if (local_addr_len >= sizeof(struct sockaddr_in)) {
3233 sin->sin_port =
3234 ((struct sockaddr_in *)&local_addr)->sin_port;
3235 }
3236 cur_local_len = sizeof(struct sockaddr_in);
3237 }
3238 }
3239#endif
3240#ifdef IP_TOS
3241 if (cmsg->cmsg_type == IP_TOS) {
3242 ecn = *(int *)CMSG_DATA(cmsg) & 0x3;
3243 }
3244#endif
3245 } else if (cmsg->cmsg_level == IPPROTO_IPV6) {
3246#ifdef IPV6_PKTINFO
3247 if (cmsg->cmsg_type == IPV6_PKTINFO) {
3248 struct in6_pktinfo *pi6 = (struct in6_pktinfo *)CMSG_DATA(cmsg);
3249 if (memcmp(&pi6->ipi6_addr, &in6addr_any,
3250 sizeof(struct in6_addr)) != 0) {
3251 struct sockaddr_in6 *sin6 =
3252 (struct sockaddr_in6 *)&cur_local_addr;
3253 sin6->sin6_family = AF_INET6;
3254 sin6->sin6_addr = pi6->ipi6_addr;
3255 if (local_addr_len >= sizeof(struct sockaddr_in6)) {
3256 sin6->sin6_port =
3257 ((struct sockaddr_in6 *)&local_addr)->sin6_port;
3258 }
3259 cur_local_len = sizeof(struct sockaddr_in6);
3260 }
3261 }
3262#endif
3263#ifdef IPV6_TCLASS
3264 if (cmsg->cmsg_type == IPV6_TCLASS) {
3265 ecn = *(int *)CMSG_DATA(cmsg) & 0x3;
3266 }
3267#endif
3268 }
3269 }
3270
3273 : NULL,
3274 (struct sockaddr *)&batch_peers[b], ctx, ecn);
3275 }
3276 }
3277 }
3278#else
3279 if (can_read) {
3280 while (1) {
3281 struct sockaddr_storage peer_addr;
3282 socklen_t peer_addr_len = sizeof(peer_addr);
3284 socklen_t cur_local_len = local_addr_len;
3285 if (local_addr_len) memcpy(&cur_local_addr, &local_addr, local_addr_len);
3286
3287 struct msghdr msg = {0};
3288 struct iovec iov = {pkt_buf, 65535};
3289 msg.msg_name = &peer_addr;
3290 msg.msg_namelen = peer_addr_len;
3291 msg.msg_iov = &iov;
3292 msg.msg_iovlen = 1;
3293
3294 char cmsg_buf[512];
3295 msg.msg_control = cmsg_buf;
3296 msg.msg_controllen = sizeof(cmsg_buf);
3297
3298 ssize_t nr = recvmsg(udp_fd, &msg, MSG_DONTWAIT);
3299 if (nr <= 0) {
3300 if (nr < 0 && errno != EAGAIN && errno != EWOULDBLOCK) {
3301 if (errno == ECONNREFUSED || errno == ENETUNREACH ||
3302 errno == EHOSTUNREACH) {
3303 continue;
3304 }
3305 if (errno == EBADF) {
3306 fprintf(stderr, "[HTTP/3] UDP socket closed.\n");
3307 break;
3308 }
3309 }
3310 break;
3311 }
3312
3313 int ecn = 0;
3314 for (struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL;
3315 cmsg = CMSG_NXTHDR(&msg, cmsg)) {
3316 if (cmsg->cmsg_level == IPPROTO_IP) {
3317#ifdef IP_PKTINFO
3318 if (cmsg->cmsg_type == IP_PKTINFO) {
3319 struct in_pktinfo *pi = (struct in_pktinfo *)CMSG_DATA(cmsg);
3320 if (pi->ipi_addr.s_addr != INADDR_ANY) {
3321 struct sockaddr_in *sin = (struct sockaddr_in *)&cur_local_addr;
3322 sin->sin_family = AF_INET;
3323 sin->sin_addr = pi->ipi_addr;
3324 if (local_addr_len >= sizeof(struct sockaddr_in)) {
3325 sin->sin_port = ((struct sockaddr_in *)&local_addr)->sin_port;
3326 }
3327 cur_local_len = sizeof(struct sockaddr_in);
3328 }
3329 }
3330#endif
3331#ifdef IP_TOS
3332 if (cmsg->cmsg_type == IP_TOS) {
3333 ecn = *(int *)CMSG_DATA(cmsg) & 0x3;
3334 }
3335#endif
3336 } else if (cmsg->cmsg_level == IPPROTO_IPV6) {
3337#ifdef IPV6_PKTINFO
3338 if (cmsg->cmsg_type == IPV6_PKTINFO) {
3339 struct in6_pktinfo *pi6 = (struct in6_pktinfo *)CMSG_DATA(cmsg);
3340 if (memcmp(&pi6->ipi6_addr, &in6addr_any, sizeof(struct in6_addr)) !=
3341 0) {
3342 struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&cur_local_addr;
3343 sin6->sin6_family = AF_INET6;
3344 sin6->sin6_addr = pi6->ipi6_addr;
3345 if (local_addr_len >= sizeof(struct sockaddr_in6)) {
3346 sin6->sin6_port =
3347 ((struct sockaddr_in6 *)&local_addr)->sin6_port;
3348 }
3349 cur_local_len = sizeof(struct sockaddr_in6);
3350 }
3351 }
3352#endif
3353#ifdef IPV6_TCLASS
3354 if (cmsg->cmsg_type == IPV6_TCLASS) {
3355 ecn = *(int *)CMSG_DATA(cmsg) & 0x3;
3356 }
3357#endif
3358 }
3359 }
3360
3361 lsquic_engine_packet_in(engine, pkt_buf, (size_t)nr,
3363 (struct sockaddr *)&peer_addr, ctx, ecn);
3364 }
3365 }
3366#endif
3367
3371 }
3372 }
3373
3374#ifdef __linux__
3375 if (epoll_fd >= 0) close(epoll_fd);
3376#endif
3377
3378#if defined(__linux__) && defined(_GNU_SOURCE)
3384#endif
3385
3386 free(pkt_buf);
3387 if (ctx && ctx->engine) {
3388 cwist_h3_engine_graceful_stop(ctx);
3390 ctx->engine = NULL;
3391 cwist_h3_hset_sweep(ctx);
3392 }
3393 err.error.err_i16 = 0;
3394 return err;
3395}
3396
3397/* ------------------------------------------------------------------ */
3398/* Push, Priority, and 0-RTT APIs */
3399/* ------------------------------------------------------------------ */
3400
3406 if (ctx) ctx->push_enabled = enabled;
3407}
3408
3417 if (!ctx) return;
3419 SSL_CTX *ssl_ctx = ctx->ssl_ctx;
3420 if (ssl_ctx) SSL_CTX_set_early_data_enabled(ssl_ctx, enabled ? 1 : 0);
3422 if (!ssl_ctx) return;
3423 ctx->early_data_enabled = enabled ? 1 : 0;
3424 if (enabled) {
3425 /* Replay protection is opt-out: restrict 0-RTT requests to
3426 * idempotent methods unless the application overrides the guard. */
3427 ctx->early_data_guard = 1;
3428 }
3429}
3430
3439 if (ctx) ctx->early_data_guard = enabled ? 1 : 0;
3440}
3441
3449 if (!method_str) return 0;
3450 /* TRACE is absent from cwist_http_method_t; match it by name. */
3451 if (strcasecmp(method_str, "TRACE") == 0) return 1;
3452 return h3_method_is_idempotent(cwist_http_string_to_method(method_str)) ? 1 : 0;
3453}
3454
3469int cwist_http3_push_resource(cwist_http_request *req, const char *path, const char *content_type) {
3470 if (!req || !req->private_data || !path) return -1;
3472 lsquic_conn_t *conn = lsquic_stream_conn(stream);
3473 if (!conn || !lsquic_conn_is_push_enabled(conn)) return -1;
3474 (void)content_type; /* response field; not valid in a push request set */
3475
3476 /* Build push headers (a request header set needs :method, :scheme,
3477 * :authority, :path; content-type is a response field and is omitted) */
3478 struct lsxpack_header headers_arr[4];
3479 char hbuf[2048];
3480 h3_hdr_out hdrs = {.arr = headers_arr, .max = 4, .buf = hbuf, .cap = sizeof(hbuf)};
3481
3482 h3_hdr_out_add_str(&hdrs, ":method", "GET");
3483 /* HTTP/3 is always over QUIC/TLS. */
3484 h3_hdr_out_add_str(&hdrs, ":scheme", "https");
3485 h3_hdr_out_add_str(&hdrs, ":path", path);
3486 /* :authority: copy from the original request if available. */
3487 const char *authority = cwist_http_header_get(req->headers, "host");
3488 h3_hdr_out_add_str(&hdrs, ":authority", authority ? authority : "localhost");
3489
3490 lsquic_http_headers_t headers = {
3491 .count = (unsigned)hdrs.count,
3492 .headers = headers_arr,
3493 };
3494
3495 return lsquic_conn_push_stream(conn, NULL, stream, &headers);
3496}
3497
3509 (void)req;
3510 (void)priority;
3511 CWIST_LOG_WARN("[HTTP/3] cwist_http3_set_stream_priority() is deprecated "
3512 "and refused (RFC 9218 violation); use the Priority header");
3513 return -1;
3514}
3515
3516/* ------------------------------------------------------------------ */
3517/* WebTransport API */
3518/* ------------------------------------------------------------------ */
3519
3531
3537 void (*handler)(void *stream, void *user_ctx),
3538 void *user_ctx) {
3539 if (ctx) {
3540 ctx->wt_new_stream_handler = handler;
3541 ctx->wt_new_stream_ctx = user_ctx;
3542 }
3543}
3544
3545#ifdef CWIST_WEBTRANSPORT_SERVER
3546
3552ssize_t cwist_webtransport_read(void *stream, void *buf, size_t len) {
3553 if (!stream || !buf) return -1;
3555 if (!s) return -1;
3556 ssize_t n = lsquic_stream_read(s, buf, len);
3557#ifdef CWIST_WT_STOCK_LSQUIC
3558 /* FIN consumed: stop on_read, which would otherwise keep firing on a
3559 * stream the app has finished reading but not closed. */
3560 if (n == 0) lsquic_stream_wantread(s, 0);
3561#endif
3562 return n;
3563}
3564
3570ssize_t cwist_webtransport_write(void *stream, const void *data, size_t len) {
3571 if (!stream || !data) return -1;
3573 if (!s) return -1;
3574 return lsquic_stream_write(s, data, len);
3575}
3576
3582int cwist_webtransport_flush(void *stream) {
3583 if (!stream) return -1;
3585 if (!s) return -1;
3586 return lsquic_stream_flush(s);
3587}
3588
3594int cwist_webtransport_close_stream(void *stream) {
3595 if (!stream) return -1;
3597 if (!s) return -1;
3598 return lsquic_stream_close(s);
3599}
3600
3601#ifdef CWIST_WT_STOCK_LSQUIC
3602
3608int cwist_webtransport_open_bidi_stream(void *session) {
3609 (void)session;
3610 return -1;
3611}
3612
3618int cwist_webtransport_open_uni_stream(void *session) {
3619 (void)session;
3620 return -1;
3621}
3622
3623#else /* CWIST_WT_SESSION_API */
3624
3634int cwist_webtransport_open_bidi_stream(void *session) {
3635 if (!session) return -1;
3639 if (!raw_session) return -1;
3641 if (!stream) return -1;
3643 if (sess) {
3645 cwist_http3_context *ctx = h3_shared_ctx(conn);
3646 if (ctx && ctx->wt_new_stream_handler) {
3648 if (!stream_handle) return -1;
3651 }
3652 }
3653 return 0;
3654}
3655
3665int cwist_webtransport_open_uni_stream(void *session) {
3666 if (!session) return -1;
3670 if (!raw_session) return -1;
3672 if (!stream) return -1;
3674 if (sess) {
3676 cwist_http3_context *ctx = h3_shared_ctx(conn);
3677 if (ctx && ctx->wt_new_stream_handler) {
3679 if (!stream_handle) return -1;
3682 }
3683 }
3684 return 0;
3685}
3686
3687#endif /* CWIST_WT_STOCK_LSQUIC */
3688
3689#else /* CWIST_WEBTRANSPORT_SERVER */
3690
3694ssize_t cwist_webtransport_read(void *stream, void *buf, size_t len) {
3695 (void)stream;
3696 (void)buf;
3697 (void)len;
3698 return -1;
3699}
3700
3704ssize_t cwist_webtransport_write(void *stream, const void *data, size_t len) {
3705 (void)stream;
3706 (void)data;
3707 (void)len;
3708 return -1;
3709}
3710
3714int cwist_webtransport_flush(void *stream) {
3715 (void)stream;
3716 return -1;
3717}
3718
3723 (void)stream;
3724 return -1;
3725}
3726
3731 (void)session;
3732 return -1;
3733}
3734
3739 (void)session;
3740 return -1;
3741}
3742
3743#endif /* CWIST_WEBTRANSPORT_SERVER */
3744
3745/* ------------------------------------------------------------------ */
3746/* Resilience knobs */
3747/* ------------------------------------------------------------------ */
3748
3754 if (ctx) ctx->idle_timeout_ms = ms > 0 ? ms : 0;
3755}
3756
3762 if (ctx) ctx->handshake_timeout_ms = ms > 0 ? ms : 0;
3763}
3764
3770 if (ctx) ctx->ping_period_ms = ms > 0 ? ms : 0;
3771}
3772
3778 if (ctx) ctx->noprogress_timeout_ms = ms > 0 ? ms : 0;
3779}
3780
3781/* ------------------------------------------------------------------ */
3782/* Datagram API */
3783/* ------------------------------------------------------------------ */
3784
3792 if (ctx) ctx->datagram_enabled = enabled;
3793}
3794
3800 void (*cb)(const void *data, size_t len, void *user_ctx),
3801 void *user_ctx) {
3802 if (ctx) {
3803 ctx->datagram_cb = cb;
3804 ctx->datagram_user_ctx = user_ctx;
3805 }
3806}
3807
3823int cwist_http3_send_datagram(void *conn, const void *data, size_t len) {
3824 lsquic_conn_t *c = (lsquic_conn_t *)conn;
3825 if (!c || !data || len == 0) return -1;
3826
3828 if (!cc) return -1;
3829
3830 h3_dgram_node_t *node = (h3_dgram_node_t *)malloc(sizeof(*node));
3831 if (!node) return -1;
3832 node->data = malloc(len);
3833 if (!node->data) {
3834 free(node);
3835 return -1;
3836 }
3837 memcpy(node->data, data, len);
3838 node->len = len;
3839 node->next = NULL;
3840
3841 /* Append to this connection's own queue - concurrent sends on other
3842 * connections touch their own h3_conn_ctx_t and never see this lock,
3843 * and concurrent sends on *this* connection now queue instead of one
3844 * clobbering (free()ing) the other's buffer. */
3845 pthread_mutex_lock(&cc->dgram_lock);
3846 if (cc->dgram_tail) {
3847 cc->dgram_tail->next = node;
3848 } else {
3849 cc->dgram_head = node;
3850 }
3851 cc->dgram_tail = node;
3852 pthread_mutex_unlock(&cc->dgram_lock);
3853
3855 return 0;
3856}
3857
3858#ifdef CWIST_WEBTRANSPORT_SERVER
3859
3860#ifdef CWIST_WT_STOCK_LSQUIC
3861
3873ssize_t cwist_webtransport_send_datagram(void *session, const void *data, size_t len) {
3874 if (!session || !data || len == 0) return -1;
3876 if (!h || len > cwist_webtransport_max_datagram_size(session)) return -1;
3877 unsigned char buf[H3_WT_DATAGRAM_BUDGET];
3878 size_t prefix = h3_wt_varint_write(buf, h->session_id >> 2);
3879 memcpy(buf + prefix, data, len);
3880 return cwist_http3_send_datagram(h->conn, buf, prefix + len) == 0 ? (ssize_t)len : -1;
3881}
3882
3889size_t cwist_webtransport_max_datagram_size(void *session) {
3891 if (!h) return 0;
3892 return H3_WT_DATAGRAM_BUDGET - h3_wt_varint_len(h->session_id >> 2);
3893}
3894
3906int cwist_webtransport_close_session(void *session, uint64_t code, const char *reason) {
3908 if (!h || code > UINT32_MAX) return -1;
3909 size_t reason_len = reason ? strlen(reason) : 0;
3910 if (reason_len > H3_WT_MAX_CLOSE_REASON) return -1;
3911 unsigned char capsule[8 + 8 + 4 + H3_WT_MAX_CLOSE_REASON];
3913 off += h3_wt_varint_write(capsule + off, 4 + reason_len);
3914 capsule[off++] = (unsigned char)(code >> 24);
3915 capsule[off++] = (unsigned char)(code >> 16);
3916 capsule[off++] = (unsigned char)(code >> 8);
3917 capsule[off++] = (unsigned char)code;
3919 off += reason_len;
3920 lsquic_stream_t *s = (lsquic_stream_t *)h->ptr;
3921 if (lsquic_stream_write(s, capsule, off) != (ssize_t)off) return -1;
3923 return lsquic_stream_shutdown(s, 1);
3924}
3925
3926#else /* CWIST_WT_SESSION_API */
3927
3935ssize_t cwist_webtransport_send_datagram(void *session, const void *data, size_t len) {
3936 if (!session || !data || len == 0) return -1;
3938 if (!sess) return -1;
3939 return lsquic_wt_send_datagram(sess, data, len);
3940}
3941
3948size_t cwist_webtransport_max_datagram_size(void *session) {
3949 if (!session) return 0;
3951 if (!sess) return 0;
3953}
3954
3964int cwist_webtransport_close_session(void *session, uint64_t code, const char *reason) {
3965 if (!session) return -1;
3967 if (!sess) return -1;
3969}
3970
3971#endif /* CWIST_WT_STOCK_LSQUIC */
3972
3973#else /* CWIST_WEBTRANSPORT_SERVER */
3974
3978ssize_t cwist_webtransport_send_datagram(void *session, const void *data, size_t len) {
3979 (void)session;
3980 (void)data;
3981 (void)len;
3982 return -1;
3983}
3984
3989 (void)session;
3990 return 0;
3991}
3992
3996int cwist_webtransport_close_session(void *session, uint64_t code, const char *reason) {
3997 (void)session;
3998 (void)code;
3999 (void)reason;
4000 return -1;
4001}
4002
4003#endif /* CWIST_WEBTRANSPORT_SERVER */
Memory allocation wrappers backed by libttak.
void cwist_free(void *ptr)
Free memory obtained via cwist_alloc/cwist_strdup/etc.
Definition alloc.c:52
void * cwist_alloc(size_t size)
Allocate zeroed memory tracked by libttak.
Definition alloc.c:17
cwist_error_t cwist_sstring_assign_len(cwist_sstring *str, const char *data, size_t len)
Assign raw bytes with length.
Definition sstring.c:88
cwist_error_t cwist_sstring_assign(cwist_sstring *str, const char *data)
Assign a C string.
Definition sstring.c:504
#define OPENSSL_cleanse
Definition crypto.h:16
@ CWIST_ERR_INT16
used when checking common errcodes in Unix/Linux
Definition cwist_err.h:16
cwist_error_t make_error(cwist_errtype_t type)
Creates an error object initialized with the specified type.
Definition error.c:14
mbedTLS TLS backend: EVP subset (HMAC digest tags, AES-256-CBC).
#define EVP_DecryptInit_ex
Definition evp.h:37
struct cwist_mtls_cipher_ctx EVP_CIPHER_CTX
Definition evp.h:21
#define EVP_EncryptInit_ex
Definition evp.h:34
#define EVP_sha256
Definition evp.h:25
#define EVP_aes_256_cbc
Definition evp.h:26
Outgoing HTTP/3 header set builder for lsquic (private).
mbedTLS TLS backend: HMAC subset (HMAC-SHA256, one-shot + streaming).
#define HMAC_Init_ex
Definition hmac.h:24
struct cwist_mtls_hmac_ctx HMAC_CTX
Definition hmac.h:19
HTTP/2 Protocol Definitions for CWIST.
struct cwist_h3_ticket_key cwist_h3_ticket_key
int cwist_http3_response_header_value_is_safe(const char *value)
Whether a response header value is safe to put on the wire.
Definition http3.c:1127
#define H3_PSEUDO_METHOD
Definition http3.c:132
int cwist_http3_method_is_idempotent(const char *method_str)
String-based idempotency check (RFC 9110), used by tests and external checks.
Definition http3.c:3448
void cwist_http3_set_early_data_guard(cwist_http3_context *ctx, int enabled)
Enable or disable the 0-RTT replay guard.
Definition http3.c:3438
#define H3_PSEUDO_PATH
Definition http3.c:134
void cwist_http3_set_webtransport_handler(cwist_http3_context *ctx, cwist_webtransport_handler_func handler)
Register the WebTransport session handler (extended CONNECT).
Definition http3.c:3527
#define H3_PSEUDO_SCHEME
Definition http3.c:133
void cwist_http3_destroy_context(cwist_http3_context *ctx)
Destroy a context: graceful engine stop, engine destroy, hset sweep, SSL_CTX and ticket-key teardown,...
Definition http3.c:2848
struct h3_dgram_node h3_dgram_node_t
cwist_error_t cwist_http3_init_context(cwist_http3_context **ctx, const char *cert_path, const char *key_path)
Create an HTTP/3 server context from a certificate/key pair.
Definition http3.c:2673
void cwist_http3_set_datagram_callback(cwist_http3_context *ctx, void(*cb)(const void *data, size_t len, void *user_ctx), void *user_ctx)
Register the callback for incoming H3/WebTransport datagrams.
Definition http3.c:3799
void cwist_http3_set_idle_timeout(cwist_http3_context *ctx, int ms)
Set the connection idle timeout (milliseconds, <= 0 disables). Takes effect at cwist_http3_server_loo...
Definition http3.c:3753
size_t cwist_webtransport_max_datagram_size(void *session)
Stub used when the WebTransport server is not compiled in: always 0.
Definition http3.c:3988
#define H3_MAX_RESPONSE_HEADERS
Definition http3.c:356
struct cwist_h3_hset cwist_h3_hset_t
void cwist_http3_set_handshake_timeout(cwist_http3_context *ctx, int ms)
Set the handshake timeout (milliseconds, <= 0 disables). Takes effect at cwist_http3_server_loop() ti...
Definition http3.c:3761
ssize_t cwist_webtransport_send_datagram(void *session, const void *data, size_t len)
Stub used when the WebTransport server is not compiled in: always -1.
Definition http3.c:3978
int cwist_webtransport_close_session(void *session, uint64_t code, const char *reason)
Stub used when the WebTransport server is not compiled in: always -1.
Definition http3.c:3996
int cwist_webtransport_open_uni_stream(void *session)
Stub used when the WebTransport server is not compiled in: always -1.
Definition http3.c:3738
cwist_error_t cwist_http3_reload_context(cwist_http3_context *ctx, const char *cert_path, const char *key_path)
Swap in a new certificate chain and key without dropping QUIC connections. See the public contract in...
Definition http3.c:2879
#define H3_FILE_CHUNKS_PER_TICK
ssize_t cwist_webtransport_read(void *stream, void *buf, size_t len)
Stub used when the WebTransport server is not compiled in: always -1.
Definition http3.c:3694
void cwist_http3_set_early_data(cwist_http3_context *ctx, bool enabled)
Enable or disable TLS 1.3 early data (0-RTT) on the context.
Definition http3.c:3416
int cwist_webtransport_open_bidi_stream(void *session)
Stub used when the WebTransport server is not compiled in: always -1.
Definition http3.c:3730
cwist_error_t cwist_http3_server_loop(int udp_fd, cwist_http3_context *ctx, cwist_http3_request_handler_func handler, void *user_ctx)
Run the blocking HTTP/3 server loop on a UDP socket.
Definition http3.c:2932
struct h3_stream_ctx h3_stream_ctx_t
void cwist_webtransport_set_new_stream_handler(cwist_http3_context *ctx, void(*handler)(void *stream, void *user_ctx), void *user_ctx)
Register the callback invoked for every new WebTransport stream.
Definition http3.c:3536
int cwist_http3_send_datagram(void *conn, const void *data, size_t len)
Queue an outgoing H3 datagram on a connection.
Definition http3.c:3823
int cwist_webtransport_flush(void *stream)
Stub used when the WebTransport server is not compiled in: always -1.
Definition http3.c:3714
#define H3_DECODE_BUF_SIZE
Definition http3.c:355
ssize_t cwist_webtransport_write(void *stream, const void *data, size_t len)
Stub used when the WebTransport server is not compiled in: always -1.
Definition http3.c:3704
#define H3_RESPONSE_HEADER_BUF_SIZE
Definition http3.c:357
#define MSG_DONTWAIT
Definition http3.c:82
void cwist_http3_set_datagram_enabled(cwist_http3_context *ctx, int enabled)
Enable or disable H3 datagrams for this context.
Definition http3.c:3791
#define H3_PSEUDO_PROTOCOL
Definition http3.c:136
#define H3_PSEUDO_AUTHORITY
Definition http3.c:135
void cwist_http3_set_push_enabled(cwist_http3_context *ctx, int enabled)
Enable or disable HTTP/3 server push for this context. Takes effect at cwist_http3_server_loop() time...
Definition http3.c:3405
void cwist_http3_set_ping_period(cwist_http3_context *ctx, int ms)
Set the keep-alive ping period (milliseconds, <= 0 disables). Takes effect at cwist_http3_server_loop...
Definition http3.c:3769
#define H3_MAX_HEADERS
Definition http3.c:354
void cwist_http3_set_noprogress_timeout(cwist_http3_context *ctx, int ms)
Set the no-progress timeout (milliseconds, <= 0 disables). Takes effect at cwist_http3_server_loop() ...
Definition http3.c:3777
int cwist_http3_normalize_response_header_name(const char *name, char *out, size_t out_len)
Normalize a response header name to lowercase HTTP/3 form.
Definition http3.c:1105
struct h3_conn_ctx h3_conn_ctx_t
int cwist_http3_set_stream_priority(cwist_http_request *req, unsigned priority)
Deprecated and always refused: returns -1.
Definition http3.c:3508
int cwist_webtransport_close_stream(void *stream)
Stub used when the WebTransport server is not compiled in: always -1.
Definition http3.c:3722
int cwist_http3_push_resource(cwist_http_request *req, const char *path, const char *content_type)
Promise a pushed resource from inside a request handler.
Definition http3.c:3469
cwist_error_t cwist_http3_init_context_ephemeral(cwist_http3_context **ctx)
Create an HTTP/3 server context with an ephemeral self-signed certificate ("localhost",...
Definition http3.c:2721
HTTP/3 (QUIC-based) Protocol Definitions for CWIST.
void(* cwist_webtransport_handler_func)(cwist_http_request *req, cwist_http_response *res, void *stream)
Callback function type for handling WebTransport sessions over HTTP/3.
Definition http3.h:27
void(* cwist_http3_request_handler_func)(void *user_ctx, cwist_http_request *req, cwist_http_response *res)
Callback function type for handling HTTP/3 requests.
Definition http3.h:79
#define CWIST_HTTP_MAX_BODY_SIZE
Definition http.h:41
char * cwist_http_header_get(cwist_http_header_node *head, const char *key)
Find a header value by key.
Definition http.c:1880
@ CWIST_HTTP_INTERNAL_ERROR
Definition http.h:35
@ CWIST_HTTP_BAD_REQUEST
Definition http.h:31
@ CWIST_HTTP_OK
Definition http.h:28
void cwist_http_request_destroy(cwist_http_request *req)
Destroy an HTTP request object.
Definition http.c:2180
cwist_http_method_t
Definition http.h:16
@ CWIST_HTTP_OPTIONS
Definition http.h:23
@ CWIST_HTTP_HEAD
Definition http.h:22
@ CWIST_HTTP_PUT
Definition http.h:19
@ CWIST_HTTP_GET
Definition http.h:17
@ CWIST_HTTP_DELETE
Definition http.h:20
cwist_error_t cwist_http_header_add(cwist_http_header_node **head, const char *key, const char *value)
Add a header to the list.
Definition http.c:1861
void cwist_http_response_destroy(cwist_http_response *res)
Destroy an HTTP response object.
Definition http.c:2315
cwist_http_response * cwist_http_response_create(void)
Create a new HTTP response object.
Definition http.c:2291
cwist_http_method_t cwist_http_string_to_method(const char *method_str)
Convert method string to enum.
Definition http.c:1629
cwist_http_request * cwist_http_request_create(void)
Create a new HTTP request object.
Definition http.c:2056
Lightweight macro-based internal logging for CWIST.
#define CWIST_LOG_ERROR(fmt,...)
Definition log.h:57
#define CWIST_LOG_WARN(fmt,...)
Definition log.h:56
cwist_query_map * cwist_query_map_create_in_arena(void *arena)
Allocate a query map within a memory arena to avoid GC/heap allocations.
Definition query.c:61
void cwist_query_map_clear(cwist_query_map *map)
Clear all entries.
Definition query.c:93
void cwist_query_map_parse(cwist_query_map *map, const char *raw_query)
Parse raw query strings (e.g., "a=1&b=2") into the map.
Definition query.c:319
size_t batch
Definition otel.c:54
size_t count
Definition otel.c:58
size_t cap
Definition otel.c:56
mbedTLS TLS backend: RAND_bytes over mbedTLS CTR-DRBG.
#define RAND_bytes
Definition rand.h:17
TCP-like sequenced message fragmentation and reassembly.
cwist_seq_assembler_t * cwist_seq_assembler_create_limited(size_t max_data_len)
Create an assembler with a maximum possible reassembled size.
Definition seq.c:211
bool cwist_seq_chunk_parse(const uint8_t *data, size_t len, cwist_seq_chunk_t *out)
Parse a sequence chunk from raw bytes.
Definition seq.c:63
void cwist_seq_assembler_destroy(cwist_seq_assembler_t *a)
Destroy an assembler and its internal buffer.
Definition seq.c:223
bool cwist_seq_assembler_get_data(cwist_seq_assembler_t *a, const uint8_t **out_data, size_t *out_len)
Borrow the assembled message when complete.
Definition seq.c:390
bool cwist_seq_assembler_feed(cwist_seq_assembler_t *a, const cwist_seq_chunk_t *chunk)
Feed one parsed chunk into the assembler.
Definition seq.c:268
struct cwist_seq_assembler cwist_seq_assembler_t
Opaque stateful assembler that reorders chunks and detects duplicates.
Definition seq.h:69
#define CWIST_SEQ_HEADER_SIZE
Definition seq.h:29
Unified Graceful Shutdown support for CWIST.
atomic_int g_cwist_running
Global running flag. Checked by all server event loops.
Definition shutdown.c:17
mbedTLS TLS backend: OpenSSL-shaped seam over the CWIST TLS subset.
#define SSL_CTX_check_private_key
Definition ssl.h:113
#define SSL_CTX_get_ex_new_index
Definition ssl.h:121
#define TLS1_3_VERSION
Definition ssl.h:49
struct cwist_mtls_ssl SSL
Definition ssl.h:44
#define SSL_CTX_set_tlsext_ticket_key_cb
Definition ssl.h:120
#define SSL_TLSEXT_ERR_OK
Definition ssl.h:84
#define SSL_TLSEXT_ERR_NOACK
Definition ssl.h:85
#define SSL_CTX_set_session_cache_mode
Definition ssl.h:114
#define SSL_CTX_sess_set_cache_size
Definition ssl.h:115
#define SSL_CTX_new
Definition ssl.h:98
#define SSL_CTX_get_ex_data
Definition ssl.h:123
#define SSL_CTX_use_PrivateKey_file
Definition ssl.h:112
#define SSL_FILETYPE_PEM
Definition ssl.h:77
#define SSL_CTX_free
Definition ssl.h:99
#define SSL_CTX_set_ex_data
Definition ssl.h:122
#define SSL_CTX_use_certificate_chain_file
Definition ssl.h:111
struct cwist_mtls_method SSL_METHOD
Definition ssl.h:45
#define SSL_CTX_set_default_verify_paths
Definition ssl.h:125
#define SSL_CTX_set_alpn_select_cb
Definition ssl.h:118
#define SSL_SESS_CACHE_SERVER
Definition ssl.h:74
struct cwist_mtls_ctx SSL_CTX
Definition ssl.h:43
#define SSL_get_SSL_CTX
Definition ssl.h:176
#define SSL_CTX_set_min_proto_version
Definition ssl.h:106
Integrated error structure used throughout the system.
Definition cwist_err.h:72
HTTP/3 server context encapsulating lsquic/BoringSSL settings.
Definition http3.h:35
cwist_webtransport_handler_func wt_handler
Definition http3.h:50
void * user_ctx
Definition http3.h:40
int noprogress_timeout_ms
Definition http3.h:57
void(* wt_new_stream_handler)(void *stream, void *user_ctx)
Definition http3.h:51
void * wt_new_stream_ctx
Definition http3.h:53
int early_data_enabled
Definition http3.h:44
volatile int running
Definition http3.h:41
void(* handler)(void *user_ctx, cwist_http_request *req, cwist_http_response *res)
Definition http3.h:39
SSL_CTX * ssl_ctx
Definition http3.h:36
void * ticket_key
Definition http3.h:69
int handshake_timeout_ms
Definition http3.h:55
void * datagram_user_ctx
Definition http3.h:49
pthread_rwlock_t ssl_ctx_lock
Definition http3.h:67
SSL_CTX * ssl_ctx_handout
Definition http3.h:68
void(* datagram_cb)(const void *data, size_t len, void *user_ctx)
Definition http3.h:48
Linked list for headers to handle multiple headers easily.
Definition http.h:59
cwist_sstring * value
Definition http.h:61
cwist_sstring * key
Definition http.h:60
struct cwist_http_header_node * next
Definition http.h:62
cwist_query_map * query_params
Parsed query parameters.
Definition http.h:69
size_t content_length
Definition http.h:79
cwist_sstring * path
e.g., "/users/1"
Definition http.h:67
cwist_sstring * query
e.g., "active=true" (raw)
Definition http.h:68
void * arena
Per-request arena for bump-allocated structs (internal).
Definition http.h:187
void * private_data
Internal framework use (protocol-specific context).
Definition http.h:78
cwist_http_header_node * headers
Definition http.h:72
cwist_http_method_t method
Definition http.h:66
HTTP Response Object. Supports standard string body or Zero-Copy pointer body.
Definition http.h:82
cwist_sstring * body
Definition http.h:87
cwist_http_status_t status_code
Definition http.h:84
cwist_http_header_node * headers
Definition http.h:86
Parsed view of one sequenced chunk.
Definition seq.h:45
size_t size
Definition sstring.h:20
char * data
Access directly only when raw handling is necessary.
Definition sstring.h:15
struct lsxpack_header * arr
Definition h3_hdr_out.h:19
Unified error code and type definitions for the CWIST system layer.
int cwist_tls_autoload_intermediates(SSL_CTX *ssl_ctx)
Autoload missing intermediate CA certificates into an SSL_CTX chain.
Definition tls_chain.c:568
unsigned long cwist_tls_sess_cache_size(void)
Definition tls_chain.c:537