cwist
Modern C web framework with libttak runtime
Loading...
Searching...
No Matches
http2.c
Go to the documentation of this file.
1
8#define _POSIX_C_SOURCE 200809L
9#include <unistd.h>
10#include <stdbool.h>
11#include <stdio.h>
12#include <stdint.h>
13#include <stdlib.h>
14#include <string.h>
15#include <strings.h>
16#include <poll.h>
17#include <fcntl.h>
18#include <pthread.h>
19#include <ctype.h>
20#include <stdatomic.h>
21#include <sys/socket.h>
22#include <arpa/inet.h>
23#include <errno.h>
24#include <time.h>
25
29#include "async_internal.h"
30#include "h2_wire.h"
32#include <cwist/core/mem/gc.h>
33#include <cwist/core/log.h>
35#include <cwist/core/seq/seq.h>
38
39#ifdef __linux__
40#include <sys/eventfd.h>
41#endif
42
43#include <openssl/ssl.h>
44#include <openssl/err.h>
45
46/* --- Internal Macros --- */
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
52/* HPACK dynamic table capacity advertised via SETTINGS_HEADER_TABLE_SIZE
53 * (RFC 7541 section 4.2 default). */
54#define CWIST_HTTP2_HEADER_TABLE_SIZE 4096
55
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
61
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
71
72/* CWIST sequenced DATA extension: each DATA frame carries one seq chunk.
73 * Disabled by default so that standard HTTP/2 clients receive plain DATA
74 * payloads. Applications that want the CWIST-specific sequenced stream can
75 * enable it per-connection via cwist_https_connection.http2_sequenced_data. */
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))
79
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
90
91/* Rapid Reset (CVE-2023-44487), CONTINUATION Flood (CVE-2024-27983), and DoS mitigation limits */
92#define CWIST_HTTP2_DEFAULT_RST_BURST 100
93#define CWIST_HTTP2_DEFAULT_RST_RATE 100 /* resets per second refill */
94#define CWIST_HTTP2_DEFAULT_PING_BURST 100
95#define CWIST_HTTP2_DEFAULT_PING_RATE 50 /* pings per second refill */
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
99
104static inline uint64_t h2_now_ns(void) {
105 struct timespec ts;
107 return (uint64_t)ts.tv_sec * 1000000000ull + (uint64_t)ts.tv_nsec;
108}
109
110static uint64_t h2_now_ms(void) {
111 return h2_now_ns() / 1000000;
112}
113
118static uint64_t h2_now_us(void) {
119 return h2_now_ns() / 1000;
120}
121
128static inline long h2_env_long(const char *env, long fallback, long min) {
129 if (!env || !*env) return fallback;
130 char *end = NULL;
131 long v = strtol(env, &end, 10);
132 return (end != env && *end == '\0' && v >= min) ? v : fallback;
133}
134
141static int h2_idle_timeout_ms(void) {
142 static int timeout_ms = 0;
143 if (timeout_ms == 0) {
144 timeout_ms =
145 (int)h2_env_long(getenv("CWIST_HTTP2_IDLE_TIMEOUT_MS"), CWIST_HTTP2_IDLE_TIMEOUT_MS, 1);
146 }
147 return timeout_ms;
148}
149
158#define CWIST_HTTP2_GOAWAY_GRACE_MS_DEFAULT 2000
159static int h2_goaway_grace_ms(void) {
160 static int grace_ms = -1;
161 if (grace_ms < 0) {
162 grace_ms = (int)h2_env_long(getenv("CWIST_HTTP2_GOAWAY_GRACE_MS"),
164 }
165 return grace_ms;
166}
167
174static uint32_t h2_max_rst_burst(void) {
175 static uint32_t val = 0;
176 if (val == 0) {
177 val = (uint32_t)h2_env_long(getenv("CWIST_HTTP2_MAX_RST_BURST"),
179 }
180 return val;
181}
182
189static uint32_t h2_max_rst_rate(void) {
190 static uint32_t val = 0;
191 if (val == 0) {
192 val = (uint32_t)h2_env_long(getenv("CWIST_HTTP2_MAX_RST_RATE"),
194 }
195 return val;
196}
197
198/* --- Stream & Connection State --- */
199
200typedef struct h2_conn h2_conn;
201
202typedef struct cwist_h2_stream {
203 uint32_t stream_id;
205 /* Adaptive flow control (send: peer-advertised credit; receive: our
206 * advertised credit). Replaces the old hand-rolled window ints. */
208 bool send_aborted; /* RST_STREAM received while sending */
209 int fc_waiters; /* handler threads parked in h2_fc_wait_credit */
210 bool fc_detached; /* stream removed / connection tearing down */
211 uint8_t recv_xor; /* running XOR of received DATA payload bytes */
212 uint8_t send_xor; /* running XOR of sent DATA payload bytes */
213 cwist_seq_assembler_t *body_assembler; /* reorders sequenced DATA chunks */
214 h2_conn *hc; /* owning connection (for hook-driven sends) */
215 void *hook_ctx; /* non-NULL when a stream hook owns this stream */
216 bool hook_offered; /* on_headers already consulted for this stream */
217 struct cwist_h2_stream *next;
219
220/* HPACK dynamic table entry (RFC 7541 section 4). Entries are chained newest-first;
221 * dynamic index 62 addresses the head (most recently inserted). */
222typedef struct h2_hpack_entry {
223 char *name;
224 char *value;
225 size_t size; /* name_len + value_len + 32 (RFC 7541 section 4.1) */
226 struct h2_hpack_entry *next;
228
229typedef struct h2_deferred_frame {
230 unsigned char hdr[9];
231 unsigned char *payload; /* owned; NULL when the frame has no payload */
232 struct h2_deferred_frame *next;
234
235/* --- Async defer completion queue ---
236 *
237 * Plan B for deferred responses: the worker thread that completes an async
238 * job only enqueues the finished (stream_id, req, res) node and pokes the
239 * wake fd; the connection thread drains the queue and does HPACK encoding +
240 * frame writes itself, preserving single-threaded HPACK dynamic table and
241 * flow-control integrity. refs: one held by the connection, one per
242 * in-flight cwist_async handle (acquired in cwist_async_defer). */
243typedef struct h2_async_node {
244 uint32_t stream_id;
246 cwist_http_response *send; /* response to transmit (final_res) */
247 cwist_http_response *res; /* handler's response (request arena) */
248 bool send_owned; /* send came from respond_with */
249 struct h2_async_node *next;
251
253 int wake_rd; /* read side: eventfd (Linux) or pipe */
254 int wake_wr; /* write side (same as wake_rd for eventfd) */
257 h2_async_node *tail;
258 bool closed; /* connection tore down: enqueue discards */
259 _Atomic int refs;
260};
261
269static void h2_async_node_discard(h2_async_node *n) {
270 if (n->send_owned && n->send && n->send != n->res) cwist_http_response_destroy(n->send);
271 if (n->res) cwist_http_response_destroy(n->res);
272 if (n->req) cwist_http_request_destroy(n->req);
273 cwist_free(n);
274}
275
283static cwist_h2_async_queue *cwist_h2_async_queue_create(void) {
285 if (!q) return NULL;
286 memset(q, 0, sizeof(*q));
287 q->wake_rd = -1;
288 q->wake_wr = -1;
289 pthread_mutex_init(&q->mu, NULL);
290 atomic_init(&q->refs, 1);
291#ifdef __linux__
292 q->wake_rd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
293 q->wake_wr = q->wake_rd;
294#else
295 int pfds[2];
296 if (pipe(pfds) == 0) {
297 for (int i = 0; i < 2; i++) {
298 int fl = fcntl(pfds[i], F_GETFL, 0);
299 if (fl >= 0) fcntl(pfds[i], F_SETFL, fl | O_NONBLOCK);
300 }
301 q->wake_rd = pfds[0];
302 q->wake_wr = pfds[1];
303 }
304#endif
305 /* Explicit queue refs can outlive the connection thread's TLS GC. */
307 return q;
308}
309
318 return q;
319}
320
327void cwist_h2_async_queue_release(cwist_h2_async_queue *q) {
328 if (!q) return;
329 if (atomic_fetch_sub_explicit(&q->refs, 1, memory_order_acq_rel) != 1) return;
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);
332 pthread_mutex_destroy(&q->mu);
333 cwist_free(q);
334}
335
336/* Worker-thread entry point: enqueue the finished exchange and wake the
337 * connection thread. After connection teardown (closed) the node is
338 * discarded without sending. */
339int cwist_h2_async_queue_enqueue(cwist_h2_async_queue *q, uint32_t stream_id,
341 cwist_http_response *res, bool send_owned) {
342 if (!q) return -1;
343 h2_async_node *n = (h2_async_node *)cwist_alloc(sizeof(*n));
344 if (!n) {
345 /* Enqueue consumes the exchange even when staging fails. Its
346 * producer already relinquished ownership before calling us. */
347 if (send_owned && send && send != res) cwist_http_response_destroy(send);
350 return -1;
351 }
352 n->stream_id = stream_id;
353 n->req = req;
354 n->send = send;
355 n->res = res;
356 n->send_owned = send_owned;
357 n->next = NULL;
358 pthread_mutex_lock(&q->mu);
359 if (q->closed) {
360 pthread_mutex_unlock(&q->mu);
361 h2_async_node_discard(n);
362 return -1;
363 }
364 /* The connection may drain immediately after the mutex is released;
365 * producer TLS sweeping must no longer own this published node. */
367 if (q->tail)
368 q->tail->next = n;
369 else
370 q->head = n;
371 q->tail = n;
372 /* Wake while holding mu: after close()+release the fds may be recycled,
373 * and closed is flipped under the same mutex before fds are closed. */
374 if (q->wake_wr >= 0) {
375 uint64_t one = 1;
376 ssize_t w = write(q->wake_wr, &one, sizeof(one));
377 (void)w; /* EAGAIN: counter already non-zero, fd stays readable */
378 }
379 pthread_mutex_unlock(&q->mu);
380 return 0;
381}
382
388static int cwist_h2_async_queue_fd(const cwist_h2_async_queue *q) {
389 return q ? q->wake_rd : -1;
390}
391
392/* Connection teardown: stop accepting completions and discard everything
393 * still queued (the worker already gave up ownership of req/res). */
394static void cwist_h2_async_queue_close(cwist_h2_async_queue *q) {
395 pthread_mutex_lock(&q->mu);
396 q->closed = true;
397 h2_async_node *list = q->head;
398 q->head = q->tail = NULL;
399 pthread_mutex_unlock(&q->mu);
400 while (list) {
401 h2_async_node *next = list->next;
402 h2_async_node_discard(list);
403 list = next;
404 }
405}
406
407typedef struct h2_conn {
409 h2_stream *streams;
410 uint32_t peer_max_frame_size;
411 uint32_t peer_initial_window_size;
412 uint32_t peer_max_concurrent_streams;
413 /* Connection-level adaptive flow control: tracks both our advertised
414 * receive credit and the peer-advertised send credit, auto-tunes the
415 * receive target from PING-measured RTT, and paces sends once RTT is
416 * known. */
418 uint64_t ping_sent_us;
419 unsigned char ping_payload[8];
420 bool ping_outstanding;
421 /* Userspace frame batching: outgoing frames accumulate here and flush on
422 * size threshold or before any blocking wait, cutting per-frame write /
423 * SSL_write syscalls for both h2c and TLS. */
424 unsigned char *out_buf;
425 size_t out_len;
426 size_t out_cap;
427 unsigned char *cont_buf;
428 size_t cont_len;
429 size_t cont_cap;
430 uint32_t cont_stream_id;
431 bool expecting_continuation;
432 bool cont_end_stream;
433 bool sequenced_data; /* CWIST extension: DATA frames carry seq chunks */
434 uint32_t last_processed_stream_id;
435 uint64_t last_activity; /* monotonic ms of the last complete frame read */
436 /* Frames the send-window wait loop had to read off the wire but must not
437 * dispatch (HEADERS/SETTINGS of other streams, etc.). The main loop
438 * drains these before touching the socket again. */
439 h2_deferred_frame *deferred_head;
440 h2_deferred_frame *deferred_tail;
441 /* HPACK decoder dynamic table state (RFC 7541 section 4). */
442 h2_hpack_entry *hpack_head;
443 uint32_t hpack_size;
444 uint32_t hpack_capacity;
445 /* Number of currently open streams; enforced against
446 * CWIST_HTTP2_MAX_CONCURRENT_STREAMS on new-stream HEADERS (section 5.1.2). */
447 uint32_t active_streams;
448 /* True when a header block was refused (stream limit) but its
449 * CONTINUATION frames must still be consumed and discarded. */
450 bool cont_discard;
451 uint32_t cont_frame_count;
452 /* Token bucket budget for Rapid Reset (CVE-2023-44487) and control frames */
453 uint32_t rst_budget;
454 uint64_t rst_last_refill_ms;
455 uint32_t ping_budget;
456 uint64_t ping_last_refill_ms;
457 /* Token bucket for *outbound* server-initiated RST_STREAM ("Made You Reset"
458 * mitigation, issue #97). Mirrors the inbound rst_budget so an attacker
459 * who holds 100 concurrent streams open and floods HEADERS can't drive
460 * unbounded RST_STREAM writes from the server. */
461 uint32_t out_rst_budget;
462 uint64_t out_rst_last_refill_ms;
463 /* Optional stream hooks (gRPC incremental delivery). hook_ctx is the
464 * per-connection context from hooks->on_conn_open (or user_ctx). */
465 const cwist_http2_stream_hooks *hooks;
466 void *hook_ctx;
467 /* Serializes the outbound batch buffer between the dispatcher loop and
468 * hook-taken stream handler threads that emit frames directly. */
469 pthread_mutex_t out_mu;
470 bool out_mu_init;
471 /* Send-credit rendezvous for hook-taken handler threads: the dispatcher
472 * owns socket reads, so a handler thread that runs out of send window
473 * parks on fc_cond and is woken when the dispatcher applies WINDOW_UPDATE
474 * credit or when the stream/connection goes away. */
475 pthread_mutex_t fc_mu;
476 pthread_cond_t fc_cond;
477 bool fc_mu_init;
478 /* Completed deferred responses, fed by async worker threads and drained
479 * by this connection thread (see cwist_h2_async_queue). */
480 cwist_h2_async_queue *async_q;
481} h2_conn;
482
492static void h2_conn_init(h2_conn *hc, cwist_https_connection *conn) {
493 memset(hc, 0, sizeof(*hc));
494 hc->conn = conn;
495 hc->peer_max_frame_size = CWIST_HTTP2_MAX_FRAME_SIZE;
496 hc->peer_initial_window_size = 65535;
497 /* Start at the RFC-default 65535 on the send side; the startup code
498 * bumps our receive credit to 2GB on the wire and mirrors that into
499 * fc.receive_window. */
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;
506 hc->ping_budget = CWIST_HTTP2_DEFAULT_PING_BURST;
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;
511 /* The extension carries application bodies and must not be enabled over
512 * h2c: its ordering metadata is not an integrity mechanism. HTTPS/TLS
513 * supplies authenticated transport protection against on-path mutation. */
514 hc->sequenced_data = conn && conn->ssl && conn->http2_sequenced_data;
515 hc->hpack_capacity = CWIST_HTTP2_HEADER_TABLE_SIZE;
516 hc->out_mu_init = (pthread_mutex_init(&hc->out_mu, NULL) == 0);
517 hc->fc_mu_init = (pthread_mutex_init(&hc->fc_mu, NULL) == 0);
518 if (hc->fc_mu_init && pthread_cond_init(&hc->fc_cond, NULL) != 0) {
519 pthread_mutex_destroy(&hc->fc_mu);
520 hc->fc_mu_init = false;
521 }
522}
523
530static void h2_fc_credit_signal(h2_conn *hc) {
531 if (!hc->fc_mu_init) return;
532 pthread_mutex_lock(&hc->fc_mu);
533 pthread_cond_broadcast(&hc->fc_cond);
534 pthread_mutex_unlock(&hc->fc_mu);
535}
536
547static void h2_stream_drain_waiters(h2_conn *hc, h2_stream *s) {
548 if (!hc->fc_mu_init) return;
549 pthread_mutex_lock(&hc->fc_mu);
550 s->fc_detached = true;
551 pthread_cond_broadcast(&hc->fc_cond);
552 while (s->fc_waiters > 0) pthread_cond_wait(&hc->fc_cond, &hc->fc_mu);
553 pthread_mutex_unlock(&hc->fc_mu);
554}
555
565static void h2_conn_destroy(h2_conn *hc) {
566 h2_stream *s = hc->streams;
567 while (s) {
568 h2_stream *next = s->next;
569 /* Drain before on_close: a handler thread parked in a send holds its
570 * session write mutex, which on_close needs to detach. */
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);
574 if (s->req) cwist_http_request_destroy(s->req);
575 cwist_seq_assembler_destroy(s->body_assembler);
576 cwist_free(s);
577 s = next;
578 }
579 if (hc->hooks && hc->hooks->on_conn_close) hc->hooks->on_conn_close(hc->hook_ctx);
580 cwist_free(hc->cont_buf);
581 cwist_free(hc->out_buf);
582 h2_hpack_entry *he = hc->hpack_head;
583 while (he) {
584 h2_hpack_entry *next = he->next;
585 cwist_free(he->name);
586 cwist_free(he->value);
587 cwist_free(he);
588 he = next;
589 }
590 hc->hpack_head = NULL;
591 hc->hpack_size = 0;
592 h2_deferred_frame *df = hc->deferred_head;
593 while (df) {
594 h2_deferred_frame *next = df->next;
595 cwist_free(df->payload);
596 cwist_free(df);
597 df = next;
598 }
599 if (hc->out_mu_init) pthread_mutex_destroy(&hc->out_mu);
600 if (hc->fc_mu_init) {
601 pthread_cond_destroy(&hc->fc_cond);
602 pthread_mutex_destroy(&hc->fc_mu);
603 }
604 if (hc->async_q) {
605 /* Pending completions are discarded; completions arriving after the
606 * close hit queue->closed and discard themselves. The handle-held
607 * references keep the queue alive until the last worker releases. */
608 cwist_h2_async_queue_close(hc->async_q);
609 cwist_h2_async_queue_release(hc->async_q);
610 hc->async_q = NULL;
611 }
612}
613
622static bool h2_conn_consume_rst_budget(h2_conn *hc) {
623 uint64_t now = h2_now_ms();
624 uint64_t elapsed = (now > hc->rst_last_refill_ms) ? (now - hc->rst_last_refill_ms) : 0;
625 if (elapsed >= 1000) {
626 hc->rst_budget = h2_max_rst_burst();
627 hc->rst_last_refill_ms = now;
628 } else if (elapsed > 0) {
629 uint32_t add = (uint32_t)((elapsed * h2_max_rst_rate()) / 1000);
630 if (add > 0) {
631 hc->rst_budget += add;
632 if (hc->rst_budget > h2_max_rst_burst()) {
633 hc->rst_budget = h2_max_rst_burst();
634 }
635 hc->rst_last_refill_ms = now;
636 }
637 }
638
639 if (hc->rst_budget == 0) {
640 return false;
641 }
642 hc->rst_budget--;
643 return true;
644}
645
653static bool h2_conn_consume_out_rst_budget(h2_conn *hc) {
654 uint64_t now = h2_now_ms();
655 uint64_t elapsed = (now > hc->out_rst_last_refill_ms) ? (now - hc->out_rst_last_refill_ms) : 0;
656 if (elapsed >= 1000) {
657 hc->out_rst_budget = h2_max_rst_burst();
658 hc->out_rst_last_refill_ms = now;
659 } else if (elapsed > 0) {
660 uint32_t add = (uint32_t)((elapsed * h2_max_rst_rate()) / 1000);
661 if (add > 0) {
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;
665 }
666 }
667 if (hc->out_rst_budget == 0) return false;
668 hc->out_rst_budget--;
669 return true;
670}
671
680static bool h2_conn_consume_ping_budget(h2_conn *hc) {
681 uint64_t now = h2_now_ms();
682 uint64_t elapsed = (now > hc->ping_last_refill_ms) ? (now - hc->ping_last_refill_ms) : 0;
683 if (elapsed >= 1000) {
684 hc->ping_budget = CWIST_HTTP2_DEFAULT_PING_BURST;
685 hc->ping_last_refill_ms = now;
686 } else if (elapsed > 0) {
688 if (add > 0) {
689 hc->ping_budget += add;
690 if (hc->ping_budget > CWIST_HTTP2_DEFAULT_PING_BURST) {
691 hc->ping_budget = CWIST_HTTP2_DEFAULT_PING_BURST;
692 }
693 hc->ping_last_refill_ms = now;
694 }
695 }
696
697 if (hc->ping_budget == 0) {
698 return false;
699 }
700 hc->ping_budget--;
701 return true;
702}
703
710static h2_stream *h2_stream_find(h2_conn *hc, uint32_t stream_id) {
711 h2_stream *s = hc->streams;
712 while (s) {
713 if (s->stream_id == stream_id) return s;
714 s = s->next;
715 }
716 return NULL;
717}
718
729static h2_stream *h2_stream_create(h2_conn *hc, uint32_t stream_id) {
730 h2_stream *s = (h2_stream *)cwist_alloc(sizeof(*s));
731 if (!s) return NULL;
732 memset(s, 0, sizeof(*s));
733 s->stream_id = stream_id;
734 s->hc = hc;
735 /* Receive credit matches the INITIAL_WINDOW_SIZE=2GB we advertise in
736 * SETTINGS; send credit starts at the peer's initial window. */
739 s->fc.send_window = hc->peer_initial_window_size;
740 s->next = hc->streams;
741 hc->streams = s;
742 hc->active_streams++;
743 return s;
744}
745
754static void h2_stream_remove(h2_conn *hc, uint32_t stream_id) {
755 h2_stream **pp = &hc->streams;
756 while (*pp) {
757 h2_stream *s = *pp;
758 if (s->stream_id == stream_id) {
759 *pp = s->next;
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);
764 if (s->req) cwist_http_request_destroy(s->req);
765 cwist_seq_assembler_destroy(s->body_assembler);
766 cwist_free(s);
767 return;
768 }
769 pp = &s->next;
770 }
771}
772
773/* --- Static Header Table --- */
774
775static const cwist_http2_static_header cwist_http2_static_table[] = {
776 {NULL, NULL},
777 {":authority", ""},
778 {":method", "GET"},
779 {":method", "POST"},
780 {":path", "/"},
781 {":path", "/index.html"},
782 {":scheme", "http"},
783 {":scheme", "https"},
784 {":status", "200"},
785 {":status", "204"},
786 {":status", "206"},
787 {":status", "304"},
788 {":status", "400"},
789 {":status", "404"},
790 {":status", "500"},
791 {"accept-charset", ""},
792 {"accept-encoding", "gzip, deflate"},
793 {"accept-language", ""},
794 {"accept-ranges", ""},
795 {"accept", ""},
796 {"access-control-allow-origin", ""},
797 {"age", ""},
798 {"allow", ""},
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", ""},
808 {"cookie", ""},
809 {"date", ""},
810 {"etag", ""},
811 {"expect", ""},
812 {"expires", ""},
813 {"from", ""},
814 {"host", ""},
815 {"if-match", ""},
816 {"if-modified-since", ""},
817 {"if-none-match", ""},
818 {"if-range", ""},
819 {"if-unmodified-since", ""},
820 {"last-modified", ""},
821 {"link", ""},
822 {"location", ""},
823 {"max-forwards", ""},
824 {"proxy-authenticate", ""},
825 {"proxy-authorization", ""},
826 {"range", ""},
827 {"referer", ""},
828 {"refresh", ""},
829 {"retry-after", ""},
830 {"server", ""},
831 {"set-cookie", ""},
832 {"strict-transport-security", ""},
833 {"transfer-encoding", ""},
834 {"user-agent", ""},
835 {"vary", ""},
836 {"via", ""},
837 {"www-authenticate", ""},
838};
839
840/* --- I/O Helpers --- */
841
842static int h2_read(cwist_https_connection *conn, void *buf, int len) {
843 if (conn->ssl) return SSL_read(conn->ssl, buf, len);
844 /* Cleartext connections may carry bytes the accept-path sniffer already
845 * consumed (e.g. the h2c preface and the frames pipelined with it);
846 * drain that prebuffer before touching the socket. */
847 if (conn->read_buf && conn->buf_len > 0) {
848 size_t n = conn->buf_len < (size_t)len ? conn->buf_len : (size_t)len;
849 memcpy(buf, conn->read_buf, n);
850 memmove(conn->read_buf, conn->read_buf + n, conn->buf_len - n);
851 conn->buf_len -= n;
852 return (int)n;
853 }
854 return read(conn->fd, buf, len);
855}
856
857/* Match h2_read: TLS owns its plaintext buffer; h2c uses sniffed bytes. */
858static bool h2_has_buffered_input(const cwist_https_connection *conn) {
859 if (conn->ssl) return SSL_pending(conn->ssl) > 0;
860 return conn->read_buf && conn->buf_len > 0;
861}
862
870static int h2_write(cwist_https_connection *conn, const void *buf, int len) {
871 if (conn->ssl) return SSL_write(conn->ssl, buf, len);
872 return write(conn->fd, buf, len);
873}
874
875/* Lightweight XOR checksum over a byte buffer. */
876static uint8_t h2_xor_bytes(const unsigned char *buf, size_t len) {
877 uint8_t x = 0;
878 for (size_t i = 0; i < len; i++) x ^= buf[i];
879 return x;
880}
881
882/* Poll the socket for the direction OpenSSL is waiting on, or for plain
883 * socket writability. Returns 0 when ready, -1 on timeout/error. */
884static int h2_wait_socket(cwist_https_connection *conn, int ssl_error, int timeout_ms) {
885 struct pollfd pfd = {.fd = conn->fd, .events = 0};
886 if (conn->ssl) {
888 pfd.events = POLLIN;
889 } else if (ssl_error == SSL_ERROR_WANT_WRITE) {
890 pfd.events = POLLOUT;
891 } else {
892 return -1;
893 }
894 } else {
895 pfd.events = POLLOUT;
896 }
897 int ret = poll(&pfd, 1, timeout_ms);
898 if (ret <= 0) return -1;
899 if (pfd.revents & (POLLERR | POLLHUP | POLLNVAL)) return -1;
900 return 0;
901}
902
903/* Poll until frame bytes can be read, an async defer completion lands on the
904 * connection queue, or the connection idle deadline expires. Skips the poll
905 * when decrypted bytes are already buffered.
906 * @p deadline_ms optionally bounds the wait to an absolute monotonic
907 * timestamp (used for the post-GOAWAY grace window); 0 disables it.
908 * Returns 0 when readable, 1 when the async queue woke the wait, -1 on idle
909 * timeout, deadline expiry, or socket error. */
910static int h2_out_flush(h2_conn *hc);
911
912static int h2_wait_readable(h2_conn *hc, uint64_t deadline_ms) {
913 /* Never block with unflushed frames in the batch buffer. */
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)) {
917 uint64_t idle_ms = (uint64_t)h2_idle_timeout_ms();
918 uint64_t now = h2_now_ms();
919 if (now - hc->last_activity >= idle_ms) return -1;
920 uint64_t idle_left = idle_ms - (now - hc->last_activity);
921 if (deadline_ms) {
922 if (now >= deadline_ms) return -1;
923 uint64_t deadline_left = deadline_ms - now;
925 }
928 struct pollfd pfd[2];
929 nfds_t nfd = 1;
930 pfd[0].fd = hc->conn->fd;
931 pfd[0].events = POLLIN;
932 pfd[0].revents = 0;
933 if (afd >= 0) {
934 pfd[1].fd = afd;
935 pfd[1].events = POLLIN;
936 pfd[1].revents = 0;
937 nfd = 2;
938 }
939 int pret = poll(pfd, nfd, wait_ms);
940 if (pret < 0) return -1;
941 if (pret > 0) {
942 if (nfd == 2 && (pfd[1].revents & POLLIN)) return 1;
943 if (pfd[0].revents & POLLIN) {
944 if (afd >= 0 && hc->async_q) {
945 /* If async queue has pending completions, drain them first */
946 pthread_mutex_lock(&hc->async_q->mu);
947 bool has_items = (hc->async_q->head != NULL);
948 pthread_mutex_unlock(&hc->async_q->mu);
949 if (has_items) return 1;
950 }
951 return 0;
952 }
953 return 0; /* error/hup on the socket: let the read path report it */
954 }
955 }
956 return 0;
957}
958
959/* Read exactly len bytes, tolerating SSL_ERROR_WANT_READ/WANT_WRITE (and
960 * plain EAGAIN) on the non-blocking sockets the pool hands us. Bounded by
961 * the connection idle deadline so a stalled peer cannot pin a worker.
962 * Returns 0 on success, -1 on EOF, timeout, or socket error. */
963static int h2_read_full(h2_conn *hc, void *buf, size_t len) {
964 unsigned char *p = (unsigned char *)buf;
965 size_t got = 0;
966 while (got < len) {
967 int n = h2_read(hc->conn, p + got, (int)(len - got));
968 if (n > 0) {
969 got += (size_t)n;
970 hc->last_activity = h2_now_ms();
971 continue;
972 }
973 if (n < 0) {
974 int retryable = 0;
975 int ssl_err = 0;
976 if (hc->conn->ssl) {
977 ssl_err = SSL_get_error(hc->conn->ssl, n);
979 } else {
980 retryable = (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR);
981 }
982 if (retryable) {
983 uint64_t idle_ms = (uint64_t)h2_idle_timeout_ms();
984 uint64_t now = h2_now_ms();
985 if (now - hc->last_activity >= idle_ms) return -1;
986 uint64_t left = idle_ms - (now - hc->last_activity);
988 if (left < (uint64_t)wait_ms) wait_ms = (int)left;
989 if (hc->conn->ssl) {
990 if (h2_wait_socket(hc->conn, ssl_err, wait_ms) != 0) return -1;
991 } else {
992 struct pollfd pfd = {.fd = hc->conn->fd, .events = POLLIN};
993 if (poll(&pfd, 1, wait_ms) <= 0) return -1;
994 }
995 continue;
996 }
997 }
998 return -1; /* EOF (0) or a real read error */
999 }
1000 return 0;
1001}
1002
1003static int h2_write_all(cwist_https_connection *conn, const void *buf, size_t len) {
1004 const unsigned char *p = (const unsigned char *)buf;
1005 int retries = 0;
1006 /* 100ms polls; keep the total wait under the connection idle deadline. */
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;
1010 while (len > 0) {
1011 int n = h2_write(conn, p, (int)len);
1012 if (n <= 0) {
1013 if (n < 0) {
1014 if (conn->ssl) {
1015 int err = SSL_get_error(conn->ssl, n);
1017 if (++retries > max_retries) return -1; /* ~overall timeout guard */
1018 if (h2_wait_socket(conn, err, 100) != 0) return -1;
1019 continue;
1020 }
1021 } else {
1022 if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) {
1023 if (++retries > max_retries) return -1;
1024 if (h2_wait_socket(conn, 0, 100) != 0) return -1;
1025 continue;
1026 }
1027 }
1028 }
1029 return -1;
1030 }
1031 retries = 0;
1032 p += n;
1033 len -= (size_t)n;
1034 }
1035 return 0;
1036}
1037
1038static void h2_make_frame_header(unsigned char out[CWIST_HTTP2_FRAME_HEADER_SIZE], uint32_t len,
1039 uint8_t type, uint8_t flags, uint32_t stream_id) {
1040 h2_put_u24(out, len);
1041 out[3] = type;
1042 out[4] = flags;
1043 h2_put_u31(out + 5, stream_id);
1044}
1045
1046/* --- Userspace frame batching ---
1047 * Frames accumulate in hc->out_buf and flush when the batch reaches the
1048 * threshold (CWIST_H2_BATCH_BYTES, default 64 KiB) or before any blocking
1049 * wait. One write/SSL_write per batch instead of two per frame. */
1050#define CWIST_H2_BATCH_DEFAULT_BYTES (64 * 1024)
1051
1052static size_t h2_batch_threshold(void) {
1053 static size_t threshold = 0;
1054 if (threshold == 0) {
1055 threshold =
1056 (size_t)h2_env_long(getenv("CWIST_H2_BATCH_BYTES"), CWIST_H2_BATCH_DEFAULT_BYTES, 4096);
1057 }
1058 return threshold;
1059}
1060
1061/* Raw variants: caller must hold out_mu (or be single-threaded setup). */
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;
1065 hc->out_len = 0;
1066 return 0;
1067}
1068
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;
1073 while (cap < hc->out_len + len) cap *= 2;
1074 unsigned char *nb = (unsigned char *)cwist_realloc(hc->out_buf, cap);
1075 if (!nb) return -1;
1076 hc->out_buf = nb;
1077 hc->out_cap = cap;
1078 }
1079 memcpy(hc->out_buf + hc->out_len, buf, len);
1080 hc->out_len += len;
1081 if (hc->out_len >= h2_batch_threshold()) return h2_out_flush_raw(hc);
1082 return 0;
1083}
1084
1085/* The batch buffer is shared with hook-taken stream handler threads that
1086 * emit frames directly, so every mutation is serialized on out_mu. */
1087static int h2_out_flush(h2_conn *hc) {
1088 if (!hc->out_mu_init) return h2_out_flush_raw(hc);
1089 pthread_mutex_lock(&hc->out_mu);
1090 int rc = h2_out_flush_raw(hc);
1091 pthread_mutex_unlock(&hc->out_mu);
1092 return rc;
1093}
1094
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);
1097 pthread_mutex_lock(&hc->out_mu);
1098 int rc = h2_out_append_raw(hc, buf, len);
1099 pthread_mutex_unlock(&hc->out_mu);
1100 return rc;
1101}
1102
1103/* Batched frame write: appends to the connection batch buffer. */
1104static int h2_write_frame(h2_conn *hc, uint8_t type, uint8_t flags, uint32_t stream_id,
1105 const unsigned char *payload, uint32_t len) {
1106 unsigned char frame[CWIST_HTTP2_FRAME_HEADER_SIZE];
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);
1110 return 0;
1111}
1112
1113/* Immediate frame write for paths without a connection context (push). */
1114static int h2_write_frame_now(cwist_https_connection *conn, uint8_t type, uint8_t flags,
1115 uint32_t stream_id, const unsigned char *payload, uint32_t len) {
1116 unsigned char frame[CWIST_HTTP2_FRAME_HEADER_SIZE];
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);
1120 return 0;
1121}
1122
1123/* Dispatch helper for helpers shared by batched and immediate paths. */
1124static int h2_write_frame_any(h2_conn *hc, cwist_https_connection *conn, uint8_t type,
1125 uint8_t flags, uint32_t stream_id, const unsigned char *payload,
1126 uint32_t 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);
1129}
1130
1131/* --- GOAWAY & WINDOW_UPDATE Helpers --- */
1132
1133static int h2_send_goaway(h2_conn *hc, uint32_t last_stream_id, uint32_t error_code) {
1134 unsigned char payload[8];
1135 h2_put_u31(payload, last_stream_id);
1136 h2_put_u32(payload + 4, error_code);
1137 return h2_write_frame(hc, CWIST_HTTP2_FRAME_GOAWAY, 0, 0, payload, 8);
1138}
1139
1140/* Send RST_STREAM for stream_id with error_code.
1141 * Returns 0 on success,
1142 * -1 on write error,
1143 * -2 when the outbound RST budget is depleted (GOAWAY/ENHANCE_YOUR_CALM
1144 * already sent; caller must close the connection).
1145 * The -2 path implements the "Made You Reset" mitigation (issue #97): once
1146 * the server has sent as many RST_STREAMs in a rolling window as it accepts
1147 * from the client (h2_max_rst_burst/h2_max_rst_rate), it switches to GOAWAY
1148 * so a client flooding new streams past the concurrency cap can't drive
1149 * unbounded frame writes from the server. */
1150static int h2_send_rst_stream(h2_conn *hc, uint32_t stream_id, uint32_t error_code) {
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)",
1154 stream_id);
1155 h2_send_goaway(hc, hc->last_processed_stream_id, H2_ERR_ENHANCE_YOUR_CALM);
1156 return -2;
1157 }
1158 unsigned char payload[4];
1159 h2_put_u32(payload, error_code);
1160 return h2_write_frame(hc, CWIST_HTTP2_FRAME_RST_STREAM, 0, stream_id, payload, 4);
1161}
1162
1163static int h2_send_window_update(h2_conn *hc, uint32_t stream_id, int32_t increment) {
1164 unsigned char payload[4];
1165 h2_put_u31(payload, (uint32_t)increment);
1166 return h2_write_frame(hc, CWIST_HTTP2_FRAME_WINDOW_UPDATE, 0, stream_id, payload, 4);
1167}
1168
1169/* Adaptive receive-window refill driven by the flow-control module: emits a
1170 * WINDOW_UPDATE only once at least half of the (RTT-scaled) target window
1171 * has been consumed, instead of the old fixed 32 KiB threshold. */
1172static int h2_auto_window_update(h2_conn *hc, h2_stream *s) {
1173 unsigned char frame_buf[13];
1174 size_t written = 0;
1175 uint64_t now = h2_now_us();
1176
1178 &written, now, false);
1179 if (r < 0) return -1;
1180 if (r > 0 && h2_out_append(hc, frame_buf, written) != 0) return -1;
1181
1182 if (s) {
1183 written = 0;
1185 &hc->fc, &s->fc, frame_buf, sizeof(frame_buf), &written, now, false);
1186 if (r < 0) return -1;
1187 if (r > 0 && h2_out_append(hc, frame_buf, written) != 0) return -1;
1188 }
1189 return 0;
1190}
1191
1192/* How many bytes may be sent right now: peer window credit, throttled by
1193 * RTT pacing once the first PING sample has calibrated it. */
1194static uint32_t h2_send_allowance(h2_conn *hc, h2_stream *s, uint32_t requested) {
1196 if (allowed > hc->fc.send_window) allowed = hc->fc.send_window;
1197 if (s && allowed > s->fc.send_window) allowed = s->fc.send_window;
1198 if (s && hc->fc.rtt_initialized && allowed > 0) {
1199 size_t paced =
1200 cwist_http2_flow_control_pacing_allowance(&hc->fc, &s->fc, allowed, h2_now_us());
1202 }
1203 return allowed;
1204}
1205
1206static void h2_commit_send(h2_conn *hc, h2_stream *s, uint32_t bytes) {
1207 if (s && hc->fc.rtt_initialized) {
1208 /* Allowance was already vetted by h2_send_allowance, so this always
1209 * succeeds and debits windows + pacing tokens together. */
1210 cwist_http2_flow_control_reserve_send(&hc->fc, &s->fc, bytes, h2_now_us());
1211 return;
1212 }
1213 hc->fc.send_window -= bytes;
1214 if (s) s->fc.send_window -= bytes;
1215}
1216
1217/* Park a hook-taken handler thread until send credit may be available again,
1218 * the stream is aborted/detached, or no frame has arrived for a full idle
1219 * deadline. The dispatcher thread owns socket reads and applies WINDOW_UPDATE
1220 * credit, then signals fc_cond; the caller must NOT hold out_mu (the
1221 * dispatcher may need it to flush its own frames while we wait).
1222 * Returns 0 when credit may be available, -1 on abort, teardown, or stall
1223 * timeout. */
1224static int h2_fc_wait_credit(h2_conn *hc, h2_stream *s) {
1225 if (!hc->fc_mu_init) return -1;
1226 pthread_mutex_lock(&hc->fc_mu);
1227 s->fc_waiters++;
1228 int rc = -1;
1229 for (;;) {
1230 if (s->send_aborted || s->fc_detached) break;
1231 if (h2_send_allowance(hc, s, 1) > 0) {
1232 rc = 0;
1233 break;
1234 }
1235 if (h2_now_ms() - hc->last_activity >= (uint64_t)h2_idle_timeout_ms()) break;
1236 struct timespec ts;
1238 ts.tv_nsec += 100000000L; /* 100ms recheck: covers pacing-token refill */
1239 if (ts.tv_nsec >= 1000000000L) {
1240 ts.tv_sec++;
1241 ts.tv_nsec -= 1000000000L;
1242 }
1243 pthread_cond_timedwait(&hc->fc_cond, &hc->fc_mu, &ts);
1244 }
1245 s->fc_waiters--;
1246 /* Wake h2_stream_drain_waiters if it is waiting for us to leave. */
1247 pthread_cond_broadcast(&hc->fc_cond);
1248 pthread_mutex_unlock(&hc->fc_mu);
1249 return rc;
1250}
1251
1252/* Send a PING carrying a monotonic timestamp; the matching ACK yields an
1253 * RTT sample that feeds the adaptive window/pacing tuning. */
1254static int h2_send_ping(h2_conn *hc) {
1255 uint64_t now = h2_now_us();
1256 unsigned char p[8];
1257 for (int i = 0; i < 8; i++) p[i] = (unsigned char)(now >> (56 - 8 * i));
1258 if (h2_write_frame(hc, CWIST_HTTP2_FRAME_PING, 0, 0, p, 8) != 0) return -1;
1259 memcpy(hc->ping_payload, p, 8);
1260 hc->ping_sent_us = now;
1261 hc->ping_outstanding = true;
1262 return 0;
1263}
1264
1265/* --- HPACK Integer Decoder --- */
1266
1267int h2_decode_integer(const unsigned char *buf, size_t len, size_t *pos, uint8_t prefix_bits,
1268 uint32_t *value) {
1269 if (*pos >= len || prefix_bits == 0 || prefix_bits > 8) return -1;
1270 uint8_t mask = (uint8_t)((1u << prefix_bits) - 1u);
1271 uint32_t n = buf[*pos] & mask;
1272 (*pos)++;
1273 if (n < mask) {
1274 *value = n;
1275 return 0;
1276 }
1277
1278 uint32_t m = 0;
1279 while (*pos < len) {
1280 uint8_t b = buf[(*pos)++];
1281 if (m > 28) return -1;
1282 n += (uint32_t)(b & 0x7f) << m;
1283 if ((b & 0x80) == 0) {
1284 *value = n;
1285 return 0;
1286 }
1287 m += 7;
1288 }
1289 return -1;
1290}
1291
1292/* --- Huffman Decoder (RFC 7541 Appendix B) --- */
1293
1294typedef struct h2_huffman_node {
1295 struct h2_huffman_node *child[2];
1296 int symbol;
1297 bool is_terminal;
1299
1300typedef struct {
1301 int16_t symbol;
1302 uint8_t bits;
1303} h2_huffman_lut_entry;
1304
1305static h2_huffman_lut_entry h2_huffman_root_lut[256];
1306
1307static h2_huffman_node h2_huffman_pool[4096];
1308static size_t h2_huffman_pool_used = 0;
1309static h2_huffman_node *h2_huffman_root = NULL;
1310/* The pool is shared mutable state; build the trie exactly once instead of
1311 * letting concurrent first connections race through lazy init (a lost race
1312 * could hand a half-built trie to a decoder or exhaust the pool twice). */
1313static pthread_once_t h2_huffman_once = PTHREAD_ONCE_INIT;
1314
1320static h2_huffman_node *h2_huffman_alloc_node(void) {
1321 if (h2_huffman_pool_used >= sizeof(h2_huffman_pool) / sizeof(h2_huffman_pool[0])) return NULL;
1322 h2_huffman_node *node = &h2_huffman_pool[h2_huffman_pool_used++];
1323 node->child[0] = node->child[1] = NULL;
1324 node->symbol = -1;
1325 node->is_terminal = false;
1326 return node;
1327}
1328
1335static void h2_huffman_init(void) {
1336 static const struct {
1337 uint32_t code;
1338 uint8_t bits;
1339 } table[257] = {
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},
1392 };
1393
1394 h2_huffman_node *root = h2_huffman_alloc_node();
1395 if (!root) return;
1396 for (int sym = 0; sym <= 256; ++sym) {
1397 uint32_t code = table[sym].code;
1398 uint8_t bits = table[sym].bits;
1399 h2_huffman_node *node = root;
1400 for (int i = bits - 1; i >= 0; --i) {
1401 int bit = (int)((code >> i) & 1);
1402 if (!node->child[bit]) node->child[bit] = h2_huffman_alloc_node();
1403 if (!node->child[bit]) return; /* pool exhausted: leave root unset */
1404 node = node->child[bit];
1405 }
1406 node->symbol = sym;
1407 node->is_terminal = true;
1408 }
1409 h2_huffman_root = root;
1410
1411 /* Precompute the 8-bit lookup table for root node transitions. */
1412 for (int i = 0; i < 256; ++i) {
1413 h2_huffman_node *n = h2_huffman_root;
1414 int16_t decoded_sym = -1;
1416 for (int b = 7; b >= 0; --b) {
1417 int bit = (i >> b) & 1;
1418 n = n->child[bit];
1419 if (!n) {
1420 break;
1421 }
1422 if (n->is_terminal) {
1423 decoded_sym = (int16_t)n->symbol;
1424 consumed_bits = (uint8_t)(8 - b);
1425 break;
1426 }
1427 }
1428 h2_huffman_root_lut[i].symbol = decoded_sym;
1429 h2_huffman_root_lut[i].bits = consumed_bits;
1430 }
1431}
1432
1442char *h2_huffman_decode(const unsigned char *src, size_t src_len, size_t *out_len) {
1443 pthread_once(&h2_huffman_once, h2_huffman_init);
1444 if (!h2_huffman_root) return NULL;
1445 size_t cap = src_len * 2 + 1;
1446 if (cap < 16) cap = 16;
1447 char *out = (char *)cwist_alloc(cap);
1448 if (!out) return NULL;
1449 size_t out_pos = 0;
1450
1451 uint32_t bit_buf = 0;
1452 int bit_cnt = 0;
1453 size_t src_idx = 0;
1454 h2_huffman_node *node = h2_huffman_root;
1455
1457 /* Keep the bit buffer filled with up to 24 bits. */
1458 while (bit_cnt <= 24 && src_idx < src_len) {
1459 bit_buf = (bit_buf << 8) | src[src_idx++];
1460 bit_cnt += 8;
1461 }
1462
1463 /* Speed-up: If we are at the root, try decoding 8 bits using the LUT. */
1464 if (node == h2_huffman_root && bit_cnt >= 8) {
1465 uint8_t index = (uint8_t)(bit_buf >> (bit_cnt - 8));
1466 h2_huffman_lut_entry entry = h2_huffman_root_lut[index];
1467 if (entry.symbol != -1) {
1468 if (entry.symbol == 256) {
1469 cwist_free(out);
1470 return NULL;
1471 }
1472 if (out_pos + 1 >= cap) {
1473 cap *= 2;
1474 char *tmp = (char *)cwist_realloc(out, cap);
1475 if (!tmp) {
1476 cwist_free(out);
1477 return NULL;
1478 }
1479 out = tmp;
1480 }
1481 out[out_pos++] = (char)entry.symbol;
1482 bit_cnt -= entry.bits;
1483 continue;
1484 }
1485 }
1486
1487 /* Fallback: bit-by-bit traversal. */
1488 if (bit_cnt > 0) {
1489 int bit = (bit_buf >> (bit_cnt - 1)) & 1;
1490 bit_cnt--;
1491 node = node->child[bit];
1492 if (!node) {
1493 cwist_free(out);
1494 return NULL;
1495 }
1496 if (node->is_terminal) {
1497 if (node->symbol == 256) {
1498 cwist_free(out);
1499 return NULL;
1500 }
1501 if (out_pos + 1 >= cap) {
1502 cap *= 2;
1503 char *tmp = (char *)cwist_realloc(out, cap);
1504 if (!tmp) {
1505 cwist_free(out);
1506 return NULL;
1507 }
1508 out = tmp;
1509 }
1510 out[out_pos++] = (char)node->symbol;
1511 node = h2_huffman_root;
1512 }
1513 } else {
1514 break;
1515 }
1516 }
1517
1518 if (node != h2_huffman_root) {
1519 h2_huffman_node *check = node;
1520 while (check && !check->is_terminal) {
1521 check = check->child[1];
1522 }
1523 if (!check || check->symbol != 256) {
1524 cwist_free(out);
1525 return NULL;
1526 }
1527 }
1528
1529 out[out_pos] = '\0';
1530 if (out_len) *out_len = out_pos;
1531 return out;
1532}
1533
1543char *h2_decode_string(const unsigned char *buf, size_t len, size_t *pos) {
1544 if (*pos >= len) return NULL;
1545 bool huffman = (buf[*pos] & 0x80) != 0;
1546 uint32_t str_len = 0;
1547 if (h2_decode_integer(buf, len, pos, 7, &str_len) != 0) return NULL;
1548 if (*pos + str_len > len) return NULL;
1549
1550 if (huffman) {
1551 size_t decoded_len = 0;
1552 char *out = h2_huffman_decode(buf + *pos, str_len, &decoded_len);
1553 *pos += str_len;
1554 return out;
1555 }
1556
1557 char *out = (char *)cwist_alloc((size_t)str_len + 1);
1558 if (!out) return NULL;
1559 memcpy(out, buf + *pos, str_len);
1560 out[str_len] = '\0';
1561 *pos += str_len;
1562 return out;
1563}
1564
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];
1574}
1575
1576/* --- HPACK Dynamic Table (RFC 7541 section 4) --- */
1577
1578/* Evict the oldest entries (list tail) until the table fits within limit. */
1579static void h2_hpack_evict_to(h2_conn *hc, uint32_t limit) {
1580 while (hc->hpack_size > limit && hc->hpack_head) {
1581 h2_hpack_entry **pp = &hc->hpack_head;
1582 while ((*pp)->next) pp = &(*pp)->next;
1583 h2_hpack_entry *old = *pp;
1584 *pp = NULL;
1585 hc->hpack_size -= (uint32_t)old->size;
1586 cwist_free(old->name);
1587 cwist_free(old->value);
1588 cwist_free(old);
1589 }
1590}
1591
1592/* Resolve a full HPACK index: 1..61 static, 62.. dynamic (62 = newest). */
1593static const h2_hpack_entry *h2_hpack_dynamic_get(const h2_conn *hc, uint32_t index) {
1594 size_t static_count = sizeof(cwist_http2_static_table) / sizeof(cwist_http2_static_table[0]);
1595 if (index < static_count) return NULL;
1596 uint32_t pos = index - (uint32_t)static_count + 1; /* 1-based into dynamic table */
1597 const h2_hpack_entry *e = hc->hpack_head;
1598 while (e && --pos) e = e->next;
1599 return e;
1600}
1601
1602/* Insert a name/value pair at the head of the dynamic table, evicting as
1603 * needed. An entry larger than the capacity empties the table and is not
1604 * added (RFC 7541 section 4.4). */
1605static int h2_hpack_insert(h2_conn *hc, const char *name, const char *value) {
1606 size_t entry_size = strlen(name) + strlen(value) + 32;
1607 if (entry_size > hc->hpack_capacity) {
1608 h2_hpack_evict_to(hc, 0);
1609 return 0;
1610 }
1612 if (!e) return -1;
1613 e->name = cwist_strdup(name);
1614 e->value = cwist_strdup(value);
1615 if (!e->name || !e->value) {
1616 cwist_free(e->name);
1617 cwist_free(e->value);
1618 cwist_free(e);
1619 return -1;
1620 }
1621 e->size = entry_size;
1622 h2_hpack_evict_to(hc, hc->hpack_capacity - (uint32_t)entry_size);
1623 e->next = hc->hpack_head;
1624 hc->hpack_head = e;
1625 hc->hpack_size += (uint32_t)entry_size;
1626 return 0;
1627}
1628
1629/* Apply a dynamic table size update from the peer's encoder, bounded by the
1630 * capacity we advertised in SETTINGS_HEADER_TABLE_SIZE. */
1631static int h2_hpack_set_capacity(h2_conn *hc, uint32_t new_size) {
1632 if (new_size > CWIST_HTTP2_HEADER_TABLE_SIZE) return -1;
1633 hc->hpack_capacity = new_size;
1634 h2_hpack_evict_to(hc, new_size);
1635 return 0;
1636}
1637
1645static void h2_parse_path(cwist_http_request *req, const char *path) {
1646 const char *q = strchr(path, '?');
1647 if (q) {
1648 cwist_sstring_assign_len(req->path, (char *)path, (size_t)(q - path));
1649 cwist_sstring_assign(req->query, (char *)(q + 1));
1650 if (req->query_params) {
1652 } else {
1654 }
1655 if (req->query_params && req->query->size > 0) {
1657 }
1658 } else {
1659 cwist_sstring_assign(req->path, (char *)path);
1660 }
1661}
1662
1663/* Per-header-block decode state for pseudo-header validation
1664 * (RFC 7540 section 8.1.2.3). */
1665typedef struct h2_header_state {
1666 bool seen_method;
1667 bool seen_path;
1668 bool seen_scheme;
1669 bool seen_authority;
1670 bool seen_regular;
1671 uint32_t header_count;
1673
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;
1677 st->header_count++;
1678 if (st->header_count > CWIST_HTTP2_MAX_HEADERS_PER_REQUEST) {
1679 CWIST_LOG_WARN("[h2] request exceeds maximum header count %u",
1681 return -1;
1682 }
1683
1684 if (name[0] == ':') {
1685 /* Pseudo-headers must precede all regular headers. */
1686 if (st->seen_regular) return -1;
1687 bool *flag = NULL;
1688 if (strcmp(name, ":method") == 0)
1689 flag = &st->seen_method;
1690 else if (strcmp(name, ":path") == 0)
1691 flag = &st->seen_path;
1692 else if (strcmp(name, ":scheme") == 0)
1693 flag = &st->seen_scheme;
1694 else if (strcmp(name, ":authority") == 0)
1695 flag = &st->seen_authority;
1696 if (flag) {
1697 if (*flag) return -1; /* duplicate pseudo-header */
1698 *flag = true;
1699 }
1700 if (strcmp(name, ":method") == 0) {
1701 req->method = cwist_http_string_to_method(value);
1702 } else if (strcmp(name, ":path") == 0) {
1703 h2_parse_path(req, value);
1704 } else if (strcmp(name, ":authority") == 0) {
1705 cwist_http_header_add(&req->headers, "host", value);
1706 }
1707 /* :scheme and unknown pseudo-headers are accepted but not mapped. */
1708 return 0;
1709 }
1710
1711 st->seen_regular = true;
1712 /* HTTP/2 field names are lowercase by mandate (RFC 9113 section 8.2.1);
1713 * normalize lenient peers so lookups (e.g. gRPC metadata) are uniform. */
1714 char lower_buf[256];
1715 if (strcmp(name, "host") != 0) {
1716 size_t name_len = strlen(name);
1717 int needs_lower = 0;
1718 for (size_t i = 0; i < name_len; ++i) {
1719 if (name[i] >= 'A' && name[i] <= 'Z') {
1720 needs_lower = 1;
1721 break;
1722 }
1723 }
1724 if (needs_lower && name_len < sizeof(lower_buf)) {
1725 for (size_t i = 0; i < name_len; ++i)
1726 lower_buf[i] = (char)tolower((unsigned char)name[i]);
1727 lower_buf[name_len] = '\0';
1728 name = lower_buf;
1729 }
1730 }
1731 if (strcmp(name, "host") == 0) {
1732 cwist_http_header_add(&req->headers, "host", value);
1733 } else {
1734 cwist_http_header_add(&req->headers, name, value);
1735 }
1736 return 0;
1737}
1738
1739/* Decode result codes: OK, stream-level error (RST_STREAM/PROTOCOL_ERROR),
1740 * or compression error (connection-level, GOAWAY). */
1741#define H2_DECODE_OK 0
1742#define H2_DECODE_STREAM_ERROR 1
1743#define H2_DECODE_COMPRESSION_ERROR 2
1744
1745static int h2_decode_header_block(h2_conn *hc, cwist_http_request *req,
1746 const unsigned char *payload, size_t len, bool is_request) {
1747 size_t pos = 0;
1749 memset(&st, 0, sizeof(st));
1750 /* Pseudo-headers are forbidden outside the request header block
1751 * (e.g. trailers); seed the state so any ':' name errors out. */
1752 st.seen_regular = !is_request;
1753 /* A semantically invalid field (duplicate/misplaced pseudo-header,
1754 * missing :method/:path, ...) is a stream error, not a connection
1755 * error: the request is answered with RST_STREAM, but the HPACK
1756 * dynamic table is a connection-wide compression context shared with
1757 * the peer's encoder. Bailing out mid-block on a stream error would
1758 * skip any incremental-indexing insertions still to come and desync
1759 * the decoder's table from the encoder's, corrupting *unrelated*
1760 * requests later on the same connection. So keep decoding the full
1761 * block to keep the table in sync, and only report the stream error
1762 * once the whole block has been consumed. */
1763 bool stream_error = false;
1764
1765 while (pos < len) {
1766 uint8_t b = payload[pos];
1767 uint32_t name_index = 0;
1768 char *name = NULL;
1769 char *value = NULL;
1770
1771 if (b & 0x80) {
1772 /* Indexed header field: static table or dynamic table. */
1773 uint32_t index = 0;
1774 if (h2_decode_integer(payload, len, &pos, 7, &index) != 0)
1776 const cwist_http2_static_header *entry = h2_static_header(index);
1777 if (entry && entry->name) {
1778 if (h2_apply_header(req, entry->name, entry->value, &st) != 0) stream_error = true;
1779 continue;
1780 }
1781 const h2_hpack_entry *dyn = h2_hpack_dynamic_get(hc, index);
1782 if (!dyn) {
1783 CWIST_LOG_WARN("[h2] header field index=%u beyond static+dynamic tables", index);
1785 }
1786 if (h2_apply_header(req, dyn->name, dyn->value, &st) != 0) stream_error = true;
1787 continue;
1788 }
1789
1790 if ((b & 0xE0) == 0x20) {
1791 /* Dynamic table size update. */
1792 uint32_t new_size = 0;
1793 if (h2_decode_integer(payload, len, &pos, 5, &new_size) != 0)
1795 if (h2_hpack_set_capacity(hc, new_size) != 0) {
1796 CWIST_LOG_WARN("[h2] dynamic table size update %u exceeds advertised capacity",
1797 new_size);
1799 }
1800 continue;
1801 }
1802
1803 bool incremental_indexing = false;
1804 if ((b & 0x40) == 0x40) {
1805 incremental_indexing = true;
1806 if (h2_decode_integer(payload, len, &pos, 6, &name_index) != 0)
1808 } else if ((b & 0xf0) == 0x00) {
1809 if (h2_decode_integer(payload, len, &pos, 4, &name_index) != 0)
1811 } else if ((b & 0xf0) == 0x10) {
1812 if (h2_decode_integer(payload, len, &pos, 4, &name_index) != 0)
1814 } else {
1816 }
1817
1818 if (name_index > 0) {
1820 if (entry && entry->name) {
1821 name = cwist_strdup(entry->name);
1822 } else {
1823 const h2_hpack_entry *dyn = h2_hpack_dynamic_get(hc, name_index);
1824 if (dyn) name = cwist_strdup(dyn->name);
1825 }
1826 if (!name) {
1827 CWIST_LOG_WARN("[h2] literal field name index=%u beyond static+dynamic tables",
1828 name_index);
1830 }
1831 } else {
1832 name = h2_decode_string(payload, len, &pos);
1833 }
1834 value = h2_decode_string(payload, len, &pos);
1835 if (!name || !value) {
1836 cwist_free(name);
1837 cwist_free(value);
1839 }
1840 int rc = h2_apply_header(req, name, value, &st);
1841 /* Insert into the dynamic table regardless of rc: incremental
1842 * indexing is a compression-context effect, independent of
1843 * whether this particular field made the request semantically
1844 * invalid (see comment above stream_error). */
1846 if (h2_hpack_insert(hc, name, value) != 0) rc = -2;
1847 }
1848 cwist_free(name);
1849 cwist_free(value);
1850 if (rc == -2) return H2_DECODE_COMPRESSION_ERROR;
1851 if (rc != 0) stream_error = true;
1852 }
1853
1855
1856 /* RFC 7540 section 8.1.2.3: request header blocks must carry :method and :path. */
1857 if (is_request && (!st.seen_method || !st.seen_path)) {
1858 CWIST_LOG_WARN("[h2] request header block missing :method or :path");
1860 }
1861 return H2_DECODE_OK;
1862}
1863
1864/* --- HPACK Response Encoder --- */
1865
1875size_t h2_encode_integer(unsigned char *dst, size_t dst_cap, uint32_t value, uint8_t prefix_bits) {
1876 uint8_t mask = (uint8_t)((1u << prefix_bits) - 1u);
1877 unsigned char first = dst[0] & ~mask;
1878 if (value < mask) {
1879 dst[0] = first | (uint8_t)value;
1880 return 1;
1881 }
1882 dst[0] = first | mask;
1883 value -= mask;
1884 size_t i = 1;
1885 while (value >= 128) {
1886 if (i >= dst_cap) return 0;
1887 dst[i++] = (unsigned char)((value & 0x7F) | 0x80);
1888 value >>= 7;
1889 }
1890 if (i >= dst_cap) return 0;
1891 dst[i++] = (unsigned char)value;
1892 return i;
1893}
1894
1903size_t h2_encode_string(unsigned char *dst, size_t dst_cap, const char *str) {
1904 size_t len = strlen(str);
1905 dst[0] = 0x00;
1906 size_t n = h2_encode_integer(dst, dst_cap, (uint32_t)len, 7);
1907 if (n == 0 || n + len > dst_cap) return 0;
1908 memcpy(dst + n, str, len);
1909 return n + len;
1910}
1911
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)
1922 return (int)i;
1923 }
1924 return 0;
1925}
1926
1936static bool h2_status_forbids_body(int status_code) {
1937 return (status_code >= 100 && status_code < 200) || status_code == 204 || status_code == 304;
1938}
1939
1950static size_t h2_encode_response_headers(cwist_http_response *res, unsigned char *dst,
1951 size_t dst_cap, bool grpc_mode) {
1952 size_t pos = 0;
1953 const bool bodyless = h2_status_forbids_body(res->status_code);
1954
1955 switch (res->status_code) {
1956 case 200:
1957 if (pos >= dst_cap) return 0;
1958 dst[pos++] = 0x88;
1959 break;
1960 case 204:
1961 if (pos >= dst_cap) return 0;
1962 dst[pos++] = 0x89;
1963 break;
1964 case 206:
1965 if (pos >= dst_cap) return 0;
1966 dst[pos++] = 0x8a;
1967 break;
1968 case 304:
1969 if (pos >= dst_cap) return 0;
1970 dst[pos++] = 0x8b;
1971 break;
1972 case 400:
1973 if (pos >= dst_cap) return 0;
1974 dst[pos++] = 0x8c;
1975 break;
1976 case 404:
1977 if (pos >= dst_cap) return 0;
1978 dst[pos++] = 0x8d;
1979 break;
1980 case 500:
1981 if (pos >= dst_cap) return 0;
1982 dst[pos++] = 0x8e;
1983 break;
1984 default: {
1985 char status_str[16];
1986 snprintf(status_str, sizeof(status_str), "%d", res->status_code);
1987 if (pos + 1 > dst_cap) return 0;
1988 dst[pos] = 0x00;
1989 size_t n = h2_encode_integer(dst + pos, dst_cap - pos, 0, 4);
1990 if (n == 0) return 0;
1991 pos += n;
1992 n = h2_encode_string(dst + pos, dst_cap - pos, ":status");
1993 if (n == 0) return 0;
1994 pos += n;
1995 n = h2_encode_string(dst + pos, dst_cap - pos, status_str);
1996 if (n == 0) return 0;
1997 pos += n;
1998 break;
1999 }
2000 }
2001
2002 size_t body_len = 0;
2003 if (res->use_file_stream)
2004 body_len = res->file_stream_len;
2005 else if (res->is_ptr_body)
2006 body_len = res->ptr_body_len;
2007 else if (res->body)
2008 body_len = res->body->size;
2009
2010 if (!grpc_mode && !bodyless) {
2011 char cl_str[32];
2012 snprintf(cl_str, sizeof(cl_str), "%zu", body_len);
2013 int name_idx = h2_static_table_find_name("content-length");
2014 if (name_idx > 0) {
2015 if (pos + 1 > dst_cap) return 0;
2016 dst[pos] = 0x00;
2017 size_t n = h2_encode_integer(dst + pos, dst_cap - pos, (uint32_t)name_idx, 4);
2018 if (n == 0) return 0;
2019 pos += n;
2020 } else {
2021 if (pos + 1 > dst_cap) return 0;
2022 dst[pos] = 0x00;
2023 size_t n = h2_encode_integer(dst + pos, dst_cap - pos, 0, 4);
2024 if (n == 0) return 0;
2025 pos += n;
2026 n = h2_encode_string(dst + pos, dst_cap - pos, "content-length");
2027 if (n == 0) return 0;
2028 pos += n;
2029 }
2030 size_t n = h2_encode_string(dst + pos, dst_cap - pos, cl_str);
2031 if (n == 0) return 0;
2032 pos += n;
2033 }
2034
2036 while (curr) {
2037 if (!curr->key || !curr->key->data || !curr->value || !curr->value->data) {
2038 curr = curr->next;
2039 continue;
2040 }
2041 /* RFC 9113: skip content-length (already encoded from body_len) */
2042 if (strcasecmp(curr->key->data, "content-length") == 0) {
2043 curr = curr->next;
2044 continue;
2045 }
2046 /* RFC 9113 section 8.2.2: connection-specific fields are malformed in
2047 * HTTP/2; te is only allowed with the value "trailers". */
2048 if (strcasecmp(curr->key->data, "connection") == 0 ||
2049 strcasecmp(curr->key->data, "keep-alive") == 0 ||
2050 strcasecmp(curr->key->data, "proxy-connection") == 0 ||
2051 strcasecmp(curr->key->data, "transfer-encoding") == 0 ||
2052 strcasecmp(curr->key->data, "upgrade") == 0 ||
2053 (strcasecmp(curr->key->data, "te") == 0 &&
2054 strcasecmp(curr->value->data, "trailers") != 0)) {
2055 curr = curr->next;
2056 continue;
2057 }
2058 if (bodyless && strcasecmp(curr->key->data, "content-length") == 0) {
2059 curr = curr->next;
2060 continue;
2061 }
2062 /* gRPC status travels in the trailer HEADERS frame, not here. */
2063 if (grpc_mode && (strcasecmp(curr->key->data, "grpc-status") == 0 ||
2064 strcasecmp(curr->key->data, "grpc-message") == 0 ||
2065 strcasecmp(curr->key->data, "grpc-retry-pushback-ms") == 0)) {
2066 curr = curr->next;
2067 continue;
2068 }
2069 /* NUL/CR/LF in a field value makes the response malformed; drop the
2070 * offending field instead of poisoning the whole response. */
2071 if (strpbrk(curr->value->data, "\r\n") || strlen(curr->value->data) != curr->value->size) {
2072 curr = curr->next;
2073 continue;
2074 }
2075
2076 char lower_name[256];
2077 size_t name_len = strlen(curr->key->data);
2078 if (name_len >= sizeof(lower_name)) name_len = sizeof(lower_name) - 1;
2079 for (size_t i = 0; i < name_len; ++i) {
2080 lower_name[i] = (char)tolower((unsigned char)curr->key->data[i]);
2081 }
2082 lower_name[name_len] = '\0';
2083
2084 int name_idx = h2_static_table_find_name(lower_name);
2085 if (name_idx > 0) {
2086 if (pos + 1 > dst_cap) return 0;
2087 dst[pos] = 0x00;
2088 size_t n = h2_encode_integer(dst + pos, dst_cap - pos, (uint32_t)name_idx, 4);
2089 if (n == 0) return 0;
2090 pos += n;
2091 } else {
2092 if (pos + 1 > dst_cap) return 0;
2093 dst[pos] = 0x00;
2094 size_t n = h2_encode_integer(dst + pos, dst_cap - pos, 0, 4);
2095 if (n == 0) return 0;
2096 pos += n;
2097 n = h2_encode_string(dst + pos, dst_cap - pos, lower_name);
2098 if (n == 0) return 0;
2099 pos += n;
2100 }
2101 size_t n = h2_encode_string(dst + pos, dst_cap - pos, curr->value->data);
2102 if (n == 0) return 0;
2103 pos += n;
2104
2105 curr = curr->next;
2106 }
2107
2108 return pos;
2109}
2110
2111/* --- Response Senders --- */
2112
2113/* Send an encoded header block as HEADERS, splitting into CONTINUATION
2114 * frames when it exceeds the peer's advertised max frame size. base_flags
2115 * (e.g. END_STREAM) applies to the HEADERS frame only. */
2116static int h2_send_header_block(h2_conn *hc, cwist_https_connection *conn, uint32_t stream_id,
2117 const unsigned char *block, size_t block_len, uint32_t max_frame,
2119 size_t sent = 0;
2121 uint8_t flags = base_flags;
2122 while (true) {
2123 size_t chunk = block_len - sent;
2124 if (chunk > max_frame) chunk = max_frame;
2125 uint8_t frame_flags = flags;
2127 if (h2_write_frame_any(hc, conn, type, frame_flags, stream_id, block + sent,
2128 (uint32_t)chunk) != 0) {
2129 return -1;
2130 }
2131 sent += chunk;
2132 if (sent == block_len) return 0;
2134 flags = 0; /* END_STREAM is illegal on CONTINUATION */
2135 }
2136}
2137
2138/* Encode response headers into a heap buffer that doubles until the block
2139 * fits (up to 1 MiB). The old fixed 8 KiB stack buffer made any response
2140 * with a large header set (big cookies, JWTs) abort the whole connection,
2141 * which browsers surface as ERR_INVALID_RESPONSE. */
2142static unsigned char *h2_encode_response_block(cwist_http_response *res, size_t *out_len,
2143 bool grpc_mode) {
2144 size_t cap = 16384;
2145 unsigned char *buf = NULL;
2146 while (cap <= (1u << 20)) {
2147 unsigned char *nb = (unsigned char *)cwist_realloc(buf, cap);
2148 if (!nb) {
2149 cwist_free(buf);
2150 return NULL;
2151 }
2152 buf = nb;
2153 size_t n = h2_encode_response_headers(res, buf, cap, grpc_mode);
2154 if (n > 0) {
2155 *out_len = n;
2156 return buf;
2157 }
2158 cap *= 2;
2159 }
2160 cwist_free(buf);
2161 return NULL;
2162}
2163
2164/* Encode literal (never-indexed, non-huffman) header pairs; used for
2165 * trailer blocks and hook-driven response headers. */
2166static size_t h2_encode_header_pairs(unsigned char *dst, size_t dst_cap,
2167 const cwist_http2_header *pairs, size_t count) {
2168 size_t pos = 0;
2169 for (size_t i = 0; i < count; i++) {
2170 if (!pairs[i].name || !pairs[i].value) continue;
2171 char lower_name[256];
2172 size_t name_len = strlen(pairs[i].name);
2173 if (name_len >= sizeof(lower_name)) name_len = sizeof(lower_name) - 1;
2174 for (size_t j = 0; j < name_len; ++j)
2175 lower_name[j] = (char)tolower((unsigned char)pairs[i].name[j]);
2176 lower_name[name_len] = '\0';
2177
2178 int name_idx = h2_static_table_find_name(lower_name);
2179 if (pos + 1 > dst_cap) return 0;
2180 dst[pos] = 0x00;
2181 size_t n = h2_encode_integer(dst + pos, dst_cap - pos, (uint32_t)name_idx, 4);
2182 if (n == 0) return 0;
2183 pos += n;
2184 if (name_idx == 0) {
2185 n = h2_encode_string(dst + pos, dst_cap - pos, lower_name);
2186 if (n == 0) return 0;
2187 pos += n;
2188 }
2189 n = h2_encode_string(dst + pos, dst_cap - pos, pairs[i].value);
2190 if (n == 0) return 0;
2191 pos += n;
2192 }
2193 return pos;
2194}
2195
2202static int h2_send_seq_data_frames(h2_conn *hc, cwist_https_connection *conn, uint32_t stream_id,
2203 const uint8_t *body_data, size_t body_len,
2204 uint32_t max_frame_size, uint8_t *send_xor) {
2206 if (chunk_payload == 0) return -1;
2207
2209 if (!cwist_seq_split(body_data, body_len, chunk_payload, &msg)) return -1;
2210
2211 for (size_t i = 0; i < msg.count; i++) {
2212 uint8_t flags = (i + 1 == msg.count) ? CWIST_HTTP2_FLAG_END_STREAM : 0;
2213 if (h2_write_frame_any(hc, conn, CWIST_HTTP2_FRAME_DATA, flags, stream_id, msg.chunks[i],
2214 (uint32_t)msg.chunk_lens[i]) != 0) {
2216 return -1;
2217 }
2218 if (send_xor) *send_xor ^= h2_xor_bytes(msg.chunks[i], msg.chunk_lens[i]);
2219 }
2220
2222 return 0;
2223}
2224
2225static int h2_send_response_raw(cwist_https_connection *conn, uint32_t stream_id,
2227 size_t block_len = 0;
2228 unsigned char *block = h2_encode_response_block(res, &block_len, false);
2229 if (!block) return -1;
2230
2231 size_t body_len = 0;
2232 const unsigned char *body_data = NULL;
2233 if (res->use_file_stream) {
2234 body_len = res->file_stream_len;
2235 } else if (res->is_ptr_body) {
2236 body_len = res->ptr_body_len;
2237 body_data = (const unsigned char *)res->ptr_body;
2238 } else if (res->body) {
2239 body_len = res->body->size;
2240 body_data = (const unsigned char *)res->body->data;
2241 }
2242
2244 if (body_len == 0 || h2_status_forbids_body(res->status_code)) {
2245 body_len = 0;
2247 }
2248
2249 int rc =
2250 h2_send_header_block(NULL, conn, stream_id, block, block_len, max_frame_size, header_flags);
2252 if (rc != 0) return -1;
2253
2254 if (body_len == 0) return 0;
2255
2256 if (res->use_file_stream && res->file_stream_fd >= 0) {
2257 /* Load file contents and sequence them. Server push rarely streams
2258 * huge files, so a single read is acceptable here. */
2260 unsigned char *file_buf = (unsigned char *)cwist_scratch_alloc(&file_buf_s, body_len);
2261 if (!file_buf) return -1;
2262 ssize_t r = pread(res->file_stream_fd, file_buf, body_len, res->file_stream_offset);
2263 if (r <= 0 || (size_t)r != body_len) return -1;
2264 return h2_send_seq_data_frames(NULL, conn, stream_id, file_buf, body_len, max_frame_size,
2265 NULL);
2266 }
2267
2268 return h2_send_seq_data_frames(NULL, conn, stream_id, body_data, body_len, max_frame_size,
2269 NULL);
2270}
2271
2281static int h2_read_all(h2_conn *hc, void *buf, int len) {
2282 /* h2_read_full tolerates WANT_READ on the non-blocking TLS sockets;
2283 * 0/EOF and real errors stay fatal. */
2284 if (h2_read_full(hc, buf, (size_t)len) != 0) return -1;
2285 return len;
2286}
2287
2288/* Returns 0 normally, -1 when the peer vanished mid-frame (EOF/error).
2289 *
2290 * Two traps are handled here:
2291 * 1. poll(fd, 0) alone misses WINDOW_UPDATEs in the TLS or h2c replay
2292 * buffer. These frames can remain after all socket bytes were read.
2293 * Without the buffered-input check, the window wait loop misses them and the
2294 * connection is torn down mid-body - the "some chunks arrive, then
2295 * stuck" symptom.
2296 * 2. Frames this loop is not responsible for (HEADERS/SETTINGS of other
2297 * streams, etc.) used to be read and discarded. They are now queued so
2298 * the main frame loop can dispatch them. */
2299static int h2_process_incoming_frames_nonblocking(h2_conn *hc, h2_stream *s) {
2300 /* Never wait on the peer with unflushed frames in the batch buffer. */
2301 if (h2_out_flush(hc) != 0) return -1;
2302 struct pollfd pfd;
2303 pfd.fd = hc->conn->fd;
2304 pfd.events = POLLIN;
2305
2306 int pr = poll(&pfd, 1, 0);
2307 if (pr > 0 && (pfd.revents & (POLLHUP | POLLERR)) && !(pfd.revents & POLLIN)) {
2308 if (!h2_has_buffered_input(hc->conn)) return -1;
2309 }
2310
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;
2315
2316 uint32_t len = h2_get_u24(hdr);
2317 uint8_t type = hdr[3];
2318 uint8_t flags = hdr[4];
2319 uint32_t stream_id = h2_get_u31(hdr + 5);
2320
2321 unsigned char *payload = NULL;
2322 if (len > 0) {
2323 payload = (unsigned char *)cwist_alloc(len);
2324 if (!payload) return -1;
2325 int r = h2_read_all(hc, payload, len);
2326 if (r != (int)len) {
2327 cwist_free(payload);
2328 return -1;
2329 }
2330 }
2331
2332 hc->last_activity = h2_now_ms();
2333
2334 if (type == 0x08) { // WINDOW_UPDATE
2335 if (len == 4) {
2336 int32_t increment = (int32_t)h2_get_u31(payload);
2337 if (increment > 0) {
2338 if (stream_id == 0) {
2340 } else if (s && stream_id == s->stream_id) {
2343 } else {
2344 h2_stream *other = h2_stream_find(hc, stream_id);
2345 if (other)
2348 }
2349 h2_fc_credit_signal(hc);
2350 }
2351 }
2352 } else if (type == 0x06) { // PING
2353 if ((flags & 0x01) == 0 && len == 8) { // ACK flag is 0x01
2354 if (!h2_conn_consume_ping_budget(hc)) {
2355 cwist_free(payload);
2356 return -1;
2357 }
2358 h2_write_frame(hc, 0x06, 0x01, 0, payload, 8);
2359 }
2360 } else if (type == 0x03) { // RST_STREAM
2361 if (!h2_conn_consume_rst_budget(hc)) {
2362 cwist_free(payload);
2363 return -1;
2364 }
2365 if (s && stream_id == s->stream_id) {
2366 s->send_aborted = true;
2367 }
2368 } else {
2369 /* Not ours to consume: hand the intact frame to the main loop. */
2371 if (!df) {
2372 cwist_free(payload);
2373 return -1;
2374 }
2375 memcpy(df->hdr, hdr, 9);
2376 df->payload = payload;
2377 df->next = NULL;
2378 if (hc->deferred_tail)
2379 hc->deferred_tail->next = df;
2380 else
2381 hc->deferred_head = df;
2382 hc->deferred_tail = df;
2383 continue;
2384 }
2385 cwist_free(payload);
2386 }
2387 return 0;
2388}
2389
2393static uint16_t h2_seq_chunk_payload(h2_conn *hc, h2_stream *s, uint32_t max_frame_size) {
2395 uint32_t eff_window = h2_send_allowance(hc, s, max_payload + CWIST_SEQ_HEADER_SIZE);
2396 if (eff_window <= CWIST_SEQ_HEADER_SIZE) return 0;
2399}
2400
2404static int h2_send_seq_file_body(h2_conn *hc, h2_stream *s, uint32_t stream_id, int fd,
2405 off_t offset, size_t body_len, uint32_t max_frame_size) {
2406 uint16_t chunk_payload = h2_seq_chunk_payload(hc, s, max_frame_size);
2407 while (chunk_payload == 0 && body_len > 0) {
2408 /* Window exhausted below one sequenced chunk: the HEADERS block is
2409 * already on the wire, so failing here tears down the connection
2410 * and the client sees a 0-byte body. Wait for WINDOW_UPDATE. */
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;
2414 struct timespec ts = {0, 2000000};
2415 nanosleep(&ts, NULL);
2416 chunk_payload = h2_seq_chunk_payload(hc, s, max_frame_size);
2417 }
2418 if (body_len == 0) return -1;
2419
2421 size_t remaining = body_len;
2422 off_t cur_offset = offset;
2423
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)) {
2429 /* Give up if no frame arrived within the idle deadline. */
2430 if (h2_now_ms() - hc->last_activity >= (uint64_t)h2_idle_timeout_ms()) return -1;
2431 struct timespec ts = {0, 2000000};
2432 nanosleep(&ts, NULL);
2433 }
2434 }
2435
2438 if (h2_send_allowance(hc, s, (uint32_t)chunk_len) < chunk_len) {
2439 /* Window too small for a full sequenced chunk; wait for update. */
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;
2442 struct timespec ts = {0, 2000000};
2443 nanosleep(&ts, NULL);
2444 i--;
2445 continue;
2446 }
2447
2449 unsigned char *chunk = (unsigned char *)cwist_scratch_alloc(&chunk_s, chunk_len);
2450 if (!chunk) return -1;
2452 if (r <= 0 || (size_t)r != plen) return -1;
2455 uint8_t flags = (i + 1 == total_chunks) ? CWIST_HTTP2_FLAG_END_STREAM : 0;
2456 if (h2_write_frame(hc, CWIST_HTTP2_FRAME_DATA, flags, stream_id, chunk,
2457 (uint32_t)chunk_len) != 0) {
2458 return -1;
2459 }
2460 if (s) s->send_xor ^= h2_xor_bytes(chunk, chunk_len);
2461 h2_commit_send(hc, s, (uint32_t)chunk_len);
2462 cur_offset += (off_t)plen;
2463 remaining -= plen;
2464 }
2465 return 0;
2466}
2467
2471static int h2_send_seq_memory_body(h2_conn *hc, h2_stream *s, uint32_t stream_id,
2472 const uint8_t *body_data, size_t body_len,
2474 uint16_t chunk_payload = h2_seq_chunk_payload(hc, s, max_frame_size);
2475 while (chunk_payload == 0) {
2476 /* Window exhausted below one sequenced chunk: the HEADERS block is
2477 * already on the wire, so failing here tears down the connection
2478 * and the client sees a 0-byte body. Wait for WINDOW_UPDATE. */
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;
2482 struct timespec ts = {0, 2000000};
2483 nanosleep(&ts, NULL);
2484 chunk_payload = h2_seq_chunk_payload(hc, s, max_frame_size);
2485 }
2486
2488 if (!cwist_seq_split(body_data, body_len, chunk_payload, &msg)) return -1;
2489
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)) {
2495 /* Give up if no frame arrived within the idle deadline. */
2496 if (h2_now_ms() - hc->last_activity >= (uint64_t)h2_idle_timeout_ms()) goto fail;
2497 struct timespec ts = {0, 2000000};
2498 nanosleep(&ts, NULL);
2499 }
2500 }
2501
2502 size_t chunk_len = msg.chunk_lens[i];
2503 if (h2_send_allowance(hc, s, (uint32_t)chunk_len) < chunk_len) {
2504 /* Window too small for a full sequenced chunk; wait for update. */
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;
2507 struct timespec ts = {0, 2000000};
2508 nanosleep(&ts, NULL);
2509 i--;
2510 continue;
2511 }
2512
2513 uint8_t flags = (i + 1 == msg.count) ? CWIST_HTTP2_FLAG_END_STREAM : 0;
2514 if (h2_write_frame(hc, CWIST_HTTP2_FRAME_DATA, flags, stream_id, msg.chunks[i],
2515 (uint32_t)chunk_len) != 0) {
2516 goto fail;
2517 }
2518 if (s) s->send_xor ^= h2_xor_bytes(msg.chunks[i], chunk_len);
2519 h2_commit_send(hc, s, (uint32_t)chunk_len);
2520 }
2521
2523 return 0;
2524
2525fail:
2527 return -1;
2528}
2529
2530/* --- Hook-driven stream senders (public API) ---
2531 *
2532 * Called from hook-taken stream handler threads; frames are appended under
2533 * out_mu (so a whole frame never interleaves with dispatcher writes) and
2534 * flushed immediately for low-latency streaming. */
2535
2541static void h2_out_lock(h2_conn *hc) {
2542 if (hc->out_mu_init) pthread_mutex_lock(&hc->out_mu);
2543}
2544
2549static void h2_out_unlock(h2_conn *hc) {
2550 if (hc->out_mu_init) pthread_mutex_unlock(&hc->out_mu);
2551}
2552
2553/* Caller holds out_mu. */
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,
2556 uint32_t len) {
2557 unsigned char frame[CWIST_HTTP2_FRAME_HEADER_SIZE];
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);
2561 return 0;
2562}
2563
2564/* Caller holds out_mu. Encode :status plus literal header pairs into a
2565 * heap block (doubling until it fits, capped at 1 MiB). */
2566static unsigned char *h2_encode_stream_block_locked(int status, const cwist_http2_header *pairs,
2567 size_t count, size_t *out_len) {
2568 size_t cap = 1024;
2569 unsigned char *buf = NULL;
2570 while (cap <= (1u << 20)) {
2571 unsigned char *nb = (unsigned char *)cwist_realloc(buf, cap);
2572 if (!nb) {
2573 cwist_free(buf);
2574 return NULL;
2575 }
2576 buf = nb;
2577 size_t pos = 0;
2578 if (status == 200) {
2579 buf[pos++] = 0x88;
2580 } else {
2581 char status_str[16];
2582 snprintf(status_str, sizeof(status_str), "%d", status);
2583 buf[pos] = 0x00;
2584 size_t n = h2_encode_integer(buf + pos, cap - pos, 0, 4);
2585 if (n == 0) goto grow;
2586 pos += n;
2587 n = h2_encode_string(buf + pos, cap - pos, ":status");
2588 if (n == 0) goto grow;
2589 pos += n;
2590 n = h2_encode_string(buf + pos, cap - pos, status_str);
2591 if (n == 0) goto grow;
2592 pos += n;
2593 }
2594 {
2595 size_t n = h2_encode_header_pairs(buf + pos, cap - pos, pairs, count);
2596 if (n == 0 && count > 0) goto grow;
2597 pos += n;
2598 }
2599 *out_len = pos;
2600 return buf;
2601 grow:
2602 cap *= 2;
2603 }
2604 cwist_free(buf);
2605 return NULL;
2606}
2607
2608int cwist_http2_stream_send_headers(cwist_h2_stream *stream, int status,
2609 const cwist_http2_header *headers, size_t header_count,
2610 int end_stream) {
2611 if (!stream || !stream->hc) return -1;
2612 h2_conn *hc = stream->hc;
2613 h2_out_lock(hc);
2614 size_t block_len = 0;
2615 unsigned char *block = h2_encode_stream_block_locked(status, headers, header_count, &block_len);
2616 if (!block) {
2617 h2_out_unlock(hc);
2618 return -1;
2619 }
2621 int rc = h2_stream_write_frame_locked(hc, CWIST_HTTP2_FRAME_HEADERS, flags, stream->stream_id,
2624 if (rc == 0) rc = h2_out_flush_raw(hc);
2625 h2_out_unlock(hc);
2626 return rc;
2627}
2628
2642int cwist_http2_stream_send_data(cwist_h2_stream *stream, const unsigned char *data, size_t len) {
2643 if (!stream || !stream->hc) return -1;
2644 h2_conn *hc = stream->hc;
2645 uint32_t max_frame = hc->peer_max_frame_size;
2647
2648 size_t sent = 0;
2649 int rc = 0;
2650 while (sent < len) {
2651 uint32_t chunk = (uint32_t)((len - sent) > max_frame ? max_frame : (len - sent));
2652 if (h2_send_allowance(hc, stream, chunk) == 0) {
2653 /* No peer credit: the handler thread must not read the socket
2654 * (the dispatcher owns reads), so flush whatever is batched and
2655 * wait for the dispatcher to apply WINDOW_UPDATE credit. */
2656 if (h2_out_flush(hc) != 0) {
2657 rc = -1;
2658 break;
2659 }
2660 if (h2_fc_wait_credit(hc, stream) != 0) {
2661 rc = -1;
2662 break;
2663 }
2664 }
2665 h2_out_lock(hc);
2666 /* Recheck under out_mu: another handler thread may have consumed the
2667 * credit between our wait and the lock. */
2668 uint32_t allowed = h2_send_allowance(hc, stream, chunk);
2669 if (allowed == 0) {
2670 h2_out_unlock(hc);
2671 continue;
2672 }
2673 if (h2_stream_write_frame_locked(hc, CWIST_HTTP2_FRAME_DATA, 0, stream->stream_id,
2674 data + sent, allowed) != 0) {
2675 h2_out_unlock(hc);
2676 rc = -1;
2677 break;
2678 }
2679 h2_commit_send(hc, stream, allowed);
2680 sent += allowed;
2681 if (sent >= len) rc = h2_out_flush_raw(hc);
2682 h2_out_unlock(hc);
2683 if (rc != 0) break;
2684 }
2685 return rc;
2686}
2687
2688int cwist_http2_stream_send_trailers(cwist_h2_stream *stream, const cwist_http2_header *trailers,
2689 size_t trailer_count) {
2690 if (!stream || !stream->hc) return -1;
2691 h2_conn *hc = stream->hc;
2692 unsigned char stack_block[1024];
2693 size_t block_len =
2694 h2_encode_header_pairs(stack_block, sizeof(stack_block), trailers, trailer_count);
2695 unsigned char *block = stack_block;
2696 if (block_len == 0 && trailer_count > 0) {
2697 /* Oversized trailer set: grow on the heap. */
2698 size_t cap = 4096;
2699 block = NULL;
2700 while (cap <= (1u << 20)) {
2701 unsigned char *nb = (unsigned char *)cwist_realloc(block, cap);
2702 if (!nb) {
2704 return -1;
2705 }
2706 block = nb;
2707 block_len = h2_encode_header_pairs(block, cap, trailers, trailer_count);
2708 if (block_len > 0) break;
2709 cap *= 2;
2710 }
2711 if (block_len == 0) {
2713 return -1;
2714 }
2715 }
2716 h2_out_lock(hc);
2717 int rc = h2_stream_write_frame_locked(
2719 stream->stream_id, block, (uint32_t)block_len);
2720 if (rc == 0) rc = h2_out_flush_raw(hc);
2721 h2_out_unlock(hc);
2723 return rc;
2724}
2725
2726/* Offer a newly completed request header block to the stream hooks. On
2727 * takeover the hook assumes ownership of the request object. */
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);
2732 if (hctx) {
2733 s->hook_ctx = hctx;
2734 s->req = NULL; /* ownership moved to the hook session */
2735 }
2736}
2737
2744static bool h2_response_is_grpc(cwist_http_response *res) {
2745 const char *ct = cwist_http_header_get(res->headers, "content-type");
2746 if (!ct) ct = cwist_http_header_get(res->headers, "Content-Type");
2747 return ct && strncmp(ct, "application/grpc", 16) == 0;
2748}
2749
2756static const char *h2_grpc_header_value(cwist_http_response *res, const char *name) {
2757 for (cwist_http_header_node *h = res->headers; h; h = h->next) {
2758 if (h->key && h->key->data && h->value && h->value->data &&
2759 strcasecmp(h->key->data, name) == 0)
2760 return h->value->data;
2761 }
2762 return NULL;
2763}
2764
2765/* Emit the gRPC trailer block (grpc-status/grpc-message, last write wins,
2766 * plus optional grpc-retry-pushback-ms) as a dedicated HEADERS frame with
2767 * END_STREAM. */
2768static int h2_send_grpc_trailers(h2_conn *hc, uint32_t stream_id, cwist_http_response *res,
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");
2774 size_t count = 0;
2775 pairs[count].name = "grpc-status";
2776 pairs[count].value = status ? status : "0";
2777 count++;
2778 if (message) {
2779 pairs[count].name = "grpc-message";
2780 pairs[count].value = message;
2781 count++;
2782 }
2783 if (pushback) {
2784 pairs[count].name = "grpc-retry-pushback-ms";
2785 pairs[count].value = pushback;
2786 count++;
2787 }
2788 unsigned char block[1024];
2789 size_t block_len = h2_encode_header_pairs(block, sizeof(block), pairs, count);
2790 if (block_len == 0) return -1;
2791 return h2_send_header_block(hc, hc->conn, stream_id, block, block_len, max_frame,
2793}
2794
2795/* gRPC error responses go out Trailers-Only (PROTOCOL-HTTP2, gRFC A6): one
2796 * HEADERS frame carrying :status 200, content-type, grpc-accept-encoding and
2797 * the grpc-status/message/pushback fields with END_STREAM, no DATA. Keeping
2798 * Response-Headers unsent is what lets conforming clients retry the RPC. */
2799static int h2_send_grpc_trailers_only(h2_conn *hc, uint32_t stream_id, cwist_http_response *res,
2801 size_t base_len = 0;
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");
2808 size_t count = 0;
2809 pairs[count].name = "grpc-status";
2810 pairs[count].value = status ? status : "13";
2811 count++;
2812 if (message) {
2813 pairs[count].name = "grpc-message";
2814 pairs[count].value = message;
2815 count++;
2816 }
2817 if (pushback) {
2818 pairs[count].name = "grpc-retry-pushback-ms";
2819 pairs[count].value = pushback;
2820 count++;
2821 }
2822 unsigned char tail[1024];
2823 size_t tail_len = h2_encode_header_pairs(tail, sizeof(tail), pairs, count);
2824 if (tail_len == 0) {
2826 return -1;
2827 }
2828 unsigned char *nb = (unsigned char *)cwist_realloc(block, base_len + tail_len);
2829 if (!nb) {
2831 return -1;
2832 }
2833 block = nb;
2834 memcpy(block + base_len, tail, tail_len);
2835 int rc = h2_send_header_block(hc, hc->conn, stream_id, block, base_len + tail_len, max_frame,
2838 return rc;
2839}
2840
2841static int h2_send_response_hc(h2_conn *hc, uint32_t stream_id, cwist_http_response *res) {
2842 h2_stream *s = h2_stream_find(hc, stream_id);
2843
2844 /* gRPC responses carry grpc-status in a trailer HEADERS frame, so the
2845 * main header block and the DATA frames never set END_STREAM. */
2846 bool grpc_mode = h2_response_is_grpc(res);
2847
2848 /* Error responses go out Trailers-Only (PROTOCOL-HTTP2, gRFC A6): one
2849 * HEADERS frame with END_STREAM, so the call stays retryable. */
2850 if (grpc_mode) {
2851 const char *gs = h2_grpc_header_value(res, "grpc-status");
2852 if (gs && strcmp(gs, "0") != 0) {
2853 uint32_t tf = hc->peer_max_frame_size;
2854 if (tf == 0) tf = CWIST_HTTP2_MAX_FRAME_SIZE;
2855 return h2_send_grpc_trailers_only(hc, stream_id, res, tf);
2856 }
2857 }
2858
2859 size_t block_len = 0;
2860 unsigned char *block = h2_encode_response_block(res, &block_len, grpc_mode);
2861 if (!block) return -1;
2862
2863 size_t body_len = 0;
2864 const unsigned char *body_data = NULL;
2865 if (grpc_mode) {
2866 /* gRPC bodies are always the in-memory framed message buffer. */
2867 if (res->body) {
2868 body_len = res->body->size;
2869 body_data = (const unsigned char *)res->body->data;
2870 }
2871 } else if (res->use_file_stream) {
2872 body_len = res->file_stream_len;
2873 } else if (res->is_ptr_body) {
2874 body_len = res->ptr_body_len;
2875 body_data = (const unsigned char *)res->ptr_body;
2876 } else if (res->body) {
2877 body_len = res->body->size;
2878 body_data = (const unsigned char *)res->body->data;
2879 }
2880
2881 /* HEAD responses and 1xx/204/304 statuses carry no content. The
2882 * content-length header (already encoded from the real body length for
2883 * HEAD; suppressed by the encoder for bodyless statuses) stays, but no
2884 * DATA frames may follow. */
2885 bool no_content = h2_status_forbids_body(res->status_code) ||
2886 (s && s->req && s->req->method == CWIST_HTTP_HEAD);
2887
2889 if (body_len == 0 || no_content) {
2890 body_len = 0;
2892 }
2893
2894 uint32_t max_frame = hc->peer_max_frame_size;
2896
2897 int rc =
2898 h2_send_header_block(hc, hc->conn, stream_id, block, block_len, max_frame, header_flags);
2900 if (rc != 0) return -1;
2901
2902 if (body_len == 0) {
2903 if (grpc_mode) return h2_send_grpc_trailers(hc, stream_id, res, max_frame);
2904 return 0;
2905 }
2906
2907 if (grpc_mode) {
2908 /* Plain DATA frames (no END_STREAM; the trailers close the stream). */
2909 size_t sent = 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())
2916 return -1;
2917 struct timespec ts = {0, 2000000};
2918 nanosleep(&ts, NULL);
2919 }
2920 }
2921 size_t remaining = body_len - sent;
2923 uint32_t allowed = h2_send_allowance(hc, s, chunk);
2924 if (allowed == 0) {
2925 if (h2_out_flush(hc) != 0) return -1;
2926 struct timespec ts = {0, 2000000};
2927 nanosleep(&ts, NULL);
2928 continue;
2929 }
2930 if (h2_write_frame(hc, CWIST_HTTP2_FRAME_DATA, 0, stream_id, body_data + sent,
2931 allowed) != 0) {
2932 return -1;
2933 }
2934 if (s) s->send_xor ^= h2_xor_bytes(body_data + sent, allowed);
2935 h2_commit_send(hc, s, allowed);
2936 sent += allowed;
2937 }
2938 return h2_send_grpc_trailers(hc, stream_id, res, max_frame);
2939 }
2940
2941 if (hc->sequenced_data) {
2942 if (res->use_file_stream && res->file_stream_fd >= 0) {
2943 return h2_send_seq_file_body(hc, s, stream_id, res->file_stream_fd,
2944 res->file_stream_offset, body_len, max_frame);
2945 }
2946 return h2_send_seq_memory_body(hc, s, stream_id, body_data, body_len, max_frame);
2947 }
2948
2949 /* Fallback: standard HTTP/2 DATA frames without sequence headers. */
2950 if (res->use_file_stream && res->file_stream_fd >= 0) {
2951 off_t offset = res->file_stream_offset;
2952 size_t remaining = res->file_stream_len;
2953 while (remaining > 0) {
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)) {
2958 /* Give up if no frame arrived within the idle deadline. */
2959 if (h2_now_ms() - hc->last_activity >= (uint64_t)h2_idle_timeout_ms())
2960 return -1;
2961 struct timespec ts = {0, 2000000};
2962 nanosleep(&ts, NULL);
2963 }
2964 }
2965
2967 uint32_t allowed = h2_send_allowance(hc, s, chunk);
2968 if (allowed == 0) {
2969 /* Pacing throttled us below one byte; flush and wait. */
2970 if (h2_out_flush(hc) != 0) return -1;
2971 struct timespec ts = {0, 2000000};
2972 nanosleep(&ts, NULL);
2973 continue;
2974 }
2975
2977 unsigned char *chunk_buf = (unsigned char *)cwist_scratch_alloc(&chunk_buf_s, allowed);
2978 if (!chunk_buf) return -1;
2979 ssize_t r = pread(res->file_stream_fd, chunk_buf, allowed, offset);
2980 if (r <= 0) return -1;
2982 if (h2_write_frame(hc, CWIST_HTTP2_FRAME_DATA, flags, stream_id, chunk_buf,
2983 (uint32_t)r) != 0) {
2984 return -1;
2985 }
2986 if (s) s->send_xor ^= h2_xor_bytes(chunk_buf, (size_t)r);
2987 h2_commit_send(hc, s, (uint32_t)r);
2988 offset += r;
2989 remaining -= (size_t)r;
2990 }
2991 } else {
2992 size_t sent = 0;
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)) {
2998 /* Give up if no frame arrived within the idle deadline. */
2999 if (h2_now_ms() - hc->last_activity >= (uint64_t)h2_idle_timeout_ms())
3000 return -1;
3001 struct timespec ts = {0, 2000000};
3002 nanosleep(&ts, NULL);
3003 }
3004 }
3005
3006 size_t remaining = body_len - sent;
3008 uint32_t allowed = h2_send_allowance(hc, s, chunk);
3009 if (allowed == 0) {
3010 /* Pacing throttled us below one byte; flush and wait. */
3011 if (h2_out_flush(hc) != 0) return -1;
3012 struct timespec ts = {0, 2000000};
3013 nanosleep(&ts, NULL);
3014 continue;
3015 }
3016
3017 uint8_t flags = (sent + allowed == body_len) ? CWIST_HTTP2_FLAG_END_STREAM : 0;
3018 if (h2_write_frame(hc, CWIST_HTTP2_FRAME_DATA, flags, stream_id, body_data + sent,
3019 allowed) != 0) {
3020 return -1;
3021 }
3022 if (s) s->send_xor ^= h2_xor_bytes(body_data + sent, allowed);
3023 h2_commit_send(hc, s, allowed);
3024 sent += allowed;
3025 }
3026 }
3027 return h2_out_flush(hc);
3028}
3029
3030/* --- CONTINUATION & Header Assembly --- */
3031
3032/* Create a stream for an incoming request header block, enforcing
3033 * CWIST_HTTP2_MAX_CONCURRENT_STREAMS (RFC 7540 section 5.1.2).
3034 * Returns the stream, or NULL with *refused set when the peer exceeded the
3035 * limit (RST_STREAM/REFUSED_STREAM already queued).
3036 * *goaway_sent is set when the outbound RST budget was exhausted and GOAWAY
3037 * was sent instead; the caller must close the connection. */
3038static h2_stream *h2_request_stream_create(h2_conn *hc, uint32_t stream_id, bool *refused,
3039 bool *goaway_sent) {
3040 *refused = false;
3041 *goaway_sent = false;
3042 if (hc->active_streams >= CWIST_HTTP2_MAX_CONCURRENT_STREAMS) {
3043 CWIST_LOG_WARN("[h2] refusing stream %u: concurrent stream limit %u reached", stream_id,
3045 int rst_rc = h2_send_rst_stream(hc, stream_id, H2_ERR_REFUSED_STREAM);
3046 if (rst_rc == -2) {
3047 *goaway_sent = true;
3048 return NULL;
3049 }
3050 *refused = true;
3051 return NULL;
3052 }
3053 h2_stream *s = h2_stream_create(hc, stream_id);
3054 if (!s) return NULL;
3055 s->req = cwist_http_request_create();
3056 if (!s->req) {
3057 h2_stream_remove(hc, stream_id);
3058 return NULL;
3059 }
3060 cwist_sstring_assign(s->req->version, "HTTP/2");
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; /* async defer completions route here */
3066 return s;
3067}
3068
3082static int h2_decode_stream_headers(h2_conn *hc, h2_stream *s, const unsigned char *block,
3083 size_t block_len, bool is_request) {
3084 int rc = h2_decode_header_block(hc, s->req, block, block_len, is_request);
3085 if (rc == H2_DECODE_COMPRESSION_ERROR) return -1;
3086 if (rc == H2_DECODE_STREAM_ERROR) {
3087 int rst_rc = h2_send_rst_stream(hc, s->stream_id, H2_ERR_PROTOCOL_ERROR);
3088 h2_stream_remove(hc, s->stream_id);
3089 /* rst_rc == -2: outbound budget exhausted, GOAWAY already sent. */
3090 return (rst_rc == -2) ? -1 : 1;
3091 }
3092 return 0;
3093}
3094
3113static int h2_begin_headers(h2_conn *hc, uint32_t stream_id, const unsigned char *payload,
3114 size_t len, bool end_headers, bool end_stream) {
3115 if (hc->expecting_continuation) {
3116 return -1; /* PROTOCOL_ERROR: HEADERS while expecting CONTINUATION */
3117 }
3118
3119 size_t block_offset = 0;
3120 size_t block_len = len;
3121
3122 bool is_new = (h2_stream_find(hc, stream_id) == NULL);
3123
3124 if (end_headers) {
3125 h2_stream *s = h2_stream_find(hc, stream_id);
3126 if (!s) {
3127 bool refused = false, goaway_sent = false;
3128 s = h2_request_stream_create(hc, stream_id, &refused, &goaway_sent);
3129 if (!s) return goaway_sent ? -2 : (refused ? 1 : -1);
3130 }
3131 if (payload && block_len > 0) {
3132 return h2_decode_stream_headers(hc, s, payload + block_offset, block_len, is_new);
3133 }
3134 /* An empty request header block still owes :method and :path. */
3135 if (is_new) {
3136 int rst_rc = h2_send_rst_stream(hc, stream_id, H2_ERR_PROTOCOL_ERROR);
3137 h2_stream_remove(hc, stream_id);
3138 return (rst_rc == -2) ? -2 : 1;
3139 }
3140 return 0;
3141 }
3142
3144 CWIST_LOG_WARN("[h2] HEADERS block length %zu exceeds maximum %u", block_len,
3146 return -2;
3147 }
3148
3149 /* Refused streams must still have their CONTINUATION sequence consumed
3150 * (and their header block ignored) so the connection framing stays in
3151 * sync. */
3152 if (is_new && hc->active_streams >= CWIST_HTTP2_MAX_CONCURRENT_STREAMS) {
3153 CWIST_LOG_WARN("[h2] refusing stream %u: concurrent stream limit %u reached", stream_id,
3155 int rst_rc = h2_send_rst_stream(hc, stream_id, H2_ERR_REFUSED_STREAM);
3156 if (rst_rc == -2) return -2; /* outbound budget gone, GOAWAY already sent */
3157 hc->expecting_continuation = true;
3158 hc->cont_stream_id = stream_id;
3159 hc->cont_end_stream = end_stream;
3160 hc->cont_discard = true;
3161 hc->cont_frame_count = 0;
3162 hc->cont_len = 0;
3163 return 1;
3164 }
3165
3166 /* Need continuation */
3167 hc->expecting_continuation = true;
3168 hc->cont_stream_id = stream_id;
3169 hc->cont_end_stream = end_stream;
3170 hc->cont_discard = false;
3171 hc->cont_frame_count = 0;
3172 hc->cont_len = block_len;
3173 hc->cont_cap = block_len < 1024 ? 1024 : block_len * 2;
3174 hc->cont_buf = (unsigned char *)cwist_alloc(hc->cont_cap);
3175 if (!hc->cont_buf) {
3176 hc->expecting_continuation = false;
3177 return -1;
3178 }
3179 if (payload && block_len > 0) {
3180 memcpy(hc->cont_buf, payload + block_offset, block_len);
3181 }
3182 return 0;
3183}
3184
3202static int h2_handle_continuation(h2_conn *hc, uint32_t stream_id, const unsigned char *payload,
3203 size_t len, bool end_headers) {
3204 if (!hc->expecting_continuation || hc->cont_stream_id != stream_id) {
3205 return -1; /* PROTOCOL_ERROR */
3206 }
3207
3208 hc->cont_frame_count++;
3209 if (hc->cont_frame_count > CWIST_HTTP2_MAX_CONTINUATIONS) {
3210 CWIST_LOG_WARN("[h2] excessive CONTINUATION frames (%u) received (CVE-2024-27983)",
3211 hc->cont_frame_count);
3212 return -2;
3213 }
3214
3215 if (hc->cont_len + len > CWIST_HTTP2_MAX_HEADER_BLOCK_SIZE) {
3216 CWIST_LOG_WARN("[h2] CONTINUATION accumulated header block size %zu exceeds maximum %u",
3217 hc->cont_len + len, (unsigned)CWIST_HTTP2_MAX_HEADER_BLOCK_SIZE);
3218 return -2;
3219 }
3220
3221 /* Header block of a stream we already refused: consume the frames so
3222 * framing stays in sync, but do not decode or buffer them. */
3223 if (hc->cont_discard) {
3224 if (end_headers) {
3225 hc->expecting_continuation = false;
3226 hc->cont_discard = false;
3227 hc->cont_stream_id = 0;
3228 hc->cont_frame_count = 0;
3229 }
3230 return 0;
3231 }
3232
3233 if (len > 0) {
3234 if (hc->cont_len + len > hc->cont_cap) {
3235 size_t new_cap = hc->cont_cap * 2;
3236 while (new_cap < hc->cont_len + len) new_cap *= 2;
3237 unsigned char *tmp = (unsigned char *)cwist_realloc(hc->cont_buf, new_cap);
3238 if (!tmp) return -1;
3239 hc->cont_buf = tmp;
3240 hc->cont_cap = new_cap;
3241 }
3242 memcpy(hc->cont_buf + hc->cont_len, payload, len);
3243 hc->cont_len += len;
3244 }
3245
3246 if (end_headers) {
3247 h2_stream *s = h2_stream_find(hc, hc->cont_stream_id);
3248 bool is_new = (s == NULL);
3249 int rc = 0;
3250 if (!s) {
3251 bool refused = false, goaway_sent = false;
3252 s = h2_request_stream_create(hc, hc->cont_stream_id, &refused, &goaway_sent);
3253 if (!s) rc = goaway_sent ? -2 : (refused ? 1 : -1);
3254 }
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) {
3258 /* Empty request header block: mandatory pseudo-headers missing. */
3259 int rst_rc = h2_send_rst_stream(hc, s->stream_id, H2_ERR_PROTOCOL_ERROR);
3260 h2_stream_remove(hc, s->stream_id);
3261 rc = (rst_rc == -2) ? -2 : 1;
3262 }
3263 hc->expecting_continuation = false;
3264 hc->cont_len = 0;
3265 hc->cont_stream_id = 0;
3266 hc->cont_frame_count = 0;
3267 return rc;
3268 }
3269 return 0;
3270}
3271
3272/* --- SETTINGS Parser --- */
3273
3286static int h2_handle_settings(h2_conn *hc, const unsigned char *payload, size_t len, bool ack) {
3287 if (ack) {
3288 return 0;
3289 }
3290
3291 if (len % 6 != 0) {
3292 return -1; /* FRAME_SIZE_ERROR */
3293 }
3294
3295 for (size_t i = 0; i + 6 <= len; i += 6) {
3296 uint16_t id = h2_get_u16(payload + i);
3297 uint32_t value = h2_get_u32(payload + i + 2);
3298 switch (id) {
3299 case 0x1: /* SETTINGS_HEADER_TABLE_SIZE */ break;
3300 case 0x2: /* SETTINGS_ENABLE_PUSH */
3301 if (value > 1) return -1; /* PROTOCOL_ERROR */
3302 break;
3303 case 0x3: /* SETTINGS_MAX_CONCURRENT_STREAMS */
3304 hc->peer_max_concurrent_streams = value;
3305 break;
3306 case 0x4: /* SETTINGS_INITIAL_WINDOW_SIZE */
3307 if (value > 2147483647U) return -1; /* FLOW_CONTROL_ERROR */
3308 {
3309 int32_t delta = (int32_t)value - (int32_t)hc->peer_initial_window_size;
3310 hc->peer_initial_window_size = value;
3311 h2_stream *s = hc->streams;
3312 while (s) {
3313 if (delta >= 0) {
3315 s->fc.send_window = inc > CWIST_HTTP2_MAX_WINDOW - s->fc.send_window
3317 : s->fc.send_window + inc;
3318 } else {
3319 uint32_t dec = (uint32_t)(-delta);
3320 /* RFC 7540 allows the adjusted window to go
3321 * negative; clamping to 0 is the safe subset. */
3322 s->fc.send_window =
3323 dec > s->fc.send_window ? 0 : s->fc.send_window - dec;
3324 }
3325 s = s->next;
3326 }
3327 h2_fc_credit_signal(hc);
3328 }
3329 break;
3330 case 0x5: /* SETTINGS_MAX_FRAME_SIZE */
3331 if (value < 16384 || value > 16777215) return -1; /* PROTOCOL_ERROR */
3332 hc->peer_max_frame_size = value;
3333 break;
3334 case 0x6: /* SETTINGS_MAX_HEADER_LIST_SIZE */ break;
3335 }
3336 }
3337 return 0;
3338}
3339
3340/* --- WINDOW_UPDATE Parser --- */
3341
3356static int h2_handle_window_update(h2_conn *hc, uint32_t stream_id, const unsigned char *payload,
3357 size_t len) {
3358 if (len != 4) return -1;
3359
3360 int32_t increment = (int32_t)h2_get_u31(payload);
3361 if (increment <= 0) return -1;
3362
3363 if (stream_id == 0) {
3364 if ((uint64_t)hc->fc.send_window + (uint32_t)increment > CWIST_HTTP2_MAX_WINDOW) return -1;
3365 hc->fc.send_window += (uint32_t)increment;
3366 } else {
3367 h2_stream *s = h2_stream_find(hc, stream_id);
3368 if (!s) {
3369 /* RFC 7540: WINDOW_UPDATE on closed stream is a STREAM_CLOSED error
3370 * or may be ignored depending on state. We ignore for leniency. */
3371 return 0;
3372 }
3373 if ((uint64_t)s->fc.send_window + (uint32_t)increment > CWIST_HTTP2_MAX_WINDOW) return -1;
3374 s->fc.send_window += (uint32_t)increment;
3375 }
3376 h2_fc_credit_signal(hc);
3377 return 0;
3378}
3379
3380/* --- Preface Verification --- */
3381
3390static int cwist_http2_verify_preface(h2_conn *hc) {
3392 /* The preface can arrive in a later TLS record than the handshake, so a
3393 * non-blocking SSL_read may legitimately report WANT_READ here; wait it
3394 * out instead of dropping the connection. */
3395 if (h2_read_full(hc, buffer, CWIST_HTTP2_CONNECTION_PREFACE_LEN) != 0) {
3396 return -1;
3397 }
3398
3400 return -1;
3401 }
3402
3403 return 0;
3404}
3405
3406/* --- Alt-Svc Injection Helper --- */
3407
3415static void h2_inject_alt_svc(cwist_https_connection *conn, cwist_http_response *res) {
3416 if (conn->http3_enabled && !cwist_http_header_get(res->headers, "Alt-Svc")) {
3417 struct sockaddr_storage ss;
3418 socklen_t ss_len = sizeof(ss);
3419 int port = 443;
3420 if (getsockname(conn->fd, (struct sockaddr *)&ss, &ss_len) == 0) {
3421 if (ss.ss_family == AF_INET) {
3422 port = ntohs(((struct sockaddr_in *)&ss)->sin_port);
3423 } else if (ss.ss_family == AF_INET6) {
3424 port = ntohs(((struct sockaddr_in6 *)&ss)->sin6_port);
3425 }
3426 }
3427 char alt_svc[64];
3428 snprintf(alt_svc, sizeof(alt_svc), "h3=\":%d\"; ma=86400", port);
3429 cwist_http_header_add(&res->headers, "Alt-Svc", alt_svc);
3430 }
3431}
3432
3433/* Drain completed deferred responses on the connection thread. For each
3434 * node: when the stream is still open the request is re-attached (HEAD
3435 * checks in the send path, and h2_stream_remove destroys it), the response
3436 * is encoded and written here — never on the worker thread — and the stream
3437 * is retired. Completions for streams the peer RST'd (already removed from
3438 * the table) are dropped without touching the socket. Returns -1 when a
3439 * send failed and the connection must be torn down. */
3440static int h2_async_drain(h2_conn *hc) {
3441 cwist_h2_async_queue *q = hc->async_q;
3442 if (!q) return 0;
3443 int afd = cwist_h2_async_queue_fd(q);
3444 if (afd >= 0) {
3445 uint64_t junk;
3446 while (read(afd, &junk, sizeof(junk)) == (ssize_t)sizeof(junk)) {
3447 }
3448 }
3449 pthread_mutex_lock(&q->mu);
3450 h2_async_node *list = q->head;
3451 q->head = q->tail = NULL;
3452 pthread_mutex_unlock(&q->mu);
3453
3454 while (list) {
3455 h2_async_node *n = list;
3456 list = n->next;
3457 h2_stream *s = h2_stream_find(hc, n->stream_id);
3458 if (!s) {
3459 h2_async_node_discard(n);
3460 continue;
3461 }
3462 s->req = NULL; /* Ownership of req/res was transferred to cwist_async */
3463 h2_inject_alt_svc(hc->conn, n->send);
3464 int rc = h2_send_response_hc(hc, n->stream_id, n->send);
3465 if (n->send_owned && n->send != n->res) cwist_http_response_destroy(n->send);
3468 h2_stream_remove(hc, n->stream_id);
3469 cwist_free(n);
3470 if (rc != 0) {
3471 while (list) {
3472 h2_async_node *next = list->next;
3473 h2_async_node_discard(list);
3474 list = next;
3475 }
3476 return -1;
3477 }
3478 }
3479 return 0;
3480}
3481
3482/* --- Connection Dispatcher --- */
3483
3495 return cwist_http2_serve_connection_ex(conn, user_ctx, handler, NULL);
3496}
3497
3498/* HTTP/2 session state that survives parking (cwist_https_park): the frame
3499 * loop's locals that must persist between pool threads live here. */
3500typedef struct h2_session {
3501 h2_conn hc;
3502 bool sent_goaway;
3503 uint64_t goaway_close_at;
3505
3510static void h2_session_free(void *p) {
3511 h2_session *st = (h2_session *)p;
3512 h2_conn_destroy(&st->hc);
3513 cwist_free(st);
3514}
3515
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))
3527 return false;
3528 return true;
3529}
3530
3544static void h2_session_disown(h2_session *st) {
3545 if (!cwist_full_gc_enabled()) return;
3546 h2_conn *hc = &st->hc;
3547 cwist_gc_scope_disown(hc->out_buf);
3548 cwist_gc_scope_disown(hc->cont_buf);
3549 for (h2_hpack_entry *he = hc->hpack_head; he; he = he->next) {
3551 cwist_gc_scope_disown(he->value);
3553 }
3554 cwist_gc_scope_disown(hc->hook_ctx);
3556}
3557
3575 const cwist_http2_stream_hooks *hooks) {
3576 cwist_error_t result;
3577
3578 if (!conn || !handler) {
3579 result = make_error(CWIST_ERR_INT16);
3580 result.error.err_i16 = -1;
3581 return result;
3582 }
3583
3584 /* A connection resumed from the HTTPS park set carries its session in
3585 * conn->proto_state; a fresh one sets the session up here. */
3586 h2_session *st = (h2_session *)conn->proto_state;
3587 bool resumed = st != NULL;
3588 if (!st) {
3589 st = (h2_session *)cwist_alloc(sizeof(*st));
3590 if (!st) {
3591 result = make_error(CWIST_ERR_INT16);
3592 result.error.err_i16 = -1;
3593 return result;
3594 }
3595 memset(st, 0, sizeof(*st));
3596 }
3597 h2_conn *hc = &st->hc;
3598 if (!resumed) {
3599 h2_conn_init(hc, conn);
3600 hc->hooks = hooks;
3601 hc->hook_ctx = (hooks && hooks->on_conn_open) ? hooks->on_conn_open(user_ctx) : user_ctx;
3602 hc->async_q = cwist_h2_async_queue_create();
3603
3604 if (cwist_http2_verify_preface(hc) != 0) {
3605 h2_conn_destroy(hc);
3606 cwist_free(st);
3607 result = make_error(CWIST_ERR_INT16);
3608 result.error.err_i16 = -1;
3609 return result;
3610 }
3611
3612 /* One SETTINGS entry per row: 2-byte identifier then 4-byte value. The
3613 row grouping is the wire format, so it is laid out by hand. */
3614 // clang-format off
3615 unsigned char settings[24] = {
3616 0x00, 0x01, 0x00, 0x00, 0x10, 0x00, // SETTINGS_HEADER_TABLE_SIZE = 4096 (CWIST_HTTP2_HEADER_TABLE_SIZE; HPACK dynamic table enabled)
3617 0x00, 0x03, 0x00, 0x00, 0x00, 0x64, // SETTINGS_MAX_CONCURRENT_STREAMS = 100 (CWIST_HTTP2_MAX_CONCURRENT_STREAMS)
3618 0x00, 0x04, 0x7f, 0xff, 0xff, 0xff, // SETTINGS_INITIAL_WINDOW_SIZE = 2147483647 (2GB)
3619 0x00, 0x06, 0x00, 0x01, 0x00, 0x00 // SETTINGS_MAX_HEADER_LIST_SIZE = 65536
3620 };
3621 // clang-format on
3622 if (h2_write_frame(hc, CWIST_HTTP2_FRAME_SETTINGS, 0, 0, settings, sizeof(settings)) != 0) {
3623 h2_conn_destroy(hc);
3624 cwist_free(st);
3625 result = make_error(CWIST_ERR_INT16);
3626 result.error.err_i16 = -1;
3627 return result;
3628 }
3629
3630 /* Upgrade connection flow control window to 2GB */
3631 h2_send_window_update(hc, 0, 2147483647 - 65535);
3632 hc->fc.receive_window = CWIST_HTTP2_MAX_WINDOW;
3633
3634 /* Kick off RTT sampling for the adaptive flow control; the ACK arrives
3635 * in the main loop below. Failure is non-fatal: pacing simply stays
3636 * uncalibrated. */
3637 h2_send_ping(hc);
3638 conn->proto_state = st;
3639 conn->proto_state_free = h2_session_free;
3640 }
3641
3642 bool connected = true;
3643 bool sent_goaway = st->sent_goaway;
3644 uint64_t goaway_close_at = st->goaway_close_at; /* monotonic ms; set once GOAWAY is out */
3645 if (resumed && conn->park_expired) {
3646 /* Parked past the idle timeout: announce the shutdown and keep the
3647 * grace window, as the in-thread idle expiry below does. */
3648 conn->park_expired = false;
3649 if (h2_send_goaway(hc, hc->last_processed_stream_id, H2_ERR_NO_ERROR) != 0) {
3650 connected = false;
3651 } else {
3652 sent_goaway = true;
3653 goaway_close_at = h2_now_ms() + (uint64_t)h2_goaway_grace_ms();
3654 }
3655 }
3656 while (connected) {
3657 /* Process shutdown: announce GOAWAY and keep serving in-flight
3658 * streams for the grace window instead of dropping them mid-flight. */
3659 if (!sent_goaway && !atomic_load(&g_cwist_running)) {
3660 if (h2_send_goaway(hc, hc->last_processed_stream_id, H2_ERR_NO_ERROR) != 0) {
3661 connected = false;
3662 break;
3663 }
3664 sent_goaway = true;
3665 goaway_close_at = h2_now_ms() + (uint64_t)h2_goaway_grace_ms();
3666 }
3667 /* The post-GOAWAY grace window elapsed: close as announced. */
3668 if (sent_goaway && goaway_close_at && h2_now_ms() >= goaway_close_at) {
3669 connected = false;
3670 break;
3671 }
3672 /* Hook-taken streams: enforce deadlines and reap finished streams. */
3673 if (hooks && hooks->on_poll) {
3674 h2_stream *ps = hc->streams;
3675 while (ps) {
3676 h2_stream *next = ps->next;
3677 if (ps->hook_ctx && hooks->on_poll(hc->hook_ctx, ps->hook_ctx))
3678 h2_stream_remove(hc, ps->stream_id);
3679 ps = next;
3680 }
3681 }
3682 unsigned char hdr[9];
3683 unsigned char *payload CWIST_DEFER_FREE = NULL;
3684
3685 /* Frames the send-window wait loop pulled off the wire but could not
3686 * dispatch are served first; only then touch the socket. */
3687 h2_deferred_frame *df = hc->deferred_head;
3688 if (df) {
3689 hc->deferred_head = df->next;
3690 if (!hc->deferred_head) hc->deferred_tail = NULL;
3691 memcpy(hdr, df->hdr, 9);
3692 payload = df->payload;
3693 cwist_free(df);
3694 } else {
3695 /* Idle between requests: hand the session to the HTTPS park set
3696 * instead of holding this pool thread in poll(). */
3697 if (!sent_goaway && h2_can_park(hc)) {
3698 if (h2_out_flush(hc) != 0) {
3699 connected = false;
3700 break;
3701 }
3702 uint64_t idle_ms = (uint64_t)h2_idle_timeout_ms();
3703 uint64_t idle_for = h2_now_ms() - hc->last_activity;
3704 if (idle_for < idle_ms && cwist_https_conn_idle(conn)) {
3705 st->sent_goaway = sent_goaway;
3706 st->goaway_close_at = goaway_close_at;
3707 h2_session_disown(st);
3709 /* Another pool thread may own the session now. */
3710 result = make_error(CWIST_ERR_INT16);
3711 result.error.err_i16 = 0;
3712 return result;
3713 }
3714 }
3715 }
3716 /* Bound the wait for the next frame with the idle deadline so an
3717 * idle connection cannot monopolize a pool worker forever. A
3718 * hook-provided deadline (gRPC timeout) tightens the wait. */
3719 uint64_t wait_deadline = sent_goaway ? goaway_close_at : 0;
3720 if (hooks && hooks->next_deadline_ms) {
3721 uint64_t gd = hooks->next_deadline_ms(hc->hook_ctx);
3722 if (gd && (!wait_deadline || gd < wait_deadline)) wait_deadline = gd;
3723 }
3724 int wait_rc = h2_wait_readable(hc, wait_deadline);
3725 if (wait_rc > 0) {
3726 /* Async defer completions: send them before reading more. */
3727 if (h2_async_drain(hc) != 0) {
3728 connected = false;
3729 break;
3730 }
3731 continue;
3732 }
3733 if (wait_rc != 0) {
3734 uint64_t now = h2_now_ms();
3735 /* A hook deadline expiring is not a connection error: the
3736 * poll sweep at the loop top expires the stream. */
3737 if (hooks && hooks->next_deadline_ms) {
3738 uint64_t gd = hooks->next_deadline_ms(hc->hook_ctx);
3739 if (gd && now >= gd) continue;
3740 }
3741 if (!sent_goaway && now - hc->last_activity >= (uint64_t)h2_idle_timeout_ms()) {
3742 /* Announce the shutdown, then keep serving briefly so a
3743 * request that raced onto the connection just before the
3744 * idle expiry still gets its response instead of a reset. */
3745 if (h2_send_goaway(hc, hc->last_processed_stream_id, H2_ERR_NO_ERROR) != 0) {
3746 connected = false;
3747 break;
3748 }
3749 sent_goaway = true;
3750 goaway_close_at = now + (uint64_t)h2_goaway_grace_ms();
3751 continue;
3752 }
3753 connected = false;
3754 break;
3755 }
3756 /* Frame bytes already in flight may still straddle TLS records or
3757 * TCP segments; h2_read_full waits out WANT_READ instead of
3758 * treating it as a dropped connection. */
3759 if (h2_read_full(hc, hdr, 9) != 0) {
3760 connected = false;
3761 break;
3762 }
3763 }
3764
3765 uint32_t len = h2_get_u24(hdr);
3766 uint8_t type = hdr[3];
3767 uint8_t flags = hdr[4];
3768 uint32_t stream_id = h2_get_u31(hdr + 5);
3769
3770 /* Frame size validation */
3771 if (len > CWIST_HTTP2_MAX_FRAME_SIZE) {
3774 if (type == CWIST_HTTP2_FRAME_SETTINGS || len > hc->peer_max_frame_size) {
3775 h2_send_goaway(hc, hc->last_processed_stream_id, H2_ERR_FRAME_SIZE_ERROR);
3776 connected = false;
3777 break;
3778 }
3779 }
3780 }
3781
3782 if (!df && len > 0) {
3783 payload = (unsigned char *)cwist_alloc(len);
3784 if (!payload) {
3785 connected = false;
3786 break;
3787 }
3788 if (h2_read_full(hc, payload, len) != 0) connected = false;
3789 if (!connected) break;
3790 }
3791
3792 hc->last_activity = h2_now_ms();
3793
3794 switch (type) {
3796 if (h2_handle_settings(hc, payload, len, flags & CWIST_HTTP2_FLAG_ACK) != 0) {
3797 h2_send_goaway(hc, hc->last_processed_stream_id, H2_ERR_PROTOCOL_ERROR);
3798 connected = false;
3799 } else if ((flags & CWIST_HTTP2_FLAG_ACK) == 0) {
3800 if (h2_write_frame(hc, CWIST_HTTP2_FRAME_SETTINGS, CWIST_HTTP2_FLAG_ACK, 0,
3801 NULL, 0) != 0) {
3802 connected = false;
3803 }
3804 }
3805 break;
3806 }
3807
3809 if (stream_id == 0 || (stream_id % 2) == 0) {
3810 h2_send_goaway(hc, 0, H2_ERR_PROTOCOL_ERROR);
3811 connected = false;
3812 break;
3813 }
3814 /* Strip PADDED and PRIORITY from payload for decoding */
3815 size_t block_offset = 0;
3816 size_t block_len = len;
3817 if ((flags & CWIST_HTTP2_FLAG_PADDED) != 0 && block_len > 0) {
3818 uint8_t pad_len = payload[block_offset++];
3819 block_len--;
3821 }
3822 if ((flags & CWIST_HTTP2_FLAG_PRIORITY) != 0 && block_len >= 5) {
3823 block_offset += 5;
3824 block_len -= 5;
3825 }
3826 int hdr_rc = h2_begin_headers(hc, stream_id, payload + block_offset, block_len,
3829 if (hdr_rc < 0) {
3830 uint32_t err =
3832 h2_send_goaway(hc, hc->last_processed_stream_id, err);
3833 connected = false;
3834 break;
3835 }
3836 if (stream_id > hc->last_processed_stream_id)
3837 hc->last_processed_stream_id = stream_id;
3838 if (hdr_rc > 0) {
3839 /* Stream refused or malformed: RST_STREAM already queued. */
3840 break;
3841 }
3842 {
3843 h2_stream *s = h2_stream_find(hc, stream_id);
3844 if (s && s->hook_ctx) {
3845 /* Client trailers on a hook-taken stream: END_STREAM
3846 * terminates the inbound message flow. */
3847 if (hooks && hooks->on_data && (flags & CWIST_HTTP2_FLAG_END_STREAM))
3848 hooks->on_data(hc->hook_ctx, s->hook_ctx, NULL, 0, 1);
3849 break;
3850 }
3851 h2_hook_offer(hc, s);
3852 if (s && s->hook_ctx) {
3853 if (hooks->on_data && (flags & CWIST_HTTP2_FLAG_END_STREAM))
3854 hooks->on_data(hc->hook_ctx, s->hook_ctx, NULL, 0, 1);
3855 break;
3856 }
3857 }
3858 if (flags & CWIST_HTTP2_FLAG_END_STREAM) {
3859 h2_stream *s = h2_stream_find(hc, stream_id);
3860 if (s && s->req) {
3861 cwist_http_response *res =
3863 if (res) {
3864 handler(user_ctx, s->req, res);
3865 cwist_async *deferred = cwist_async_dispatch_take(res);
3866 if (deferred) {
3867 /* Deferred-response handoff: ownership of
3868 * req/res moved to cwist_async; the stream
3869 * stays in the table so the queue drain can
3870 * find it, and RST_STREAM must not touch the
3871 * request (s->req is NULL from here on). */
3872 s->req = NULL;
3873 cwist_async_dispatch_ack(deferred);
3874 break;
3875 }
3876 h2_inject_alt_svc(conn, res);
3877 if (h2_send_response_hc(hc, stream_id, res) != 0) connected = false;
3879 }
3880 h2_stream_remove(hc, stream_id);
3881 }
3882 }
3883 break;
3884 }
3885
3887 if (stream_id == 0 || !hc->expecting_continuation ||
3888 hc->cont_stream_id != stream_id) {
3889 h2_send_goaway(hc, hc->last_processed_stream_id, H2_ERR_PROTOCOL_ERROR);
3890 connected = false;
3891 break;
3892 }
3893 int cont_rc = h2_handle_continuation(hc, stream_id, payload, len,
3895 if (cont_rc < 0) {
3896 uint32_t err =
3898 h2_send_goaway(hc, hc->last_processed_stream_id, err);
3899 connected = false;
3900 break;
3901 }
3902 if (cont_rc > 0) {
3903 /* Refused/malformed header block: RST_STREAM already sent. */
3904 break;
3905 }
3906 if (flags & CWIST_HTTP2_FLAG_END_STREAM) {
3907 /* END_STREAM on CONTINUATION is a protocol violation */
3908 h2_send_goaway(hc, hc->last_processed_stream_id, H2_ERR_PROTOCOL_ERROR);
3909 connected = false;
3910 break;
3911 }
3912 if ((flags & CWIST_HTTP2_FLAG_END_HEADERS) && hc->cont_end_stream) {
3913 h2_stream *s = h2_stream_find(hc, stream_id);
3914 h2_hook_offer(hc, s);
3915 if (s && s->hook_ctx) {
3916 if (hooks->on_data) hooks->on_data(hc->hook_ctx, s->hook_ctx, NULL, 0, 1);
3917 break;
3918 }
3919 if (s && s->req) {
3920 cwist_http_response *res =
3922 if (res) {
3923 handler(user_ctx, s->req, res);
3924 cwist_async *deferred = cwist_async_dispatch_take(res);
3925 if (deferred) {
3926 /* Deferred-response handoff: ownership of
3927 * req/res moved to cwist_async; the stream
3928 * stays in the table so the queue drain can
3929 * find it, and RST_STREAM must not touch the
3930 * request (s->req is NULL from here on). */
3931 s->req = NULL;
3932 cwist_async_dispatch_ack(deferred);
3933 break;
3934 }
3935 h2_inject_alt_svc(conn, res);
3936 if (h2_send_response_hc(hc, stream_id, res) != 0) connected = false;
3938 }
3939 h2_stream_remove(hc, stream_id);
3940 }
3941 }
3942 break;
3943 }
3944
3946 if (stream_id == 0) {
3947 h2_send_goaway(hc, 0, H2_ERR_PROTOCOL_ERROR);
3948 connected = false;
3949 break;
3950 }
3951 h2_stream *s = h2_stream_find(hc, stream_id);
3952 if (!s) {
3953 /* DATA on closed/unknown stream: must check if it violates flow control */
3954 /* For simplicity, we treat it as a stream error by ignoring the data
3955 * but still accounting connection-level window. */
3956 if (!cwist_http2_flow_control_receive(&hc->fc, len)) {
3957 h2_send_goaway(hc, hc->last_processed_stream_id, H2_ERR_FLOW_CONTROL_ERROR);
3958 connected = false;
3959 } else {
3961 }
3962 break;
3963 }
3964 if (len > 0) {
3965 if (!cwist_http2_flow_control_receive(&hc->fc, len) ||
3967 h2_send_goaway(hc, hc->last_processed_stream_id, H2_ERR_FLOW_CONTROL_ERROR);
3968 connected = false;
3969 break;
3970 }
3973 }
3974 if (s->hook_ctx) {
3975 /* Hook-taken stream: DATA is fed to the hook as it
3976 * arrives instead of being buffered for dispatch. */
3977 int end_stream = (flags & CWIST_HTTP2_FLAG_END_STREAM) != 0;
3978 int hook_rc = 0;
3979 if ((len > 0 || end_stream) && hooks->on_data)
3980 hook_rc =
3981 hooks->on_data(hc->hook_ctx, s->hook_ctx, payload, len, end_stream);
3982 if (hook_rc != 0) {
3983 h2_stream_remove(hc, stream_id);
3984 s = NULL;
3985 break;
3986 }
3987 if (end_stream && hooks->on_poll && hooks->on_poll(hc->hook_ctx, s->hook_ctx)) {
3988 h2_auto_window_update(hc, s);
3989 h2_stream_remove(hc, stream_id);
3990 s = NULL;
3991 break;
3992 }
3993 h2_auto_window_update(hc, s);
3994 break;
3995 }
3996 if (payload && len > 0) {
3997 if (hc->sequenced_data && len >= CWIST_SEQ_HEADER_SIZE) {
3999 if (cwist_seq_chunk_parse(payload, len, &chunk)) {
4000 if (!s->body_assembler) {
4001 s->body_assembler =
4003 if (!s->body_assembler) {
4004 connected = false;
4005 break;
4006 }
4007 }
4008 if (!cwist_seq_assembler_feed(s->body_assembler, &chunk)) {
4009 /* A conflicting duplicate is corruption, not a
4010 * retransmission. Refuse the stream without
4011 * exposing a mixed body to the application. */
4012 uint8_t rst[4] = {0, 0, 0, H2_ERR_PROTOCOL_ERROR};
4013 h2_write_frame(hc, CWIST_HTTP2_FRAME_RST_STREAM, 0, stream_id, rst,
4014 sizeof(rst));
4015 h2_stream_remove(hc, stream_id);
4016 break;
4017 }
4018 }
4019 /* Malformed sequence chunks are ignored (discarded). */
4020 } else if (!hc->sequenced_data) {
4021 if (s->req->body->size + len > CWIST_HTTP_MAX_BODY_SIZE) {
4022 uint8_t rst[4] = {0, 0, 0, H2_ERR_REFUSED_STREAM};
4023 h2_write_frame(hc, CWIST_HTTP2_FRAME_RST_STREAM, 0, stream_id, rst, 4);
4024 h2_stream_remove(hc, stream_id);
4025 break;
4026 }
4027 s->recv_xor ^= h2_xor_bytes(payload, len);
4028 cwist_sstring_append_len(s->req->body, (const char *)payload, len);
4029 }
4030 }
4031 if (flags & CWIST_HTTP2_FLAG_END_STREAM) {
4032 /* No assembler means no sequenced chunks ever arrived;
4033 * an empty DATA END_STREAM is a complete empty body. */
4034 bool sequence_complete = !hc->sequenced_data || !s->body_assembler;
4035 if (hc->sequenced_data && s->body_assembler) {
4036 const uint8_t *assembled = NULL;
4037 size_t assembled_len = 0;
4038 if (cwist_seq_assembler_get_data(s->body_assembler, &assembled,
4039 &assembled_len)) {
4040 if (s->req->body->size + assembled_len <= CWIST_HTTP_MAX_BODY_SIZE) {
4041 s->recv_xor ^= h2_xor_bytes(assembled, assembled_len);
4042 cwist_sstring_append_len(s->req->body, (const char *)assembled,
4044 sequence_complete = true;
4045 }
4046 }
4047 }
4048 if (!sequence_complete) {
4049 /* TASFA-style ARQ boundary: the assembler retains the
4050 * exact missing sequence numbers as recovery targets;
4051 * HTTP/2 represents "retry this unprocessed request"
4052 * with REFUSED_STREAM. Never call the handler with a
4053 * partial sequenced body. */
4054 uint8_t rst[4] = {0, 0, 0, H2_ERR_REFUSED_STREAM};
4055 h2_write_frame(hc, CWIST_HTTP2_FRAME_RST_STREAM, 0, stream_id, rst,
4056 sizeof(rst));
4057 h2_auto_window_update(hc, s);
4058 h2_stream_remove(hc, stream_id);
4059 break;
4060 }
4062 if (res) {
4063 handler(user_ctx, s->req, res);
4064 cwist_async *deferred = cwist_async_dispatch_take(res);
4065 if (deferred) {
4066 /* Deferred-response handoff: req/res owned by
4067 * cwist_async; keep the stream in the table so
4068 * the queue drain can find it later. */
4069 s->req = NULL;
4070 cwist_async_dispatch_ack(deferred);
4071 h2_auto_window_update(hc, s);
4072 break;
4073 }
4074 h2_inject_alt_svc(conn, res);
4075 if (h2_send_response_hc(hc, stream_id, res) != 0) connected = false;
4077 }
4078 /* Keep the stream state alive until its final window
4079 * credit is returned; h2_auto_window_update reads it. */
4080 if (connected) h2_auto_window_update(hc, s);
4081 h2_stream_remove(hc, stream_id);
4082 s = NULL;
4083 }
4084 if (connected && s) {
4085 h2_auto_window_update(hc, s);
4086 }
4087 break;
4088 }
4089
4091 if (stream_id == 0 || len != 4) {
4092 h2_send_goaway(hc, hc->last_processed_stream_id, H2_ERR_PROTOCOL_ERROR);
4093 connected = false;
4094 break;
4095 }
4096 if (stream_id > hc->last_processed_stream_id) {
4097 /* RFC 7540 section 5.1: RST_STREAM frames MUST NOT be sent for a
4098 * stream in the "idle" state. Connection error of type
4099 * PROTOCOL_ERROR. */
4101 "[h2] RST_STREAM received for idle stream %u > last_processed %u",
4102 stream_id, hc->last_processed_stream_id);
4103 h2_send_goaway(hc, hc->last_processed_stream_id, H2_ERR_PROTOCOL_ERROR);
4104 connected = false;
4105 break;
4106 }
4107 if (!h2_conn_consume_rst_budget(hc)) {
4109 "[h2] peer exceeded RST_STREAM rate limit (Rapid Reset CVE-2023-44487 detected)");
4110 h2_send_goaway(hc, hc->last_processed_stream_id, H2_ERR_ENHANCE_YOUR_CALM);
4111 connected = false;
4112 break;
4113 }
4114 {
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);
4118 }
4119 h2_stream_remove(hc, stream_id);
4120 break;
4121 }
4122
4124 if (len != 8) {
4125 h2_send_goaway(hc, hc->last_processed_stream_id, H2_ERR_FRAME_SIZE_ERROR);
4126 connected = false;
4127 break;
4128 }
4129 if ((flags & CWIST_HTTP2_FLAG_ACK) == 0) {
4130 if (!h2_conn_consume_ping_budget(hc)) {
4131 CWIST_LOG_WARN("[h2] peer exceeded PING rate limit (CVE-2019-9512)");
4132 h2_send_goaway(hc, hc->last_processed_stream_id, H2_ERR_ENHANCE_YOUR_CALM);
4133 connected = false;
4134 break;
4135 }
4136 if (h2_write_frame(hc, CWIST_HTTP2_FRAME_PING, CWIST_HTTP2_FLAG_ACK, 0,
4137 payload, 8) != 0) {
4138 connected = false;
4139 }
4140 } else if (hc->ping_outstanding && memcmp(payload, hc->ping_payload, 8) == 0) {
4141 /* RTT sample for the adaptive window/pacing tuner. Keep
4142 * one ping in flight while streams are active so the
4143 * estimate tracks changing network conditions. */
4144 uint64_t sample = h2_now_us() - hc->ping_sent_us;
4145 hc->ping_outstanding = false;
4147 if (hc->streams) h2_send_ping(hc);
4148 }
4149 break;
4150 }
4151
4153 connected = false;
4154 break;
4155 }
4156
4158 if (len != 4) {
4159 h2_send_goaway(hc, hc->last_processed_stream_id, H2_ERR_FRAME_SIZE_ERROR);
4160 connected = false;
4161 break;
4162 }
4163 if (h2_handle_window_update(hc, stream_id, payload, len) != 0) {
4164 h2_send_goaway(hc, hc->last_processed_stream_id, H2_ERR_FLOW_CONTROL_ERROR);
4165 connected = false;
4166 }
4167 break;
4168 }
4169
4170 default:
4171 /* Unknown frames MUST be ignored (RFC 7540 Section 5.5) */
4172 break;
4173 }
4174 }
4175
4176 if (connected && !atomic_load(&g_cwist_running) && !sent_goaway) {
4177 h2_send_goaway(hc, hc->last_processed_stream_id, H2_ERR_NO_ERROR);
4178 sent_goaway = true;
4179 }
4180
4181 h2_out_flush(hc); /* best effort: last GOAWAY/batched frames */
4182 conn->proto_state = NULL;
4183 conn->proto_state_free = NULL;
4184 h2_session_free(st);
4185
4186 result = make_error(CWIST_ERR_INT16);
4187 result.error.err_i16 = 0;
4188 return result;
4189}
4190
4191/* ------------------------------------------------------------------ */
4192/* HTTP/2 Server Push */
4193/* ------------------------------------------------------------------ */
4194
4210int cwist_http2_push_resource(cwist_http_request *req, const char *path, const char *content_type,
4211 const unsigned char *data, size_t data_len) {
4212 if (!req || !req->private_data || !path) return -1;
4213
4216
4217 const char *authority = NULL;
4219 while (h) {
4220 if (h->key && h->key->data && strcasecmp(h->key->data, "host") == 0) {
4221 authority = h->value ? h->value->data : NULL;
4222 break;
4223 }
4224 h = h->next;
4225 }
4226 if (!authority) return -1;
4227
4228 static _Atomic uint32_t next_server_stream = 2;
4230
4231 unsigned char header_block[4096];
4232 size_t pos = 0;
4233
4234 if (pos >= sizeof(header_block)) return -1;
4235 header_block[pos++] = 0x82;
4236
4237 if (pos >= sizeof(header_block)) return -1;
4238 header_block[pos++] = 0x87;
4239
4240 if (pos + 1 > sizeof(header_block)) return -1;
4241 header_block[pos] = 0x00;
4242 size_t n = h2_encode_integer(header_block + pos, sizeof(header_block) - pos, 0, 4);
4243 if (n == 0) return -1;
4244 pos += n;
4245 n = h2_encode_string(header_block + pos, sizeof(header_block) - pos, ":authority");
4246 if (n == 0) return -1;
4247 pos += n;
4248 n = h2_encode_string(header_block + pos, sizeof(header_block) - pos, authority);
4249 if (n == 0) return -1;
4250 pos += n;
4251
4252 if (pos + 1 > sizeof(header_block)) return -1;
4253 header_block[pos] = 0x00;
4254 n = h2_encode_integer(header_block + pos, sizeof(header_block) - pos, 0, 4);
4255 if (n == 0) return -1;
4256 pos += n;
4257 n = h2_encode_string(header_block + pos, sizeof(header_block) - pos, ":path");
4258 if (n == 0) return -1;
4259 pos += n;
4260 n = h2_encode_string(header_block + pos, sizeof(header_block) - pos, path);
4261 if (n == 0) return -1;
4262 pos += n;
4263
4264 unsigned char pp_payload[4096 + 4];
4265 h2_put_u31(pp_payload, promised_stream_id);
4266 memcpy(pp_payload + 4, header_block, pos);
4267
4268 if (h2_write_frame_now(conn, 0x05, CWIST_HTTP2_FLAG_END_HEADERS, original_stream_id, pp_payload,
4269 (uint32_t)(pos + 4)) != 0) {
4270 return -1;
4271 }
4272
4274 if (!res) return -1;
4276 if (content_type) {
4277 cwist_http_header_add(&res->headers, "content-type", content_type);
4278 }
4279 if (data && data_len > 0) {
4280 cwist_sstring_assign_len(res->body, (const char *)data, data_len);
4281 }
4282
4283 int ret = h2_send_response_raw(conn, promised_stream_id, res, CWIST_HTTP2_MAX_FRAME_SIZE);
4285 return ret;
4286}
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_realloc(void *ptr, size_t new_size)
Resize an existing allocation (libttak tracked).
Definition alloc.c:27
char * cwist_strdup(const char *src)
Duplicate a string using libttak-backed storage.
Definition alloc.c:32
#define CWIST_DEFER_FREE
Attach to a pointer variable's declaration to cwist_free() it automatically when that variable's encl...
Definition alloc.h:101
void * cwist_alloc(size_t size)
Allocate zeroed memory tracked by libttak.
Definition alloc.c:17
#define CWIST_SCRATCH_DEFER
Attach to a cwist_scratch_t variable's declaration so its allocation (arena- or heap-backed,...
Definition alloc.h:190
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...
Definition async.c:167
struct cwist_async cwist_async
Definition async.h:54
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)
Definition async_tls.c:57
cwist_error_t cwist_sstring_append_len(cwist_sstring *str, const char *data, size_t len)
Append raw bytes with length.
Definition sstring.c:212
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
@ 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: 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...
Definition gc.c:766
bool cwist_full_gc_enabled(void)
Check whether full-GC mode is currently enabled.
Definition gc.c:515
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).
Definition http2.c:316
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.
Definition http2.c:1267
#define CWIST_HTTP2_FRAME_HEADERS
Definition http2.c:63
#define CWIST_HTTP2_FRAME_WINDOW_UPDATE
Definition http2.c:68
#define H2_ERR_FRAME_SIZE_ERROR
Definition http2.c:85
int cwist_http2_stream_send_trailers(cwist_h2_stream *stream, const cwist_http2_header *trailers, size_t trailer_count)
Definition http2.c:2688
#define CWIST_HTTP2_DEFAULT_RST_BURST
Definition http2.c:92
int cwist_http2_stream_send_headers(cwist_h2_stream *stream, int status, const cwist_http2_header *headers, size_t header_count, int end_stream)
Definition http2.c:2608
const cwist_http2_static_header * h2_static_header(uint32_t index)
Resolve an RFC 7541 static table entry by 1-based index.
Definition http2.c:1570
#define H2_ERR_NO_ERROR
Definition http2.c:80
#define CWIST_HTTP2_FLAG_ACK
Definition http2.c:60
#define CWIST_H2_BATCH_DEFAULT_BYTES
Definition http2.c:1050
#define CWIST_HTTP2_CONNECTION_PREFACE
Definition http2.c:48
#define CWIST_HTTP2_FRAME_SETTINGS
Definition http2.c:65
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).
Definition http2.c:1903
#define CWIST_HTTP2_CONNECTION_PREFACE_LEN
Definition http2.c:49
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...
Definition http2.c:1543
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,...
Definition http2.c:3573
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)
Definition http2.c:339
#define CWIST_HTTP2_FLAG_END_STREAM
Definition http2.c:56
#define CWIST_HTTP2_SEQ_CHUNK_PAYLOAD(max_frame)
Definition http2.c:77
#define CWIST_HTTP2_FRAME_CONTINUATION
Definition http2.c:69
#define CWIST_HTTP2_MAX_HEADERS_PER_REQUEST
Definition http2.c:98
#define CWIST_HTTP2_DEFAULT_PING_BURST
Definition http2.c:94
#define H2_DECODE_COMPRESSION_ERROR
Definition http2.c:1743
#define CWIST_HTTP2_FRAME_DATA
Definition http2.c:62
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...
Definition http2.c:3493
#define CWIST_HTTP2_MAX_CONCURRENT_STREAMS
Definition http2.c:51
struct h2_conn h2_conn
Definition http2.c:200
#define H2_DECODE_OK
Definition http2.c:1741
#define CWIST_HTTP2_FRAME_GOAWAY
Definition http2.c:67
#define CWIST_HTTP2_DEFAULT_PING_RATE
Definition http2.c:95
#define CWIST_HTTP2_FRAME_PUSH_PROMISE
Definition http2.c:70
#define CWIST_HTTP2_MAX_CONTINUATIONS
Definition http2.c:97
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,...
Definition http2.c:327
struct h2_huffman_node h2_huffman_node
struct h2_async_node h2_async_node
#define CWIST_HTTP2_MAX_HEADER_BLOCK_SIZE
Definition http2.c:96
struct cwist_h2_stream h2_stream
#define CWIST_HTTP2_FRAME_PING
Definition http2.c:66
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 ...
Definition http2.c:4210
#define CWIST_HTTP2_MAX_FRAME_SIZE
Definition http2.c:50
#define CWIST_HTTP2_FRAME_RST_STREAM
Definition http2.c:64
#define H2_ERR_ENHANCE_YOUR_CALM
Definition http2.c:89
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...
Definition http2.c:1442
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...
Definition http2.c:1875
#define CWIST_HTTP2_FLAG_PRIORITY
Definition http2.c:59
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 ...
Definition http2.c:2642
#define CWIST_HTTP2_FLAG_END_HEADERS
Definition http2.c:57
#define H2_ERR_PROTOCOL_ERROR
Definition http2.c:81
#define H2_DECODE_STREAM_ERROR
Definition http2.c:1742
#define CWIST_HTTP2_HEADER_TABLE_SIZE
Definition http2.c:54
#define H2_ERR_REFUSED_STREAM
Definition http2.c:86
#define CWIST_HTTP2_DEFAULT_RST_RATE
Definition http2.c:93
#define H2_ERR_FLOW_CONTROL_ERROR
Definition http2.c:82
#define CWIST_HTTP2_FRAME_HEADER_SIZE
Definition http2.c:47
#define CWIST_HTTP2_GOAWAY_GRACE_MS_DEFAULT
Grace window between sending GOAWAY on idle timeout and closing the connection. A client that raced a...
Definition http2.c:158
#define CWIST_HTTP2_FLAG_PADDED
Definition http2.c:58
HTTP/2 Protocol Definitions for CWIST.
struct cwist_h2_stream cwist_h2_stream
Definition http2.h:23
struct cwist_h2_async_queue cwist_h2_async_queue
Definition http2.h:123
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.
Definition http2.h:19
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
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_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_HEAD
Definition http.h:22
#define CWIST_HTTP_TIMEOUT_MS
Definition http.h:43
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_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_WARN(fmt,...)
Definition log.h:56
Production-grade metrics engine for CWIST.
#define CWIST_HTTP2_IDLE_TIMEOUT_MS
Definition http.h:149
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...
Definition http.c:2306
@ CWIST_HTTPS_PARK_HTTP2
Definition https.h:60
bool cwist_https_conn_idle(cwist_https_connection *conn)
True when conn has no buffered TLS bytes and nothing readable.
Definition https.c:683
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.
Definition https.c:672
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 head
Definition otel.c:57
size_t count
Definition otel.c:58
size_t cap
Definition otel.c:56
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
void cwist_seq_message_free(cwist_seq_message_t *msg)
Free all buffers allocated by cwist_seq_split().
Definition seq.c:178
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
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.
Definition seq.c:97
#define CWIST_SEQ_HEADER_SIZE
Definition seq.h:29
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.
Definition seq.c:125
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_ERROR_WANT_READ
Definition ssl.h:54
#define SSL_write
Definition ssl.h:172
#define SSL_read
Definition ssl.h:171
#define SSL_get_error
Definition ssl.h:175
#define SSL_pending
Definition ssl.h:173
#define SSL_ERROR_WANT_WRITE
Definition ssl.h:55
Integrated error structure used throughout the system.
Definition cwist_err.h:72
__prim_cwist_error_t error
Definition cwist_err.h:74
const char * name
Definition http2.h:27
HPACK static table entry (RFC 7541 Appendix A).
Definition http2.h:154
const char * name
Definition http2.h:155
const char * value
Definition http2.h:156
int(* on_poll)(void *conn_ctx, void *stream_ctx)
Definition http2.h:53
int(* on_data)(void *conn_ctx, void *stream_ctx, const unsigned char *data, size_t len, int end_stream)
Definition http2.h:47
void *(* on_conn_open)(void *user_ctx)
Definition http2.h:39
void(* on_cancel)(void *conn_ctx, void *stream_ctx)
Definition http2.h:50
uint64_t(* next_deadline_ms)(void *conn_ctx)
Definition http2.h:56
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
cwist_sstring * path
e.g., "/users/1"
Definition http.h:67
cwist_sstring * query
e.g., "active=true" (raw)
Definition http.h:68
uint32_t stream_id
HTTP/2 or HTTP/3 stream ID (0 for HTTP/1.1).
Definition http.h:77
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
int file_stream_fd
Open descriptor for sendfile.
Definition http.h:217
bool use_file_stream
Fast File Streaming.
Definition http.h:216
cwist_sstring * body
Definition http.h:87
const void * ptr_body
Pointer to external data (e.g., mmap region)
Definition http.h:210
cwist_http_status_t status_code
Definition http.h:84
off_t file_stream_offset
Current offset for sendfile loop.
Definition http.h:219
cwist_http_header_node * headers
Definition http.h:86
bool is_ptr_body
Zero-Copy Pointer Body.
Definition http.h:209
size_t ptr_body_len
Length of external data.
Definition http.h:211
size_t file_stream_len
Total bytes to stream.
Definition http.h:218
void(* proto_state_free)(void *state)
Definition https.h:51
bool http2_sequenced_data
Definition https.h:46
Backing state for cwist_scratch_alloc()/CWIST_SCRATCH_DEFER: a single block-scoped allocation that pr...
Definition alloc.h:131
Parsed view of one sequenced chunk.
Definition seq.h:45
Split a message into sequenced chunks.
Definition seq.h:60
size_t size
Definition sstring.h:20
char * data
Access directly only when raw handling is necessary.
Definition sstring.h:15
Unified error code and type definitions for the CWIST system layer.