11#ifndef _DARWIN_C_SOURCE
12#define _DARWIN_C_SOURCE
14#elif !defined(__FreeBSD__) && !defined(__NetBSD__) && !defined(__OpenBSD__) && \
15 !defined(__DragonFly__)
16#define _POSIX_C_SOURCE 200809L
25#include <ttak/timing/timing.h>
26#include <ttak/async/sched.h>
37#include <netinet/in.h>
38#include <sys/socket.h>
51#include <openssl/x509.h>
53#include <openssl/rsa.h>
54#include <openssl/bn.h>
66#ifdef CWIST_WEBTRANSPORT_SERVER
67#ifdef CWIST_HAVE_LSQUIC_WT
69#define CWIST_WT_SESSION_API 1
70#elif LSQUIC_WEBTRANSPORT_SERVER_SUPPORT
71#define CWIST_WT_STOCK_LSQUIC 1
73#error "CWIST_WEBTRANSPORT_SERVER needs lsquic_wt.h or LSQUIC_WEBTRANSPORT_SERVER_SUPPORT"
76#include <lsxpack_header.h>
86#if defined(CMSG_SPACE) && defined(CMSG_FIRSTHDR) && defined(CMSG_NXTHDR) && defined(IP_TOS)
87#define CWIST_H3_HAVE_ECN_CMSG 1
95static int g_h3_global_ref = 0;
102static void h3_global_init(
void) {
104 if (g_h3_global_ref == 0) {
116static void h3_global_cleanup(
void) {
118 if (g_h3_global_ref > 0) {
120 if (g_h3_global_ref == 0) {
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
138typedef struct h3_stream_ctx {
152 unsigned char *seq_buf;
156 unsigned pseudo_seen;
157 int seen_regular_header;
161#ifdef CWIST_WEBTRANSPORT_SERVER
165#ifdef CWIST_WT_STOCK_LSQUIC
177typedef struct h3_dgram_node {
178 struct h3_dgram_node *next;
192typedef struct h3_conn_ctx {
197#ifdef CWIST_WT_STOCK_LSQUIC
215#ifdef CWIST_WEBTRANSPORT_SERVER
231#ifdef CWIST_WT_STOCK_LSQUIC
238#define CWIST_WT_HANDLE_MAGIC UINT64_C(0x4357495354575431)
239#define CWIST_WT_HANDLE_VERSION 1
248 if (!ptr)
return NULL;
250 if (!handle)
return NULL;
266 while (handle->first_child) {
268 handle->first_child = next->next_sibling;
272 next->first_child =
NULL;
273 next->next_sibling =
NULL;
276 if (handle->parent) {
279 if (*
link == handle) {
280 *
link = handle->next_sibling;
283 link = &(*link)->next_sibling;
288 handle->parent =
NULL;
289 handle->first_child =
NULL;
290 handle->next_sibling =
NULL;
294#ifdef CWIST_WT_SESSION_API
300 if (!
parent || !child)
return;
302 child->next_sibling =
parent->first_child;
303 parent->first_child = child;
333#ifdef CWIST_WT_SESSION_API
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
359typedef struct cwist_h3_hset {
361 struct cwist_h3_hset *next;
362 struct cwist_h3_hset **prev;
378 if (!ctx || !
hset)
return;
411 cwist_h3_hset_untrack(
hset);
427 hset->stream = stream;
452 (
size_t)
xhdr->name_offset >=
sizeof(
hset->decode_buf) ||
455 "[HTTP/3] Rejecting oversized QPACK header resize (space=%zu, offset=%d)\n",
466 sizeof(
hset->decode_buf) -
hset->decode_off);
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,
491 hset->decode_off += total;
500static void cwist_h3_hsi_discard(
void *
hset_p) {
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,
512#ifdef CWIST_WT_SESSION_API
536 if (!h3_ctx)
return NULL;
571#if defined(__linux__) && defined(UDP_SEGMENT)
584 if (
spec->local_sa &&
spec->dest_sa) {
588#if defined(__linux__) && defined(IP_PKTINFO)
597#elif defined(IP_SENDSRCADDR)
610#if defined(IPV6_PKTINFO)
624#if defined(CWIST_H3_HAVE_ECN_CMSG)
635#if defined(IPV6_TCLASS)
665 msg.msg_name = (
void *)
spec->dest_sa;
675#if defined(__linux__)
677#define UDP_SEGMENT 103
688 for (
unsigned k = 0;
k <
spec->iovlen;
k++)
n +=
spec->iov[
k].iov_len;
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) {
707 if (
x->sin_port !=
y->sin_port ||
x->sin_addr.s_addr !=
y->sin_addr.s_addr)
712 if (
x->sin6_port !=
y->sin6_port ||
713 memcmp(&
x->sin6_addr, &
y->sin6_addr,
sizeof(
x->sin6_addr)) != 0)
717 if (
a->dest_sa->sa_family ==
AF_INET) {
720 return x->sin_port ==
y->sin_port &&
x->sin_addr.s_addr ==
y->sin_addr.s_addr;
724 return x->sin6_port ==
y->sin6_port &&
725 memcmp(&
x->sin6_addr, &
y->sin6_addr,
sizeof(
x->sin6_addr)) == 0;
729#if defined(__linux__)
757 for (
unsigned k = 0;
k <
batch;
k++) {
761 msg->msg_name = (
void *)
spec->dest_sa;
766 msg->msg_iovlen =
spec->iovlen;
774 if ((
unsigned)res <
batch)
break;
779 if (
i > 0)
return (
int)
i;
780 int nw = h3_send_one(udp_fd, &
specs[
i]);
810 int udp_fd = *(
int *)ctx;
811#if defined(__linux__)
826 if (
seg > 0 &&
seg <= 65535) {
842 for (
unsigned k = 0;
k <
run;
k++)
843 for (
unsigned m = 0;
m <
specs[
i +
k].iovlen;
m++)
867 int nw = h3_send_one(udp_fd, &
specs[
i]);
875 return (
i > 0) ? (
int)
i : -1;
887 int nw = h3_send_one(udp_fd, &
specs[
i]);
893 return (
i > 0) ? (
int)
i : -1;
915 static const char *
const protos[] = {
"h3",
"h3-29"};
923 if (ptr + len >
end)
break;
943static int cwist_h3_log_stderr(
void *ctx,
const char *buf,
size_t len) {
989 fprintf(
stderr,
"[HTTP/3] Conn close status=%d msg=%s\n", (
int)status,
994 "[HTTP/3] Conn closed rtt=%u rttvar=%u "
996 "pkts_retx=%" PRIu64 " cwnd=%u\n",
1005 cc->dgram_head =
cc->dgram_tail =
NULL;
1022 st->stream = stream;
1029 st->req->private_data = stream;
1030#ifdef CWIST_WEBTRANSPORT_SERVER
1031 st->is_webtransport = 0;
1059 const char *q =
strchr(path,
'?');
1080static uint8_t h3_xor_bytes(
const unsigned char *buf,
size_t len) {
1082 for (
size_t i = 0;
i < len;
i++)
x ^= buf[
i];
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 ==
'~';
1106 if (!name || !out || out_len == 0)
return -1;
1108 size_t len =
strlen(name);
1109 if (len == 0 || len >= out_len || name[0] ==
':')
return -1;
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;
1128 if (!value)
return 0;
1129 for (
const unsigned char *
p = (
const unsigned char *)value; *
p;
p++) {
1132 if (*
p < 0x20 || *
p == 0x7f)
return 0;
1156static void h3_apply_header(
cwist_http_request *req,
const char *name,
const char *value) {
1157 if (
strcmp(name,
":method") == 0) {
1159 }
else if (
strcmp(name,
":path") == 0) {
1160 h3_parse_path(req, value);
1161 }
else if (
strcmp(name,
":authority") == 0 ||
strcmp(name,
"host") == 0) {
1165 }
else if (
strcmp(name,
":scheme") == 0) {
1167 }
else if (
strcmp(name,
"content-length") == 0) {
1174 }
else if (
strcmp(name,
"content-type") == 0) {
1176 }
else if (
strcmp(name,
"cookie") == 0) {
1178 }
else if (
strcmp(name,
"authorization") == 0) {
1180 }
else if (
strcmp(name,
"accept") == 0) {
1182 }
else if (
strcmp(name,
"user-agent") == 0) {
1184 }
else if (
strcmp(name,
"accept-encoding") == 0) {
1186 }
else if (
strcmp(name,
"accept-language") == 0) {
1188 }
else if (
strcmp(name,
"referer") == 0 ||
strcmp(name,
"referrer") == 0) {
1190 }
else if (
strcmp(name,
"origin") == 0) {
1192 }
else if (
strcmp(name,
"x-requested-with") == 0) {
1194 }
else if (
strcmp(name,
"priority") == 0) {
1201 }
else if (name[0] !=
':') {
1219static int h3_seq_append_and_feed(
h3_stream_ctx_t *
st,
const unsigned char *data,
size_t len) {
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;
1233 if (!
tmp)
return -1;
1237 memcpy(
st->seq_buf +
st->seq_len, data, len);
1241 size_t payload_len = ((
size_t)
st->seq_buf[4] << 8) |
st->seq_buf[5];
1243 if (payload_len == 0 ||
st->seq_len <
chunk_len)
break;
1246 if (!
st->body_assembler) {
1248 if (!
st->body_assembler)
return -1;
1266 st->headers_done = 1;
1270 st->response_ready = 1;
1289 if (
st->headers_done)
return true;
1291 if (!
hset)
return false;
1306 for (
size_t i = 0;
i <
hs->count; ++
i) {
1320 if (name[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);
1339 st->pseudo_seen |=
bit;
1344 st->saw_empty_path = 1;
1347 st->seen_regular_header = 1;
1349 h3_apply_header(
st->req, name, value);
1350#ifdef CWIST_WEBTRANSPORT_SERVER
1353 if (
strcmp(name,
":protocol") == 0 &&
strcmp(value,
"webtransport") == 0) {
1354 st->is_webtransport = 1;
1366 h3_reject_malformed_request(stream,
st);
1369 }
else if (
st->is_connect) {
1374 h3_reject_malformed_request(stream,
st);
1385 h3_reject_malformed_request(stream,
st);
1390 h3_reject_malformed_request(stream,
st);
1394#ifdef CWIST_WEBTRANSPORT_SERVER
1395 if (!
st->is_connect)
st->is_webtransport = 0;
1397 st->headers_done = 1;
1399 st->sequenced_data =
1415 default:
return false;
1419#ifdef CWIST_WT_STOCK_LSQUIC
1433#define H3_WT_CLOSE_SESSION_CAPSULE UINT64_C(0x2843)
1434#define H3_WT_MAX_CLOSE_REASON 1024
1437#define H3_WT_DATAGRAM_BUDGET (1200 - 64)
1441 return v < 64 ? 1 :
v < 16384 ? 2 :
v < 1073741824 ? 4 : 8;
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);
1460 if (len == 0)
return 0;
1461 size_t n = (
size_t)1 << (
p[0] >> 6);
1462 if (len <
n)
return 0;
1464 for (
size_t i = 1;
i <
n;
i++)
v = (
v << 8) |
p[
i];
1473 if (h->session_id ==
id)
return h;
1483 if (*
link == session) {
1484 *
link = session->next_session;
1488 session->next_session =
NULL;
1500 st->wt_dispatched = 1;
1510 session->conn = conn;
1512 session->next_session =
cc->wt_sessions;
1513 cc->wt_sessions = session;
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");
1524 st->response_ready = 1;
1530 st->response_ready = 1;
1544 unsigned char buf[1024];
1565 if (
st->wt_stream)
return true;
1576 st->wt_stream = handle;
1591 unsigned char buf[4096];
1606 const void **payload,
size_t *payload_len) {
1608 if (!
cc || !
cc->wt_sessions)
return false;
1614 *payload = (
const unsigned char *)buf +
n;
1615 *payload_len = len -
n;
1635 unsigned char buf[8192];
1638#ifdef CWIST_WT_STOCK_LSQUIC
1643 if (
st->wt_session) {
1648 h3_process_stream_headers(stream,
st);
1649 if (
st->malformed)
return;
1650#ifdef CWIST_WT_STOCK_LSQUIC
1653 if (
st->headers_done &&
st->is_webtransport && !
st->wt_dispatched) {
1663 if (
st->sequenced_data) {
1664 if (h3_seq_append_and_feed(
st, buf, (
size_t)
nread) != 0) {
1677 h3_json_error(
st->res, 413 ,
1678 "{\"error\":\"payload too large\"}");
1679 st->response_ready = 1;
1688 if (
need >
st->body_cap) {
1689 size_t new_cap =
st->body_cap ?
st->body_cap * 2 : 4096;
1700 st->recv_xor ^= h3_xor_bytes((
const unsigned char *)buf, (
size_t)
nread);
1706 h3_process_stream_headers(stream,
st);
1708 if (
st->malformed)
return;
1710 if (!
st->headers_done) {
1716 st->response_ready = 1;
1723 if (
st->sequenced_data) {
1726 if (
st->seq_len != 0 || !
st->body_assembler ||
1733 "{\"error\":\"incomplete sequenced body, retry\"}");
1735 st->response_ready = 1;
1761 if (
st->body_len > 0 &&
st->req &&
st->req->body) {
1766 if (
st->res &&
st->req) {
1768#ifdef CWIST_WT_SESSION_API
1769 if (
st->is_webtransport && h3_ctx && h3_ctx->
wt_handler) {
1773 .wtci_authority = host,
1774 .wtci_path =
st->req->path ?
st->req->path->data :
NULL,
1776 .wtci_protocol =
NULL,
1782 .wtap_wt_if_ctx =
st,
1783 .wtap_connect_info = &
info,
1795 CWIST_LOG_WARN(
"[HTTP/3] WebTransport accept failed; answering 500");
1797 if (
st->res->body &&
st->res->body->size == 0) {
1799 "{\"error\":\"webtransport accept failed\"}");
1801 if (
st->res->headers) {
1806 if (h3_ctx && h3_ctx->
handler) {
1821 h3_json_error(
st->res, 425 ,
1822 "{\"error\":\"too early, retry after handshake\"}");
1828 st->response_ready = 1;
1833#ifdef CWIST_WT_STOCK_LSQUIC
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;
1864static bool h3_status_forbids_body(
int status_code) {
1865 return (status_code >= 100 && status_code < 200) || status_code == 204 || status_code == 304;
1883 if (!
st || !
st->response_ready)
return;
1891 if (
st->write_state == 0) {
1897 bool bodyless = h3_status_forbids_body(
st->res->status_code);
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");
1915 if (
st->res->status_code < 200 ||
st->res->status_code > 999) {
1922 CWIST_LOG_WARN(
"[HTTP/3] invalid status %d; coercing to 500",
st->res->status_code);
1925 if (!
is_head &&
st->res->body &&
st->res->body->size == 0 && !
st->res->is_ptr_body &&
1926 !
st->res->use_file_stream) {
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);
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;
1957 if (h3_content_length_is_valid(
user_cl)) {
1973 if (
st->res->headers && (body_len > 0 ||
is_head)) {
1975 if (
ct &&
ct[0] !=
'\0') h3_hdr_out_add_str(&
hdrs,
"content-type",
ct);
1980 while (node &&
hdrs.count <
hdrs.max) {
2015#ifdef CWIST_WT_STOCK_LSQUIC
2026 if (
st->res->use_file_stream &&
st->res->file_stream_fd >= 0) {
2028 }
else if (
st->res->is_ptr_body &&
st->res->ptr_body) {
2030 }
else if (
st->res->body) {
2034#ifdef CWIST_WT_STOCK_LSQUIC
2035 if (
st->wt_session)
eos = 0;
2050#ifdef CWIST_WT_STOCK_LSQUIC
2051 if (
st->wt_session) {
2054 st->write_state = 3;
2068 st->write_state = 2;
2072 st->write_state = 1;
2077 if (
st->write_state == 1 &&
st->res) {
2078 bool bodyless = h3_status_forbids_body(
st->res->status_code);
2080 size_t body_len = 0;
2083 if (
st->res->use_file_stream) {
2097#define H3_FILE_CHUNKS_PER_TICK 4
2099 while (
st->res->file_stream_fd >= 0 &&
st->body_sent <
st->res->file_stream_len) {
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;
2124 st->send_xor ^= h3_xor_bytes((
const unsigned char *)
file_buf, (
size_t)
nw);
2132 }
else if (
nr == 0) {
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) {
2158 body_len =
st->res->ptr_body_len;
2160 }
else if (
st->res->body) {
2161 body_len =
st->res->body->size;
2167 st->write_state = 2;
2173 while (
body_data && body_len > 0 &&
st->body_sent < body_len) {
2178 h3_xor_bytes((
const unsigned char *)(
body_data +
st->body_sent), (
size_t)
n);
2180 if (
st->body_sent < body_len) {
2198 if (
st->body_sent >= body_len) {
2199 st->write_state = 2;
2215#ifdef CWIST_WT_STOCK_LSQUIC
2216 if (
st->wt_session) {
2253 size_t to_copy =
n->len < len ?
n->len : len;
2255 cc->dgram_head =
n->next;
2256 if (!
cc->dgram_head)
cc->dgram_tail =
NULL;
2272static void cwist_h3_on_datagram(
lsquic_conn_t *conn,
const void *buf,
size_t len) {
2275#ifdef CWIST_WT_STOCK_LSQUIC
2278 const void *payload =
NULL;
2279 size_t payload_len = 0;
2289#ifdef CWIST_WT_SESSION_API
2305 if (
st &&
st->req &&
st->res) {
2323 unsigned status,
const char *
reason,
2361 ctx = h3_shared_ctx(conn);
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,
2493 unsigned char name[16];
2494 unsigned char aes_key[32];
2495 unsigned char hmac_key[32];
2498static int g_h3_ticket_key_ex_data_idx = -1;
2505static void cwist_h3_ticket_key_ex_data_init(
void) {
2523 if (ssl_ctx && g_h3_ticket_key_ex_data_idx >= 0) {
2555static void cwist_h3_setup_session_tickets(
SSL_CTX *ssl_ctx, cwist_h3_ticket_key **
key_io) {
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;
2591 cwist_h3_setup_session_tickets(ssl_ctx,
key_io);
2605static void cwist_h3_free_ticket_key(cwist_h3_ticket_key *key) {
2625 cwist_h3_ticket_key **
key_io) {
2631 err.error.err_i16 = -1;
2640 err.error.err_i16 = -1;
2646 err.error.err_i16 = -1;
2652 err.error.err_i16 = -1;
2657 err.error.err_i16 = 0;
2674 const char *key_path) {
2676 if (!ctx || !cert_path || !key_path) {
2677 err.error.err_i16 = -1;
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();
2695 cwist_h3_free_ticket_key(
tkey);
2696 h3_global_cleanup();
2697 err.error.err_i16 = -1;
2702 (*ctx)->ssl_ctx = ssl_ctx;
2703 (*ctx)->udp_fd = -1;
2704 (*ctx)->ticket_key =
tkey;
2706 err.error.err_i16 = 0;
2724 err.error.err_i16 = -1;
2734 h3_global_cleanup();
2735 err.error.err_i16 = -1;
2739 cwist_h3_ssl_ctx_init(ssl_ctx, 0, &
tkey);
2743 cwist_h3_free_ticket_key(
tkey);
2745 h3_global_cleanup();
2746 err.error.err_i16 = -1;
2751 cwist_h3_free_ticket_key(
tkey);
2753 h3_global_cleanup();
2754 err.error.err_i16 = -1;
2760 cwist_h3_free_ticket_key(
tkey);
2762 h3_global_cleanup();
2763 err.error.err_i16 = -1;
2771 cwist_h3_free_ticket_key(
tkey);
2773 h3_global_cleanup();
2774 err.error.err_i16 = -1;
2792 cwist_h3_free_ticket_key(
tkey);
2794 h3_global_cleanup();
2795 err.error.err_i16 = -1;
2804 cwist_h3_free_ticket_key(
tkey);
2806 h3_global_cleanup();
2807 err.error.err_i16 = -1;
2812 (*ctx)->ssl_ctx = ssl_ctx;
2813 (*ctx)->udp_fd = -1;
2814 (*ctx)->ticket_key =
tkey;
2816 err.error.err_i16 = 0;
2832 if (!engine)
return;
2851 cwist_h3_engine_graceful_stop(ctx);
2855 cwist_h3_hset_sweep(ctx);
2871 h3_global_cleanup();
2880 const char *key_path) {
2882 if (!ctx || !cert_path || !key_path) {
2883 err.error.err_i16 = -1;
2892 if (!cwist_error_is_ok(&
err)) {
2905 err.error.err_i16 = 0;
2936 err.error.err_i16 = -1;
2944 settings.es_max_delayed_0rtt_packets = 32;
2946#ifdef CWIST_WT_STOCK_LSQUIC
2951 settings.es_webtransport_server = 1;
2952 settings.es_max_webtransport_server_streams = 1;
2955#elif defined(CWIST_WT_SESSION_API)
2964 settings.es_max_webtransport_sessions = 1;
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;
2992 err.error.err_i16 = -1;
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,
3016 err.error.err_i16 = -1;
3022 if (
getenv(
"CWIST_H3_DEBUG")) {
3024 .log_buf = cwist_h3_log_stderr,
3044 socklen_t local_addr_len =
sizeof(local_addr);
3057#ifdef IPV6_RECVPKTINFO
3061#ifdef IPV6_RECVTCLASS
3070 err.error.err_i16 = -1;
3078 err.error.err_i16 = -1;
3085 ev.data.fd = udp_fd;
3089 err.error.err_i16 = -1;
3096#if defined(__linux__) && defined(_GNU_SOURCE)
3097#define H3_RECV_BATCH 32
3125 err.error.err_i16 = -1;
3137 int timeout_ms = 50;
3142 else if (
diff < 1000)
3144 else if (
diff > 1000000)
3147 timeout_ms = (
diff + 999) / 1000;
3161 fprintf(
stderr,
"[HTTP/3] UDP socket error, exiting loop.\n");
3172 fprintf(
stderr,
"[HTTP/3] UDP socket closed, exiting loop.\n");
3179 fprintf(
stderr,
"[HTTP/3] UDP socket error, exiting loop.\n");
3185#if defined(__linux__) && defined(_GNU_SOURCE)
3213 for (
int b = 0; b <
nmsgs; b++) {
3215 if (
nr == 0)
continue;
3231 sin->sin_addr =
pi->ipi_addr;
3232 if (local_addr_len >=
sizeof(
struct sockaddr_in)) {
3254 sin6->sin6_addr =
pi6->ipi6_addr;
3282 socklen_t peer_addr_len =
sizeof(peer_addr);
3289 msg.msg_name = &peer_addr;
3290 msg.msg_namelen = peer_addr_len;
3323 sin->sin_addr =
pi->ipi_addr;
3324 if (local_addr_len >=
sizeof(
struct sockaddr_in)) {
3344 sin6->sin6_addr =
pi6->ipi6_addr;
3378#if defined(__linux__) && defined(_GNU_SOURCE)
3387 if (ctx && ctx->
engine) {
3388 cwist_h3_engine_graceful_stop(ctx);
3391 cwist_h3_hset_sweep(ctx);
3393 err.error.err_i16 = 0;
3422 if (!ssl_ctx)
return;
3482 h3_hdr_out_add_str(&
hdrs,
":method",
"GET");
3484 h3_hdr_out_add_str(&
hdrs,
":scheme",
"https");
3485 h3_hdr_out_add_str(&
hdrs,
":path", path);
3488 h3_hdr_out_add_str(&
hdrs,
":authority", authority ? authority :
"localhost");
3511 CWIST_LOG_WARN(
"[HTTP/3] cwist_http3_set_stream_priority() is deprecated "
3512 "and refused (RFC 9218 violation); use the Priority header");
3537 void (*handler)(
void *stream,
void *user_ctx),
3545#ifdef CWIST_WEBTRANSPORT_SERVER
3553 if (!stream || !buf)
return -1;
3557#ifdef CWIST_WT_STOCK_LSQUIC
3571 if (!stream || !data)
return -1;
3583 if (!stream)
return -1;
3595 if (!stream)
return -1;
3601#ifdef CWIST_WT_STOCK_LSQUIC
3635 if (!session)
return -1;
3641 if (!stream)
return -1;
3666 if (!session)
return -1;
3672 if (!stream)
return -1;
3800 void (*cb)(
const void *data,
size_t len,
void *user_ctx),
3825 if (!
c || !data || len == 0)
return -1;
3831 if (!node)
return -1;
3832 node->data =
malloc(len);
3837 memcpy(node->data, data, len);
3846 if (
cc->dgram_tail) {
3847 cc->dgram_tail->next = node;
3849 cc->dgram_head = node;
3851 cc->dgram_tail = node;
3858#ifdef CWIST_WEBTRANSPORT_SERVER
3860#ifdef CWIST_WT_STOCK_LSQUIC
3874 if (!session || !data || len == 0)
return -1;
3879 memcpy(buf + prefix, data, len);
3936 if (!session || !data || len == 0)
return -1;
3938 if (!
sess)
return -1;
3949 if (!session)
return 0;
3951 if (!
sess)
return 0;
3965 if (!session)
return -1;
3967 if (!
sess)
return -1;
Memory allocation wrappers backed by libttak.
void cwist_free(void *ptr)
Free memory obtained via cwist_alloc/cwist_strdup/etc.
void * cwist_alloc(size_t size)
Allocate zeroed memory tracked by libttak.
cwist_error_t cwist_sstring_assign_len(cwist_sstring *str, const char *data, size_t len)
Assign raw bytes with length.
cwist_error_t cwist_sstring_assign(cwist_sstring *str, const char *data)
Assign a C string.
@ CWIST_ERR_INT16
used when checking common errcodes in Unix/Linux
cwist_error_t make_error(cwist_errtype_t type)
Creates an error object initialized with the specified type.
mbedTLS TLS backend: EVP subset (HMAC digest tags, AES-256-CBC).
#define EVP_DecryptInit_ex
struct cwist_mtls_cipher_ctx EVP_CIPHER_CTX
#define EVP_EncryptInit_ex
Outgoing HTTP/3 header set builder for lsquic (private).
mbedTLS TLS backend: HMAC subset (HMAC-SHA256, one-shot + streaming).
struct cwist_mtls_hmac_ctx HMAC_CTX
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.
int cwist_http3_method_is_idempotent(const char *method_str)
String-based idempotency check (RFC 9110), used by tests and external checks.
void cwist_http3_set_early_data_guard(cwist_http3_context *ctx, int enabled)
Enable or disable the 0-RTT replay guard.
void cwist_http3_set_webtransport_handler(cwist_http3_context *ctx, cwist_webtransport_handler_func handler)
Register the WebTransport session handler (extended CONNECT).
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,...
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.
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.
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...
size_t cwist_webtransport_max_datagram_size(void *session)
Stub used when the WebTransport server is not compiled in: always 0.
#define H3_MAX_RESPONSE_HEADERS
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...
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.
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.
int cwist_webtransport_open_uni_stream(void *session)
Stub used when the WebTransport server is not compiled in: always -1.
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...
#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.
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.
int cwist_webtransport_open_bidi_stream(void *session)
Stub used when the WebTransport server is not compiled in: always -1.
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.
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.
int cwist_http3_send_datagram(void *conn, const void *data, size_t len)
Queue an outgoing H3 datagram on a connection.
int cwist_webtransport_flush(void *stream)
Stub used when the WebTransport server is not compiled in: always -1.
#define H3_DECODE_BUF_SIZE
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.
#define H3_RESPONSE_HEADER_BUF_SIZE
void cwist_http3_set_datagram_enabled(cwist_http3_context *ctx, int enabled)
Enable or disable H3 datagrams for this context.
#define H3_PSEUDO_PROTOCOL
#define H3_PSEUDO_AUTHORITY
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...
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...
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() ...
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.
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.
int cwist_webtransport_close_stream(void *stream)
Stub used when the WebTransport server is not compiled in: always -1.
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.
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",...
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.
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.
#define CWIST_HTTP_MAX_BODY_SIZE
char * cwist_http_header_get(cwist_http_header_node *head, const char *key)
Find a header value by key.
@ CWIST_HTTP_INTERNAL_ERROR
void cwist_http_request_destroy(cwist_http_request *req)
Destroy an HTTP request object.
cwist_error_t cwist_http_header_add(cwist_http_header_node **head, const char *key, const char *value)
Add a header to the list.
void cwist_http_response_destroy(cwist_http_response *res)
Destroy an HTTP response object.
cwist_http_response * cwist_http_response_create(void)
Create a new HTTP response object.
cwist_http_method_t cwist_http_string_to_method(const char *method_str)
Convert method string to enum.
cwist_http_request * cwist_http_request_create(void)
Create a new HTTP request object.
Lightweight macro-based internal logging for CWIST.
#define CWIST_LOG_ERROR(fmt,...)
#define CWIST_LOG_WARN(fmt,...)
cwist_query_map * cwist_query_map_create_in_arena(void *arena)
Allocate a query map within a memory arena to avoid GC/heap allocations.
void cwist_query_map_clear(cwist_query_map *map)
Clear all entries.
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.
mbedTLS TLS backend: RAND_bytes over mbedTLS CTR-DRBG.
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.
bool cwist_seq_chunk_parse(const uint8_t *data, size_t len, cwist_seq_chunk_t *out)
Parse a sequence chunk from raw bytes.
void cwist_seq_assembler_destroy(cwist_seq_assembler_t *a)
Destroy an assembler and its internal buffer.
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.
bool cwist_seq_assembler_feed(cwist_seq_assembler_t *a, const cwist_seq_chunk_t *chunk)
Feed one parsed chunk into the assembler.
struct cwist_seq_assembler cwist_seq_assembler_t
Opaque stateful assembler that reorders chunks and detects duplicates.
#define CWIST_SEQ_HEADER_SIZE
Unified Graceful Shutdown support for CWIST.
atomic_int g_cwist_running
Global running flag. Checked by all server event loops.
mbedTLS TLS backend: OpenSSL-shaped seam over the CWIST TLS subset.
#define SSL_CTX_check_private_key
#define SSL_CTX_get_ex_new_index
struct cwist_mtls_ssl SSL
#define SSL_CTX_set_tlsext_ticket_key_cb
#define SSL_TLSEXT_ERR_OK
#define SSL_TLSEXT_ERR_NOACK
#define SSL_CTX_set_session_cache_mode
#define SSL_CTX_sess_set_cache_size
#define SSL_CTX_get_ex_data
#define SSL_CTX_use_PrivateKey_file
#define SSL_CTX_set_ex_data
#define SSL_CTX_use_certificate_chain_file
struct cwist_mtls_method SSL_METHOD
#define SSL_CTX_set_default_verify_paths
#define SSL_CTX_set_alpn_select_cb
#define SSL_SESS_CACHE_SERVER
struct cwist_mtls_ctx SSL_CTX
#define SSL_CTX_set_min_proto_version
Integrated error structure used throughout the system.
HTTP/3 server context encapsulating lsquic/BoringSSL settings.
cwist_webtransport_handler_func wt_handler
int noprogress_timeout_ms
void(* wt_new_stream_handler)(void *stream, void *user_ctx)
void(* handler)(void *user_ctx, cwist_http_request *req, cwist_http_response *res)
pthread_rwlock_t ssl_ctx_lock
SSL_CTX * ssl_ctx_handout
void(* datagram_cb)(const void *data, size_t len, void *user_ctx)
cwist_query_map * query_params
Parsed query parameters.
cwist_sstring * path
e.g., "/users/1"
cwist_sstring * query
e.g., "active=true" (raw)
void * arena
Per-request arena for bump-allocated structs (internal).
void * private_data
Internal framework use (protocol-specific context).
cwist_http_header_node * headers
cwist_http_method_t method
HTTP Response Object. Supports standard string body or Zero-Copy pointer body.
cwist_http_status_t status_code
cwist_http_header_node * headers
Parsed view of one sequenced chunk.
char * data
Access directly only when raw handling is necessary.
struct lsxpack_header * arr
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.
unsigned long cwist_tls_sess_cache_size(void)