8#define _POSIX_C_SOURCE 200809L
21#include <sys/socket.h>
40#include <sys/eventfd.h>
47#define CWIST_HTTP2_FRAME_HEADER_SIZE 9
48#define CWIST_HTTP2_CONNECTION_PREFACE "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"
49#define CWIST_HTTP2_CONNECTION_PREFACE_LEN 24
50#define CWIST_HTTP2_MAX_FRAME_SIZE 16384
51#define CWIST_HTTP2_MAX_CONCURRENT_STREAMS 100
54#define CWIST_HTTP2_HEADER_TABLE_SIZE 4096
56#define CWIST_HTTP2_FLAG_END_STREAM 0x01
57#define CWIST_HTTP2_FLAG_END_HEADERS 0x04
58#define CWIST_HTTP2_FLAG_PADDED 0x08
59#define CWIST_HTTP2_FLAG_PRIORITY 0x20
60#define CWIST_HTTP2_FLAG_ACK 0x01
62#define CWIST_HTTP2_FRAME_DATA 0x00
63#define CWIST_HTTP2_FRAME_HEADERS 0x01
64#define CWIST_HTTP2_FRAME_RST_STREAM 0x03
65#define CWIST_HTTP2_FRAME_SETTINGS 0x04
66#define CWIST_HTTP2_FRAME_PING 0x06
67#define CWIST_HTTP2_FRAME_GOAWAY 0x07
68#define CWIST_HTTP2_FRAME_WINDOW_UPDATE 0x08
69#define CWIST_HTTP2_FRAME_CONTINUATION 0x09
70#define CWIST_HTTP2_FRAME_PUSH_PROMISE 0x05
76#define CWIST_HTTP2_USE_SEQUENCED_DATA 0
77#define CWIST_HTTP2_SEQ_CHUNK_PAYLOAD(max_frame) \
78 ((uint16_t)((max_frame) > CWIST_SEQ_HEADER_SIZE ? (max_frame) - CWIST_SEQ_HEADER_SIZE : 0))
80#define H2_ERR_NO_ERROR 0x0
81#define H2_ERR_PROTOCOL_ERROR 0x1
82#define H2_ERR_FLOW_CONTROL_ERROR 0x3
83#define H2_ERR_SETTINGS_TIMEOUT 0x4
84#define H2_ERR_STREAM_CLOSED 0x5
85#define H2_ERR_FRAME_SIZE_ERROR 0x6
86#define H2_ERR_REFUSED_STREAM 0x7
87#define H2_ERR_COMPRESSION_ERROR 0x9
88#define H2_ERR_CONNECT_ERROR 0xa
89#define H2_ERR_ENHANCE_YOUR_CALM 0xb
92#define CWIST_HTTP2_DEFAULT_RST_BURST 100
93#define CWIST_HTTP2_DEFAULT_RST_RATE 100
94#define CWIST_HTTP2_DEFAULT_PING_BURST 100
95#define CWIST_HTTP2_DEFAULT_PING_RATE 50
96#define CWIST_HTTP2_MAX_HEADER_BLOCK_SIZE 65536
97#define CWIST_HTTP2_MAX_CONTINUATIONS 32
98#define CWIST_HTTP2_MAX_HEADERS_PER_REQUEST 256
104static inline uint64_t h2_now_ns(
void) {
111 return h2_now_ns() / 1000000;
119 return h2_now_ns() / 1000;
128static inline long h2_env_long(
const char *
env,
long fallback,
long min) {
141static int h2_idle_timeout_ms(
void) {
142 static int timeout_ms = 0;
143 if (timeout_ms == 0) {
158#define CWIST_HTTP2_GOAWAY_GRACE_MS_DEFAULT 2000
159static int h2_goaway_grace_ms(
void) {
174static uint32_t h2_max_rst_burst(
void) {
189static uint32_t h2_max_rst_rate(
void) {
230 unsigned char hdr[9];
231 unsigned char *payload;
269static void h2_async_node_discard(h2_async_node *
n) {
293 q->wake_wr = q->wake_rd;
297 for (
int i = 0;
i < 2;
i++) {
301 q->wake_rd =
pfds[0];
302 q->wake_wr =
pfds[1];
330 if (q->wake_rd >= 0)
close(q->wake_rd);
331 if (q->wake_wr >= 0 && q->wake_wr != q->wake_rd)
close(q->wake_wr);
352 n->stream_id = stream_id;
356 n->send_owned = send_owned;
361 h2_async_node_discard(
n);
374 if (q->wake_wr >= 0) {
388static int cwist_h2_async_queue_fd(
const cwist_h2_async_queue *q) {
389 return q ? q->wake_rd : -1;
394static void cwist_h2_async_queue_close(cwist_h2_async_queue *q) {
398 q->head = q->tail =
NULL;
402 h2_async_node_discard(
list);
412 uint32_t peer_max_concurrent_streams;
419 unsigned char ping_payload[8];
420 bool ping_outstanding;
424 unsigned char *out_buf;
427 unsigned char *cont_buf;
431 bool expecting_continuation;
432 bool cont_end_stream;
493 memset(hc, 0,
sizeof(*hc));
496 hc->peer_initial_window_size = 65535;
501 hc->fc.send_window = 65535;
502 hc->cont_end_stream =
false;
503 hc->last_activity = h2_now_ms();
504 hc->rst_budget = h2_max_rst_burst();
505 hc->rst_last_refill_ms = hc->last_activity;
507 hc->ping_last_refill_ms = hc->last_activity;
508 hc->out_rst_budget = h2_max_rst_burst();
509 hc->out_rst_last_refill_ms = hc->last_activity;
510 hc->cont_frame_count = 0;
520 hc->fc_mu_init =
false;
530static void h2_fc_credit_signal(h2_conn *hc) {
531 if (!hc->fc_mu_init)
return;
547static void h2_stream_drain_waiters(h2_conn *hc,
h2_stream *s) {
548 if (!hc->fc_mu_init)
return;
550 s->fc_detached =
true;
565static void h2_conn_destroy(h2_conn *hc) {
571 h2_stream_drain_waiters(hc, s);
572 if (s->hook_ctx && hc->hooks && hc->hooks->on_close)
573 hc->hooks->on_close(hc->hook_ctx, s->hook_ctx);
579 if (hc->hooks && hc->hooks->on_conn_close) hc->hooks->on_conn_close(hc->hook_ctx);
590 hc->hpack_head =
NULL;
600 if (hc->fc_mu_init) {
608 cwist_h2_async_queue_close(hc->async_q);
622static bool h2_conn_consume_rst_budget(h2_conn *hc) {
626 hc->rst_budget = h2_max_rst_burst();
627 hc->rst_last_refill_ms =
now;
631 hc->rst_budget +=
add;
632 if (hc->rst_budget > h2_max_rst_burst()) {
633 hc->rst_budget = h2_max_rst_burst();
635 hc->rst_last_refill_ms =
now;
639 if (hc->rst_budget == 0) {
653static bool h2_conn_consume_out_rst_budget(h2_conn *hc) {
657 hc->out_rst_budget = h2_max_rst_burst();
658 hc->out_rst_last_refill_ms =
now;
662 hc->out_rst_budget +=
add;
663 if (hc->out_rst_budget > h2_max_rst_burst()) hc->out_rst_budget = h2_max_rst_burst();
664 hc->out_rst_last_refill_ms =
now;
667 if (hc->out_rst_budget == 0)
return false;
668 hc->out_rst_budget--;
680static bool h2_conn_consume_ping_budget(h2_conn *hc) {
685 hc->ping_last_refill_ms =
now;
689 hc->ping_budget +=
add;
693 hc->ping_last_refill_ms =
now;
697 if (hc->ping_budget == 0) {
713 if (s->stream_id == stream_id)
return s;
733 s->stream_id = stream_id;
739 s->fc.send_window = hc->peer_initial_window_size;
740 s->next = hc->streams;
742 hc->active_streams++;
754static void h2_stream_remove(h2_conn *hc,
uint32_t stream_id) {
758 if (s->stream_id == stream_id) {
760 if (hc->active_streams > 0) hc->active_streams--;
761 h2_stream_drain_waiters(hc, s);
762 if (s->hook_ctx && hc->hooks && hc->hooks->on_close)
763 hc->hooks->on_close(hc->hook_ctx, s->hook_ctx);
781 {
":path",
"/index.html"},
783 {
":scheme",
"https"},
791 {
"accept-charset",
""},
792 {
"accept-encoding",
"gzip, deflate"},
793 {
"accept-language",
""},
794 {
"accept-ranges",
""},
796 {
"access-control-allow-origin",
""},
799 {
"authorization",
""},
800 {
"cache-control",
""},
801 {
"content-disposition",
""},
802 {
"content-encoding",
""},
803 {
"content-language",
""},
804 {
"content-length",
""},
805 {
"content-location",
""},
806 {
"content-range",
""},
807 {
"content-type",
""},
816 {
"if-modified-since",
""},
817 {
"if-none-match",
""},
819 {
"if-unmodified-since",
""},
820 {
"last-modified",
""},
823 {
"max-forwards",
""},
824 {
"proxy-authenticate",
""},
825 {
"proxy-authorization",
""},
832 {
"strict-transport-security",
""},
833 {
"transfer-encoding",
""},
837 {
"www-authenticate",
""},
854 return read(conn->
fd, buf, len);
872 return write(conn->
fd, buf, len);
876static uint8_t h2_xor_bytes(
const unsigned char *buf,
size_t len) {
878 for (
size_t i = 0;
i < len;
i++)
x ^= buf[
i];
898 if (
ret <= 0)
return -1;
910static int h2_out_flush(h2_conn *hc);
912static int h2_wait_readable(h2_conn *hc,
uint64_t deadline_ms) {
914 if (h2_out_flush(hc) != 0)
return -1;
915 int afd = cwist_h2_async_queue_fd(hc->async_q);
916 while (!h2_has_buffered_input(hc->conn)) {
919 if (
now - hc->last_activity >=
idle_ms)
return -1;
922 if (
now >= deadline_ms)
return -1;
930 pfd[0].fd = hc->conn->fd;
940 if (
pret < 0)
return -1;
944 if (
afd >= 0 && hc->async_q) {
963static int h2_read_full(h2_conn *hc,
void *buf,
size_t len) {
964 unsigned char *
p = (
unsigned char *)buf;
967 int n = h2_read(hc->conn,
p +
got, (
int)(len -
got));
970 hc->last_activity = h2_now_ms();
985 if (
now - hc->last_activity >=
idle_ms)
return -1;
1004 const unsigned char *
p = (
const unsigned char *)buf;
1007 int max_retries = h2_idle_timeout_ms() / 100;
1008 if (max_retries > 1000) max_retries = 1000;
1009 if (max_retries < 1) max_retries = 1;
1011 int n = h2_write(conn,
p, (
int)len);
1017 if (++
retries > max_retries)
return -1;
1018 if (h2_wait_socket(conn,
err, 100) != 0)
return -1;
1023 if (++
retries > max_retries)
return -1;
1024 if (h2_wait_socket(conn, 0, 100) != 0)
return -1;
1040 h2_put_u24(out, len);
1043 h2_put_u31(out + 5, stream_id);
1050#define CWIST_H2_BATCH_DEFAULT_BYTES (64 * 1024)
1052static size_t h2_batch_threshold(
void) {
1053 static size_t threshold = 0;
1054 if (threshold == 0) {
1062static int h2_out_flush_raw(h2_conn *hc) {
1063 if (hc->out_len == 0)
return 0;
1064 if (h2_write_all(hc->conn, hc->out_buf, hc->out_len) != 0)
return -1;
1069static int h2_out_append_raw(h2_conn *hc,
const void *buf,
size_t len) {
1070 if (len == 0)
return 0;
1071 if (hc->out_len + len > hc->out_cap) {
1072 size_t cap = hc->out_cap ? hc->out_cap : 16384;
1079 memcpy(hc->out_buf + hc->out_len, buf, len);
1081 if (hc->out_len >= h2_batch_threshold())
return h2_out_flush_raw(hc);
1087static int h2_out_flush(h2_conn *hc) {
1088 if (!hc->out_mu_init)
return h2_out_flush_raw(hc);
1090 int rc = h2_out_flush_raw(hc);
1095static int h2_out_append(h2_conn *hc,
const void *buf,
size_t len) {
1096 if (!hc->out_mu_init)
return h2_out_append_raw(hc, buf, len);
1098 int rc = h2_out_append_raw(hc, buf, len);
1105 const unsigned char *payload,
uint32_t len) {
1107 h2_make_frame_header(
frame, len, type, flags, stream_id);
1108 if (h2_out_append(hc,
frame,
sizeof(
frame)) != 0)
return -1;
1109 if (len > 0 && payload)
return h2_out_append(hc, payload, len);
1117 h2_make_frame_header(
frame, len, type, flags, stream_id);
1118 if (h2_write_all(conn,
frame,
sizeof(
frame)) != 0)
return -1;
1119 if (len > 0 && payload)
return h2_write_all(conn, payload, len);
1127 if (hc)
return h2_write_frame(hc, type, flags, stream_id, payload, len);
1128 return h2_write_frame_now(conn, type, flags, stream_id, payload, len);
1134 unsigned char payload[8];
1151 if (!h2_conn_consume_out_rst_budget(hc)) {
1152 CWIST_LOG_WARN(
"[h2] outbound RST_STREAM budget exhausted on stream %u "
1153 "(Made You Reset / issue #97 mitigation triggered)",
1158 unsigned char payload[4];
1164 unsigned char payload[4];
1172static int h2_auto_window_update(h2_conn *hc,
h2_stream *s) {
1179 if (r < 0)
return -1;
1186 if (r < 0)
return -1;
1197 if (s &&
allowed > s->fc.send_window)
allowed = s->fc.send_window;
1198 if (s && hc->fc.rtt_initialized &&
allowed > 0) {
1207 if (s && hc->fc.rtt_initialized) {
1213 hc->fc.send_window -= bytes;
1214 if (s) s->fc.send_window -= bytes;
1224static int h2_fc_wait_credit(h2_conn *hc,
h2_stream *s) {
1225 if (!hc->fc_mu_init)
return -1;
1230 if (s->send_aborted || s->fc_detached)
break;
1231 if (h2_send_allowance(hc, s, 1) > 0) {
1235 if (h2_now_ms() - hc->last_activity >= (
uint64_t)h2_idle_timeout_ms())
break;
1238 ts.tv_nsec += 100000000L;
1239 if (
ts.tv_nsec >= 1000000000L) {
1241 ts.tv_nsec -= 1000000000L;
1254static int h2_send_ping(h2_conn *hc) {
1257 for (
int i = 0;
i < 8;
i++)
p[
i] = (
unsigned char)(
now >> (56 - 8 *
i));
1259 memcpy(hc->ping_payload,
p, 8);
1260 hc->ping_sent_us =
now;
1261 hc->ping_outstanding =
true;
1279 while (*pos < len) {
1281 if (
m > 28)
return -1;
1283 if ((b & 0x80) == 0) {
1303} h2_huffman_lut_entry;
1305static h2_huffman_lut_entry h2_huffman_root_lut[256];
1308static size_t h2_huffman_pool_used = 0;
1321 if (h2_huffman_pool_used >=
sizeof(h2_huffman_pool) /
sizeof(h2_huffman_pool[0]))
return NULL;
1323 node->child[0] = node->child[1] =
NULL;
1325 node->is_terminal =
false;
1335static void h2_huffman_init(
void) {
1336 static const struct {
1340 {0x00001ff8, 13}, {0x007fffd8, 23}, {0x0fffffe2, 28}, {0x0fffffe3, 28}, {0x0fffffe4, 28},
1341 {0x0fffffe5, 28}, {0x0fffffe6, 28}, {0x0fffffe7, 28}, {0x0fffffe8, 28}, {0x00ffffea, 24},
1342 {0x3ffffffc, 30}, {0x0fffffe9, 28}, {0x0fffffea, 28}, {0x3ffffffd, 30}, {0x0fffffeb, 28},
1343 {0x0fffffec, 28}, {0x0fffffed, 28}, {0x0fffffee, 28}, {0x0fffffef, 28}, {0x0ffffff0, 28},
1344 {0x0ffffff1, 28}, {0x0ffffff2, 28}, {0x3ffffffe, 30}, {0x0ffffff3, 28}, {0x0ffffff4, 28},
1345 {0x0ffffff5, 28}, {0x0ffffff6, 28}, {0x0ffffff7, 28}, {0x0ffffff8, 28}, {0x0ffffff9, 28},
1346 {0x0ffffffa, 28}, {0x0ffffffb, 28}, {0x00000014, 6}, {0x000003f8, 10}, {0x000003f9, 10},
1347 {0x00000ffa, 12}, {0x00001ff9, 13}, {0x00000015, 6}, {0x000000f8, 8}, {0x000007fa, 11},
1348 {0x000003fa, 10}, {0x000003fb, 10}, {0x000000f9, 8}, {0x000007fb, 11}, {0x000000fa, 8},
1349 {0x00000016, 6}, {0x00000017, 6}, {0x00000018, 6}, {0x00000000, 5}, {0x00000001, 5},
1350 {0x00000002, 5}, {0x00000019, 6}, {0x0000001a, 6}, {0x0000001b, 6}, {0x0000001c, 6},
1351 {0x0000001d, 6}, {0x0000001e, 6}, {0x0000001f, 6}, {0x0000005c, 7}, {0x000000fb, 8},
1352 {0x00007ffc, 15}, {0x00000020, 6}, {0x00000ffb, 12}, {0x000003fc, 10}, {0x00001ffa, 13},
1353 {0x00000021, 6}, {0x0000005d, 7}, {0x0000005e, 7}, {0x0000005f, 7}, {0x00000060, 7},
1354 {0x00000061, 7}, {0x00000062, 7}, {0x00000063, 7}, {0x00000064, 7}, {0x00000065, 7},
1355 {0x00000066, 7}, {0x00000067, 7}, {0x00000068, 7}, {0x00000069, 7}, {0x0000006a, 7},
1356 {0x0000006b, 7}, {0x0000006c, 7}, {0x0000006d, 7}, {0x0000006e, 7}, {0x0000006f, 7},
1357 {0x00000070, 7}, {0x00000071, 7}, {0x00000072, 7}, {0x000000fc, 8}, {0x00000073, 7},
1358 {0x000000fd, 8}, {0x00001ffb, 13}, {0x0007fff0, 19}, {0x00001ffc, 13}, {0x00003ffc, 14},
1359 {0x00000022, 6}, {0x00007ffd, 15}, {0x00000003, 5}, {0x00000023, 6}, {0x00000004, 5},
1360 {0x00000024, 6}, {0x00000005, 5}, {0x00000025, 6}, {0x00000026, 6}, {0x00000027, 6},
1361 {0x00000006, 5}, {0x00000074, 7}, {0x00000075, 7}, {0x00000028, 6}, {0x00000029, 6},
1362 {0x0000002a, 6}, {0x00000007, 5}, {0x0000002b, 6}, {0x00000076, 7}, {0x0000002c, 6},
1363 {0x00000008, 5}, {0x00000009, 5}, {0x0000002d, 6}, {0x00000077, 7}, {0x00000078, 7},
1364 {0x00000079, 7}, {0x0000007a, 7}, {0x0000007b, 7}, {0x00007ffe, 15}, {0x000007fc, 11},
1365 {0x00003ffd, 14}, {0x00001ffd, 13}, {0x0ffffffc, 28}, {0x000fffe6, 20}, {0x003fffd2, 22},
1366 {0x000fffe7, 20}, {0x000fffe8, 20}, {0x003fffd3, 22}, {0x003fffd4, 22}, {0x003fffd5, 22},
1367 {0x007fffd9, 23}, {0x003fffd6, 22}, {0x007fffda, 23}, {0x007fffdb, 23}, {0x007fffdc, 23},
1368 {0x007fffdd, 23}, {0x007fffde, 23}, {0x00ffffeb, 24}, {0x007fffdf, 23}, {0x00ffffec, 24},
1369 {0x00ffffed, 24}, {0x003fffd7, 22}, {0x007fffe0, 23}, {0x00ffffee, 24}, {0x007fffe1, 23},
1370 {0x007fffe2, 23}, {0x007fffe3, 23}, {0x007fffe4, 23}, {0x001fffdc, 21}, {0x003fffd8, 22},
1371 {0x007fffe5, 23}, {0x003fffd9, 22}, {0x007fffe6, 23}, {0x007fffe7, 23}, {0x00ffffef, 24},
1372 {0x003fffda, 22}, {0x001fffdd, 21}, {0x000fffe9, 20}, {0x003fffdb, 22}, {0x003fffdc, 22},
1373 {0x007fffe8, 23}, {0x007fffe9, 23}, {0x001fffde, 21}, {0x007fffea, 23}, {0x003fffdd, 22},
1374 {0x003fffde, 22}, {0x00fffff0, 24}, {0x001fffdf, 21}, {0x003fffdf, 22}, {0x007fffeb, 23},
1375 {0x007fffec, 23}, {0x001fffe0, 21}, {0x001fffe1, 21}, {0x003fffe0, 22}, {0x001fffe2, 21},
1376 {0x007fffed, 23}, {0x003fffe1, 22}, {0x007fffee, 23}, {0x007fffef, 23}, {0x000fffea, 20},
1377 {0x003fffe2, 22}, {0x003fffe3, 22}, {0x003fffe4, 22}, {0x007ffff0, 23}, {0x003fffe5, 22},
1378 {0x003fffe6, 22}, {0x007ffff1, 23}, {0x03ffffe0, 26}, {0x03ffffe1, 26}, {0x000fffeb, 20},
1379 {0x0007fff1, 19}, {0x003fffe7, 22}, {0x007ffff2, 23}, {0x003fffe8, 22}, {0x01ffffec, 25},
1380 {0x03ffffe2, 26}, {0x03ffffe3, 26}, {0x03ffffe4, 26}, {0x07ffffde, 27}, {0x07ffffdf, 27},
1381 {0x03ffffe5, 26}, {0x00fffff1, 24}, {0x01ffffed, 25}, {0x0007fff2, 19}, {0x001fffe3, 21},
1382 {0x03ffffe6, 26}, {0x07ffffe0, 27}, {0x07ffffe1, 27}, {0x03ffffe7, 26}, {0x07ffffe2, 27},
1383 {0x00fffff2, 24}, {0x001fffe4, 21}, {0x001fffe5, 21}, {0x03ffffe8, 26}, {0x03ffffe9, 26},
1384 {0x0ffffffd, 28}, {0x07ffffe3, 27}, {0x07ffffe4, 27}, {0x07ffffe5, 27}, {0x000fffec, 20},
1385 {0x00fffff3, 24}, {0x000fffed, 20}, {0x001fffe6, 21}, {0x003fffe9, 22}, {0x001fffe7, 21},
1386 {0x001fffe8, 21}, {0x007ffff3, 23}, {0x003fffea, 22}, {0x003fffeb, 22}, {0x01ffffee, 25},
1387 {0x01ffffef, 25}, {0x00fffff4, 24}, {0x00fffff5, 24}, {0x03ffffea, 26}, {0x007ffff4, 23},
1388 {0x03ffffeb, 26}, {0x07ffffe6, 27}, {0x03ffffec, 26}, {0x03ffffed, 26}, {0x07ffffe7, 27},
1389 {0x07ffffe8, 27}, {0x07ffffe9, 27}, {0x07ffffea, 27}, {0x07ffffeb, 27}, {0x0ffffffe, 28},
1390 {0x07ffffec, 27}, {0x07ffffed, 27}, {0x07ffffee, 27}, {0x07ffffef, 27}, {0x07fffff0, 27},
1391 {0x03ffffee, 26}, {0x3fffffff, 30},
1400 for (
int i = bits - 1;
i >= 0; --
i) {
1402 if (!node->child[
bit]) node->child[
bit] = h2_huffman_alloc_node();
1403 if (!node->child[
bit])
return;
1404 node = node->child[
bit];
1407 node->is_terminal =
true;
1409 h2_huffman_root =
root;
1412 for (
int i = 0;
i < 256; ++
i) {
1416 for (
int b = 7; b >= 0; --b) {
1417 int bit = (
i >> b) & 1;
1422 if (
n->is_terminal) {
1444 if (!h2_huffman_root)
return NULL;
1448 if (!out)
return NULL;
1464 if (node == h2_huffman_root &&
bit_cnt >= 8) {
1466 h2_huffman_lut_entry entry = h2_huffman_root_lut[index];
1467 if (entry.symbol != -1) {
1468 if (entry.symbol == 256) {
1472 if (out_pos + 1 >=
cap) {
1481 out[out_pos++] = (
char)entry.symbol;
1491 node = node->child[
bit];
1496 if (node->is_terminal) {
1497 if (node->symbol == 256) {
1501 if (out_pos + 1 >=
cap) {
1510 out[out_pos++] = (
char)node->symbol;
1511 node = h2_huffman_root;
1518 if (node != h2_huffman_root) {
1529 out[out_pos] =
'\0';
1530 if (out_len) *out_len = out_pos;
1544 if (*pos >= len)
return NULL;
1545 bool huffman = (buf[*pos] & 0x80) != 0;
1558 if (!out)
return NULL;
1571 size_t count =
sizeof(cwist_http2_static_table) /
sizeof(cwist_http2_static_table[0]);
1572 if (index == 0 || index >=
count)
return NULL;
1573 return &cwist_http2_static_table[index];
1580 while (hc->hpack_size >
limit && hc->hpack_head) {
1582 while ((*pp)->next)
pp = &(*pp)->next;
1594 size_t static_count =
sizeof(cwist_http2_static_table) /
sizeof(cwist_http2_static_table[0]);
1598 while (
e && --pos)
e =
e->next;
1605static int h2_hpack_insert(h2_conn *hc,
const char *name,
const char *value) {
1608 h2_hpack_evict_to(hc, 0);
1615 if (!
e->name || !
e->value) {
1623 e->next = hc->hpack_head;
1646 const char *q =
strchr(path,
'?');
1669 bool seen_authority;
1674static int h2_apply_header(
cwist_http_request *req,
const char *name,
const char *value,
1675 h2_header_state *
st) {
1676 if (!req || !name || !value || !
st)
return -1;
1684 if (name[0] ==
':') {
1686 if (
st->seen_regular)
return -1;
1688 if (
strcmp(name,
":method") == 0)
1690 else if (
strcmp(name,
":path") == 0)
1692 else if (
strcmp(name,
":scheme") == 0)
1694 else if (
strcmp(name,
":authority") == 0)
1695 flag = &
st->seen_authority;
1697 if (*
flag)
return -1;
1700 if (
strcmp(name,
":method") == 0) {
1702 }
else if (
strcmp(name,
":path") == 0) {
1703 h2_parse_path(req, value);
1704 }
else if (
strcmp(name,
":authority") == 0) {
1711 st->seen_regular =
true;
1715 if (
strcmp(name,
"host") != 0) {
1719 if (name[
i] >=
'A' && name[
i] <=
'Z') {
1731 if (
strcmp(name,
"host") == 0) {
1741#define H2_DECODE_OK 0
1742#define H2_DECODE_STREAM_ERROR 1
1743#define H2_DECODE_COMPRESSION_ERROR 2
1746 const unsigned char *payload,
size_t len,
bool is_request) {
1777 if (entry && entry->
name) {
1783 CWIST_LOG_WARN(
"[h2] header field index=%u beyond static+dynamic tables", index);
1790 if ((b & 0xE0) == 0x20) {
1795 if (h2_hpack_set_capacity(hc,
new_size) != 0) {
1796 CWIST_LOG_WARN(
"[h2] dynamic table size update %u exceeds advertised capacity",
1804 if ((b & 0x40) == 0x40) {
1808 }
else if ((b & 0xf0) == 0x00) {
1811 }
else if ((b & 0xf0) == 0x10) {
1820 if (entry && entry->
name) {
1827 CWIST_LOG_WARN(
"[h2] literal field name index=%u beyond static+dynamic tables",
1835 if (!name || !value) {
1840 int rc = h2_apply_header(req, name, value, &
st);
1846 if (h2_hpack_insert(hc, name, value) != 0)
rc = -2;
1858 CWIST_LOG_WARN(
"[h2] request header block missing :method or :path");
1885 while (value >= 128) {
1887 dst[
i++] = (
unsigned char)((value & 0x7F) | 0x80);
1904 size_t len =
strlen(str);
1907 if (
n == 0 ||
n + len >
dst_cap)
return 0;
1917static int h2_static_table_find_name(
const char *name) {
1918 size_t count =
sizeof(cwist_http2_static_table) /
sizeof(cwist_http2_static_table[0]);
1919 for (
size_t i = 1;
i <
count; ++
i) {
1920 if (cwist_http2_static_table[
i].name &&
1921 strcasecmp(cwist_http2_static_table[
i].name, name) == 0)
1936static bool h2_status_forbids_body(
int status_code) {
1937 return (status_code >= 100 && status_code < 200) || status_code == 204 || status_code == 304;
1985 char status_str[16];
1987 if (pos + 1 >
dst_cap)
return 0;
1990 if (
n == 0)
return 0;
1993 if (
n == 0)
return 0;
1996 if (
n == 0)
return 0;
2002 size_t body_len = 0;
2013 int name_idx = h2_static_table_find_name(
"content-length");
2015 if (pos + 1 >
dst_cap)
return 0;
2018 if (
n == 0)
return 0;
2021 if (pos + 1 >
dst_cap)
return 0;
2024 if (
n == 0)
return 0;
2027 if (
n == 0)
return 0;
2031 if (
n == 0)
return 0;
2037 if (!
curr->key || !
curr->key->data || !
curr->value || !
curr->value->data) {
2086 if (pos + 1 >
dst_cap)
return 0;
2089 if (
n == 0)
return 0;
2092 if (pos + 1 >
dst_cap)
return 0;
2095 if (
n == 0)
return 0;
2098 if (
n == 0)
return 0;
2102 if (
n == 0)
return 0;
2145 unsigned char *buf =
NULL;
2146 while (
cap <= (1u << 20)) {
2153 size_t n = h2_encode_response_headers(res, buf,
cap,
grpc_mode);
2166static size_t h2_encode_header_pairs(
unsigned char *
dst,
size_t dst_cap,
2169 for (
size_t i = 0;
i <
count;
i++) {
2179 if (pos + 1 >
dst_cap)
return 0;
2182 if (
n == 0)
return 0;
2186 if (
n == 0)
return 0;
2190 if (
n == 0)
return 0;
2211 for (
size_t i = 0;
i <
msg.count;
i++) {
2218 if (send_xor) *send_xor ^= h2_xor_bytes(
msg.chunks[
i],
msg.chunk_lens[
i]);
2228 unsigned char *
block = h2_encode_response_block(res, &
block_len,
false);
2229 if (!
block)
return -1;
2231 size_t body_len = 0;
2238 }
else if (res->
body) {
2244 if (body_len == 0 || h2_status_forbids_body(res->
status_code)) {
2252 if (
rc != 0)
return -1;
2254 if (body_len == 0)
return 0;
2260 unsigned char *
file_buf = (
unsigned char *)cwist_scratch_alloc(&
file_buf_s, body_len);
2263 if (r <= 0 || (
size_t)r != body_len)
return -1;
2281static int h2_read_all(h2_conn *hc,
void *buf,
int len) {
2284 if (h2_read_full(hc, buf, (
size_t)len) != 0)
return -1;
2299static int h2_process_incoming_frames_nonblocking(h2_conn *hc,
h2_stream *s) {
2301 if (h2_out_flush(hc) != 0)
return -1;
2303 pfd.fd = hc->conn->fd;
2308 if (!h2_has_buffered_input(hc->conn))
return -1;
2311 while (h2_has_buffered_input(hc->conn) || (
poll(&
pfd, 1, 0) > 0 && (
pfd.revents &
POLLIN))) {
2312 unsigned char hdr[9];
2313 int n = h2_read_all(hc, hdr, 9);
2314 if (
n != 9)
return -1;
2319 uint32_t stream_id = h2_get_u31(hdr + 5);
2321 unsigned char *payload =
NULL;
2324 if (!payload)
return -1;
2325 int r = h2_read_all(hc, payload, len);
2326 if (r != (
int)len) {
2332 hc->last_activity = h2_now_ms();
2338 if (stream_id == 0) {
2340 }
else if (s && stream_id == s->stream_id) {
2349 h2_fc_credit_signal(hc);
2352 }
else if (type == 0x06) {
2353 if ((flags & 0x01) == 0 && len == 8) {
2354 if (!h2_conn_consume_ping_budget(hc)) {
2358 h2_write_frame(hc, 0x06, 0x01, 0, payload, 8);
2360 }
else if (type == 0x03) {
2361 if (!h2_conn_consume_rst_budget(hc)) {
2365 if (s && stream_id == s->stream_id) {
2366 s->send_aborted =
true;
2376 df->payload = payload;
2378 if (hc->deferred_tail)
2379 hc->deferred_tail->next =
df;
2381 hc->deferred_head =
df;
2382 hc->deferred_tail =
df;
2404static int h2_send_seq_file_body(h2_conn *hc,
h2_stream *s,
uint32_t stream_id,
int fd,
2411 if (h2_process_incoming_frames_nonblocking(hc, s) != 0)
return -1;
2412 if (s && s->send_aborted)
return -1;
2413 if (h2_now_ms() - hc->last_activity >= (
uint64_t)h2_idle_timeout_ms())
return -1;
2418 if (body_len == 0)
return -1;
2425 while (hc->fc.send_window == 0 || (s && s->fc.send_window == 0)) {
2426 if (h2_process_incoming_frames_nonblocking(hc, s) != 0)
return -1;
2427 if (s && s->send_aborted)
return -1;
2428 if (hc->fc.send_window == 0 || (s && s->fc.send_window == 0)) {
2430 if (h2_now_ms() - hc->last_activity >= (
uint64_t)h2_idle_timeout_ms())
return -1;
2440 if (h2_process_incoming_frames_nonblocking(hc, s) != 0)
return -1;
2441 if (h2_now_ms() - hc->last_activity >= (
uint64_t)h2_idle_timeout_ms())
return -1;
2450 if (!
chunk)
return -1;
2452 if (r <= 0 || (
size_t)r !=
plen)
return -1;
2471static int h2_send_seq_memory_body(h2_conn *hc,
h2_stream *s,
uint32_t stream_id,
2479 if (h2_process_incoming_frames_nonblocking(hc, s) != 0)
return -1;
2480 if (s && s->send_aborted)
return -1;
2481 if (h2_now_ms() - hc->last_activity >= (
uint64_t)h2_idle_timeout_ms())
return -1;
2490 for (
size_t i = 0;
i <
msg.count;
i++) {
2491 while (hc->fc.send_window == 0 || (s && s->fc.send_window == 0)) {
2492 if (h2_process_incoming_frames_nonblocking(hc, s) != 0)
goto fail;
2493 if (s && s->send_aborted)
goto fail;
2494 if (hc->fc.send_window == 0 || (s && s->fc.send_window == 0)) {
2496 if (h2_now_ms() - hc->last_activity >= (
uint64_t)h2_idle_timeout_ms())
goto fail;
2505 if (h2_process_incoming_frames_nonblocking(hc, s) != 0)
goto fail;
2506 if (h2_now_ms() - hc->last_activity >= (
uint64_t)h2_idle_timeout_ms())
goto fail;
2518 if (s) s->send_xor ^= h2_xor_bytes(
msg.chunks[
i],
chunk_len);
2541static void h2_out_lock(h2_conn *hc) {
2549static void h2_out_unlock(h2_conn *hc) {
2554static int h2_stream_write_frame_locked(h2_conn *hc,
uint8_t type,
uint8_t flags,
2555 uint32_t stream_id,
const unsigned char *payload,
2558 h2_make_frame_header(
frame, len, type, flags, stream_id);
2559 if (h2_out_append_raw(hc,
frame,
sizeof(
frame)) != 0)
return -1;
2560 if (len > 0 && payload)
return h2_out_append_raw(hc, payload, len);
2567 size_t count,
size_t *out_len) {
2569 unsigned char *buf =
NULL;
2570 while (
cap <= (1u << 20)) {
2578 if (status == 200) {
2581 char status_str[16];
2582 snprintf(status_str,
sizeof(status_str),
"%d", status);
2585 if (
n == 0)
goto grow;
2588 if (
n == 0)
goto grow;
2591 if (
n == 0)
goto grow;
2595 size_t n = h2_encode_header_pairs(buf + pos,
cap - pos,
pairs,
count);
2611 if (!stream || !stream->hc)
return -1;
2615 unsigned char *
block = h2_encode_stream_block_locked(status, headers, header_count, &
block_len);
2624 if (
rc == 0)
rc = h2_out_flush_raw(hc);
2643 if (!stream || !stream->hc)
return -1;
2650 while (
sent < len) {
2652 if (h2_send_allowance(hc, stream,
chunk) == 0) {
2656 if (h2_out_flush(hc) != 0) {
2660 if (h2_fc_wait_credit(hc, stream) != 0) {
2679 h2_commit_send(hc, stream,
allowed);
2681 if (
sent >= len)
rc = h2_out_flush_raw(hc);
2690 if (!stream || !stream->hc)
return -1;
2700 while (
cap <= (1u << 20)) {
2717 int rc = h2_stream_write_frame_locked(
2720 if (
rc == 0)
rc = h2_out_flush_raw(hc);
2728static void h2_hook_offer(h2_conn *hc,
h2_stream *s) {
2729 if (!s || s->hook_offered || !hc->hooks || !hc->hooks->on_headers || !s->req)
return;
2730 s->hook_offered =
true;
2731 void *
hctx = hc->hooks->on_headers(hc->hook_ctx, s->req, s);
2747 return ct &&
strncmp(
ct,
"application/grpc", 16) == 0;
2758 if (h->key && h->key->data && h->value && h->value->data &&
2760 return h->value->data;
2770 const char *status = h2_grpc_header_value(res,
"grpc-status");
2771 const char *message = h2_grpc_header_value(res,
"grpc-message");
2772 const char *
pushback = h2_grpc_header_value(res,
"grpc-retry-pushback-ms");
2784 pairs[
count].name =
"grpc-retry-pushback-ms";
2788 unsigned char block[1024];
2802 unsigned char *
block = h2_encode_response_block(res, &
base_len,
true);
2803 if (!
block)
return -1;
2804 const char *status = h2_grpc_header_value(res,
"grpc-status");
2805 const char *message = h2_grpc_header_value(res,
"grpc-message");
2806 const char *
pushback = h2_grpc_header_value(res,
"grpc-retry-pushback-ms");
2818 pairs[
count].name =
"grpc-retry-pushback-ms";
2822 unsigned char tail[1024];
2842 h2_stream *s = h2_stream_find(hc, stream_id);
2846 bool grpc_mode = h2_response_is_grpc(res);
2851 const char *
gs = h2_grpc_header_value(res,
"grpc-status");
2855 return h2_send_grpc_trailers_only(hc, stream_id, res,
tf);
2861 if (!
block)
return -1;
2863 size_t body_len = 0;
2876 }
else if (res->
body) {
2900 if (
rc != 0)
return -1;
2902 if (body_len == 0) {
2910 while (
sent < body_len) {
2911 while (hc->fc.send_window == 0 || (s && s->fc.send_window == 0)) {
2912 if (h2_process_incoming_frames_nonblocking(hc, s) != 0)
return -1;
2913 if (s && s->send_aborted)
return -1;
2914 if (hc->fc.send_window == 0 || (s && s->fc.send_window == 0)) {
2915 if (h2_now_ms() - hc->last_activity >= (
uint64_t)h2_idle_timeout_ms())
2925 if (h2_out_flush(hc) != 0)
return -1;
2935 h2_commit_send(hc, s,
allowed);
2938 return h2_send_grpc_trailers(hc, stream_id, res,
max_frame);
2941 if (hc->sequenced_data) {
2943 return h2_send_seq_file_body(hc, s, stream_id, res->
file_stream_fd,
2954 while (hc->fc.send_window == 0 || (s && s->fc.send_window == 0)) {
2955 if (h2_process_incoming_frames_nonblocking(hc, s) != 0)
return -1;
2956 if (s && s->send_aborted)
return -1;
2957 if (hc->fc.send_window == 0 || (s && s->fc.send_window == 0)) {
2959 if (h2_now_ms() - hc->last_activity >= (
uint64_t)h2_idle_timeout_ms())
2970 if (h2_out_flush(hc) != 0)
return -1;
2980 if (r <= 0)
return -1;
2986 if (s) s->send_xor ^= h2_xor_bytes(
chunk_buf, (
size_t)r);
2987 h2_commit_send(hc, s, (
uint32_t)r);
2993 while (
sent < body_len) {
2994 while (hc->fc.send_window == 0 || (s && s->fc.send_window == 0)) {
2995 if (h2_process_incoming_frames_nonblocking(hc, s) != 0)
return -1;
2996 if (s && s->send_aborted)
return -1;
2997 if (hc->fc.send_window == 0 || (s && s->fc.send_window == 0)) {
2999 if (h2_now_ms() - hc->last_activity >= (
uint64_t)h2_idle_timeout_ms())
3011 if (h2_out_flush(hc) != 0)
return -1;
3023 h2_commit_send(hc, s,
allowed);
3027 return h2_out_flush(hc);
3043 CWIST_LOG_WARN(
"[h2] refusing stream %u: concurrent stream limit %u reached", stream_id,
3053 h2_stream *s = h2_stream_create(hc, stream_id);
3054 if (!s)
return NULL;
3057 h2_stream_remove(hc, stream_id);
3061 s->req->stream_id = stream_id;
3062 s->req->private_data = hc->conn;
3063 s->req->https_conn = hc->conn;
3064 s->req->client_fd = hc->conn ? hc->conn->fd : -1;
3065 s->req->h2_queue = hc->async_q;
3082static int h2_decode_stream_headers(h2_conn *hc,
h2_stream *s,
const unsigned char *
block,
3088 h2_stream_remove(hc, s->stream_id);
3090 return (
rst_rc == -2) ? -1 : 1;
3113static int h2_begin_headers(h2_conn *hc,
uint32_t stream_id,
const unsigned char *payload,
3115 if (hc->expecting_continuation) {
3122 bool is_new = (h2_stream_find(hc, stream_id) ==
NULL);
3125 h2_stream *s = h2_stream_find(hc, stream_id);
3137 h2_stream_remove(hc, stream_id);
3138 return (
rst_rc == -2) ? -2 : 1;
3153 CWIST_LOG_WARN(
"[h2] refusing stream %u: concurrent stream limit %u reached", stream_id,
3156 if (
rst_rc == -2)
return -2;
3157 hc->expecting_continuation =
true;
3158 hc->cont_stream_id = stream_id;
3160 hc->cont_discard =
true;
3161 hc->cont_frame_count = 0;
3167 hc->expecting_continuation =
true;
3168 hc->cont_stream_id = stream_id;
3170 hc->cont_discard =
false;
3171 hc->cont_frame_count = 0;
3174 hc->cont_buf = (
unsigned char *)
cwist_alloc(hc->cont_cap);
3175 if (!hc->cont_buf) {
3176 hc->expecting_continuation =
false;
3202static int h2_handle_continuation(h2_conn *hc,
uint32_t stream_id,
const unsigned char *payload,
3204 if (!hc->expecting_continuation || hc->cont_stream_id != stream_id) {
3208 hc->cont_frame_count++;
3210 CWIST_LOG_WARN(
"[h2] excessive CONTINUATION frames (%u) received (CVE-2024-27983)",
3211 hc->cont_frame_count);
3216 CWIST_LOG_WARN(
"[h2] CONTINUATION accumulated header block size %zu exceeds maximum %u",
3223 if (hc->cont_discard) {
3225 hc->expecting_continuation =
false;
3226 hc->cont_discard =
false;
3227 hc->cont_stream_id = 0;
3228 hc->cont_frame_count = 0;
3234 if (hc->cont_len + len > hc->cont_cap) {
3235 size_t new_cap = hc->cont_cap * 2;
3238 if (!
tmp)
return -1;
3242 memcpy(hc->cont_buf + hc->cont_len, payload, len);
3243 hc->cont_len += len;
3247 h2_stream *s = h2_stream_find(hc, hc->cont_stream_id);
3255 if (s && hc->cont_buf && hc->cont_len > 0) {
3256 rc = h2_decode_stream_headers(hc, s, hc->cont_buf, hc->cont_len,
is_new);
3257 }
else if (s &&
is_new) {
3260 h2_stream_remove(hc, s->stream_id);
3263 hc->expecting_continuation =
false;
3265 hc->cont_stream_id = 0;
3266 hc->cont_frame_count = 0;
3286static int h2_handle_settings(h2_conn *hc,
const unsigned char *payload,
size_t len,
bool ack) {
3295 for (
size_t i = 0;
i + 6 <= len;
i += 6) {
3297 uint32_t value = h2_get_u32(payload +
i + 2);
3301 if (value > 1)
return -1;
3304 hc->peer_max_concurrent_streams = value;
3307 if (value > 2147483647U)
return -1;
3310 hc->peer_initial_window_size = value;
3317 : s->fc.send_window +
inc;
3323 dec > s->fc.send_window ? 0 : s->fc.send_window -
dec;
3327 h2_fc_credit_signal(hc);
3332 hc->peer_max_frame_size = value;
3356static int h2_handle_window_update(h2_conn *hc,
uint32_t stream_id,
const unsigned char *payload,
3358 if (len != 4)
return -1;
3363 if (stream_id == 0) {
3367 h2_stream *s = h2_stream_find(hc, stream_id);
3376 h2_fc_credit_signal(hc);
3390static int cwist_http2_verify_preface(h2_conn *hc) {
3428 snprintf(alt_svc,
sizeof(alt_svc),
"h3=\":%d\"; ma=86400", port);
3440static int h2_async_drain(h2_conn *hc) {
3443 int afd = cwist_h2_async_queue_fd(q);
3451 q->head = q->tail =
NULL;
3457 h2_stream *s = h2_stream_find(hc,
n->stream_id);
3459 h2_async_node_discard(
n);
3463 h2_inject_alt_svc(hc->conn,
n->send);
3464 int rc = h2_send_response_hc(hc,
n->stream_id,
n->send);
3468 h2_stream_remove(hc,
n->stream_id);
3473 h2_async_node_discard(
list);
3510static void h2_session_free(
void *
p) {
3512 h2_conn_destroy(&
st->hc);
3524static bool h2_can_park(h2_conn *hc) {
3525 if (hc->streams || hc->deferred_head || hc->cont_len > 0)
return false;
3526 if (hc->hooks && hc->hooks->next_deadline_ms && hc->hooks->next_deadline_ms(hc->hook_ctx))
3544static void h2_session_disown(h2_session *
st) {
3549 for (h2_hpack_entry *
he = hc->hpack_head;
he;
he =
he->next) {
3578 if (!conn || !handler) {
3599 h2_conn_init(hc, conn);
3602 hc->async_q = cwist_h2_async_queue_create();
3604 if (cwist_http2_verify_preface(hc) != 0) {
3605 h2_conn_destroy(hc);
3616 0x00, 0x01, 0x00, 0x00, 0x10, 0x00,
3617 0x00, 0x03, 0x00, 0x00, 0x00, 0x64,
3618 0x00, 0x04, 0x7f, 0xff, 0xff, 0xff,
3619 0x00, 0x06, 0x00, 0x01, 0x00, 0x00
3623 h2_conn_destroy(hc);
3631 h2_send_window_update(hc, 0, 2147483647 - 65535);
3643 bool sent_goaway =
st->sent_goaway;
3644 uint64_t goaway_close_at =
st->goaway_close_at;
3649 if (h2_send_goaway(hc, hc->last_processed_stream_id,
H2_ERR_NO_ERROR) != 0) {
3653 goaway_close_at = h2_now_ms() + (
uint64_t)h2_goaway_grace_ms();
3660 if (h2_send_goaway(hc, hc->last_processed_stream_id,
H2_ERR_NO_ERROR) != 0) {
3665 goaway_close_at = h2_now_ms() + (
uint64_t)h2_goaway_grace_ms();
3668 if (sent_goaway && goaway_close_at && h2_now_ms() >= goaway_close_at) {
3673 if (hooks && hooks->
on_poll) {
3677 if (
ps->hook_ctx && hooks->
on_poll(hc->hook_ctx,
ps->hook_ctx))
3678 h2_stream_remove(hc,
ps->stream_id);
3682 unsigned char hdr[9];
3689 hc->deferred_head =
df->next;
3690 if (!hc->deferred_head) hc->deferred_tail =
NULL;
3692 payload =
df->payload;
3697 if (!sent_goaway && h2_can_park(hc)) {
3698 if (h2_out_flush(hc) != 0) {
3705 st->sent_goaway = sent_goaway;
3706 st->goaway_close_at = goaway_close_at;
3707 h2_session_disown(
st);
3727 if (h2_async_drain(hc) != 0) {
3739 if (
gd &&
now >=
gd)
continue;
3741 if (!sent_goaway &&
now - hc->last_activity >= (
uint64_t)h2_idle_timeout_ms()) {
3745 if (h2_send_goaway(hc, hc->last_processed_stream_id,
H2_ERR_NO_ERROR) != 0) {
3750 goaway_close_at =
now + (
uint64_t)h2_goaway_grace_ms();
3759 if (h2_read_full(hc, hdr, 9) != 0) {
3768 uint32_t stream_id = h2_get_u31(hdr + 5);
3782 if (!
df && len > 0) {
3788 if (h2_read_full(hc, payload, len) != 0)
connected =
false;
3792 hc->last_activity = h2_now_ms();
3809 if (stream_id == 0 || (stream_id % 2) == 0) {
3832 h2_send_goaway(hc, hc->last_processed_stream_id,
err);
3836 if (stream_id > hc->last_processed_stream_id)
3837 hc->last_processed_stream_id = stream_id;
3843 h2_stream *s = h2_stream_find(hc, stream_id);
3844 if (s && s->hook_ctx) {
3848 hooks->
on_data(hc->hook_ctx, s->hook_ctx,
NULL, 0, 1);
3851 h2_hook_offer(hc, s);
3852 if (s && s->hook_ctx) {
3854 hooks->
on_data(hc->hook_ctx, s->hook_ctx,
NULL, 0, 1);
3859 h2_stream *s = h2_stream_find(hc, stream_id);
3864 handler(user_ctx, s->req, res);
3876 h2_inject_alt_svc(conn, res);
3877 if (h2_send_response_hc(hc, stream_id, res) != 0)
connected =
false;
3880 h2_stream_remove(hc, stream_id);
3887 if (stream_id == 0 || !hc->expecting_continuation ||
3888 hc->cont_stream_id != stream_id) {
3893 int cont_rc = h2_handle_continuation(hc, stream_id, payload, len,
3898 h2_send_goaway(hc, hc->last_processed_stream_id,
err);
3913 h2_stream *s = h2_stream_find(hc, stream_id);
3914 h2_hook_offer(hc, s);
3915 if (s && s->hook_ctx) {
3923 handler(user_ctx, s->req, res);
3935 h2_inject_alt_svc(conn, res);
3936 if (h2_send_response_hc(hc, stream_id, res) != 0)
connected =
false;
3939 h2_stream_remove(hc, stream_id);
3946 if (stream_id == 0) {
3951 h2_stream *s = h2_stream_find(hc, stream_id);
3983 h2_stream_remove(hc, stream_id);
3988 h2_auto_window_update(hc, s);
3989 h2_stream_remove(hc, stream_id);
3993 h2_auto_window_update(hc, s);
3996 if (payload && len > 0) {
4000 if (!s->body_assembler) {
4003 if (!s->body_assembler) {
4015 h2_stream_remove(hc, stream_id);
4020 }
else if (!hc->sequenced_data) {
4024 h2_stream_remove(hc, stream_id);
4027 s->recv_xor ^= h2_xor_bytes(payload, len);
4035 if (hc->sequenced_data && s->body_assembler) {
4057 h2_auto_window_update(hc, s);
4058 h2_stream_remove(hc, stream_id);
4063 handler(user_ctx, s->req, res);
4071 h2_auto_window_update(hc, s);
4074 h2_inject_alt_svc(conn, res);
4075 if (h2_send_response_hc(hc, stream_id, res) != 0)
connected =
false;
4080 if (
connected) h2_auto_window_update(hc, s);
4081 h2_stream_remove(hc, stream_id);
4085 h2_auto_window_update(hc, s);
4091 if (stream_id == 0 || len != 4) {
4096 if (stream_id > hc->last_processed_stream_id) {
4101 "[h2] RST_STREAM received for idle stream %u > last_processed %u",
4102 stream_id, hc->last_processed_stream_id);
4107 if (!h2_conn_consume_rst_budget(hc)) {
4109 "[h2] peer exceeded RST_STREAM rate limit (Rapid Reset CVE-2023-44487 detected)");
4115 h2_stream *s = h2_stream_find(hc, stream_id);
4116 if (s && s->hook_ctx && hooks && hooks->
on_cancel)
4117 hooks->
on_cancel(hc->hook_ctx, s->hook_ctx);
4119 h2_stream_remove(hc, stream_id);
4130 if (!h2_conn_consume_ping_budget(hc)) {
4131 CWIST_LOG_WARN(
"[h2] peer exceeded PING rate limit (CVE-2019-9512)");
4140 }
else if (hc->ping_outstanding &&
memcmp(payload, hc->ping_payload, 8) == 0) {
4145 hc->ping_outstanding =
false;
4147 if (hc->streams) h2_send_ping(hc);
4163 if (h2_handle_window_update(hc, stream_id, payload, len) != 0) {
4184 h2_session_free(
st);
4211 const unsigned char *data,
size_t data_len) {
4217 const char *authority =
NULL;
4226 if (!authority)
return -1;
4243 if (
n == 0)
return -1;
4246 if (
n == 0)
return -1;
4249 if (
n == 0)
return -1;
4255 if (
n == 0)
return -1;
4258 if (
n == 0)
return -1;
4261 if (
n == 0)
return -1;
4274 if (!res)
return -1;
4279 if (data && data_len > 0) {
Memory allocation wrappers backed by libttak.
void cwist_free(void *ptr)
Free memory obtained via cwist_alloc/cwist_strdup/etc.
void * cwist_realloc(void *ptr, size_t new_size)
Resize an existing allocation (libttak tracked).
char * cwist_strdup(const char *src)
Duplicate a string using libttak-backed storage.
#define CWIST_DEFER_FREE
Attach to a pointer variable's declaration to cwist_free() it automatically when that variable's encl...
void * cwist_alloc(size_t size)
Allocate zeroed memory tracked by libttak.
#define CWIST_SCRATCH_DEFER
Attach to a cwist_scratch_t variable's declaration so its allocation (arena- or heap-backed,...
Deferred-response (async handler) API for CWIST.
void cwist_async_dispatch_ack(cwist_async *a)
Internal: on the dispatch thread, after all middleware has unwound. Call exactly once; do not touch t...
struct cwist_async cwist_async
Private wiring between the deferred-response path (async.c) and the C1M connection engine (http....
struct cwist_async * cwist_async_dispatch_take(cwist_http_response *res)
cwist_error_t cwist_sstring_append_len(cwist_sstring *str, const char *data, size_t len)
Append raw bytes with length.
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: error queue subset.
Epoch-based garbage collection wrapper.
bool cwist_gc_scope_disown(void *ptr)
Remove ptr from the current thread's pending-sweep list without freeing it, because ownership is bein...
bool cwist_full_gc_enabled(void)
Check whether full-GC mode is currently enabled.
HTTP/2 wire integers in network byte order (private).
cwist_h2_async_queue * cwist_h2_async_queue_acquire(cwist_h2_async_queue *q)
Take an additional reference on the queue (for an in-flight cwist_async handle).
int h2_decode_integer(const unsigned char *buf, size_t len, size_t *pos, uint8_t prefix_bits, uint32_t *value)
Decode an HPACK integer.
#define CWIST_HTTP2_FRAME_HEADERS
#define CWIST_HTTP2_FRAME_WINDOW_UPDATE
#define H2_ERR_FRAME_SIZE_ERROR
int cwist_http2_stream_send_trailers(cwist_h2_stream *stream, const cwist_http2_header *trailers, size_t trailer_count)
#define CWIST_HTTP2_DEFAULT_RST_BURST
int cwist_http2_stream_send_headers(cwist_h2_stream *stream, int status, const cwist_http2_header *headers, size_t header_count, int end_stream)
const cwist_http2_static_header * h2_static_header(uint32_t index)
Resolve an RFC 7541 static table entry by 1-based index.
#define CWIST_HTTP2_FLAG_ACK
#define CWIST_H2_BATCH_DEFAULT_BYTES
#define CWIST_HTTP2_CONNECTION_PREFACE
#define CWIST_HTTP2_FRAME_SETTINGS
size_t h2_encode_string(unsigned char *dst, size_t dst_cap, const char *str)
Encode a literal string without Huffman coding (RFC 7541 section 5.2).
#define CWIST_HTTP2_CONNECTION_PREFACE_LEN
char * h2_decode_string(const unsigned char *buf, size_t len, size_t *pos)
Decode one HPACK string literal (with optional Huffman coding). Reads the length prefix (7-bit prefix...
struct h2_deferred_frame h2_deferred_frame
struct h2_header_state h2_header_state
cwist_error_t cwist_http2_serve_connection_ex(cwist_https_connection *conn, void *user_ctx, cwist_http2_request_handler_func handler, const cwist_http2_stream_hooks *hooks)
Serve one HTTP/2 connection until it closes. Verifies the preface, sends our SETTINGS,...
int cwist_h2_async_queue_enqueue(cwist_h2_async_queue *q, uint32_t stream_id, cwist_http_request *req, cwist_http_response *send, cwist_http_response *res, bool send_owned)
#define CWIST_HTTP2_FLAG_END_STREAM
#define CWIST_HTTP2_SEQ_CHUNK_PAYLOAD(max_frame)
#define CWIST_HTTP2_FRAME_CONTINUATION
#define CWIST_HTTP2_MAX_HEADERS_PER_REQUEST
#define CWIST_HTTP2_DEFAULT_PING_BURST
#define H2_DECODE_COMPRESSION_ERROR
#define CWIST_HTTP2_FRAME_DATA
cwist_error_t cwist_http2_serve_connection(cwist_https_connection *conn, void *user_ctx, cwist_http2_request_handler_func handler)
Serve one HTTP/2 connection until it closes (no stream hooks). Convenience wrapper over cwist_http2_s...
#define CWIST_HTTP2_MAX_CONCURRENT_STREAMS
#define CWIST_HTTP2_FRAME_GOAWAY
#define CWIST_HTTP2_DEFAULT_PING_RATE
#define CWIST_HTTP2_FRAME_PUSH_PROMISE
#define CWIST_HTTP2_MAX_CONTINUATIONS
struct h2_session h2_session
void cwist_h2_async_queue_release(cwist_h2_async_queue *q)
Drop a reference on the queue. On the last reference: closes the wake descriptors,...
struct h2_huffman_node h2_huffman_node
struct h2_async_node h2_async_node
#define CWIST_HTTP2_MAX_HEADER_BLOCK_SIZE
struct cwist_h2_stream h2_stream
#define CWIST_HTTP2_FRAME_PING
struct h2_hpack_entry h2_hpack_entry
int cwist_http2_push_resource(cwist_http_request *req, const char *path, const char *content_type, const unsigned char *data, size_t data_len)
Push a resource to the client (HTTP/2 server push). Sends a PUSH_PROMISE on the request's stream for ...
#define CWIST_HTTP2_MAX_FRAME_SIZE
#define CWIST_HTTP2_FRAME_RST_STREAM
#define H2_ERR_ENHANCE_YOUR_CALM
char * h2_huffman_decode(const unsigned char *src, size_t src_len, size_t *out_len)
Decode an HPACK Huffman-coded string (RFC 7541 Appendix B). Lazily initializes the decode tree on fir...
size_t h2_encode_integer(unsigned char *dst, size_t dst_cap, uint32_t value, uint8_t prefix_bits)
Encode an integer with an N-bit prefix (RFC 7541 section 5.1). Preserves the high bits of dst[0] outs...
#define CWIST_HTTP2_FLAG_PRIORITY
int cwist_http2_stream_send_data(cwist_h2_stream *stream, const unsigned char *data, size_t len)
Send DATA on a hook-taken stream, honoring flow-control credit. Splits data into peer_max_frame_size ...
#define CWIST_HTTP2_FLAG_END_HEADERS
#define H2_ERR_PROTOCOL_ERROR
#define H2_DECODE_STREAM_ERROR
#define CWIST_HTTP2_HEADER_TABLE_SIZE
#define H2_ERR_REFUSED_STREAM
#define CWIST_HTTP2_DEFAULT_RST_RATE
#define H2_ERR_FLOW_CONTROL_ERROR
#define CWIST_HTTP2_FRAME_HEADER_SIZE
#define CWIST_HTTP2_GOAWAY_GRACE_MS_DEFAULT
Grace window between sending GOAWAY on idle timeout and closing the connection. A client that raced a...
#define CWIST_HTTP2_FLAG_PADDED
HTTP/2 Protocol Definitions for CWIST.
struct cwist_h2_stream cwist_h2_stream
struct cwist_h2_async_queue cwist_h2_async_queue
void(* cwist_http2_request_handler_func)(void *user_ctx, cwist_http_request *req, cwist_http_response *res)
Callback function type for handling HTTP/2 requests.
void cwist_http2_stream_flow_control_add_send_window(cwist_http2_stream_flow_control *flow_control, uint32_t increment)
Credit a stream send window from a WINDOW_UPDATE frame.
int cwist_http2_flow_control_maybe_window_update(cwist_http2_flow_control *flow_control, uint8_t *buffer, size_t buffer_len, size_t *written, uint64_t now_us, bool force)
Emit a connection-level WINDOW_UPDATE when enough credit is due.
void cwist_http2_flow_control_add_send_window(cwist_http2_flow_control *flow_control, uint32_t increment)
Credit the connection send window from a WINDOW_UPDATE frame.
void cwist_http2_stream_flow_control_consume(cwist_http2_stream_flow_control *flow_control, uint32_t bytes)
Record application-consumed bytes for a stream.
bool cwist_http2_stream_flow_control_receive(cwist_http2_stream_flow_control *flow_control, uint32_t bytes)
Account received DATA bytes against a stream receive window.
#define CWIST_HTTP2_MAX_WINDOW
void cwist_http2_flow_control_update_rtt(cwist_http2_flow_control *flow_control, uint64_t rtt_sample_us)
Feed a new RTT sample into the smoothed estimator and retune.
void cwist_http2_flow_control_consume(cwist_http2_flow_control *flow_control, uint32_t bytes)
Record application-consumed bytes for the connection.
bool cwist_http2_flow_control_reserve_send(cwist_http2_flow_control *connection_flow_control, cwist_http2_stream_flow_control *stream_flow_control, uint32_t bytes, uint64_t now_us)
Reserve send credit for an outgoing write.
bool cwist_http2_flow_control_receive(cwist_http2_flow_control *flow_control, uint32_t bytes)
Account received DATA bytes against the connection receive window.
size_t cwist_http2_flow_control_pacing_allowance(cwist_http2_flow_control *connection_flow_control, const cwist_http2_stream_flow_control *stream_flow_control, size_t requested, uint64_t now_us)
Compute how much of a send request may proceed right now.
int cwist_http2_stream_flow_control_maybe_window_update(cwist_http2_flow_control *connection_flow_control, cwist_http2_stream_flow_control *stream_flow_control, uint8_t *buffer, size_t buffer_len, size_t *written, uint64_t now_us, bool force)
Emit a stream-level WINDOW_UPDATE when enough credit is due.
void cwist_http2_stream_flow_control_init(cwist_http2_stream_flow_control *flow_control, uint32_t stream_id, uint32_t initial_stream_window, uint32_t max_stream_window)
Initialize per-stream flow-control state.
void cwist_http2_flow_control_init(cwist_http2_flow_control *flow_control, uint32_t initial_connection_window, uint32_t max_connection_window)
Initialize connection-level flow-control state.
#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.
void cwist_http_request_destroy(cwist_http_request *req)
Destroy an HTTP request object.
#define CWIST_HTTP_TIMEOUT_MS
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_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_WARN(fmt,...)
Production-grade metrics engine for CWIST.
#define CWIST_HTTP2_IDLE_TIMEOUT_MS
cwist_http_response * cwist_http_response_create_in_arena(void *arena)
Create a response inside an existing (request) arena. The response is bump-allocated from arena and s...
bool cwist_https_conn_idle(cwist_https_connection *conn)
True when conn has no buffered TLS bytes and nothing readable.
bool cwist_https_park(cwist_https_connection *conn, uint64_t idle_ms, cwist_https_park_class cls)
Hand an idle connection back to the HTTPS pool's park set.
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.
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.
void cwist_seq_message_free(cwist_seq_message_t *msg)
Free all buffers allocated by cwist_seq_split().
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.
void cwist_seq_chunk_build_header(uint8_t out[CWIST_SEQ_HEADER_SIZE], uint16_t seq, uint16_t total, uint16_t payload_len, uint16_t chunk_size)
Write an 8-byte sequence header.
#define CWIST_SEQ_HEADER_SIZE
bool cwist_seq_split(const uint8_t *data, size_t len, uint16_t chunk_payload_size, cwist_seq_message_t *out)
Split data into sequenced chunks.
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_ERROR_WANT_READ
#define SSL_ERROR_WANT_WRITE
Integrated error structure used throughout the system.
__prim_cwist_error_t error
int(* on_poll)(void *conn_ctx, void *stream_ctx)
int(* on_data)(void *conn_ctx, void *stream_ctx, const unsigned char *data, size_t len, int end_stream)
void *(* on_conn_open)(void *user_ctx)
void(* on_cancel)(void *conn_ctx, void *stream_ctx)
uint64_t(* next_deadline_ms)(void *conn_ctx)
cwist_query_map * query_params
Parsed query parameters.
cwist_sstring * path
e.g., "/users/1"
cwist_sstring * query
e.g., "active=true" (raw)
uint32_t stream_id
HTTP/2 or HTTP/3 stream ID (0 for HTTP/1.1).
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.
int file_stream_fd
Open descriptor for sendfile.
bool use_file_stream
Fast File Streaming.
const void * ptr_body
Pointer to external data (e.g., mmap region)
cwist_http_status_t status_code
off_t file_stream_offset
Current offset for sendfile loop.
cwist_http_header_node * headers
bool is_ptr_body
Zero-Copy Pointer Body.
size_t ptr_body_len
Length of external data.
size_t file_stream_len
Total bytes to stream.
void(* proto_state_free)(void *state)
bool http2_sequenced_data
Backing state for cwist_scratch_alloc()/CWIST_SCRATCH_DEFER: a single block-scoped allocation that pr...
Parsed view of one sequenced chunk.
Split a message into sequenced chunks.
char * data
Access directly only when raw handling is necessary.
Unified error code and type definitions for the CWIST system layer.