Skip to main content

rsurl/
http2.rs

1//! HTTP/2 support (RFC 9113), with HPACK header compression (RFC 7541).
2//!
3//! HTTP/2 reuses the `https://` URL scheme; the version is selected at
4//! connect time, typically via ALPN ("h2"). This module exposes a backend
5//! that can serve [`crate::Request`] over a TLS connection negotiated with
6//! ALPN, returning a [`crate::Response`] just like HTTP/1.1.
7//!
8//! Scope of this implementation:
9//!
10//! - Multiplexed streams within one connection (RFC 9113 §5.1). A single
11//!   `Connection` carries many streams, and `send()` reuses a pooled
12//!   `Connection` across calls: a process-wide pool keyed on
13//!   `(scheme, host, port)` parks idle post-handshake connections so a
14//!   follow-up request to the same authority skips the TCP + TLS + h2-preface
15//!   handshake and simply opens the next odd stream id (1, 3, 5, …) on the warm
16//!   connection. The single-request `send()` path drives one request at a time
17//!   over a pooled connection (sequential reuse). For TRUE concurrency,
18//!   `send_multiplexed()` issues a batch of requests to one origin over a
19//!   single connection, opening up to `SETTINGS_MAX_CONCURRENT_STREAMS` streams
20//!   at once, sending their bodies non-blockingly (no head-of-line stall across
21//!   streams), and demultiplexing the interleaved responses from one frame
22//!   loop. See `run_multiplexed` / `pump_pending_sends`.
23//! - ALPN is offered as `h2`. If the server does not select it, we still
24//!   attempt the HTTP/2 preface (a server that didn't agree will close us
25//!   or respond with a GOAWAY, which we surface as `BadResponse`).
26//! - HPACK encoder uses indexed references against the static AND dynamic
27//!   tables, literal-with-incremental-indexing for new headers (so repeats
28//!   collapse to one byte on subsequent requests), Huffman literal strings
29//!   when shorter than the raw form, and emits dynamic-table-size-update
30//!   signals when the peer changes `SETTINGS_HEADER_TABLE_SIZE`. Volatile
31//!   headers (cookies, authorization) are currently still added to the
32//!   dynamic table — see the §6.2.3 "never-indexed" note as future work.
33//! - HPACK decoder handles the static table, indexed-name+literal-value,
34//!   full literal, dynamic table insertions (capped, no resize signals
35//!   beyond the default 4096), and Huffman-coded literals (RFC 7541
36//!   Appendix B).
37//! - Frame I/O covers HEADERS, CONTINUATION, DATA, SETTINGS, PING,
38//!   WINDOW_UPDATE, GOAWAY, RST_STREAM. We auto-ACK SETTINGS, auto-PONG
39//!   PING; everything else on a non-target stream is ignored.
40//! - Flow control is fully implemented (RFC 9113 §5.2 / §6.9), at both the
41//!   connection and stream level, in each direction. On send we never emit
42//!   DATA past the smaller of the conn/stream send windows, blocking on the
43//!   peer's WINDOW_UPDATE frames when a body outruns the window, and we honour
44//!   `SETTINGS_INITIAL_WINDOW_SIZE` — including the §6.9.2 retroactive delta
45//!   applied to open streams when the peer changes it mid-connection. On
46//!   receive we bill inbound DATA against both windows and replenish the
47//!   peer's allowance with WINDOW_UPDATE as the client consumes it (tied to
48//!   actual consumption, not unconditional), while the `MAX_RESPONSE_BYTES`
49//!   cap still bounds total memory. Window overflow past 2^31-1 and
50//!   zero-increment WINDOW_UPDATEs are rejected per §6.9.1.
51
52use std::collections::{HashMap, VecDeque};
53use std::io::{self, Read, Write};
54use std::net::TcpStream;
55use std::sync::{Arc, Mutex, OnceLock};
56
57use crate::error::{Error, Result};
58use crate::tls::TlsStream;
59use crate::{Request, Response};
60
61// ---------------------------------------------------------------------------
62// Connection preface and frame types.
63// ---------------------------------------------------------------------------
64
65/// The 24-byte client connection preface from RFC 9113 §3.4.
66const PREFACE: &[u8] = b"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n";
67
68// Frame types (RFC 9113 §6).
69const F_DATA: u8 = 0x0;
70const F_HEADERS: u8 = 0x1;
71const F_PRIORITY: u8 = 0x2;
72const F_RST_STREAM: u8 = 0x3;
73const F_SETTINGS: u8 = 0x4;
74const F_PUSH_PROMISE: u8 = 0x5;
75const F_PING: u8 = 0x6;
76const F_GOAWAY: u8 = 0x7;
77const F_WINDOW_UPDATE: u8 = 0x8;
78const F_CONTINUATION: u8 = 0x9;
79
80// Flags.
81const FLAG_END_STREAM: u8 = 0x01;
82const FLAG_ACK: u8 = 0x01;
83const FLAG_END_HEADERS: u8 = 0x04;
84const FLAG_PADDED: u8 = 0x08;
85const FLAG_PRIORITY: u8 = 0x20;
86
87// SETTINGS parameter identifiers (RFC 9113 §6.5.2).
88const S_HEADER_TABLE_SIZE: u16 = 0x1;
89const S_ENABLE_PUSH: u16 = 0x2;
90const S_MAX_CONCURRENT_STREAMS: u16 = 0x3;
91const S_INITIAL_WINDOW_SIZE: u16 = 0x4;
92const S_MAX_FRAME_SIZE: u16 = 0x5;
93const S_MAX_HEADER_LIST_SIZE: u16 = 0x6;
94
95// RFC 9113 §6.5.2 bounds for SETTINGS validation.
96const INITIAL_WINDOW_SIZE_MAX: u32 = 0x7fff_ffff; // 2^31 - 1
97const MAX_FRAME_SIZE_MIN: u32 = 16_384; // 2^14
98const MAX_FRAME_SIZE_MAX: u32 = 16_777_215; // 2^24 - 1
99
100/// Cumulative response-body ceiling (per stream). HTTP/2 flow control
101/// auto-replenishes, so without an absolute cap a server can stream DATA
102/// frames forever and exhaust memory. Mirrors HTTP/3's `MAX_RESPONSE_BYTES`.
103const MAX_RESPONSE_BYTES: usize = 256 * 1024 * 1024;
104
105/// Cap on the aggregate size of a single (HEADERS + CONTINUATION) header
106/// block we will buffer before END_HEADERS, in *compressed* wire bytes.
107/// Bounds the CONTINUATION-flood / unbounded-header-block class
108/// (CVE-2024-27316). We don't advertise `SETTINGS_MAX_HEADER_LIST_SIZE`, so
109/// this is a sane fixed ceiling on the on-the-wire block.
110const MAX_HEADERS_BUF: usize = 256 * 1024;
111
112use crate::http::{header_octets_ok, MAX_DECODED_HEADER_LIST};
113
114// ---------------------------------------------------------------------------
115// Flood / no-progress budgets.
116//
117// The inbound frame loop (`drive_until_stream_done` / `run_multiplexed`) must
118// not be steerable into an unbounded spin or an unbounded cheap-control-frame
119// reply storm by a hostile peer. Flow control alone does not save us: an empty
120// (0-byte) DATA frame with no END_STREAM bills `consume(0)`, appends nothing
121// (so `MAX_RESPONSE_BYTES` never trips) and leaves the stream Open — the loop
122// would spin forever. SETTINGS / PING each force an ACK write+flush, and
123// RST_STREAM is the Rapid-Reset (CVE-2023-44487) primitive — all free to the
124// attacker, all unbounded without an explicit budget.
125//
126// These ceilings are deliberately far above anything a conformant server does
127// over the lifetime of a request batch, but low enough that a tight flood loop
128// trips within milliseconds. Exceeding any of them is treated as a fatal
129// protocol abuse and surfaced as `Error::BadResponse`.
130
131/// Maximum number of consecutive inbound frames that make NO forward progress
132/// before we declare the peer is spinning us and abort. "Progress" is any of:
133/// a DATA byte appended to a response body, a header block completed, or a
134/// WINDOW_UPDATE that raised a send window (see `frame_made_progress`). The
135/// counter resets to 0 on every such frame. Kills the empty-DATA spin and any
136/// other do-nothing frame loop. Generous: a legitimate server interleaves
137/// progress frames long before this.
138const MAX_NO_PROGRESS_FRAMES: u32 = 10_000;
139
140/// Maximum non-ACK SETTINGS frames we will accept on one connection. Each one
141/// costs us an ACK write+flush; a conformant server sends a handful (initial +
142/// the occasional reconfigure). Bounds the SETTINGS-flood reply storm.
143const MAX_SETTINGS_FRAMES: u32 = 2_000;
144
145/// Maximum non-ACK PING frames we will accept on one connection. Each one costs
146/// us a PONG write+flush. Bounds the PING-flood reply storm.
147const MAX_PING_FRAMES: u32 = 2_000;
148
149/// Maximum RST_STREAM frames we will accept on one connection. This is the
150/// Rapid-Reset (CVE-2023-44487) aggregate budget: even though we drive a small
151/// number of streams, an attacker controlling the server can spray RST_STREAM
152/// to churn our state. A legitimate server resets at most a few of our streams.
153const MAX_RST_STREAM_FRAMES: u32 = 2_000;
154
155/// Peer (server) SETTINGS values, with RFC 9113 defaults for any parameter
156/// the peer hasn't sent. We track all six standard parameters even if we
157/// don't yet act on each of them; future tasks will consume more.
158#[derive(Debug, Clone, PartialEq, Eq)]
159struct PeerSettings {
160    header_table_size: u32,
161    enable_push: bool,
162    max_concurrent_streams: u32,
163    initial_window_size: u32,
164    max_frame_size: u32,
165    max_header_list_size: u32,
166}
167
168impl Default for PeerSettings {
169    fn default() -> Self {
170        // Defaults from RFC 9113 §6.5.2. "No limit" parameters are represented
171        // as u32::MAX so callers can compare uniformly without special-casing.
172        PeerSettings {
173            header_table_size: 4096,
174            enable_push: true,
175            max_concurrent_streams: u32::MAX,
176            initial_window_size: 65_535,
177            max_frame_size: 16_384,
178            max_header_list_size: u32::MAX,
179        }
180    }
181}
182
183impl PeerSettings {
184    /// Apply a SETTINGS frame payload to this state, per RFC 9113 §6.5.2.
185    ///
186    /// The payload is a sequence of 6-byte entries (u16 identifier, u32 value,
187    /// both big-endian). Unknown identifiers MUST be ignored. Out-of-range
188    /// values for known identifiers are reported as `Error::BadResponse`
189    /// (the RFC distinguishes PROTOCOL_ERROR vs FLOW_CONTROL_ERROR, but this
190    /// module doesn't surface H2 error codes yet).
191    fn apply_settings_payload(&mut self, payload: &[u8]) -> Result<()> {
192        if !payload.len().is_multiple_of(6) {
193            return Err(Error::BadResponse(format!(
194                "SETTINGS payload length {} not a multiple of 6",
195                payload.len()
196            )));
197        }
198        for chunk in payload.chunks_exact(6) {
199            let id = u16::from_be_bytes([chunk[0], chunk[1]]);
200            let val = u32::from_be_bytes([chunk[2], chunk[3], chunk[4], chunk[5]]);
201            match id {
202                S_HEADER_TABLE_SIZE => self.header_table_size = val,
203                S_ENABLE_PUSH => {
204                    self.enable_push = match val {
205                        0 => false,
206                        1 => true,
207                        _ => {
208                            return Err(Error::BadResponse(format!(
209                                "SETTINGS_ENABLE_PUSH must be 0 or 1, got {val}"
210                            )));
211                        }
212                    };
213                }
214                S_MAX_CONCURRENT_STREAMS => self.max_concurrent_streams = val,
215                S_INITIAL_WINDOW_SIZE => {
216                    if val > INITIAL_WINDOW_SIZE_MAX {
217                        return Err(Error::BadResponse(format!(
218                            "SETTINGS_INITIAL_WINDOW_SIZE {val} exceeds 2^31-1 (FLOW_CONTROL_ERROR)"
219                        )));
220                    }
221                    self.initial_window_size = val;
222                }
223                S_MAX_FRAME_SIZE => {
224                    if !(MAX_FRAME_SIZE_MIN..=MAX_FRAME_SIZE_MAX).contains(&val) {
225                        return Err(Error::BadResponse(format!(
226                            "SETTINGS_MAX_FRAME_SIZE {val} out of range [16384, 16777215]"
227                        )));
228                    }
229                    self.max_frame_size = val;
230                }
231                S_MAX_HEADER_LIST_SIZE => self.max_header_list_size = val,
232                _ => {
233                    // Unknown identifiers MUST be ignored (RFC 9113 §6.5.2).
234                }
235            }
236        }
237        Ok(())
238    }
239}
240
241// ---------------------------------------------------------------------------
242// Flow control (RFC 9113 §5.2 / §6.9).
243// ---------------------------------------------------------------------------
244//
245// Both endpoints maintain a connection-level window AND a per-stream window;
246// only DATA frames consume window. Defaults are 65,535 octets (§6.9.2).
247//
248// We split these into four types because the connection-level windows live on
249// the `Connection` and the stream-level windows live on each `Stream`. Mixing
250// the two in one struct (as the single-stream code did) made the boundary
251// fuzzy; splitting it means a `Stream` can't accidentally mutate the conn
252// window and vice versa. All windows use `i64` because §6.9.2 permits a
253// stream's send window to go negative when `SETTINGS_INITIAL_WINDOW_SIZE`
254// shrinks; conn windows can't go negative but the wider type keeps the
255// arithmetic uniform.
256
257/// Hard cap on either window: RFC 9113 §6.9.1.
258const WINDOW_MAX: i64 = 0x7fff_ffff;
259
260/// The RFC 9113 §6.9.2 default flow-control window: the connection receive
261/// window starts here before we raise it with a `WINDOW_UPDATE`, and it is the
262/// per-stream default a peer assumes until our `SETTINGS_INITIAL_WINDOW_SIZE`
263/// arrives.
264const OUR_INITIAL_WINDOW: i64 = 65_535;
265
266/// Default receive window (connection and per-stream) we advertise, unless the
267/// caller overrides it via [`crate::Request::recv_window`]. The RFC default of
268/// 64 KiB caps throughput at `window / RTT` (~0.6 MB/s at 100 ms) because the
269/// peer can never have more than one window in flight; 8 MiB lifts that ceiling
270/// to ~80 MB/s at the same RTT while bounding per-stream buffering. curl/nghttp2
271/// use comparable multi-MiB defaults.
272pub(crate) const DEFAULT_RECV_WINDOW: u32 = 8 * 1024 * 1024;
273
274/// Connection-level outbound flow-control window: how many DATA bytes we are
275/// still allowed to send across *any* stream on this connection before the
276/// peer has to grant more with `WINDOW_UPDATE` on stream 0 (§6.9). The
277/// connection window is not affected by `SETTINGS_INITIAL_WINDOW_SIZE`.
278#[derive(Debug, Clone, PartialEq, Eq)]
279struct ConnSendWindow {
280    available: i64,
281}
282
283impl ConnSendWindow {
284    fn new() -> Self {
285        ConnSendWindow { available: 65_535 }
286    }
287
288    /// Apply a `WINDOW_UPDATE` for stream 0. Zero increment and overflow past
289    /// `2^31-1` are both errors (RFC 9113 §6.9.1).
290    fn apply_window_update(&mut self, increment: u32) -> Result<()> {
291        if increment == 0 {
292            return Err(Error::BadResponse(
293                "WINDOW_UPDATE with zero increment on connection (FLOW_CONTROL_ERROR)".into(),
294            ));
295        }
296        let new_val = self.available + increment as i64;
297        if new_val > WINDOW_MAX {
298            return Err(Error::BadResponse(format!(
299                "WINDOW_UPDATE pushes conn send window to {new_val} > 2^31-1 (FLOW_CONTROL_ERROR)"
300            )));
301        }
302        self.available = new_val;
303        Ok(())
304    }
305
306    /// Decrement the connection window by `n` after writing a DATA frame.
307    fn consume(&mut self, n: usize) {
308        self.available -= n as i64;
309    }
310}
311
312/// Per-stream outbound flow-control window. Each stream tracks its own budget
313/// alongside the basis we use to compute future `SETTINGS_INITIAL_WINDOW_SIZE`
314/// deltas (§6.9.2).
315#[derive(Debug, Clone, PartialEq, Eq)]
316struct StreamSendWindow {
317    available: i64,
318    /// Last applied peer `SETTINGS_INITIAL_WINDOW_SIZE` — the basis for the
319    /// next delta calculation. New streams pick this up from the
320    /// `Connection`'s current peer settings at open time.
321    initial_peer_window: i64,
322}
323
324impl StreamSendWindow {
325    fn new(initial: i64) -> Self {
326        StreamSendWindow {
327            available: initial,
328            initial_peer_window: initial,
329        }
330    }
331
332    /// Apply a `WINDOW_UPDATE` targeting this stream. Same validation as the
333    /// connection-level one (§6.9.1).
334    fn apply_window_update(&mut self, increment: u32) -> Result<()> {
335        if increment == 0 {
336            return Err(Error::BadResponse(
337                "WINDOW_UPDATE with zero increment on stream (PROTOCOL_ERROR)".into(),
338            ));
339        }
340        let new_val = self.available + increment as i64;
341        if new_val > WINDOW_MAX {
342            return Err(Error::BadResponse(format!(
343                "WINDOW_UPDATE pushes stream send window to {new_val} > 2^31-1 (FLOW_CONTROL_ERROR)"
344            )));
345        }
346        self.available = new_val;
347        Ok(())
348    }
349
350    /// Apply a `SETTINGS_INITIAL_WINDOW_SIZE` change: shift `available` by
351    /// `(new - old)` (RFC 9113 §6.9.2). Negative results are allowed; only
352    /// the upper bound is enforced.
353    fn apply_initial_window_change(&mut self, new_initial: u32) -> Result<()> {
354        let new_i = new_initial as i64;
355        let delta = new_i - self.initial_peer_window;
356        let new_available = self.available + delta;
357        if new_available > WINDOW_MAX {
358            return Err(Error::BadResponse(format!(
359                "SETTINGS_INITIAL_WINDOW_SIZE delta pushes stream send window to {new_available} > 2^31-1 (FLOW_CONTROL_ERROR)"
360            )));
361        }
362        self.available = new_available;
363        self.initial_peer_window = new_i;
364        Ok(())
365    }
366
367    fn consume(&mut self, n: usize) {
368        self.available -= n as i64;
369    }
370}
371
372/// Connection-level inbound flow-control state: bytes the peer may still send
373/// us on stream 0's behalf (the aggregate of all streams). Like the conn send
374/// window, this is unaffected by `SETTINGS_INITIAL_WINDOW_SIZE`.
375#[derive(Debug, Clone, PartialEq, Eq)]
376struct ConnRecvWindow {
377    available: i64,
378    initial: i64,
379}
380
381impl ConnRecvWindow {
382    /// `initial` is the window we advertise (RFC default raised by the
383    /// `WINDOW_UPDATE` [`Connection::new`] sends on stream 0); `replenish` tops
384    /// back up to it.
385    fn new(initial: i64) -> Self {
386        ConnRecvWindow {
387            available: initial,
388            initial,
389        }
390    }
391
392    fn consume(&mut self, n: usize) {
393        self.available -= n as i64;
394    }
395
396    /// Emit a `WINDOW_UPDATE` for stream 0 if the window has fallen below
397    /// half its initial size; returns at most one frame.
398    fn replenish(&mut self) -> Option<Frame> {
399        let threshold = self.initial / 2;
400        if self.available < threshold {
401            let inc = (self.initial - self.available) as u32;
402            self.available = self.initial;
403            Some(window_update_frame(0, inc))
404        } else {
405            None
406        }
407    }
408}
409
410/// Per-stream inbound flow-control state. Mirrors `ConnRecvWindow` but the
411/// `replenish` frame targets the specific stream id.
412#[derive(Debug, Clone, PartialEq, Eq)]
413struct StreamRecvWindow {
414    available: i64,
415    initial: i64,
416}
417
418impl StreamRecvWindow {
419    /// `initial` is the per-stream window we advertise via
420    /// `SETTINGS_INITIAL_WINDOW_SIZE`; new streams open at it.
421    fn new(initial: i64) -> Self {
422        StreamRecvWindow {
423            available: initial,
424            initial,
425        }
426    }
427
428    fn consume(&mut self, n: usize) {
429        self.available -= n as i64;
430    }
431
432    fn replenish(&mut self, stream_id: u32) -> Option<Frame> {
433        let threshold = self.initial / 2;
434        if self.available < threshold {
435            let inc = (self.initial - self.available) as u32;
436            self.available = self.initial;
437            Some(window_update_frame(stream_id, inc))
438        } else {
439            None
440        }
441    }
442}
443
444/// Build a 4-byte-payload WINDOW_UPDATE frame (RFC 9113 §6.9). Caller is
445/// responsible for ensuring `increment` is non-zero and within `2^31 - 1`.
446fn window_update_frame(stream_id: u32, increment: u32) -> Frame {
447    let mut payload = Vec::with_capacity(4);
448    payload.extend_from_slice(&(increment & 0x7fff_ffff).to_be_bytes());
449    Frame {
450        typ: F_WINDOW_UPDATE,
451        flags: 0,
452        stream_id,
453        payload,
454    }
455}
456
457/// Parse a WINDOW_UPDATE payload: 4 bytes, high bit reserved, low 31 bits are
458/// the increment. Returns the increment as a `u32`. Length errors are mapped
459/// to `BadResponse` here (RFC calls them FRAME_SIZE_ERROR).
460fn parse_window_update(payload: &[u8]) -> Result<u32> {
461    if payload.len() != 4 {
462        return Err(Error::BadResponse(format!(
463            "WINDOW_UPDATE payload length {} (expected 4) (FRAME_SIZE_ERROR)",
464            payload.len()
465        )));
466    }
467    let raw = u32::from_be_bytes([payload[0], payload[1], payload[2], payload[3]]);
468    Ok(raw & 0x7fff_ffff)
469}
470
471/// One HTTP/2 frame on the wire.
472#[derive(Debug, Clone, PartialEq, Eq)]
473struct Frame {
474    typ: u8,
475    flags: u8,
476    stream_id: u32,
477    payload: Vec<u8>,
478}
479
480const MAX_FRAME_PAYLOAD: usize = 1 << 20; // 1 MiB hard cap, plenty for our use.
481
482fn read_exact<R: Read>(r: &mut R, buf: &mut [u8]) -> io::Result<()> {
483    r.read_exact(buf)
484}
485
486fn read_frame<R: Read>(r: &mut R) -> io::Result<Frame> {
487    let mut hdr = [0u8; 9];
488    read_exact(r, &mut hdr)?;
489    let length = ((hdr[0] as usize) << 16) | ((hdr[1] as usize) << 8) | (hdr[2] as usize);
490    let typ = hdr[3];
491    let flags = hdr[4];
492    let stream_id = (((hdr[5] & 0x7f) as u32) << 24)
493        | ((hdr[6] as u32) << 16)
494        | ((hdr[7] as u32) << 8)
495        | (hdr[8] as u32);
496    if length > MAX_FRAME_PAYLOAD {
497        return Err(io::Error::new(
498            io::ErrorKind::InvalidData,
499            format!("frame payload too large: {length}"),
500        ));
501    }
502    let mut payload = vec![0u8; length];
503    if length > 0 {
504        read_exact(r, &mut payload)?;
505    }
506    Ok(Frame {
507        typ,
508        flags,
509        stream_id,
510        payload,
511    })
512}
513
514fn write_frame<W: Write>(w: &mut W, f: &Frame) -> io::Result<()> {
515    if f.payload.len() > MAX_FRAME_PAYLOAD {
516        return Err(io::Error::new(
517            io::ErrorKind::InvalidInput,
518            "frame payload too large",
519        ));
520    }
521    let len = f.payload.len();
522    let hdr = [
523        ((len >> 16) & 0xff) as u8,
524        ((len >> 8) & 0xff) as u8,
525        (len & 0xff) as u8,
526        f.typ,
527        f.flags,
528        ((f.stream_id >> 24) & 0x7f) as u8, // R bit clear.
529        ((f.stream_id >> 16) & 0xff) as u8,
530        ((f.stream_id >> 8) & 0xff) as u8,
531        (f.stream_id & 0xff) as u8,
532    ];
533    w.write_all(&hdr)?;
534    if !f.payload.is_empty() {
535        w.write_all(&f.payload)?;
536    }
537    Ok(())
538}
539
540/// Map a request [`Priority`](crate::http::Priority) to the PRIORITY frame's
541/// 1-byte weight field (on-wire weight = value + 1). `Normal` returns `None`,
542/// meaning "send no PRIORITY frame" — the peer uses the default weight (16).
543fn priority_weight_byte(priority: crate::http::Priority) -> Option<u8> {
544    match priority {
545        crate::http::Priority::High => Some(255), // weight 256 (max)
546        crate::http::Priority::Normal => None,    // default weight 16
547        crate::http::Priority::Low => Some(0),    // weight 1 (min)
548    }
549}
550
551// ---------------------------------------------------------------------------
552// HPACK integer codec (RFC 7541 §5.1).
553// ---------------------------------------------------------------------------
554
555/// Encode an integer with `prefix_bits` of the first byte available.
556/// The non-integer high bits of the first byte are left as zero; the caller
557/// OR's its flag bits in afterwards.
558fn encode_int(value: u64, prefix_bits: u8) -> Vec<u8> {
559    let max_prefix: u64 = (1u64 << prefix_bits) - 1;
560    let mut out = Vec::new();
561    if value < max_prefix {
562        out.push(value as u8);
563        return out;
564    }
565    out.push(max_prefix as u8);
566    let mut rem = value - max_prefix;
567    while rem >= 128 {
568        out.push(((rem & 0x7f) as u8) | 0x80);
569        rem >>= 7;
570    }
571    out.push(rem as u8);
572    out
573}
574
575/// Decode an HPACK integer starting at `buf[0]`. Returns `(value, bytes_consumed)`.
576fn decode_int(buf: &[u8], prefix_bits: u8) -> Result<(u64, usize)> {
577    if buf.is_empty() {
578        return Err(Error::BadResponse("hpack: empty integer".into()));
579    }
580    let max_prefix: u64 = (1u64 << prefix_bits) - 1;
581    let mut value = (buf[0] as u64) & max_prefix;
582    if value < max_prefix {
583        return Ok((value, 1));
584    }
585    let mut i = 1usize;
586    let mut shift = 0u32;
587    loop {
588        if i >= buf.len() {
589            return Err(Error::BadResponse("hpack: truncated integer".into()));
590        }
591        let b = buf[i];
592        i += 1;
593        value = value
594            .checked_add(((b & 0x7f) as u64) << shift)
595            .ok_or_else(|| Error::BadResponse("hpack: integer overflow".into()))?;
596        if b & 0x80 == 0 {
597            return Ok((value, i));
598        }
599        shift += 7;
600        if shift > 63 {
601            return Err(Error::BadResponse("hpack: integer overflow".into()));
602        }
603    }
604}
605
606// ---------------------------------------------------------------------------
607// HPACK static table (RFC 7541 Appendix A).
608// ---------------------------------------------------------------------------
609
610/// Indexed by `index - 1`. Each entry is (name, value).
611const STATIC_TABLE: &[(&str, &str)] = &[
612    (":authority", ""),                   // 1
613    (":method", "GET"),                   // 2
614    (":method", "POST"),                  // 3
615    (":path", "/"),                       // 4
616    (":path", "/index.html"),             // 5
617    (":scheme", "http"),                  // 6
618    (":scheme", "https"),                 // 7
619    (":status", "200"),                   // 8
620    (":status", "204"),                   // 9
621    (":status", "206"),                   // 10
622    (":status", "304"),                   // 11
623    (":status", "400"),                   // 12
624    (":status", "404"),                   // 13
625    (":status", "500"),                   // 14
626    ("accept-charset", ""),               // 15
627    ("accept-encoding", "gzip, deflate"), // 16
628    ("accept-language", ""),              // 17
629    ("accept-ranges", ""),                // 18
630    ("accept", ""),                       // 19
631    ("access-control-allow-origin", ""),  // 20
632    ("age", ""),                          // 21
633    ("allow", ""),                        // 22
634    ("authorization", ""),                // 23
635    ("cache-control", ""),                // 24
636    ("content-disposition", ""),          // 25
637    ("content-encoding", ""),             // 26
638    ("content-language", ""),             // 27
639    ("content-length", ""),               // 28
640    ("content-location", ""),             // 29
641    ("content-range", ""),                // 30
642    ("content-type", ""),                 // 31
643    ("cookie", ""),                       // 32
644    ("date", ""),                         // 33
645    ("etag", ""),                         // 34
646    ("expect", ""),                       // 35
647    ("expires", ""),                      // 36
648    ("from", ""),                         // 37
649    ("host", ""),                         // 38
650    ("if-match", ""),                     // 39
651    ("if-modified-since", ""),            // 40
652    ("if-none-match", ""),                // 41
653    ("if-range", ""),                     // 42
654    ("if-unmodified-since", ""),          // 43
655    ("last-modified", ""),                // 44
656    ("link", ""),                         // 45
657    ("location", ""),                     // 46
658    ("max-forwards", ""),                 // 47
659    ("proxy-authenticate", ""),           // 48
660    ("proxy-authorization", ""),          // 49
661    ("range", ""),                        // 50
662    ("referer", ""),                      // 51
663    ("refresh", ""),                      // 52
664    ("retry-after", ""),                  // 53
665    ("server", ""),                       // 54
666    ("set-cookie", ""),                   // 55
667    ("strict-transport-security", ""),    // 56
668    ("transfer-encoding", ""),            // 57
669    ("user-agent", ""),                   // 58
670    ("vary", ""),                         // 59
671    ("via", ""),                          // 60
672    ("www-authenticate", ""),             // 61
673];
674
675/// 1-based index of a (name, value) pair, if present in the static table.
676fn static_full_index(name: &str, value: &str) -> Option<usize> {
677    STATIC_TABLE
678        .iter()
679        .position(|(n, v)| *n == name && *v == value)
680        .map(|i| i + 1)
681}
682
683/// 1-based index of the first static-table entry with this name, if any.
684fn static_name_index(name: &str) -> Option<usize> {
685    STATIC_TABLE
686        .iter()
687        .position(|(n, _)| *n == name)
688        .map(|i| i + 1)
689}
690
691// ---------------------------------------------------------------------------
692// HPACK Huffman decoder (RFC 7541 Appendix B).
693// ---------------------------------------------------------------------------
694//
695// Stored as a flat (code, bit_len) table indexed by symbol (0..=255), plus a
696// pseudo entry 256 for the EOS marker. Decoding walks the bitstream
697// symbol-by-symbol against a sorted lookup; we use a simple bit-by-bit walk
698// over a precomputed `Vec<(code, len)>` rather than a tree, trading memory
699// for code size. With only 257 symbols and at most 30 bits, total work per
700// decode byte is bounded.
701
702/// `(code, bit_length)` for each Huffman symbol, from RFC 7541 Appendix B.
703const HUFFMAN: [(u32, u8); 257] = [
704    (0x1ff8, 13),
705    (0x7fffd8, 23),
706    (0xfffffe2, 28),
707    (0xfffffe3, 28),
708    (0xfffffe4, 28),
709    (0xfffffe5, 28),
710    (0xfffffe6, 28),
711    (0xfffffe7, 28),
712    (0xfffffe8, 28),
713    (0xffffea, 24),
714    (0x3ffffffc, 30),
715    (0xfffffe9, 28),
716    (0xfffffea, 28),
717    (0x3ffffffd, 30),
718    (0xfffffeb, 28),
719    (0xfffffec, 28),
720    (0xfffffed, 28),
721    (0xfffffee, 28),
722    (0xfffffef, 28),
723    (0xffffff0, 28),
724    (0xffffff1, 28),
725    (0xffffff2, 28),
726    (0x3ffffffe, 30),
727    (0xffffff3, 28),
728    (0xffffff4, 28),
729    (0xffffff5, 28),
730    (0xffffff6, 28),
731    (0xffffff7, 28),
732    (0xffffff8, 28),
733    (0xffffff9, 28),
734    (0xffffffa, 28),
735    (0xffffffb, 28),
736    (0x14, 6),
737    (0x3f8, 10),
738    (0x3f9, 10),
739    (0xffa, 12),
740    (0x1ff9, 13),
741    (0x15, 6),
742    (0xf8, 8),
743    (0x7fa, 11),
744    (0x3fa, 10),
745    (0x3fb, 10),
746    (0xf9, 8),
747    (0x7fb, 11),
748    (0xfa, 8),
749    (0x16, 6),
750    (0x17, 6),
751    (0x18, 6),
752    (0x0, 5),
753    (0x1, 5),
754    (0x2, 5),
755    (0x19, 6),
756    (0x1a, 6),
757    (0x1b, 6),
758    (0x1c, 6),
759    (0x1d, 6),
760    (0x1e, 6),
761    (0x1f, 6),
762    (0x5c, 7),
763    (0xfb, 8),
764    (0x7ffc, 15),
765    (0x20, 6),
766    (0xffb, 12),
767    (0x3fc, 10),
768    (0x1ffa, 13),
769    (0x21, 6),
770    (0x5d, 7),
771    (0x5e, 7),
772    (0x5f, 7),
773    (0x60, 7),
774    (0x61, 7),
775    (0x62, 7),
776    (0x63, 7),
777    (0x64, 7),
778    (0x65, 7),
779    (0x66, 7),
780    (0x67, 7),
781    (0x68, 7),
782    (0x69, 7),
783    (0x6a, 7),
784    (0x6b, 7),
785    (0x6c, 7),
786    (0x6d, 7),
787    (0x6e, 7),
788    (0x6f, 7),
789    (0x70, 7),
790    (0x71, 7),
791    (0x72, 7),
792    (0xfc, 8),
793    (0x73, 7),
794    (0xfd, 8),
795    (0x1ffb, 13),
796    (0x7fff0, 19),
797    (0x1ffc, 13),
798    (0x3ffc, 14),
799    (0x22, 6),
800    (0x7ffd, 15),
801    (0x3, 5),
802    (0x23, 6),
803    (0x4, 5),
804    (0x24, 6),
805    (0x5, 5),
806    (0x25, 6),
807    (0x26, 6),
808    (0x27, 6),
809    (0x6, 5),
810    (0x74, 7),
811    (0x75, 7),
812    (0x28, 6),
813    (0x29, 6),
814    (0x2a, 6),
815    (0x7, 5),
816    (0x2b, 6),
817    (0x76, 7),
818    (0x2c, 6),
819    (0x8, 5),
820    (0x9, 5),
821    (0x2d, 6),
822    (0x77, 7),
823    (0x78, 7),
824    (0x79, 7),
825    (0x7a, 7),
826    (0x7b, 7),
827    (0x7ffe, 15),
828    (0x7fc, 11),
829    (0x3ffd, 14),
830    (0x1ffd, 13),
831    (0xffffffc, 28),
832    (0xfffe6, 20),
833    (0x3fffd2, 22),
834    (0xfffe7, 20),
835    (0xfffe8, 20),
836    (0x3fffd3, 22),
837    (0x3fffd4, 22),
838    (0x3fffd5, 22),
839    (0x7fffd9, 23),
840    (0x3fffd6, 22),
841    (0x7fffda, 23),
842    (0x7fffdb, 23),
843    (0x7fffdc, 23),
844    (0x7fffdd, 23),
845    (0x7fffde, 23),
846    (0xffffeb, 24),
847    (0x7fffdf, 23),
848    (0xffffec, 24),
849    (0xffffed, 24),
850    (0x3fffd7, 22),
851    (0x7fffe0, 23),
852    (0xffffee, 24),
853    (0x7fffe1, 23),
854    (0x7fffe2, 23),
855    (0x7fffe3, 23),
856    (0x7fffe4, 23),
857    (0x1fffdc, 21),
858    (0x3fffd8, 22),
859    (0x7fffe5, 23),
860    (0x3fffd9, 22),
861    (0x7fffe6, 23),
862    (0x7fffe7, 23),
863    (0xffffef, 24),
864    (0x3fffda, 22),
865    (0x1fffdd, 21),
866    (0xfffe9, 20),
867    (0x3fffdb, 22),
868    (0x3fffdc, 22),
869    (0x7fffe8, 23),
870    (0x7fffe9, 23),
871    (0x1fffde, 21),
872    (0x7fffea, 23),
873    (0x3fffdd, 22),
874    (0x3fffde, 22),
875    (0xfffff0, 24),
876    (0x1fffdf, 21),
877    (0x3fffdf, 22),
878    (0x7fffeb, 23),
879    (0x7fffec, 23),
880    (0x1fffe0, 21),
881    (0x1fffe1, 21),
882    (0x3fffe0, 22),
883    (0x1fffe2, 21),
884    (0x7fffed, 23),
885    (0x3fffe1, 22),
886    (0x7fffee, 23),
887    (0x7fffef, 23),
888    (0xfffea, 20),
889    (0x3fffe2, 22),
890    (0x3fffe3, 22),
891    (0x3fffe4, 22),
892    (0x7ffff0, 23),
893    (0x3fffe5, 22),
894    (0x3fffe6, 22),
895    (0x7ffff1, 23),
896    (0x3ffffe0, 26),
897    (0x3ffffe1, 26),
898    (0xfffeb, 20),
899    (0x7fff1, 19),
900    (0x3fffe7, 22),
901    (0x7ffff2, 23),
902    (0x3fffe8, 22),
903    (0x1ffffec, 25),
904    (0x3ffffe2, 26),
905    (0x3ffffe3, 26),
906    (0x3ffffe4, 26),
907    (0x7ffffde, 27),
908    (0x7ffffdf, 27),
909    (0x3ffffe5, 26),
910    (0xfffff1, 24),
911    (0x1ffffed, 25),
912    (0x7fff2, 19),
913    (0x1fffe3, 21),
914    (0x3ffffe6, 26),
915    (0x7ffffe0, 27),
916    (0x7ffffe1, 27),
917    (0x3ffffe7, 26),
918    (0x7ffffe2, 27),
919    (0xfffff2, 24),
920    (0x1fffe4, 21),
921    (0x1fffe5, 21),
922    (0x3ffffe8, 26),
923    (0x3ffffe9, 26),
924    (0xffffffd, 28),
925    (0x7ffffe3, 27),
926    (0x7ffffe4, 27),
927    (0x7ffffe5, 27),
928    (0xfffec, 20),
929    (0xfffff3, 24),
930    (0xfffed, 20),
931    (0x1fffe6, 21),
932    (0x3fffe9, 22),
933    (0x1fffe7, 21),
934    (0x1fffe8, 21),
935    (0x7ffff3, 23),
936    (0x3fffea, 22),
937    (0x3fffeb, 22),
938    (0x1ffffee, 25),
939    (0x1ffffef, 25),
940    (0xfffff4, 24),
941    (0xfffff5, 24),
942    (0x3ffffea, 26),
943    (0x7ffff4, 23),
944    (0x3ffffeb, 26),
945    (0x7ffffe6, 27),
946    (0x3ffffec, 26),
947    (0x3ffffed, 26),
948    (0x7ffffe7, 27),
949    (0x7ffffe8, 27),
950    (0x7ffffe9, 27),
951    (0x7ffffea, 27),
952    (0x7ffffeb, 27),
953    (0xffffffe, 28),
954    (0x7ffffec, 27),
955    (0x7ffffed, 27),
956    (0x7ffffee, 27),
957    (0x7ffffef, 27),
958    (0x7fffff0, 27),
959    (0x3ffffee, 26),
960    (0x3fffffff, 30), // EOS, index 256
961];
962
963/// Decode a Huffman-coded literal. We walk bit-by-bit over the input, OR each
964/// bit into an accumulator, and check after every bit whether the accumulator
965/// (left-aligned for that length) matches any code of that length. With 257
966/// symbols this is small enough to scan linearly.
967fn huffman_decode(input: &[u8]) -> Result<Vec<u8>> {
968    let mut out = Vec::with_capacity(input.len().saturating_mul(2));
969    let mut acc: u64 = 0;
970    let mut acc_len: u8 = 0;
971
972    for &byte in input {
973        acc = (acc << 8) | (byte as u64);
974        acc_len += 8;
975        // Pull as many symbols as possible from the accumulator.
976        while acc_len >= 5 {
977            let mut matched = false;
978            // Try lengths 5..=30 (no symbol shorter than 5 bits in the table).
979            let max_len = acc_len.min(30);
980            for try_len in 5..=max_len {
981                let code = (acc >> (acc_len - try_len)) & ((1u64 << try_len) - 1);
982                // Linear scan: small table, predictable performance.
983                if let Some(sym) = lookup_huffman(code as u32, try_len) {
984                    if sym == 256 {
985                        // EOS in a literal is a decoder error per RFC 7541 §5.2.
986                        return Err(Error::BadResponse(
987                            "hpack: EOS symbol in Huffman literal".into(),
988                        ));
989                    }
990                    out.push(sym as u8);
991                    acc_len -= try_len;
992                    matched = true;
993                    break;
994                }
995            }
996            if !matched {
997                break;
998            }
999        }
1000    }
1001
1002    // Tail: remaining bits must be the most-significant bits of the EOS code
1003    // (all-ones), and there must be fewer than 8 of them (RFC 7541 §5.2).
1004    if acc_len >= 8 {
1005        return Err(Error::BadResponse(
1006            "hpack: trailing Huffman bits >= 8".into(),
1007        ));
1008    }
1009    if acc_len > 0 {
1010        let pad_mask = (1u64 << acc_len) - 1;
1011        let tail = acc & pad_mask;
1012        if tail != pad_mask {
1013            return Err(Error::BadResponse("hpack: bad Huffman padding".into()));
1014        }
1015    }
1016    Ok(out)
1017}
1018
1019fn lookup_huffman(code: u32, len: u8) -> Option<u16> {
1020    for (i, (c, l)) in HUFFMAN.iter().enumerate() {
1021        if *l == len && *c == code {
1022            return Some(i as u16);
1023        }
1024    }
1025    None
1026}
1027
1028// ---------------------------------------------------------------------------
1029// HPACK encoder / decoder.
1030// ---------------------------------------------------------------------------
1031
1032/// Default and maximum dynamic-table size we accept from the server. RFC 7541
1033/// default is 4096 bytes; we never SETTINGS_HEADER_TABLE_SIZE up from that.
1034const DYN_TABLE_CAP: usize = 4096;
1035
1036/// HPACK decoder state. The dynamic table is FIFO: newest entries pushed at
1037/// the front (index 62 in the combined table), oldest evicted from the back.
1038struct Decoder {
1039    dyn_table: Vec<(String, String)>,
1040    dyn_table_size: usize,
1041    dyn_table_cap: usize,
1042}
1043
1044impl Decoder {
1045    fn new() -> Self {
1046        Decoder {
1047            dyn_table: Vec::new(),
1048            dyn_table_size: 0,
1049            dyn_table_cap: DYN_TABLE_CAP,
1050        }
1051    }
1052
1053    fn entry_size(name: &str, value: &str) -> usize {
1054        name.len() + value.len() + 32
1055    }
1056
1057    fn evict_to_fit(&mut self, incoming: usize) {
1058        while self.dyn_table_size + incoming > self.dyn_table_cap && !self.dyn_table.is_empty() {
1059            let (n, v) = self.dyn_table.pop().unwrap();
1060            self.dyn_table_size = self.dyn_table_size.saturating_sub(Self::entry_size(&n, &v));
1061        }
1062    }
1063
1064    fn insert(&mut self, name: String, value: String) {
1065        let sz = Self::entry_size(&name, &value);
1066        if sz > self.dyn_table_cap {
1067            // Larger than the whole table: clear, do not insert (RFC 7541 §4.4).
1068            self.dyn_table.clear();
1069            self.dyn_table_size = 0;
1070            return;
1071        }
1072        self.evict_to_fit(sz);
1073        self.dyn_table.insert(0, (name, value));
1074        self.dyn_table_size += sz;
1075    }
1076
1077    fn lookup(&self, index: u64) -> Result<(String, String)> {
1078        if index == 0 {
1079            return Err(Error::BadResponse("hpack: index 0".into()));
1080        }
1081        let idx = index as usize;
1082        if idx <= STATIC_TABLE.len() {
1083            let (n, v) = STATIC_TABLE[idx - 1];
1084            return Ok((n.to_string(), v.to_string()));
1085        }
1086        let dyn_idx = idx - STATIC_TABLE.len() - 1;
1087        if dyn_idx >= self.dyn_table.len() {
1088            return Err(Error::BadResponse(format!(
1089                "hpack: index {idx} out of range"
1090            )));
1091        }
1092        let (n, v) = &self.dyn_table[dyn_idx];
1093        Ok((n.clone(), v.clone()))
1094    }
1095
1096    fn lookup_name(&self, index: u64) -> Result<String> {
1097        Ok(self.lookup(index)?.0)
1098    }
1099
1100    /// Decode a length-prefixed string literal: 1 bit Huffman flag, 7-bit
1101    /// integer length prefix, then `length` bytes.
1102    fn read_string(&self, buf: &[u8], pos: &mut usize) -> Result<String> {
1103        if *pos >= buf.len() {
1104            return Err(Error::BadResponse("hpack: truncated string".into()));
1105        }
1106        let huffman = buf[*pos] & 0x80 != 0;
1107        let (len, consumed) = decode_int(&buf[*pos..], 7)?;
1108        *pos += consumed;
1109        let end = pos
1110            .checked_add(len as usize)
1111            .ok_or_else(|| Error::BadResponse("hpack: string length overflow".into()))?;
1112        if end > buf.len() {
1113            return Err(Error::BadResponse("hpack: truncated string body".into()));
1114        }
1115        let raw = &buf[*pos..end];
1116        *pos = end;
1117        if huffman {
1118            let bytes = huffman_decode(raw)?;
1119            String::from_utf8(bytes)
1120                .map_err(|_| Error::BadResponse("hpack: non-utf8 Huffman literal".into()))
1121        } else {
1122            String::from_utf8(raw.to_vec())
1123                .map_err(|_| Error::BadResponse("hpack: non-utf8 literal".into()))
1124        }
1125    }
1126
1127    fn decode_block(&mut self, buf: &[u8]) -> Result<Vec<(String, String)>> {
1128        let mut out = Vec::new();
1129        let mut pos = 0;
1130        // Running total of the decoded header-list size (RFC 7541 §4.1:
1131        // `name.len() + value.len() + 32` per entry). Bounds a decompression
1132        // bomb where a small compressed block expands into a huge list.
1133        let mut list_size: usize = 0;
1134        while pos < buf.len() {
1135            let b = buf[pos];
1136            let entry: (String, String);
1137            if b & 0x80 != 0 {
1138                // Indexed header field (RFC 7541 §6.1).
1139                let (idx, n) = decode_int(&buf[pos..], 7)?;
1140                pos += n;
1141                entry = self.lookup(idx)?;
1142            } else if b & 0x40 != 0 {
1143                // Literal header field with incremental indexing (§6.2.1).
1144                let (idx, n) = decode_int(&buf[pos..], 6)?;
1145                pos += n;
1146                let name = if idx == 0 {
1147                    self.read_string(buf, &mut pos)?
1148                } else {
1149                    self.lookup_name(idx)?
1150                };
1151                let value = self.read_string(buf, &mut pos)?;
1152                self.insert(name.clone(), value.clone());
1153                entry = (name, value);
1154            } else if b & 0x20 != 0 {
1155                // Dynamic table size update (§6.3).
1156                let (new_size, n) = decode_int(&buf[pos..], 5)?;
1157                pos += n;
1158                let cap = (new_size as usize).min(DYN_TABLE_CAP);
1159                self.dyn_table_cap = cap;
1160                self.evict_to_fit(0);
1161                continue;
1162            } else {
1163                // Literal w/o indexing (b & 0x10 == 0) or never-indexed
1164                // (b & 0x10 != 0). Both use a 4-bit prefix; we treat them
1165                // the same on decode (we never re-emit headers, so the
1166                // privacy hint is moot for our caller).
1167                let (idx, n) = decode_int(&buf[pos..], 4)?;
1168                pos += n;
1169                let name = if idx == 0 {
1170                    self.read_string(buf, &mut pos)?
1171                } else {
1172                    self.lookup_name(idx)?
1173                };
1174                let value = self.read_string(buf, &mut pos)?;
1175                entry = (name, value);
1176            }
1177            // Reject malformed/forbidden octets in field names and values
1178            // (RFC 9113 §8.2.1). This covers every code path — indexed,
1179            // literal, and table-sourced — so a malicious peer can't smuggle
1180            // CR/LF/NUL or non-token name bytes through to a re-serializing
1181            // consumer (header/response splitting, trace corruption).
1182            if !header_octets_ok(entry.0.as_bytes(), entry.1.as_bytes()) {
1183                return Err(Error::BadResponse(
1184                    "hpack: forbidden octet in decoded header".into(),
1185                ));
1186            }
1187            list_size = list_size
1188                .saturating_add(entry.0.len())
1189                .saturating_add(entry.1.len())
1190                .saturating_add(32);
1191            if list_size > MAX_DECODED_HEADER_LIST {
1192                return Err(Error::BadResponse(
1193                    "hpack: decoded header list exceeds limit".into(),
1194                ));
1195            }
1196            out.push(entry);
1197        }
1198        Ok(out)
1199    }
1200}
1201
1202/// Huffman-encode `input` per RFC 7541 §5.2: concatenate the per-symbol
1203/// codes (MSB-first within each code), then pad the trailing partial byte
1204/// with the high bits of the EOS code (all-ones).
1205fn huffman_encode(input: &[u8]) -> Vec<u8> {
1206    // Total bit length first so we can size the output exactly.
1207    let total_bits: usize = input.iter().map(|b| HUFFMAN[*b as usize].1 as usize).sum();
1208    let out_len = total_bits.div_ceil(8);
1209    let mut out = vec![0u8; out_len];
1210
1211    // Shift each symbol's `bit_len`-bit code into the buffer MSB-first. We
1212    // keep a bit cursor (`bit_pos`) tracking the next free bit position
1213    // measured from the MSB of byte 0.
1214    let mut bit_pos: usize = 0;
1215    for &b in input {
1216        let (code, len) = HUFFMAN[b as usize];
1217        let len = len as usize;
1218        // Place the code so its MSB lands at `bit_pos`.
1219        let mut remaining = len;
1220        let mut code_left = code as u64;
1221        while remaining > 0 {
1222            let byte_index = bit_pos / 8;
1223            let bit_in_byte = bit_pos % 8; // 0 = MSB.
1224            let space_in_byte = 8 - bit_in_byte;
1225            let take = remaining.min(space_in_byte);
1226            // Top `take` bits of the still-unwritten part of the code.
1227            let shift = (remaining - take) as u32;
1228            let chunk = ((code_left >> shift) & ((1u64 << take) - 1)) as u8;
1229            // Place those bits into the byte, left-justified within the
1230            // remaining space.
1231            out[byte_index] |= chunk << (space_in_byte - take);
1232            // Mask the bits we just wrote out of `code_left`.
1233            if shift > 0 {
1234                code_left &= (1u64 << shift) - 1;
1235            } else {
1236                code_left = 0;
1237            }
1238            remaining -= take;
1239            bit_pos += take;
1240        }
1241    }
1242
1243    // Pad trailing bits (if any) with 1s — the most-significant bits of the
1244    // EOS code (`0x3fffffff`, 30 bits, top bits all 1).
1245    let trailing = (8 - (total_bits % 8)) % 8;
1246    if trailing > 0 {
1247        let last = out.len() - 1;
1248        out[last] |= (1u8 << trailing) - 1;
1249    }
1250    out
1251}
1252
1253/// Encode one length-prefixed string literal (RFC 7541 §5.2). The high bit
1254/// of the length-prefix byte is the Huffman flag: 1 = Huffman, 0 = raw.
1255/// We pick whichever encoding is shorter on the wire (ties go to raw,
1256/// which slightly favours decoder speed and avoids a needless Huffman pass).
1257fn encode_literal_string(out: &mut Vec<u8>, s: &str) {
1258    let raw = s.as_bytes();
1259    let huff = huffman_encode(raw);
1260    if huff.len() < raw.len() {
1261        let mut len_bytes = encode_int(huff.len() as u64, 7);
1262        len_bytes[0] |= 0x80; // Huffman bit set.
1263        out.extend_from_slice(&len_bytes);
1264        out.extend_from_slice(&huff);
1265    } else {
1266        let mut len_bytes = encode_int(raw.len() as u64, 7);
1267        len_bytes[0] &= 0x7f; // Huffman bit cleared.
1268        out.extend_from_slice(&len_bytes);
1269        out.extend_from_slice(raw);
1270    }
1271}
1272
1273// ---------------------------------------------------------------------------
1274// HPACK encoder with dynamic-table insertion (RFC 7541 §6.2.1 / §6.3).
1275// ---------------------------------------------------------------------------
1276//
1277// The encoder mirrors the decoder's dynamic table: every header we emit with
1278// "incremental indexing" (§6.2.1) MUST be appended to our local dynamic
1279// table because the receiver will do the same on its side. The two tables
1280// must stay byte-for-byte identical or the indices we emit on the next
1281// header block will misreference entries on the receiver.
1282//
1283// Indexing policy: we always use incremental indexing for headers not
1284// already in either table. This maximises compression on repeated headers
1285// across requests on the same connection (e.g. cookies, user-agent, etc.)
1286// FUTURE: per RFC 7541 §7.1.3, secrets like `cookie` and `authorization`
1287// SHOULD use the "never-indexed" representation (§6.2.3) to avoid leaking
1288// via compression-side-channel attacks. We don't do that today — see the
1289// out-of-scope note in the module-level docs.
1290
1291/// Per-connection HPACK encoder. The dynamic table here mirrors what we
1292/// tell the peer to insert; the newest entry sits at `dyn_table[0]` and
1293/// corresponds to HPACK index `STATIC_TABLE.len() + 1` (62 today).
1294struct Encoder {
1295    dyn_table: VecDeque<(String, String)>,
1296    dyn_table_size: usize,
1297    max_dyn_table_size: usize,
1298    /// Pending "Dynamic Table Size Update" signal (§6.3). Set whenever the
1299    /// peer changes `SETTINGS_HEADER_TABLE_SIZE`; consumed (and cleared)
1300    /// at the head of the next header block we emit. The signal MUST
1301    /// precede any header field representation in that block.
1302    pending_max_table_size_signal: Option<usize>,
1303}
1304
1305impl Encoder {
1306    fn new() -> Self {
1307        Encoder {
1308            dyn_table: VecDeque::new(),
1309            dyn_table_size: 0,
1310            max_dyn_table_size: DYN_TABLE_CAP,
1311            pending_max_table_size_signal: None,
1312        }
1313    }
1314
1315    fn entry_size(name: &str, value: &str) -> usize {
1316        name.len() + value.len() + 32
1317    }
1318
1319    /// Apply a new `SETTINGS_HEADER_TABLE_SIZE` cap from the peer. The
1320    /// encoder MUST emit a §6.3 size-update signal in the next header
1321    /// block to acknowledge the change, and MUST evict entries
1322    /// immediately so the table never exceeds the new cap.
1323    fn set_peer_max_table_size(&mut self, n: usize) {
1324        self.max_dyn_table_size = n;
1325        self.evict_to_fit(0);
1326        self.pending_max_table_size_signal = Some(n);
1327    }
1328
1329    fn evict_to_fit(&mut self, incoming: usize) {
1330        while self.dyn_table_size + incoming > self.max_dyn_table_size && !self.dyn_table.is_empty()
1331        {
1332            // Oldest entry lives at the back of the deque.
1333            let (n, v) = self.dyn_table.pop_back().unwrap();
1334            self.dyn_table_size = self.dyn_table_size.saturating_sub(Self::entry_size(&n, &v));
1335        }
1336    }
1337
1338    fn insert(&mut self, name: &str, value: &str) {
1339        let sz = Self::entry_size(name, value);
1340        if sz > self.max_dyn_table_size {
1341            // Entry larger than the entire table: clear and skip (§4.4).
1342            self.dyn_table.clear();
1343            self.dyn_table_size = 0;
1344            return;
1345        }
1346        self.evict_to_fit(sz);
1347        self.dyn_table
1348            .push_front((name.to_string(), value.to_string()));
1349        self.dyn_table_size += sz;
1350    }
1351
1352    /// 1-based combined HPACK index for an exact (name, value) match.
1353    /// Checks the static table first (indices 1..=61), then the dynamic
1354    /// table (index 62 = newest, the front of the deque).
1355    fn combined_full_index(&self, name: &str, value: &str) -> Option<u32> {
1356        if let Some(i) = static_full_index(name, value) {
1357            return Some(i as u32);
1358        }
1359        for (i, (n, v)) in self.dyn_table.iter().enumerate() {
1360            if n == name && v == value {
1361                return Some((STATIC_TABLE.len() + 1 + i) as u32);
1362            }
1363        }
1364        None
1365    }
1366
1367    /// 1-based combined HPACK index for the first entry matching `name`.
1368    fn combined_name_index(&self, name: &str) -> Option<u32> {
1369        if let Some(i) = static_name_index(name) {
1370            return Some(i as u32);
1371        }
1372        for (i, (n, _)) in self.dyn_table.iter().enumerate() {
1373            if n == name {
1374                return Some((STATIC_TABLE.len() + 1 + i) as u32);
1375            }
1376        }
1377        None
1378    }
1379
1380    /// Encode one header field. Names MUST already be lowercased by the
1381    /// caller (RFC 9113 §8.2.1). On the wire we emit, in order:
1382    ///
1383    /// 1. Any pending §6.3 dynamic-table-size-update signal.
1384    /// 2. If both name and value are in the combined table: an indexed
1385    ///    field representation (high bit 1, §6.1).
1386    /// 3. Else if the name alone is in the combined table: a literal
1387    ///    with incremental indexing + indexed name (`01xxxxxx`, 6-bit
1388    ///    name index, §6.2.1). The entry is inserted into our table.
1389    /// 4. Else: literal with incremental indexing + literal name
1390    ///    (`0x40` marker, §6.2.1). The entry is inserted into our table.
1391    fn encode_header(&mut self, out: &mut Vec<u8>, name: &str, value: &str) {
1392        // (1) Pending size-update signal: 5-bit prefix, top three bits `001`.
1393        if let Some(n) = self.pending_max_table_size_signal.take() {
1394            let mut bytes = encode_int(n as u64, 5);
1395            bytes[0] |= 0x20;
1396            out.extend_from_slice(&bytes);
1397        }
1398
1399        // (2) Indexed field representation.
1400        if let Some(idx) = self.combined_full_index(name, value) {
1401            let mut bytes = encode_int(idx as u64, 7);
1402            bytes[0] |= 0x80;
1403            out.extend_from_slice(&bytes);
1404            return;
1405        }
1406
1407        // (3) Literal with incremental indexing, indexed name.
1408        if let Some(idx) = self.combined_name_index(name) {
1409            let mut bytes = encode_int(idx as u64, 6);
1410            bytes[0] |= 0x40;
1411            out.extend_from_slice(&bytes);
1412            encode_literal_string(out, value);
1413            self.insert(name, value);
1414            return;
1415        }
1416
1417        // (4) Literal with incremental indexing, literal name.
1418        out.push(0x40);
1419        encode_literal_string(out, name);
1420        encode_literal_string(out, value);
1421        self.insert(name, value);
1422    }
1423}
1424
1425// ---------------------------------------------------------------------------
1426// TLS with ALPN = h2 — uses the shared driver in `crate::tls`.
1427// ---------------------------------------------------------------------------
1428
1429// ---------------------------------------------------------------------------
1430// Per-stream state machine (RFC 9113 §5.1) and the `Stream` it lives on.
1431// ---------------------------------------------------------------------------
1432
1433/// Simplified client-side stream lifecycle (RFC 9113 §5.1). We collapse the
1434/// `ReservedLocal`/`ReservedRemote` states because we disable server push.
1435/// `Idle` is included for completeness but in practice we transition into
1436/// `Open` (or `HalfClosedLocal` if the request has no body) the instant we
1437/// emit HEADERS, so callers rarely observe it.
1438#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1439enum StreamState {
1440    Idle,
1441    Open,
1442    HalfClosedLocal,
1443    HalfClosedRemote,
1444    Closed,
1445}
1446
1447impl StreamState {
1448    /// Validate that we can SEND a DATA frame in the current state. Returns
1449    /// the new state if `end_stream` is set, or the same state otherwise.
1450    fn send_data(self, end_stream: bool) -> Result<StreamState> {
1451        match self {
1452            StreamState::Idle => {
1453                if end_stream {
1454                    Ok(StreamState::HalfClosedLocal)
1455                } else {
1456                    Ok(StreamState::Open)
1457                }
1458            }
1459            StreamState::Open => Ok(if end_stream {
1460                StreamState::HalfClosedLocal
1461            } else {
1462                StreamState::Open
1463            }),
1464            StreamState::HalfClosedRemote => Ok(if end_stream {
1465                StreamState::Closed
1466            } else {
1467                StreamState::HalfClosedRemote
1468            }),
1469            StreamState::HalfClosedLocal | StreamState::Closed => Err(Error::BadResponse(format!(
1470                "internal: tried to send DATA in stream state {self:?}"
1471            ))),
1472        }
1473    }
1474
1475    /// Validate inbound DATA. Returns the (possibly updated) state.
1476    fn recv_data(self, end_stream: bool) -> Result<StreamState> {
1477        match self {
1478            StreamState::Open => Ok(if end_stream {
1479                StreamState::HalfClosedRemote
1480            } else {
1481                StreamState::Open
1482            }),
1483            StreamState::HalfClosedLocal => Ok(if end_stream {
1484                StreamState::Closed
1485            } else {
1486                StreamState::HalfClosedLocal
1487            }),
1488            StreamState::Idle | StreamState::HalfClosedRemote | StreamState::Closed => {
1489                Err(Error::BadResponse(format!(
1490                    "received DATA in stream state {self:?} (RFC 9113 §5.1)"
1491                )))
1492            }
1493        }
1494    }
1495
1496    /// Validate inbound HEADERS / CONTINUATION. Returns the (possibly
1497    /// updated) state. `Closed` returns `Closed` (we'll ignore the frame).
1498    fn recv_headers(self, end_stream: bool) -> Result<StreamState> {
1499        match self {
1500            StreamState::Open => Ok(if end_stream {
1501                StreamState::HalfClosedRemote
1502            } else {
1503                StreamState::Open
1504            }),
1505            StreamState::HalfClosedLocal => Ok(if end_stream {
1506                StreamState::Closed
1507            } else {
1508                StreamState::HalfClosedLocal
1509            }),
1510            StreamState::Closed => Ok(StreamState::Closed),
1511            StreamState::Idle | StreamState::HalfClosedRemote => Err(Error::BadResponse(format!(
1512                "received HEADERS in stream state {self:?} (RFC 9113 §5.1)"
1513            ))),
1514        }
1515    }
1516
1517    /// Inbound RST_STREAM: transition to `Closed` from any state (idle is the
1518    /// one true exception per §5.1, but we surface that as an error too).
1519    fn recv_rst(self) -> Result<StreamState> {
1520        match self {
1521            StreamState::Idle => Err(Error::BadResponse(
1522                "RST_STREAM on idle stream (RFC 9113 §5.1)".into(),
1523            )),
1524            _ => Ok(StreamState::Closed),
1525        }
1526    }
1527}
1528
1529/// One in-flight HTTP/2 stream. Mostly owned mutably by the `Connection` for
1530/// the lifetime of the request; on completion the closed stream is moved out
1531/// of `Connection::streams` and returned to the caller.
1532struct Stream {
1533    /// Stream identifier. Stored for debugging and so the type stays
1534    /// self-describing when reaped from `Connection::streams`; the public
1535    /// `Response` doesn't need it but it's cheap to keep.
1536    #[allow(dead_code)]
1537    id: u32,
1538    state: StreamState,
1539    send_window: StreamSendWindow,
1540    recv_window: StreamRecvWindow,
1541    /// Accumulator for the in-progress header block (HEADERS + CONTINUATION
1542    /// fragments) before HPACK decoding fires at END_HEADERS.
1543    headers_buf: Vec<u8>,
1544    /// Fully decoded response headers, once the response's END_HEADERS has
1545    /// been seen. `None` until then.
1546    response_headers: Option<Vec<(String, String)>>,
1547    /// Accumulator for response body bytes (DATA payloads, post-padding).
1548    /// Stays empty when the body is streamed straight to a caller-supplied sink
1549    /// (see [`Connection::process_data`]); `streamed_len` then holds the count.
1550    body: Vec<u8>,
1551    /// Bytes written directly to the streaming sink (when streaming, `body` is
1552    /// empty). Used only for the `* Received N body bytes` trace.
1553    streamed_len: u64,
1554    /// True once the peer's END_STREAM has been observed.
1555    end_stream_recv: bool,
1556    /// Outbound request body not yet written, with a cursor into it. Used by
1557    /// the multiplexed driver's non-blocking sender (`pump_pending_sends`):
1558    /// when a stream's send window is exhausted we leave the unsent suffix
1559    /// here and move on to other streams, resuming when WINDOW_UPDATE arrives.
1560    /// `None` once the whole body has been flushed (and END_STREAM emitted).
1561    /// The single-stream `send_request_on` path never populates this — it
1562    /// writes the body inline with its own blocking loop.
1563    pending_body: Option<PendingBody>,
1564}
1565
1566/// A request body that is being streamed out across multiple `pump`
1567/// iterations because flow control would not let it all go at once.
1568struct PendingBody {
1569    /// The full request body bytes.
1570    bytes: Vec<u8>,
1571    /// How many bytes of `bytes` have already been written to the wire.
1572    sent: usize,
1573}
1574
1575impl Stream {
1576    fn new(id: u32, initial_peer_window: i64, our_recv_window: i64) -> Self {
1577        Stream {
1578            id,
1579            state: StreamState::Idle,
1580            send_window: StreamSendWindow::new(initial_peer_window),
1581            recv_window: StreamRecvWindow::new(our_recv_window),
1582            headers_buf: Vec::new(),
1583            response_headers: None,
1584            body: Vec::new(),
1585            streamed_len: 0,
1586            end_stream_recv: false,
1587            pending_body: None,
1588        }
1589    }
1590
1591    /// Smallest of conn / stream send windows — the budget for the next DATA
1592    /// chunk on this stream.
1593    fn send_budget(&self, conn_window: &ConnSendWindow) -> i64 {
1594        self.send_window.available.min(conn_window.available)
1595    }
1596
1597    /// Append a header-block fragment (HEADERS / CONTINUATION) to the
1598    /// accumulator, refusing to grow past [`MAX_HEADERS_BUF`]. Returning an
1599    /// error here terminates the connection, which bounds the
1600    /// CONTINUATION-flood / unbounded-header-block class (CVE-2024-27316):
1601    /// without this an attacker can stream END_HEADERS-less CONTINUATION
1602    /// frames forever and exhaust memory.
1603    fn push_header_fragment(&mut self, frag: &[u8]) -> Result<()> {
1604        if self.headers_buf.len().saturating_add(frag.len()) > MAX_HEADERS_BUF {
1605            return Err(Error::BadResponse(
1606                "header block exceeds size limit (CONTINUATION flood?)".into(),
1607            ));
1608        }
1609        self.headers_buf.extend_from_slice(frag);
1610        Ok(())
1611    }
1612}
1613
1614// ---------------------------------------------------------------------------
1615// The Connection: owns the TLS stream, peer settings, decoder, and all the
1616// streams currently in flight. Replaces the single-stream `ConnState` from
1617// earlier tasks.
1618// ---------------------------------------------------------------------------
1619
1620/// `Connection` is the multiplexing core. It owns the TLS transport plus the
1621/// per-stream state for every request currently in flight, drives the
1622/// connection-level state machine (preface, SETTINGS exchange, GOAWAY), and
1623/// dispatches inbound frames to the right stream by `stream_id`.
1624///
1625/// One `Connection` per TLS session. A `Connection` outlives a single
1626/// request: after a request completes cleanly and the connection is still
1627/// usable ([`Connection::is_usable`]), `send()` parks it in the process-wide
1628/// pool so the next `send()` to the same authority reuses it — opening the
1629/// next odd stream id on the warm transport instead of re-handshaking. See
1630/// the pool section near the bottom of this module.
1631struct Connection<S: Read + Write> {
1632    tls: S,
1633    peer: PeerSettings,
1634    conn_send_window: ConnSendWindow,
1635    conn_recv_window: ConnRecvWindow,
1636    /// The receive window (bytes) we advertise per stream via
1637    /// `SETTINGS_INITIAL_WINDOW_SIZE`; new streams open their recv window here.
1638    our_recv_window: i64,
1639    decoder: Decoder,
1640    encoder: Encoder,
1641    streams: HashMap<u32, Stream>,
1642    /// Next client-initiated stream id to allocate. Per §5.1.1, client
1643    /// streams are odd-numbered and strictly increasing: 1, 3, 5, …
1644    next_stream_id: u32,
1645    /// If the peer sent GOAWAY, the last-stream-id they advertised. We refuse
1646    /// to allocate ids strictly greater than this; existing streams with id
1647    /// ≤ this can still complete.
1648    goaway_received: Option<u32>,
1649    /// If we are mid-header-block on some stream — i.e. we processed a
1650    /// HEADERS frame without END_HEADERS — this holds the stream id we are
1651    /// waiting on. While `Some(_)`, the peer is forbidden from interleaving
1652    /// any other frame (RFC 9113 §6.10).
1653    expecting_continuation: Option<u32>,
1654    /// Per-connection flood / no-progress accounting. Updated by every inbound
1655    /// frame via `process_frame`; trips `Error::BadResponse` once any budget is
1656    /// exhausted. Shared by both the single-stream and multiplexed loops since
1657    /// both funnel through `process_frame`.
1658    budget: FloodBudget,
1659    /// Set by a frame handler whenever the frame it processed made real forward
1660    /// progress (a DATA byte appended, a header block completed, or a
1661    /// WINDOW_UPDATE that raised a send window). `process_frame` reads and
1662    /// clears it after each dispatch to drive the no-progress counter.
1663    made_progress: bool,
1664    /// Negotiated TLS parameters of this connection, captured at dial time.
1665    /// Carried so every response on the connection (including pooled reuse) can
1666    /// report [`crate::Response::tls`]. `None` for non-TLS test connections.
1667    tls_info: Option<crate::http::TlsInfo>,
1668    /// Per-phase dial timing (namelookup/connect/appconnect), captured at dial.
1669    /// Applied to a response only on the fresh-dial path (pooled reuse leaves
1670    /// these phases unset, matching curl's reuse semantics).
1671    dial_timing: crate::http::Timing,
1672}
1673
1674/// Per-connection budget counters that bound hostile-peer frame floods. See the
1675/// `MAX_*_FRAMES` / `MAX_NO_PROGRESS_FRAMES` constants for the rationale behind
1676/// each ceiling.
1677#[derive(Debug, Default, Clone, PartialEq, Eq)]
1678struct FloodBudget {
1679    /// Consecutive inbound frames that made no forward progress. Reset to 0 on
1680    /// any progress frame; aborts at `MAX_NO_PROGRESS_FRAMES`.
1681    no_progress: u32,
1682    /// Total non-ACK SETTINGS frames received; aborts at `MAX_SETTINGS_FRAMES`.
1683    settings: u32,
1684    /// Total non-ACK PING frames received; aborts at `MAX_PING_FRAMES`.
1685    ping: u32,
1686    /// Total RST_STREAM frames received; aborts at `MAX_RST_STREAM_FRAMES`.
1687    rst_stream: u32,
1688}
1689
1690impl FloodBudget {
1691    /// Bill the cheap-control-frame floods (SETTINGS / PING / RST_STREAM) for
1692    /// one inbound frame. `typ`/`flags` are the frame header fields. Called
1693    /// *before* dispatch so the budget counts a frame even when its handler
1694    /// returns `Err` — critically, `process_rst` returns a per-stream error on
1695    /// a successful reset, so billing RST_STREAM here (not after dispatch) is
1696    /// what makes the Rapid-Reset (CVE-2023-44487) budget actually bite when
1697    /// the peer resets *our* in-flight streams. Only variants that cost us a
1698    /// reply or churn stream state are counted; ACKs and benign types are free.
1699    fn record_control_frame(&mut self, typ: u8, flags: u8) -> Result<()> {
1700        match typ {
1701            F_SETTINGS if flags & FLAG_ACK == 0 => {
1702                self.settings += 1;
1703                if self.settings > MAX_SETTINGS_FRAMES {
1704                    return Err(Error::BadResponse(format!(
1705                        "http2: peer sent {} SETTINGS frames (flood)",
1706                        self.settings
1707                    )));
1708                }
1709            }
1710            F_PING if flags & FLAG_ACK == 0 => {
1711                self.ping += 1;
1712                if self.ping > MAX_PING_FRAMES {
1713                    return Err(Error::BadResponse(format!(
1714                        "http2: peer sent {} PING frames (flood)",
1715                        self.ping
1716                    )));
1717                }
1718            }
1719            F_RST_STREAM => {
1720                self.rst_stream += 1;
1721                if self.rst_stream > MAX_RST_STREAM_FRAMES {
1722                    return Err(Error::BadResponse(format!(
1723                        "http2: peer sent {} RST_STREAM frames (Rapid-Reset flood)",
1724                        self.rst_stream
1725                    )));
1726                }
1727            }
1728            _ => {}
1729        }
1730        Ok(())
1731    }
1732
1733    /// No-progress spin guard. `made_progress` is whether the just-dispatched
1734    /// frame reported real forward progress (a DATA byte appended, a header
1735    /// block completed, a WINDOW_UPDATE that raised a send window, or a
1736    /// terminal `Done`). Any progress frame resets the streak; otherwise the
1737    /// streak grows and we abort at `MAX_NO_PROGRESS_FRAMES`. Called after a
1738    /// successful dispatch.
1739    fn record_progress(&mut self, made_progress: bool) -> Result<()> {
1740        if made_progress {
1741            self.no_progress = 0;
1742        } else {
1743            self.no_progress += 1;
1744            if self.no_progress > MAX_NO_PROGRESS_FRAMES {
1745                return Err(Error::BadResponse(format!(
1746                    "http2: peer sent {} consecutive frames with no forward progress (flood)",
1747                    self.no_progress
1748                )));
1749            }
1750        }
1751        Ok(())
1752    }
1753}
1754
1755/// Result of dispatching one inbound frame.
1756///
1757/// `Done(stream_id)` means a stream has hit a terminal condition (Closed or
1758/// HalfClosedRemote with response fully received) and should be reaped by the
1759/// caller. `Continue` means keep reading.
1760#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1761enum DispatchOutcome {
1762    Continue,
1763    Done(u32),
1764}
1765
1766impl<S: Read + Write> Connection<S> {
1767    /// Construct a `Connection` from an already-handshaken transport and send
1768    /// the client preface + initial SETTINGS frame. We advertise
1769    /// `ENABLE_PUSH=0` (no PUSH_PROMISE — we don't implement it) and
1770    /// `SETTINGS_INITIAL_WINDOW_SIZE=recv_window` to raise the per-stream
1771    /// receive window, then a `WINDOW_UPDATE` on stream 0 to raise the
1772    /// connection receive window to the same size (SETTINGS does not affect the
1773    /// connection window). `recv_window` is clamped to a sane
1774    /// `[64 KiB, 2^31-1]` range. See [`DEFAULT_RECV_WINDOW`] for why the RFC
1775    /// default is too small for high-bandwidth-delay links.
1776    fn new(mut tls: S, recv_window: u32) -> Result<Self> {
1777        let recv_window = recv_window.clamp(OUR_INITIAL_WINDOW as u32, INITIAL_WINDOW_SIZE_MAX);
1778        tls.write_all(PREFACE)?;
1779        let mut settings_payload = Vec::with_capacity(12);
1780        settings_payload.extend_from_slice(&S_ENABLE_PUSH.to_be_bytes());
1781        settings_payload.extend_from_slice(&0u32.to_be_bytes());
1782        settings_payload.extend_from_slice(&S_INITIAL_WINDOW_SIZE.to_be_bytes());
1783        settings_payload.extend_from_slice(&recv_window.to_be_bytes());
1784        let our_settings = Frame {
1785            typ: F_SETTINGS,
1786            flags: 0,
1787            stream_id: 0,
1788            payload: settings_payload,
1789        };
1790        write_frame(&mut tls, &our_settings)?;
1791        // Raise the connection-level receive window from the RFC default to
1792        // `recv_window` with a stream-0 WINDOW_UPDATE (SETTINGS_INITIAL_WINDOW
1793        // covers streams only). Skip it when the caller asked for exactly the
1794        // default, since a zero increment is a protocol error.
1795        let conn_bump = recv_window - OUR_INITIAL_WINDOW as u32;
1796        if conn_bump > 0 {
1797            write_frame(&mut tls, &window_update_frame(0, conn_bump))?;
1798        }
1799        tls.flush()?;
1800        Ok(Connection {
1801            tls,
1802            peer: PeerSettings::default(),
1803            conn_send_window: ConnSendWindow::new(),
1804            conn_recv_window: ConnRecvWindow::new(recv_window as i64),
1805            our_recv_window: recv_window as i64,
1806            decoder: Decoder::new(),
1807            encoder: Encoder::new(),
1808            streams: HashMap::new(),
1809            next_stream_id: 1,
1810            goaway_received: None,
1811            expecting_continuation: None,
1812            budget: FloodBudget::default(),
1813            made_progress: false,
1814            tls_info: None,
1815            dial_timing: crate::http::Timing::default(),
1816        })
1817    }
1818
1819    /// Cheap, no-I/O check that this connection is structurally safe to hand
1820    /// out from the pool for another request:
1821    ///
1822    /// - we have not received a GOAWAY (or, if we did, we still have at least
1823    ///   one in-flight stream — but for pooling purposes we treat *any*
1824    ///   GOAWAY as "do not pool"); and
1825    /// - there are no streams left from a previous request (a checked-out
1826    ///   conn with non-empty `streams` means the previous user crashed mid-
1827    ///   request and left state behind; we cannot trust the wire position).
1828    ///
1829    /// We deliberately do NOT probe the socket. If the peer closed silently,
1830    /// the next read/write will surface that as an I/O error and the caller
1831    /// drops the conn instead of re-pooling it.
1832    fn is_usable(&self) -> bool {
1833        if self.goaway_received.is_some() {
1834            return false;
1835        }
1836        if !self.streams.is_empty() {
1837            return false;
1838        }
1839        // Defensive: if we somehow exhausted the stream-id space, the next
1840        // open_stream would error — don't bother handing this conn back.
1841        if self.next_stream_id >= 0x8000_0000 {
1842            return false;
1843        }
1844        true
1845    }
1846
1847    /// Drop every stream that has reached a terminal state from `self.streams`.
1848    ///
1849    /// `drive_until_stream_done` already removes the stream it was waiting on,
1850    /// but a connection that is reused across requests could otherwise
1851    /// accumulate `Closed` / fully-received entries for streams that finished
1852    /// while we were blocked on another one. Reaping them here keeps the map
1853    /// from growing unbounded across pooled reuses (task requirement #6) and
1854    /// is a no-op for the common single-stream-per-request case.
1855    fn prune_completed_streams(&mut self) {
1856        self.streams.retain(|_, s| {
1857            let terminal = matches!(s.state, StreamState::Closed)
1858                || (matches!(s.state, StreamState::HalfClosedRemote)
1859                    && s.response_headers.is_some()
1860                    && s.end_stream_recv);
1861            !terminal
1862        });
1863    }
1864
1865    /// Allocate the next client-initiated stream id and register an empty
1866    /// `Stream` in `self.streams`.
1867    ///
1868    /// Errors:
1869    /// - At `MAX_CONCURRENT_STREAMS`: refuse so the caller can open another
1870    ///   connection. `is_usable` honours the same limit before a pooled
1871    ///   connection is handed back out.
1872    /// - Past 2^31: stream ids are bounded by RFC 9113 §5.1.1; the caller
1873    ///   must discard this connection.
1874    /// - After a GOAWAY that names a last-stream-id below what we'd allocate:
1875    ///   refuse so the caller knows to retry on a fresh connection.
1876    fn open_stream(&mut self) -> Result<u32> {
1877        if (self.streams.len() as u64) >= self.peer.max_concurrent_streams as u64 {
1878            return Err(Error::BadResponse("at MAX_CONCURRENT_STREAMS limit".into()));
1879        }
1880        // 2^31 is the boundary; the highest legal client stream id is
1881        // 2^31 - 1 (which happens to be odd). RFC 9113 §5.1.1.
1882        if self.next_stream_id >= 0x8000_0000 {
1883            return Err(Error::BadResponse(
1884                "stream id space exhausted (RFC 9113 §5.1.1)".into(),
1885            ));
1886        }
1887        if let Some(last) = self.goaway_received {
1888            if self.next_stream_id > last {
1889                return Err(Error::BadResponse(format!(
1890                    "GOAWAY received with last-stream-id={last}; cannot allocate id={}",
1891                    self.next_stream_id
1892                )));
1893            }
1894        }
1895        let id = self.next_stream_id;
1896        self.next_stream_id = self.next_stream_id.saturating_add(2);
1897        self.streams.insert(
1898            id,
1899            Stream::new(
1900                id,
1901                self.peer.initial_window_size as i64,
1902                self.our_recv_window,
1903            ),
1904        );
1905        Ok(id)
1906    }
1907
1908    /// Emit a PRIORITY frame (RFC 9113 §6.3) carrying the request's priority
1909    /// weight, before its HEADERS. Only for a non-default hint — `Normal` uses
1910    /// the protocol default weight (16) and sends nothing. Priority-aware peers
1911    /// honour the weight; others ignore it (it is a harmless standalone frame).
1912    fn send_priority_hint(
1913        &mut self,
1914        stream_id: u32,
1915        priority: crate::http::Priority,
1916    ) -> Result<()> {
1917        let Some(weight) = priority_weight_byte(priority) else {
1918            return Ok(());
1919        };
1920        // Payload: 4-byte stream dependency (exclusive=0, dep=0) + 1-byte weight.
1921        let payload = vec![0, 0, 0, 0, weight];
1922        write_frame(
1923            &mut self.tls,
1924            &Frame {
1925                typ: F_PRIORITY,
1926                flags: 0,
1927                stream_id,
1928                payload,
1929            },
1930        )?;
1931        Ok(())
1932    }
1933
1934    /// Build and write the HEADERS + CONTINUATION + DATA frames for `req` on
1935    /// `stream_id`. Blocks on flow control by reading inbound frames in-place
1936    /// when the send window is depleted. The stream's `state` is advanced
1937    /// for each outbound transition.
1938    fn send_request_on(&mut self, stream_id: u32, req: &Request) -> Result<()> {
1939        self.send_priority_hint(stream_id, req.priority)?;
1940        let header_block = build_header_block(&mut self.encoder, req);
1941        let has_body = !req.body.is_empty();
1942        let max_frame_size = self.peer.max_frame_size as usize;
1943        let header_frames =
1944            fragment_header_block(stream_id, &header_block, max_frame_size, !has_body);
1945
1946        // HEADERS frame(s). RFC 9113 §6.10: no other frames may interleave
1947        // between HEADERS and its CONTINUATION fragments — our single-threaded
1948        // writer satisfies this automatically.
1949        for f in &header_frames {
1950            write_frame(&mut self.tls, f)?;
1951        }
1952        {
1953            let s = self
1954                .streams
1955                .get_mut(&stream_id)
1956                .ok_or_else(|| Error::BadResponse(format!("stream {stream_id} not found")))?;
1957            s.state = s.state.send_data(!has_body)?;
1958        }
1959
1960        if has_body {
1961            let mut remaining: &[u8] = req.body.as_slice();
1962            while !remaining.is_empty() {
1963                // Block until both windows have budget.
1964                loop {
1965                    let budget = {
1966                        let s = self.streams.get(&stream_id).ok_or_else(|| {
1967                            Error::BadResponse(format!("stream {stream_id} disappeared mid-send"))
1968                        })?;
1969                        s.send_budget(&self.conn_send_window)
1970                    };
1971                    if budget > 0 {
1972                        break;
1973                    }
1974                    match self.read_and_dispatch(None)? {
1975                        DispatchOutcome::Continue => {}
1976                        DispatchOutcome::Done(done_id) if done_id == stream_id => {
1977                            // The peer ended our stream before we finished
1978                            // sending the body — protocol error from our side.
1979                            return Err(Error::BadResponse(
1980                                "server ended stream before request body was fully sent".into(),
1981                            ));
1982                        }
1983                        DispatchOutcome::Done(_) => {
1984                            // Some other stream finished while we were
1985                            // blocked; that's fine, keep waiting on ours.
1986                        }
1987                    }
1988                }
1989
1990                let max_frame_size = self.peer.max_frame_size as usize;
1991                let budget = self
1992                    .streams
1993                    .get(&stream_id)
1994                    .unwrap()
1995                    .send_budget(&self.conn_send_window);
1996                let n = next_data_chunk_size(max_frame_size, budget, remaining.len());
1997                debug_assert!(n > 0, "loop above guarantees positive budget");
1998                let chunk = &remaining[..n];
1999                remaining = &remaining[n..];
2000                let is_last = remaining.is_empty();
2001                let data_frame = Frame {
2002                    typ: F_DATA,
2003                    flags: if is_last { FLAG_END_STREAM } else { 0 },
2004                    stream_id,
2005                    payload: chunk.to_vec(),
2006                };
2007                write_frame(&mut self.tls, &data_frame)?;
2008                self.conn_send_window.consume(n);
2009                let s = self.streams.get_mut(&stream_id).unwrap();
2010                s.send_window.consume(n);
2011                s.state = s.state.send_data(is_last)?;
2012            }
2013        }
2014        self.tls.flush()?;
2015        Ok(())
2016    }
2017
2018    /// Drive the connection's read side until `stream_id` reaches a terminal
2019    /// state, then remove that stream from the map and return it.
2020    fn drive_until_stream_done(&mut self, stream_id: u32) -> Result<Stream> {
2021        self.drive_until_stream_done_to(stream_id, None, None)
2022    }
2023
2024    /// As [`drive_until_stream_done`], but DATA payloads for `stream_id` are
2025    /// written to `sink` instead of buffered (when the response permits — see
2026    /// [`Connection::process_data`]). The reborrow keeps the `&mut` usable
2027    /// across loop iterations.
2028    fn drive_until_stream_done_to(
2029        &mut self,
2030        stream_id: u32,
2031        mut sink: Option<&mut dyn Write>,
2032        mut on_head: Option<crate::http::HeadObserver<'_>>,
2033    ) -> Result<Stream> {
2034        loop {
2035            // Has the stream already completed in an earlier dispatch?
2036            if let Some(s) = self.streams.get(&stream_id) {
2037                if matches!(s.state, StreamState::Closed | StreamState::HalfClosedRemote)
2038                    && s.response_headers.is_some()
2039                    && s.end_stream_recv
2040                {
2041                    self.fire_head(stream_id, &mut on_head);
2042                    return Ok(self.streams.remove(&stream_id).unwrap());
2043                }
2044            } else {
2045                return Err(Error::BadResponse(format!(
2046                    "stream {stream_id} not registered"
2047                )));
2048            }
2049
2050            let reborrow: Option<&mut dyn Write> = match &mut sink {
2051                Some(w) => Some(&mut **w),
2052                None => None,
2053            };
2054            let outcome = self.read_and_dispatch(reborrow)?;
2055            // Fire the head callback the moment the response HEADERS for our
2056            // stream are decoded — which, since DATA frames are distinct frames
2057            // arriving after HEADERS, is guaranteed before the first body byte
2058            // is written to `sink`.
2059            self.fire_head(stream_id, &mut on_head);
2060            match outcome {
2061                DispatchOutcome::Continue => {}
2062                DispatchOutcome::Done(done_id) if done_id == stream_id => {
2063                    return Ok(self.streams.remove(&stream_id).unwrap());
2064                }
2065                DispatchOutcome::Done(_) => {
2066                    // Some other stream finished; loop continues.
2067                }
2068            }
2069        }
2070    }
2071
2072    /// Invoke `on_head` once, the first time `stream_id`'s response headers are
2073    /// available. Takes the observer out of the `Option` so it never fires
2074    /// twice (e.g. on a trailing HEADERS frame).
2075    fn fire_head(&self, stream_id: u32, on_head: &mut Option<crate::http::HeadObserver<'_>>) {
2076        if on_head.is_none() {
2077            return;
2078        }
2079        let Some(s) = self.streams.get(&stream_id) else {
2080            return;
2081        };
2082        let Some(headers) = s.response_headers.as_ref() else {
2083            return;
2084        };
2085        let mut status: Option<u16> = None;
2086        let mut clean: Vec<(String, String)> = Vec::with_capacity(headers.len());
2087        for (k, v) in headers {
2088            if k == ":status" {
2089                status = v.parse::<u16>().ok();
2090            } else if !k.starts_with(':') {
2091                clean.push((k.clone(), v.clone()));
2092            }
2093        }
2094        // Interim 1xx responses (e.g. 100/103) are not the final head; wait.
2095        let Some(status) = status.filter(|s| *s >= 200) else {
2096            return;
2097        };
2098        if let Some(obs) = on_head.take() {
2099            obs(&crate::http::ResponseHead {
2100                status,
2101                reason: String::new(),
2102                version: "HTTP/2".to_string(),
2103                headers: clean,
2104            });
2105        }
2106    }
2107
2108    // -----------------------------------------------------------------------
2109    // Concurrent multiplexing driver.
2110    //
2111    // The single-stream path above (`send_request_on` + `drive_until_stream_done`)
2112    // blocks on one stream at a time. The methods below instead keep many
2113    // streams in flight on ONE connection and drive them all from a single
2114    // frame loop, so a slow body on one stream cannot stall the others
2115    // (no head-of-line blocking on send).
2116    // -----------------------------------------------------------------------
2117
2118    /// Write the HEADERS (+ CONTINUATION) frames for `req` on `stream_id` and
2119    /// stage its request body for later, non-blocking transmission.
2120    ///
2121    /// Unlike [`send_request_on`], this NEVER blocks on flow control: the body
2122    /// is parked in the stream's `pending_body` and drained incrementally by
2123    /// [`pump_pending_sends`] as send-window budget becomes available across
2124    /// all streams. Returns immediately after the HEADERS are on the wire (the
2125    /// caller flushes once after staging every stream in a batch).
2126    ///
2127    /// `send_request_on` is the verb name; this is `stage_request_on` because
2128    /// it only commits the request headers, deferring the body.
2129    fn stage_request_on(&mut self, stream_id: u32, req: &Request) -> Result<()> {
2130        self.send_priority_hint(stream_id, req.priority)?;
2131        let header_block = build_header_block(&mut self.encoder, req);
2132        let has_body = !req.body.is_empty();
2133        let max_frame_size = self.peer.max_frame_size as usize;
2134        let header_frames =
2135            fragment_header_block(stream_id, &header_block, max_frame_size, !has_body);
2136        for f in &header_frames {
2137            write_frame(&mut self.tls, f)?;
2138        }
2139        let s = self
2140            .streams
2141            .get_mut(&stream_id)
2142            .ok_or_else(|| Error::BadResponse(format!("stream {stream_id} not found")))?;
2143        s.state = s.state.send_data(!has_body)?;
2144        if has_body {
2145            s.pending_body = Some(PendingBody {
2146                bytes: req.body.clone(),
2147                sent: 0,
2148            });
2149        }
2150        Ok(())
2151    }
2152
2153    /// Write as much pending request-body DATA as the connection and per-stream
2154    /// send windows currently allow, across EVERY stream with bytes left to
2155    /// send. This is the non-blocking heart of multiplexed sending: each call
2156    /// makes whatever forward progress the windows permit and returns; it never
2157    /// waits on a WINDOW_UPDATE. When a stream's body is fully flushed its
2158    /// `pending_body` is cleared and END_STREAM is emitted on the final DATA
2159    /// frame. Returns whether any byte was written (so the caller knows to
2160    /// flush the transport).
2161    ///
2162    /// Streams are visited in ascending id order for deterministic, fair-ish
2163    /// scheduling (lower ids — issued first — drain first), and to keep the
2164    /// `-v` trace stable across runs.
2165    fn pump_pending_sends(&mut self, trace: &mut dyn Write) -> Result<bool> {
2166        let mut wrote = false;
2167        let max_frame_size = self.peer.max_frame_size as usize;
2168        // Snapshot the ids with work to do so we don't borrow `self.streams`
2169        // while mutating it inside the loop.
2170        let mut ids: Vec<u32> = self
2171            .streams
2172            .iter()
2173            .filter(|(_, s)| s.pending_body.is_some())
2174            .map(|(id, _)| *id)
2175            .collect();
2176        ids.sort_unstable();
2177
2178        for id in ids {
2179            // The stream is present (we just collected it) and is never removed
2180            // mid-pump, so look it up once and drain it until the window stalls
2181            // or its body is exhausted.
2182            if !self.streams.contains_key(&id) {
2183                continue;
2184            }
2185            loop {
2186                // Recompute the budget each iteration — the conn window is
2187                // shared, so an earlier stream's writes shrink it for later
2188                // ones within this same pass.
2189                let s = self.streams.get(&id).unwrap();
2190                let budget = s.send_budget(&self.conn_send_window);
2191                let (remaining_len, sent) = match s.pending_body.as_ref() {
2192                    Some(pb) => (pb.bytes.len() - pb.sent, pb.sent),
2193                    None => break, // body fully sent
2194                };
2195                if remaining_len == 0 {
2196                    // Defensive: an empty pending body is cleared below; this
2197                    // shouldn't happen because we never stage an empty body.
2198                    self.streams.get_mut(&id).unwrap().pending_body = None;
2199                    break;
2200                }
2201                let n = next_data_chunk_size(max_frame_size, budget, remaining_len);
2202                if n == 0 {
2203                    // Window exhausted for this stream right now — move on to
2204                    // the next one rather than blocking (no head-of-line stall).
2205                    break;
2206                }
2207                let is_last = n == remaining_len;
2208                let chunk: Vec<u8> = {
2209                    let pb = self
2210                        .streams
2211                        .get(&id)
2212                        .unwrap()
2213                        .pending_body
2214                        .as_ref()
2215                        .unwrap();
2216                    pb.bytes[sent..sent + n].to_vec()
2217                };
2218                let data_frame = Frame {
2219                    typ: F_DATA,
2220                    flags: if is_last { FLAG_END_STREAM } else { 0 },
2221                    stream_id: id,
2222                    payload: chunk,
2223                };
2224                write_frame(&mut self.tls, &data_frame)?;
2225                self.conn_send_window.consume(n);
2226                let s = self.streams.get_mut(&id).unwrap();
2227                s.send_window.consume(n);
2228                s.state = s.state.send_data(is_last)?;
2229                let pb = s.pending_body.as_mut().unwrap();
2230                pb.sent += n;
2231                if is_last {
2232                    s.pending_body = None;
2233                    let _ = writeln!(trace, "* [stream {id}] request body sent");
2234                }
2235                wrote = true;
2236                if is_last {
2237                    break;
2238                }
2239            }
2240        }
2241        Ok(wrote)
2242    }
2243
2244    /// Run `reqs` concurrently over this single connection, returning one
2245    /// result per request, in the same order as `reqs`.
2246    ///
2247    /// Design:
2248    /// - Open a stream per request up to the peer's `SETTINGS_MAX_CONCURRENT_STREAMS`,
2249    ///   queueing the rest. Each opened stream writes its HEADERS immediately;
2250    ///   request bodies are staged and streamed out non-blockingly.
2251    /// - A SINGLE frame loop alternates between pumping outbound body DATA
2252    ///   (whatever the windows allow, across all streams) and reading one
2253    ///   inbound frame, dispatching it to its stream by id. As each in-flight
2254    ///   stream completes, the next queued request is started, keeping at most
2255    ///   `MAX_CONCURRENT_STREAMS` streams open.
2256    /// - Demultiplexing: every request gets its own `Response`. A single
2257    ///   stream's RST_STREAM (or per-stream protocol error) fails ONLY that
2258    ///   request; the others keep running. A connection-level failure
2259    ///   (transport error, conn-level protocol error) fails all not-yet-
2260    ///   completed requests. On GOAWAY, streams with id above the peer's
2261    ///   advertised last-stream-id are failed while lower ones finish, and no
2262    ///   queued request is started beyond the GOAWAY boundary.
2263    ///
2264    /// The single-request `send` / `run_one_request` path is untouched; this is
2265    /// purely additive.
2266    fn run_multiplexed(
2267        &mut self,
2268        reqs: &[Request],
2269        trace: &mut dyn Write,
2270    ) -> Vec<Result<Response>> {
2271        let n = reqs.len();
2272        // Per-request slot: `None` while in flight or queued, `Some` once a
2273        // terminal result (Ok response / Err) is known.
2274        let mut results: Vec<Option<Result<Response>>> = (0..n).map(|_| None).collect();
2275        // Map live stream id -> request index, so a completed/failed stream
2276        // routes its outcome back to the right slot.
2277        let mut id_to_idx: HashMap<u32, usize> = HashMap::new();
2278        // Indices not yet started, in order. We start them as slots free up.
2279        let mut queue: VecDeque<usize> = (0..n).collect();
2280
2281        // Helper closure-free start: open a stream for request `idx` and write
2282        // its HEADERS. On failure to open (e.g. GOAWAY boundary,
2283        // MAX_CONCURRENT, id exhaustion) record the error for that request.
2284        // We inline this rather than use a closure so it can borrow `self`.
2285
2286        // Prime: start as many as the concurrency limit allows.
2287        self.start_queued(&mut queue, &mut id_to_idx, &mut results, reqs, trace);
2288        // Flush the HEADERS we just wrote, plus any body bytes we can send.
2289        match self.pump_pending_sends(trace) {
2290            Ok(_) => {}
2291            Err(e) => {
2292                // A write failure here is connection-fatal: fail everything
2293                // still outstanding and bail.
2294                self.fail_all_outstanding(&id_to_idx, &mut results, &queue, &e);
2295                return collect_results(results);
2296            }
2297        }
2298        if let Err(e) = self.tls.flush() {
2299            let e = Error::Io(e);
2300            self.fail_all_outstanding(&id_to_idx, &mut results, &queue, &e);
2301            return collect_results(results);
2302        }
2303
2304        // Single frame loop: keep going until every request has a result.
2305        while results.iter().any(Option::is_none) {
2306            // If nothing is in flight but the queue is non-empty, we couldn't
2307            // open any stream (e.g. all blocked by GOAWAY) — drain the queue
2308            // as failures to avoid spinning forever.
2309            if id_to_idx.is_empty() {
2310                if queue.is_empty() {
2311                    break;
2312                }
2313                // Try once more to start queued work; if still nothing opens,
2314                // fail the rest.
2315                self.start_queued(&mut queue, &mut id_to_idx, &mut results, reqs, trace);
2316                if id_to_idx.is_empty() {
2317                    while let Some(idx) = queue.pop_front() {
2318                        if results[idx].is_none() {
2319                            results[idx] = Some(Err(Error::BadResponse(
2320                                "no usable stream to issue request (GOAWAY?)".into(),
2321                            )));
2322                        }
2323                    }
2324                    break;
2325                }
2326                if self.flush_pending(trace).is_err() {
2327                    break;
2328                }
2329            }
2330
2331            // Read and dispatch one inbound frame.
2332            let outcome = match self.read_and_dispatch(None) {
2333                Ok(o) => o,
2334                Err(e) => {
2335                    // Distinguish a per-stream RST (carried as BadResponse from
2336                    // `process_rst`) from a connection-fatal error. `process_rst`
2337                    // sets the offending stream to Closed before returning Err,
2338                    // so we can detect a single closed-but-unfinished stream and
2339                    // fail just that request, then keep the loop running.
2340                    if let Some(idx) = self.take_stream_error(&mut id_to_idx) {
2341                        results[idx] = Some(Err(e));
2342                        // After a per-stream error a slot may have freed up.
2343                        self.start_queued(&mut queue, &mut id_to_idx, &mut results, reqs, trace);
2344                        if let Err(fe) = self.flush_pending(trace) {
2345                            self.fail_all_outstanding(&id_to_idx, &mut results, &queue, &fe);
2346                            break;
2347                        }
2348                        continue;
2349                    }
2350                    // Connection-fatal: fail everything still outstanding.
2351                    self.fail_all_outstanding(&id_to_idx, &mut results, &queue, &e);
2352                    break;
2353                }
2354            };
2355
2356            // GOAWAY may have doomed some high-id streams (state forced to
2357            // Closed by `process_conn_frame`). Reap any that the peer abandoned.
2358            if self.goaway_received.is_some() {
2359                self.fail_goaway_doomed(&mut id_to_idx, &mut results);
2360            }
2361
2362            if let DispatchOutcome::Done(done_id) = outcome {
2363                if let Some(idx) = id_to_idx.remove(&done_id) {
2364                    let stream = self
2365                        .streams
2366                        .remove(&done_id)
2367                        .expect("Done stream must still be registered");
2368                    let mut built = build_response_from_stream_labelled(
2369                        stream,
2370                        Some(done_id),
2371                        reqs[idx].decompress,
2372                        trace,
2373                    );
2374                    if let Ok(resp) = &mut built {
2375                        resp.tls = self.tls_info.clone();
2376                    }
2377                    results[idx] = Some(built);
2378                    // A slot freed up — start the next queued request.
2379                    self.start_queued(&mut queue, &mut id_to_idx, &mut results, reqs, trace);
2380                }
2381            }
2382
2383            // Make outbound progress on every loop turn: a WINDOW_UPDATE we
2384            // just processed may have unblocked a stalled body.
2385            if self.flush_pending(trace).is_err() {
2386                let e = Error::BadResponse("write error pumping multiplexed sends".into());
2387                self.fail_all_outstanding(&id_to_idx, &mut results, &queue, &e);
2388                break;
2389            }
2390        }
2391
2392        self.prune_completed_streams();
2393        collect_results(results)
2394    }
2395
2396    /// Pump pending sends and flush the transport. Small wrapper so the loop
2397    /// reads cleanly.
2398    fn flush_pending(&mut self, trace: &mut dyn Write) -> Result<()> {
2399        self.pump_pending_sends(trace)?;
2400        self.tls.flush().map_err(Error::Io)
2401    }
2402
2403    /// Start queued requests until we hit the concurrency limit or run out.
2404    /// For each started request, open a stream, write its HEADERS, stage its
2405    /// body, and record the id→index mapping. Open failures are recorded as
2406    /// per-request errors (the request simply doesn't run).
2407    fn start_queued(
2408        &mut self,
2409        queue: &mut VecDeque<usize>,
2410        id_to_idx: &mut HashMap<u32, usize>,
2411        results: &mut [Option<Result<Response>>],
2412        reqs: &[Request],
2413        trace: &mut dyn Write,
2414    ) {
2415        while !queue.is_empty() {
2416            // Respect the peer's concurrency cap based on currently-open streams.
2417            if (self.streams.len() as u64) >= self.peer.max_concurrent_streams as u64 {
2418                break;
2419            }
2420            let idx = *queue.front().unwrap();
2421            let id = match self.open_stream() {
2422                Ok(id) => id,
2423                Err(e) => {
2424                    // Can't open any more right now (GOAWAY boundary / id space
2425                    // / concurrency). If it's the concurrency limit we just
2426                    // stop; otherwise the request fails. Distinguish by retry:
2427                    // a GOAWAY/exhaustion error is permanent for this request.
2428                    queue.pop_front();
2429                    results[idx] = Some(Err(e));
2430                    continue;
2431                }
2432            };
2433            queue.pop_front();
2434            let req = &reqs[idx];
2435            trace_request_labelled(req, id, trace);
2436            if let Err(e) = self.stage_request_on(id, req) {
2437                // HEADERS write failed — record the error and drop the stream.
2438                self.streams.remove(&id);
2439                results[idx] = Some(Err(e));
2440                continue;
2441            }
2442            id_to_idx.insert(id, idx);
2443        }
2444    }
2445
2446    /// On a dispatch error, if exactly one open stream was just forced to
2447    /// `Closed` (the RST_STREAM target) and it has no complete response, treat
2448    /// the error as scoped to that one stream: return its request index and
2449    /// drop it from the live map. Returns `None` if the error is not cleanly
2450    /// attributable to a single stream (caller treats it as connection-fatal).
2451    fn take_stream_error(&mut self, id_to_idx: &mut HashMap<u32, usize>) -> Option<usize> {
2452        let mut culprit: Option<u32> = None;
2453        for (&id, s) in self.streams.iter() {
2454            if !id_to_idx.contains_key(&id) {
2455                continue;
2456            }
2457            let complete = matches!(s.state, StreamState::Closed | StreamState::HalfClosedRemote)
2458                && s.end_stream_recv
2459                && s.response_headers.is_some();
2460            if matches!(s.state, StreamState::Closed) && !complete {
2461                if culprit.is_some() {
2462                    // More than one candidate — ambiguous, treat as fatal.
2463                    return None;
2464                }
2465                culprit = Some(id);
2466            }
2467        }
2468        let id = culprit?;
2469        let idx = id_to_idx.remove(&id)?;
2470        self.streams.remove(&id);
2471        Some(idx)
2472    }
2473
2474    /// After a GOAWAY, any in-flight stream with id above the peer's
2475    /// last-stream-id is abandoned: `process_conn_frame` already forced its
2476    /// state to `Closed`. Fail the matching requests and drop those streams,
2477    /// but only when they have no complete response of their own.
2478    fn fail_goaway_doomed(
2479        &mut self,
2480        id_to_idx: &mut HashMap<u32, usize>,
2481        results: &mut [Option<Result<Response>>],
2482    ) {
2483        let last = match self.goaway_received {
2484            Some(l) => l,
2485            None => return,
2486        };
2487        let doomed: Vec<u32> = id_to_idx
2488            .keys()
2489            .copied()
2490            .filter(|id| *id > last)
2491            .filter(|id| {
2492                // Don't clobber a stream that actually completed.
2493                match self.streams.get(id) {
2494                    Some(s) => !(s.end_stream_recv && s.response_headers.is_some()),
2495                    None => true,
2496                }
2497            })
2498            .collect();
2499        for id in doomed {
2500            if let Some(idx) = id_to_idx.remove(&id) {
2501                self.streams.remove(&id);
2502                results[idx] = Some(Err(Error::BadResponse(format!(
2503                    "stream {id} abandoned by GOAWAY (last-stream-id={last})"
2504                ))));
2505            }
2506        }
2507    }
2508
2509    /// Fail every request that has no result yet: all in-flight streams plus
2510    /// everything still queued. Used when the connection itself is lost.
2511    fn fail_all_outstanding(
2512        &self,
2513        id_to_idx: &HashMap<u32, usize>,
2514        results: &mut [Option<Result<Response>>],
2515        queue: &VecDeque<usize>,
2516        err: &Error,
2517    ) {
2518        for &idx in id_to_idx.values() {
2519            if results[idx].is_none() {
2520                results[idx] = Some(Err(clone_error(err)));
2521            }
2522        }
2523        for &idx in queue.iter() {
2524            if results[idx].is_none() {
2525                results[idx] = Some(Err(clone_error(err)));
2526            }
2527        }
2528    }
2529
2530    /// Read one frame from the wire and route it to the right place. The
2531    /// connection-scoped frames (SETTINGS / PING / GOAWAY / WINDOW_UPDATE on
2532    /// stream 0) are handled here directly; stream-scoped frames are looked
2533    /// up in `self.streams` and dispatched.
2534    fn read_and_dispatch(&mut self, sink: Option<&mut dyn Write>) -> Result<DispatchOutcome> {
2535        let frame = match read_frame(&mut self.tls) {
2536            Ok(f) => f,
2537            Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => {
2538                return Err(Error::UnexpectedEof);
2539            }
2540            Err(e) => return Err(Error::Io(e)),
2541        };
2542        self.process_frame(frame, sink)
2543    }
2544
2545    /// Apply one already-read frame. Split out from `read_and_dispatch` so
2546    /// tests can drive the dispatch ladder with synthetic frames.
2547    fn process_frame(
2548        &mut self,
2549        frame: Frame,
2550        sink: Option<&mut dyn Write>,
2551    ) -> Result<DispatchOutcome> {
2552        // RFC 9113 §6.10: between HEADERS-without-END_HEADERS and the
2553        // matching final CONTINUATION on the same stream, NO other frame
2554        // type — and no HEADERS/CONTINUATION on any other stream — may
2555        // appear. Enforce that gate up front.
2556        if let Some(awaiting) = self.expecting_continuation {
2557            let ok = frame.typ == F_CONTINUATION && frame.stream_id == awaiting;
2558            if !ok {
2559                return Err(Error::BadResponse(format!(
2560                    "expected CONTINUATION on stream {awaiting}, got type=0x{:x} stream={}",
2561                    frame.typ, frame.stream_id
2562                )));
2563            }
2564        }
2565
2566        // Flood / no-progress accounting. Both the single-stream and
2567        // multiplexed loops reach the wire through this method, so accounting
2568        // here covers both paths. We snapshot `typ`/`flags` up front so the
2569        // budget can be billed without cloning the payload.
2570        let frame_typ = frame.typ;
2571        let frame_flags = frame.flags;
2572        // Cheap-control-frame floods are billed BEFORE dispatch: a successful
2573        // RST_STREAM dispatch returns a per-stream `Err`, so billing it here
2574        // (rather than after) is what makes the Rapid-Reset budget bite.
2575        self.budget.record_control_frame(frame_typ, frame_flags)?;
2576        // Handlers signal real forward progress by setting `self.made_progress`
2577        // (cleared here before each dispatch); a terminal `Done` outcome also
2578        // counts. The no-progress streak is judged only on a successful
2579        // dispatch (an error already aborts, or is handled per-stream upstream).
2580        self.made_progress = false;
2581        let outcome = if frame.stream_id == 0 {
2582            self.process_conn_frame(frame)
2583        } else {
2584            self.process_stream_frame(frame, sink)
2585        }?;
2586        let progress = self.made_progress || matches!(outcome, DispatchOutcome::Done(_));
2587        self.budget.record_progress(progress)?;
2588        Ok(outcome)
2589    }
2590
2591    /// Connection-scoped frames (stream_id == 0): SETTINGS / SETTINGS-ACK,
2592    /// PING / PING-ACK, GOAWAY, WINDOW_UPDATE on stream 0. PRIORITY at stream
2593    /// 0 would be a protocol error but we just ignore it.
2594    fn process_conn_frame(&mut self, frame: Frame) -> Result<DispatchOutcome> {
2595        match frame.typ {
2596            F_SETTINGS if frame.flags & FLAG_ACK == 0 => {
2597                let old_initial = self.peer.initial_window_size;
2598                let old_header_table_size = self.peer.header_table_size;
2599                self.peer.apply_settings_payload(&frame.payload)?;
2600                let new_initial = self.peer.initial_window_size;
2601                if new_initial != old_initial {
2602                    // §6.9.2: retroactively shift every existing stream's
2603                    // send window by (new - old). The conn window is
2604                    // untouched.
2605                    for s in self.streams.values_mut() {
2606                        s.send_window.apply_initial_window_change(new_initial)?;
2607                    }
2608                }
2609                if self.peer.header_table_size != old_header_table_size {
2610                    // RFC 7541 §6.3: the encoder MUST emit a dynamic-table-
2611                    // size-update signal in the next header block to
2612                    // acknowledge the new cap. `set_peer_max_table_size`
2613                    // also evicts entries immediately so we never exceed it.
2614                    self.encoder
2615                        .set_peer_max_table_size(self.peer.header_table_size as usize);
2616                }
2617                let ack = Frame {
2618                    typ: F_SETTINGS,
2619                    flags: FLAG_ACK,
2620                    stream_id: 0,
2621                    payload: Vec::new(),
2622                };
2623                write_frame(&mut self.tls, &ack)?;
2624                self.tls.flush()?;
2625            }
2626            F_SETTINGS => { /* ACK from server: silently absorb. */ }
2627            F_PING if frame.flags & FLAG_ACK == 0 => {
2628                let pong = Frame {
2629                    typ: F_PING,
2630                    flags: FLAG_ACK,
2631                    stream_id: 0,
2632                    payload: frame.payload.clone(),
2633                };
2634                write_frame(&mut self.tls, &pong)?;
2635                self.tls.flush()?;
2636            }
2637            F_PING => {}
2638            F_WINDOW_UPDATE => {
2639                let inc = parse_window_update(&frame.payload)?;
2640                self.conn_send_window.apply_window_update(inc)?;
2641                // Raising the connection send window can unblock a stalled
2642                // body: count as forward progress.
2643                self.made_progress = true;
2644            }
2645            F_GOAWAY => {
2646                // First 4 bytes of payload are the last-stream-id (high bit
2647                // reserved). Anything earlier than that the peer promises to
2648                // process; ids ≥ this are abandoned. We refuse to allocate
2649                // any new id beyond `last` but allow existing streams ≤ last
2650                // to keep running.
2651                let last = if frame.payload.len() >= 4 {
2652                    u32::from_be_bytes([
2653                        frame.payload[0],
2654                        frame.payload[1],
2655                        frame.payload[2],
2656                        frame.payload[3],
2657                    ]) & 0x7fff_ffff
2658                } else {
2659                    0
2660                };
2661                self.goaway_received = Some(last);
2662                // If any of our open streams has id > last, the peer will not
2663                // process them; mark them closed and let the caller see that.
2664                // (For now we just transition state; the body/headers stay
2665                // empty so the caller surfaces a BadResponse.)
2666                let doomed: Vec<u32> = self
2667                    .streams
2668                    .iter()
2669                    .filter(|(id, _)| **id > last)
2670                    .map(|(id, _)| *id)
2671                    .collect();
2672                for id in doomed {
2673                    if let Some(s) = self.streams.get_mut(&id) {
2674                        s.state = StreamState::Closed;
2675                    }
2676                }
2677            }
2678            _ => {
2679                // PRIORITY on stream 0 is technically a PROTOCOL_ERROR; we
2680                // tolerate by ignoring. Unknown frame types are explicitly
2681                // ignorable per RFC 9113 §4.1.
2682            }
2683        }
2684        Ok(DispatchOutcome::Continue)
2685    }
2686
2687    /// Stream-scoped frames (stream_id != 0). Validates the per-stream state
2688    /// machine and applies the frame.
2689    fn process_stream_frame(
2690        &mut self,
2691        frame: Frame,
2692        sink: Option<&mut dyn Write>,
2693    ) -> Result<DispatchOutcome> {
2694        match frame.typ {
2695            F_HEADERS => self.process_headers(frame),
2696            F_CONTINUATION => self.process_continuation(frame),
2697            F_DATA => self.process_data(frame, sink),
2698            F_RST_STREAM => self.process_rst(frame),
2699            F_WINDOW_UPDATE => {
2700                let inc = parse_window_update(&frame.payload)?;
2701                if let Some(s) = self.streams.get_mut(&frame.stream_id) {
2702                    s.send_window.apply_window_update(inc)?;
2703                    // Raising a live stream's send window can unblock a stalled
2704                    // body: count as forward progress. A WINDOW_UPDATE on an
2705                    // unknown / closed stream (dropped below) is NOT progress —
2706                    // it must not be usable to defeat the no-progress guard.
2707                    self.made_progress = true;
2708                }
2709                // WINDOW_UPDATE on an unknown / closed stream: silently drop.
2710                Ok(DispatchOutcome::Continue)
2711            }
2712            F_PUSH_PROMISE => {
2713                // We disabled push (ENABLE_PUSH=0); any PUSH_PROMISE is a
2714                // protocol violation by the peer.
2715                Err(Error::BadResponse(
2716                    "received PUSH_PROMISE despite SETTINGS_ENABLE_PUSH=0".into(),
2717                ))
2718            }
2719            _ => {
2720                // PRIORITY and unknown types — ignore per §4.1.
2721                Ok(DispatchOutcome::Continue)
2722            }
2723        }
2724    }
2725
2726    fn process_headers(&mut self, frame: Frame) -> Result<DispatchOutcome> {
2727        // Strip PADDED / PRIORITY framing from the payload to find the actual
2728        // header-block fragment.
2729        let mut payload = frame.payload.as_slice();
2730        let mut pad_len = 0usize;
2731        if frame.flags & FLAG_PADDED != 0 {
2732            if payload.is_empty() {
2733                return Err(Error::BadResponse(
2734                    "HEADERS PADDED with empty payload".into(),
2735                ));
2736            }
2737            pad_len = payload[0] as usize;
2738            payload = &payload[1..];
2739        }
2740        if frame.flags & FLAG_PRIORITY != 0 {
2741            if payload.len() < 5 {
2742                return Err(Error::BadResponse(
2743                    "HEADERS PRIORITY with insufficient payload".into(),
2744                ));
2745            }
2746            payload = &payload[5..];
2747        }
2748        if payload.len() < pad_len {
2749            return Err(Error::BadResponse(
2750                "HEADERS padding overruns payload".into(),
2751            ));
2752        }
2753        let frag = &payload[..payload.len() - pad_len];
2754        let end_headers = frame.flags & FLAG_END_HEADERS != 0;
2755        let end_stream = frame.flags & FLAG_END_STREAM != 0;
2756
2757        let stream_id = frame.stream_id;
2758        // Server push: a HEADERS on a stream we never opened with an even id
2759        // (or a higher odd id from the peer) violates ENABLE_PUSH=0.
2760        let known = self.streams.contains_key(&stream_id);
2761        if !known {
2762            return Err(Error::BadResponse(format!(
2763                "HEADERS on unknown stream {stream_id} (server push disabled)"
2764            )));
2765        }
2766
2767        // State check: HEADERS on a Closed stream is ignored (trailers after
2768        // close, RFC 9113 §5.1). Any other illegal state is an error.
2769        let state = self.streams.get(&stream_id).unwrap().state;
2770        if state == StreamState::Closed {
2771            // Drain the fragment but do not decode; this keeps the decoder's
2772            // dynamic table consistent with the peer's view (HPACK requires
2773            // the receiver to process header blocks even if it ignores them
2774            // logically — RFC 7541 §2.2). The peer sent end_headers either on
2775            // this frame or via CONTINUATION; for simplicity we only honour
2776            // it inline (we already require end_headers immediately for
2777            // closed-stream trailers since we don't track expecting_continuation
2778            // for closed streams in the test surface). Decoding errors propagate.
2779            if end_headers {
2780                let _ = self.decoder.decode_block(frag)?;
2781            } else {
2782                // Conservatively buffer on the closed stream so the
2783                // CONTINUATION still finds its target.
2784                self.streams
2785                    .get_mut(&stream_id)
2786                    .unwrap()
2787                    .push_header_fragment(frag)?;
2788                self.expecting_continuation = Some(stream_id);
2789            }
2790            return Ok(DispatchOutcome::Continue);
2791        }
2792        let new_state = state.recv_headers(end_stream)?;
2793
2794        let s = self.streams.get_mut(&stream_id).unwrap();
2795        s.push_header_fragment(frag)?;
2796        if end_stream {
2797            s.end_stream_recv = true;
2798        }
2799        if end_headers {
2800            // Decode now; clear the buffer.
2801            let block = std::mem::take(&mut s.headers_buf);
2802            // Drop the &mut borrow before reaching for the decoder.
2803            let decoded = self.decoder.decode_block(&block)?;
2804            let s = self.streams.get_mut(&stream_id).unwrap();
2805            s.response_headers = Some(decoded);
2806            s.state = new_state;
2807            self.expecting_continuation = None;
2808            // A header block completed: forward progress.
2809            self.made_progress = true;
2810        } else {
2811            s.state = new_state;
2812            self.expecting_continuation = Some(stream_id);
2813        }
2814
2815        let done = matches!(
2816            self.streams.get(&stream_id).unwrap().state,
2817            StreamState::Closed | StreamState::HalfClosedRemote
2818        ) && self.streams.get(&stream_id).unwrap().end_stream_recv
2819            && self
2820                .streams
2821                .get(&stream_id)
2822                .unwrap()
2823                .response_headers
2824                .is_some();
2825        Ok(if done {
2826            DispatchOutcome::Done(stream_id)
2827        } else {
2828            DispatchOutcome::Continue
2829        })
2830    }
2831
2832    fn process_continuation(&mut self, frame: Frame) -> Result<DispatchOutcome> {
2833        let stream_id = frame.stream_id;
2834        // §6.10: CONTINUATION must match the stream we set as expecting.
2835        match self.expecting_continuation {
2836            Some(awaiting) if awaiting == stream_id => {}
2837            _ => {
2838                return Err(Error::BadResponse(format!(
2839                    "unexpected CONTINUATION on stream {stream_id}"
2840                )));
2841            }
2842        }
2843        let s = self.streams.get_mut(&stream_id).ok_or_else(|| {
2844            Error::BadResponse(format!("CONTINUATION on unknown stream {stream_id}"))
2845        })?;
2846        s.push_header_fragment(&frame.payload)?;
2847        let end_headers = frame.flags & FLAG_END_HEADERS != 0;
2848        if end_headers {
2849            let block = std::mem::take(&mut s.headers_buf);
2850            let decoded = self.decoder.decode_block(&block)?;
2851            let s = self.streams.get_mut(&stream_id).unwrap();
2852            if s.state != StreamState::Closed {
2853                s.response_headers = Some(decoded);
2854            }
2855            self.expecting_continuation = None;
2856            // A header block completed: forward progress.
2857            self.made_progress = true;
2858        }
2859        let done = matches!(
2860            self.streams.get(&stream_id).unwrap().state,
2861            StreamState::Closed | StreamState::HalfClosedRemote
2862        ) && self.streams.get(&stream_id).unwrap().end_stream_recv
2863            && self
2864                .streams
2865                .get(&stream_id)
2866                .unwrap()
2867                .response_headers
2868                .is_some();
2869        Ok(if done {
2870            DispatchOutcome::Done(stream_id)
2871        } else {
2872            DispatchOutcome::Continue
2873        })
2874    }
2875
2876    fn process_data(
2877        &mut self,
2878        frame: Frame,
2879        sink: Option<&mut dyn Write>,
2880    ) -> Result<DispatchOutcome> {
2881        let stream_id = frame.stream_id;
2882        let frame_bytes = frame.payload.len();
2883        // Connection window is billed regardless of whether the stream is
2884        // known — that's what the RFC requires.
2885        self.conn_recv_window.consume(frame_bytes);
2886        // RFC 9113 §6.9.1: a receiver MUST treat a peer that sends more DATA
2887        // than the advertised connection window as a FLOW_CONTROL_ERROR and
2888        // tear down the connection. A conformant peer keeps `available` >= 0;
2889        // only a strict overrun drives it negative (an exactly-full window
2890        // reaching 0 is legitimate and must NOT be rejected).
2891        if self.conn_recv_window.available < 0 {
2892            return Err(Error::BadResponse(
2893                "http2: flow-control window exceeded by peer".into(),
2894            ));
2895        }
2896        let known = self.streams.contains_key(&stream_id);
2897        if !known {
2898            // DATA for an unknown / already-evicted stream: drop silently per
2899            // RFC 9113 §5.1 ("frames for closed streams MAY be ignored").
2900            // We still need to replenish the conn window so the peer can keep
2901            // sending.
2902            if let Some(upd) = self.conn_recv_window.replenish() {
2903                write_frame(&mut self.tls, &upd)?;
2904                self.tls.flush()?;
2905            }
2906            return Ok(DispatchOutcome::Continue);
2907        }
2908        let state = self.streams.get(&stream_id).unwrap().state;
2909        if state == StreamState::Closed {
2910            // Per §5.1, DATA on a closed stream is a STREAM_CLOSED error;
2911            // we surface it as such.
2912            return Err(Error::BadResponse(format!(
2913                "DATA on closed stream {stream_id}"
2914            )));
2915        }
2916        let end_stream = frame.flags & FLAG_END_STREAM != 0;
2917        let new_state = state.recv_data(end_stream)?;
2918
2919        let s = self.streams.get_mut(&stream_id).unwrap();
2920        s.recv_window.consume(frame_bytes);
2921        // Per-stream counterpart of the connection-level check above
2922        // (RFC 9113 §6.9.1). A negative window means the peer sent more DATA
2923        // on this stream than we granted; exactly 0 is still legitimate.
2924        if s.recv_window.available < 0 {
2925            return Err(Error::BadResponse(
2926                "http2: flow-control window exceeded by peer".into(),
2927            ));
2928        }
2929
2930        // Strip padding to find the application bytes.
2931        let mut payload = frame.payload.as_slice();
2932        if frame.flags & FLAG_PADDED != 0 {
2933            if payload.is_empty() {
2934                return Err(Error::BadResponse("DATA PADDED with empty payload".into()));
2935            }
2936            let pad_len = payload[0] as usize;
2937            payload = &payload[1..];
2938            if payload.len() < pad_len {
2939                return Err(Error::BadResponse("DATA padding overruns payload".into()));
2940            }
2941            payload = &payload[..payload.len() - pad_len];
2942        }
2943        // Stream the body straight to the caller's sink when possible: a sink
2944        // is present, nothing has been buffered yet (so byte order is kept),
2945        // and the response is not content-encoded (encoded bodies need the
2946        // buffered decode path). Otherwise accumulate into `body`.
2947        let encoded = s.response_headers.as_ref().is_some_and(|h| {
2948            h.iter()
2949                .any(|(k, _)| k.eq_ignore_ascii_case("content-encoding"))
2950        });
2951        let to_sink = sink.is_some() && !encoded && s.body.is_empty();
2952        // Cumulative body cap for the *buffered* path. HTTP/2 flow control
2953        // auto-replenishes (see the `replenish()` calls below), so it never
2954        // stops a server that streams DATA forever — only an absolute ceiling
2955        // does. Streamed-to-disk bytes aren't held in memory, so they are
2956        // bounded by the sink (e.g. `--max-filesize`), like curl `-o`.
2957        if !to_sink && s.body.len().saturating_add(payload.len()) > MAX_RESPONSE_BYTES {
2958            return Err(Error::BadResponse(
2959                "response body exceeds size limit".into(),
2960            ));
2961        }
2962        // A real body byte landed: this frame made forward progress, so it
2963        // resets the no-progress flood counter. An empty DATA frame appends
2964        // nothing and is (correctly) NOT counted as progress — that is exactly
2965        // the empty-DATA spin we are guarding against. (Recorded after the
2966        // `s` borrow ends, below.)
2967        let appended_body = !payload.is_empty();
2968        if to_sink {
2969            if let Some(w) = sink {
2970                w.write_all(payload)?;
2971            }
2972            s.streamed_len += payload.len() as u64;
2973        } else {
2974            s.body.extend_from_slice(payload);
2975        }
2976        if end_stream {
2977            s.end_stream_recv = true;
2978        }
2979        s.state = new_state;
2980        if appended_body {
2981            self.made_progress = true;
2982        }
2983
2984        // Replenish either window if it's dropped below half. Both checks are
2985        // independent — a single large DATA frame can fire both.
2986        if let Some(upd) = self.conn_recv_window.replenish() {
2987            write_frame(&mut self.tls, &upd)?;
2988        }
2989        if let Some(upd) = self
2990            .streams
2991            .get_mut(&stream_id)
2992            .unwrap()
2993            .recv_window
2994            .replenish(stream_id)
2995        {
2996            write_frame(&mut self.tls, &upd)?;
2997        }
2998        self.tls.flush()?;
2999
3000        let s = self.streams.get(&stream_id).unwrap();
3001        let done = matches!(s.state, StreamState::Closed | StreamState::HalfClosedRemote)
3002            && s.end_stream_recv
3003            && s.response_headers.is_some();
3004        Ok(if done {
3005            DispatchOutcome::Done(stream_id)
3006        } else {
3007            DispatchOutcome::Continue
3008        })
3009    }
3010
3011    fn process_rst(&mut self, frame: Frame) -> Result<DispatchOutcome> {
3012        let stream_id = frame.stream_id;
3013        let code = if frame.payload.len() >= 4 {
3014            u32::from_be_bytes([
3015                frame.payload[0],
3016                frame.payload[1],
3017                frame.payload[2],
3018                frame.payload[3],
3019            ])
3020        } else {
3021            0
3022        };
3023        match self.streams.get_mut(&stream_id) {
3024            Some(s) => {
3025                // RST_STREAM on closed → silently ignore (RFC 9113 §5.1).
3026                if s.state == StreamState::Closed {
3027                    return Ok(DispatchOutcome::Continue);
3028                }
3029                s.state = s.state.recv_rst()?;
3030                Err(Error::BadResponse(format!(
3031                    "stream {stream_id} reset by server, error code {code}"
3032                )))
3033            }
3034            None => {
3035                // RST_STREAM on unknown stream: harmless, ignore.
3036                Ok(DispatchOutcome::Continue)
3037            }
3038        }
3039    }
3040}
3041
3042/// Split an HPACK-encoded header block into one HEADERS frame followed by
3043/// zero or more CONTINUATION frames, each ≤ `max_frame_size` octets
3044/// (RFC 9113 §6.10).
3045///
3046/// `end_stream` controls FLAG_END_STREAM on the HEADERS frame; FLAG_END_HEADERS
3047/// is set automatically on the final fragment (which is the HEADERS frame
3048/// itself when one fragment suffices). `stream_id` is the stream the caller
3049/// has allocated for this request.
3050///
3051/// Edge cases:
3052/// - `header_block.len() == max_frame_size` produces one HEADERS frame.
3053/// - `header_block.is_empty()` produces one HEADERS frame with empty payload
3054///   (impossible in practice — we always emit at least the four pseudo-headers
3055///   — but the function still handles it cleanly).
3056/// - Any combination of CONTINUATION fragments is emitted with no flags set
3057///   except FLAG_END_HEADERS on the last one. The caller is responsible for
3058///   writing them back-to-back with no interleaved frames on the wire, which
3059///   our single-threaded writer guarantees.
3060fn fragment_header_block(
3061    stream_id: u32,
3062    header_block: &[u8],
3063    max_frame_size: usize,
3064    end_stream: bool,
3065) -> Vec<Frame> {
3066    debug_assert!(max_frame_size > 0, "max_frame_size must be > 0");
3067    let mut frames = Vec::new();
3068
3069    if header_block.is_empty() {
3070        let mut flags = FLAG_END_HEADERS;
3071        if end_stream {
3072            flags |= FLAG_END_STREAM;
3073        }
3074        frames.push(Frame {
3075            typ: F_HEADERS,
3076            flags,
3077            stream_id,
3078            payload: Vec::new(),
3079        });
3080        return frames;
3081    }
3082
3083    let total_chunks = header_block.len().div_ceil(max_frame_size);
3084    for (i, chunk) in header_block.chunks(max_frame_size).enumerate() {
3085        let is_last = i + 1 == total_chunks;
3086        if i == 0 {
3087            let mut flags = 0u8;
3088            if end_stream {
3089                flags |= FLAG_END_STREAM;
3090            }
3091            if is_last {
3092                flags |= FLAG_END_HEADERS;
3093            }
3094            frames.push(Frame {
3095                typ: F_HEADERS,
3096                flags,
3097                stream_id,
3098                payload: chunk.to_vec(),
3099            });
3100        } else {
3101            let flags = if is_last { FLAG_END_HEADERS } else { 0 };
3102            frames.push(Frame {
3103                typ: F_CONTINUATION,
3104                flags,
3105                stream_id,
3106                payload: chunk.to_vec(),
3107            });
3108        }
3109    }
3110    frames
3111}
3112
3113/// Clamp the next DATA chunk size to the smallest of: `max_frame_size`, the
3114/// remaining bytes to send, and the currently available send window.
3115///
3116/// Extracted so the chunking logic can be unit-tested in isolation from the
3117/// I/O loop. Returns 0 only when the send window is depleted; callers must
3118/// then wait for a WINDOW_UPDATE before retrying.
3119fn next_data_chunk_size(max_frame_size: usize, available: i64, remaining: usize) -> usize {
3120    if available <= 0 {
3121        return 0;
3122    }
3123    let cap_window = available.min(remaining as i64).min(max_frame_size as i64);
3124    cap_window as usize
3125}
3126
3127// ---------------------------------------------------------------------------
3128// Connection pool.
3129// ---------------------------------------------------------------------------
3130//
3131// A process-wide, lazy-init pool of idle HTTP/2 connections keyed by
3132// `(scheme, host, port)`. The goal is conservative: avoid the TCP+TLS+h2
3133// preface handshake on repeat requests to the same authority. We are still
3134// fully synchronous (no async / threads / executor) — two threads that both
3135// hit the same checked-out conn will serialize on its `Mutex`. That gives
3136// "sequential multiplexing" reuse, not concurrent streams on one conn; the
3137// latter would require I/O multiplexing we don't have. Saving the handshake
3138// is the bulk of the win anyway.
3139//
3140// Connections with non-default TLS opts (`verify_tls=false` or `ca_bundle`
3141// set) bypass the pool entirely. Putting those into the same map as default
3142// conns risks reusing a "skip-verify" session for a future caller that did
3143// NOT ask to skip verification — silent loss of TLS verification. The cheap
3144// fix is to refuse to pool them; a future task can either (a) add the TLS
3145// opts into the key, or (b) maintain separate pools per opts profile. We
3146// pick the cheap fix and document it inline at the call site in `send()`.
3147
3148/// Map key for a pooled HTTP/2 connection. URL userinfo, path, and query
3149/// are intentionally absent — they don't affect TLS reuse.
3150#[derive(Hash, Eq, PartialEq, Clone, Debug)]
3151pub(crate) struct PoolKey {
3152    scheme: String,
3153    host: String,
3154    port: u16,
3155    /// Caller-supplied partition key (e.g. top-level site); isolates pooled h2
3156    /// connections per partition. `None` for unpartitioned requests.
3157    partition: Option<String>,
3158}
3159
3160impl PoolKey {
3161    fn from_request(req: &Request) -> Self {
3162        PoolKey {
3163            scheme: req.url.scheme.clone(),
3164            host: req.url.host.clone(),
3165            port: req.url.port,
3166            partition: req.partition_key.clone(),
3167        }
3168    }
3169}
3170
3171// The HTTP/2 pool shares the runtime-tunable size limits with the HTTP/1.1
3172// pool; see [`crate::pool::configure`] and `crate::pool::{per_key_cap,
3173// global_cap}`.
3174
3175/// One pooled connection's transport type. We only pool the production
3176/// transport — `TlsStream<TcpStream>` over `connect_over_tls`. Test fakes do
3177/// not use the pool; the pool tests construct `PoolInner` directly with
3178/// `Arc<Mutex<Connection<S>>>` values built from `FakeTls` (i.e. the pool
3179/// API is generic over the transport so tests can drive it without I/O).
3180type PooledConn<S> = Arc<Mutex<Connection<S>>>;
3181
3182/// The pool's data is internal: a `HashMap` of vectors of `Arc<Mutex<Conn>>`.
3183/// The outer `Mutex<PoolInner>` is held only during the brief map mutations.
3184pub(crate) struct PoolInner<S: Read + Write> {
3185    entries: HashMap<PoolKey, Vec<PooledConn<S>>>,
3186}
3187
3188impl<S: Read + Write> PoolInner<S> {
3189    fn new() -> Self {
3190        PoolInner {
3191            entries: HashMap::new(),
3192        }
3193    }
3194
3195    /// Pop one idle conn for `key`, if any. We pop from the back so reuse is
3196    /// LIFO: most recently used = most likely still alive on the wire.
3197    fn checkout(&mut self, key: &PoolKey) -> Option<PooledConn<S>> {
3198        let bucket = self.entries.get_mut(key)?;
3199        let conn = bucket.pop();
3200        if bucket.is_empty() {
3201            self.entries.remove(key);
3202        }
3203        conn
3204    }
3205
3206    /// Return a conn to the pool. Enforces both caps; on overflow we drop the
3207    /// new conn rather than evicting an existing one (a warm conn we've used
3208    /// once already is more likely to survive the next request than a fresh
3209    /// arrival).
3210    fn release(&mut self, key: PoolKey, conn: PooledConn<S>) {
3211        // Global cap takes precedence: even if this bucket has room, we
3212        // refuse to grow the pool past the global ceiling.
3213        let total: usize = self.entries.values().map(Vec::len).sum();
3214        if total >= crate::pool::global_cap() {
3215            return;
3216        }
3217        let bucket = self.entries.entry(key).or_default();
3218        if bucket.len() >= crate::pool::per_key_cap() {
3219            return;
3220        }
3221        bucket.push(conn);
3222    }
3223
3224    /// For tests/diagnostics: total number of pooled conns.
3225    #[cfg(test)]
3226    fn total_len(&self) -> usize {
3227        self.entries.values().map(Vec::len).sum()
3228    }
3229}
3230
3231/// Process-global pool of production HTTP/2 connections. `OnceLock` keeps
3232/// init lazy and lock-free after the first observed access; the inner
3233/// `Mutex` serializes the brief map updates.
3234static POOL: OnceLock<Mutex<PoolInner<TlsStream<TcpStream>>>> = OnceLock::new();
3235
3236fn global_pool() -> &'static Mutex<PoolInner<TlsStream<TcpStream>>> {
3237    POOL.get_or_init(|| Mutex::new(PoolInner::new()))
3238}
3239
3240/// Build a brand-new HTTP/2 connection: TCP → TLS (ALPN=h2) → preface.
3241/// Used both by `send()` on a cold path and indirectly by anything that
3242/// wants a fresh `Connection` (currently no other call sites).
3243type DialedH2 = (
3244    Connection<TlsStream<TcpStream>>,
3245    Option<crate::cancel::CancelGuard>,
3246);
3247
3248fn dial_h2(req: &Request, trace: &mut dyn Write) -> Result<DialedH2> {
3249    // Reuse the shared TCP dialer so the `*   Trying ...` / `* Connected to ...`
3250    // trace lines and the actual socket come from the same code as HTTP/1.1.
3251    // The cancel guard (when a token is attached) shuts the socket down on a
3252    // concurrent `cancel()`; the caller keeps it alive for the request.
3253    let start = std::time::Instant::now();
3254    let (tcp, cancel_guard, namelookup) = crate::http::tcp_connect_cancellable(req, trace)?;
3255    let connect = start.elapsed();
3256    // HTTPS-over-proxy: CONNECT to establish a transparent tunnel before
3257    // the TLS handshake. h2c (cleartext HTTP/2) over a proxy is rejected
3258    // higher up in `send()`, so by here we know scheme == "https".
3259    if let Some(p) = req
3260        .proxy
3261        .as_ref()
3262        .filter(|_| !crate::http::proxy_bypassed(req))
3263    {
3264        crate::http::connect_tunnel(&tcp, &req.url, p, trace)?;
3265    }
3266    let opts = crate::http::tls_opts_from(req, &[b"h2"])?;
3267    let tls = crate::tls::connect_over_tls(tcp, &req.url.host, opts)?;
3268    let appconnect = start.elapsed();
3269    crate::http::write_tls_info(&tls, trace);
3270    let negotiated_h2 = tls.alpn_selected().map(|p| p == b"h2").unwrap_or(false);
3271    if !negotiated_h2 {
3272        // Bail before emitting any request `>` lines — the caller (Auto mode)
3273        // will fall back to HTTP/1.1 on a fresh connection.
3274        return Err(Error::H2NotNegotiated);
3275    }
3276    let _ = writeln!(trace, "* using HTTP/2");
3277    let tls_info = crate::http::tls_info_from(&tls);
3278    let mut conn = Connection::new(tls, req.h2_recv_window)?;
3279    conn.tls_info = Some(tls_info);
3280    conn.dial_timing = crate::http::Timing {
3281        namelookup,
3282        connect: Some(connect),
3283        appconnect: Some(appconnect),
3284        pretransfer: Some(appconnect),
3285        ..Default::default()
3286    };
3287    Ok((conn, cancel_guard))
3288}
3289
3290/// True if `req`'s TLS options match what the pool can safely reuse. We
3291/// refuse to pool when verification is off or a custom CA bundle is set —
3292/// see the module comment above the pool definitions for the rationale.
3293fn pool_eligible(req: &Request) -> bool {
3294    req.verify_tls && req.ca_bundle.is_none()
3295}
3296
3297/// Send a single request/response over an HTTP/2 connection, reusing a
3298/// pooled connection for the same `(scheme, host, port)` when possible.
3299///
3300/// Flow:
3301///
3302/// 1. Build a `PoolKey` and check whether the request's TLS opts are pool-
3303///    eligible. Non-default opts (`-k` / `--cacert`) bypass the pool.
3304/// 2. On a pool hit, lock the conn's `Mutex`, sanity-check `is_usable`, run
3305///    one request on it. On success and still-usable, release back. On any
3306///    error during send/drive, drop the conn (its wire position may be
3307///    inconsistent — mid-frame, mid-CONTINUATION — and we cannot recover).
3308/// 3. On a pool miss, dial a fresh conn, run the request, and release on
3309///    success.
3310pub fn send(req: Request, trace: &mut dyn Write) -> Result<Response> {
3311    if req.url.scheme != "https" {
3312        // h2c (cleartext HTTP/2 with upgrade) is out of scope for v1.
3313        return Err(Error::UnsupportedScheme(format!(
3314            "http/2 over {} not supported",
3315            req.url.scheme
3316        )));
3317    }
3318
3319    let key = PoolKey::from_request(&req);
3320    let eligible = pool_eligible(&req);
3321
3322    // -------- Pool path --------
3323    // We make at most one attempt against a pooled conn. If the pooled
3324    // conn turns out to be unusable (or fails mid-request) we fall through
3325    // to the cold-dial path below; we don't loop popping more pooled conns,
3326    // because in practice a dead pooled conn is almost always the first
3327    // symptom of a dead idle pool — better to spend the handshake than
3328    // burn through every entry.
3329    if eligible {
3330        let pooled = {
3331            // Poison-tolerant locking (matches the HTTP/1.1 pool and the TLS
3332            // verify-posture isolation fix): a panic while another caller held
3333            // the pool lock must not wedge every future request. We only ever
3334            // mutate a small map under this lock, so an observer that proceeds
3335            // past poison sees a structurally valid (if possibly stale) pool.
3336            let mut guard = global_pool().lock().unwrap_or_else(|e| e.into_inner());
3337            guard.checkout(&key)
3338        };
3339        if let Some(arc) = pooled {
3340            // Hold the per-conn lock for the whole request — sequential
3341            // reuse only. The pool-wide lock has already been released.
3342            let mut conn_guard = arc.lock().unwrap_or_else(|e| e.into_inner());
3343            if conn_guard.is_usable() {
3344                let _ = writeln!(trace, "* Reusing existing connection from pool");
3345                match run_one_request(&mut conn_guard, &req, trace) {
3346                    Ok(resp) => {
3347                        let still_usable = conn_guard.is_usable();
3348                        drop(conn_guard);
3349                        if still_usable {
3350                            let mut guard = global_pool().lock().unwrap_or_else(|e| e.into_inner());
3351                            guard.release(key.clone(), arc);
3352                            let _ = writeln!(trace, "* Connection kept alive (pooled)");
3353                        } else {
3354                            let _ = writeln!(trace, "* Connection closed");
3355                        }
3356                        return Ok(resp);
3357                    }
3358                    Err(_e) => {
3359                        // Wire state may now be inconsistent. Drop the conn
3360                        // and fall through to a cold dial; the original error
3361                        // is intentionally discarded in favour of the
3362                        // (likely cleaner) error from the fresh attempt.
3363                        drop(conn_guard);
3364                        let _ = writeln!(
3365                            trace,
3366                            "* Pooled connection unusable (request failed); reconnecting"
3367                        );
3368                    }
3369                }
3370            } else {
3371                // Unusable on checkout: just drop, do not re-pool.
3372                let _ = writeln!(
3373                    trace,
3374                    "* Pooled connection unusable (connection closed); reconnecting"
3375                );
3376            }
3377        }
3378    }
3379
3380    // -------- Cold-dial path --------
3381    let (mut fresh, _cancel_guard) = dial_h2(&req, trace)?;
3382    let mut resp = run_one_request(&mut fresh, &req, trace)?;
3383    apply_dial_timing(&mut resp, &fresh);
3384    if eligible && fresh.is_usable() {
3385        let arc = Arc::new(Mutex::new(fresh));
3386        let mut guard = global_pool().lock().unwrap_or_else(|e| e.into_inner());
3387        guard.release(key, arc);
3388        let _ = writeln!(trace, "* Connection kept alive (pooled)");
3389    } else {
3390        let _ = writeln!(trace, "* Connection closed");
3391    }
3392    Ok(resp)
3393}
3394
3395/// Drive one request/response exchange on an already-established conn.
3396/// Factored out so both pool-hit and pool-miss paths share the same body.
3397fn run_one_request<S: Read + Write>(
3398    conn: &mut Connection<S>,
3399    req: &Request,
3400    trace: &mut dyn Write,
3401) -> Result<Response> {
3402    let stream_id = conn.open_stream()?;
3403    // Trace the request `>` lines right before they go on the wire, so the
3404    // trace reflects exactly what `send_request_on` is about to encode.
3405    trace_request(req, trace);
3406    conn.send_request_on(stream_id, req)?;
3407    if !req.body.is_empty() {
3408        let _ = writeln!(trace, "* uploading {} body bytes", req.body.len());
3409    }
3410    let stream = conn.drive_until_stream_done(stream_id)?;
3411    // Reap any other streams that completed while we were driving this one, so
3412    // a pooled connection's `streams` map doesn't grow across reuses.
3413    conn.prune_completed_streams();
3414    let mut resp = build_response_from_stream(stream, req.decompress, trace)?;
3415    // Surface the connection's negotiated TLS parameters (a property of the
3416    // live connection — reported on pooled reuse too).
3417    resp.tls = conn.tls_info.clone();
3418    Ok(resp)
3419}
3420
3421/// Translate a fully-received `Stream` into the public `Response` type.
3422/// Extracts the `:status` pseudo-header, drops any other pseudo-headers
3423/// (none are defined for responses but be conservative), and inherits the
3424/// accumulated body. The `<` trace lines are unlabelled (single-stream path);
3425/// the multiplexed driver uses [`build_response_from_stream_labelled`].
3426fn build_response_from_stream(
3427    stream: Stream,
3428    decompress: bool,
3429    trace: &mut dyn Write,
3430) -> Result<Response> {
3431    build_response_from_stream_labelled(stream, None, decompress, trace)
3432}
3433
3434/// Like [`build_response_from_stream`] but prefixes every `<` / `*` trace line
3435/// with `[stream N]` so concurrently-multiplexed responses stay readable when
3436/// their frames interleave on the wire.
3437fn build_response_from_stream_labelled(
3438    stream: Stream,
3439    label_id: Option<u32>,
3440    decompress: bool,
3441    trace: &mut dyn Write,
3442) -> Result<Response> {
3443    let headers = stream
3444        .response_headers
3445        .ok_or_else(|| Error::BadResponse("response ended before any HEADERS frame".into()))?;
3446
3447    let mut status: Option<u16> = None;
3448    let mut clean_headers: Vec<(String, String)> = Vec::with_capacity(headers.len());
3449    for (k, v) in headers {
3450        if k == ":status" {
3451            status = Some(
3452                v.parse::<u16>()
3453                    .map_err(|_| Error::BadResponse(format!("bad :status {v:?}")))?,
3454            );
3455        } else if k.starts_with(':') {
3456            // Other pseudo-headers (none defined for responses) — drop.
3457        } else {
3458            clean_headers.push((k, v));
3459        }
3460    }
3461    let status = status.ok_or_else(|| Error::BadResponse("response missing :status".into()))?;
3462
3463    // Response `<` trace, mirroring the HTTP/1.1 reader: a status line carrying
3464    // the HTTP/2 version + numeric status, then each header field in received
3465    // order (lowercase, as h2 delivers them), then a closing blank `< `.
3466    let tag = match label_id {
3467        Some(id) => format!("[stream {id}] "),
3468        None => String::new(),
3469    };
3470    let _ = writeln!(trace, "< {tag}HTTP/2 {status}");
3471    for (k, v) in &clean_headers {
3472        let _ = writeln!(trace, "< {tag}{k}: {v}");
3473    }
3474    let _ = writeln!(trace, "< {tag}");
3475
3476    let wire_len = stream.body.len();
3477    let _ = writeln!(trace, "* {tag}Received {wire_len} body bytes");
3478
3479    // Shared with HTTP/1.1 and HTTP/3: peel off any `Content-Encoding`
3480    // layer rsurl knows how to decode (gzip / deflate / x-gzip / identity).
3481    let (clean_headers, body) =
3482        crate::http::maybe_decode_body(clean_headers, stream.body, decompress, trace)?;
3483
3484    Ok(Response {
3485        status,
3486        reason: String::new(), // HTTP/2 has no reason phrase (RFC 9113 §8.3.1).
3487        version: "HTTP/2".to_string(),
3488        headers: clean_headers,
3489        body,
3490        timing: crate::http::Timing::default(),
3491        // Set by the buffered `send_to` redirect loop; empty on the raw
3492        // multiplexed path, where callers fall back to the request URL.
3493        final_url: String::new(),
3494        tls: None,
3495    })
3496}
3497
3498/// Like [`build_response_from_stream`] but for the streaming path: the body has
3499/// already been written to `sink` by [`Connection::process_data`] (so
3500/// `stream.body` is empty), unless it was a content-encoded response, which the
3501/// streaming path deliberately buffers — in that case decode it now and write
3502/// the plaintext to `sink`. The returned `Response` always carries an empty
3503/// `body` (the bytes are in the sink).
3504fn build_response_from_stream_streaming(
3505    stream: Stream,
3506    sink: &mut dyn Write,
3507    decompress: bool,
3508    trace: &mut dyn Write,
3509) -> Result<Response> {
3510    let headers = stream
3511        .response_headers
3512        .ok_or_else(|| Error::BadResponse("response ended before any HEADERS frame".into()))?;
3513
3514    let mut status: Option<u16> = None;
3515    let mut clean_headers: Vec<(String, String)> = Vec::with_capacity(headers.len());
3516    for (k, v) in headers {
3517        if k == ":status" {
3518            status = Some(
3519                v.parse::<u16>()
3520                    .map_err(|_| Error::BadResponse(format!("bad :status {v:?}")))?,
3521            );
3522        } else if !k.starts_with(':') {
3523            clean_headers.push((k, v));
3524        }
3525    }
3526    let status = status.ok_or_else(|| Error::BadResponse("response missing :status".into()))?;
3527
3528    let _ = writeln!(trace, "< HTTP/2 {status}");
3529    for (k, v) in &clean_headers {
3530        let _ = writeln!(trace, "< {k}: {v}");
3531    }
3532    let _ = writeln!(trace, "< ");
3533    let total = stream.body.len() as u64 + stream.streamed_len;
3534    let _ = writeln!(trace, "* Received {total} body bytes (streamed)");
3535
3536    // `stream.body` is non-empty only on the buffered fallback (content-encoded
3537    // response): decode it and write the plaintext through to the sink.
3538    let (clean_headers, body) =
3539        crate::http::maybe_decode_body(clean_headers, stream.body, decompress, trace)?;
3540    if !body.is_empty() {
3541        sink.write_all(&body)?;
3542    }
3543
3544    Ok(Response {
3545        status,
3546        reason: String::new(),
3547        version: "HTTP/2".to_string(),
3548        headers: clean_headers,
3549        body: Vec::new(),
3550        timing: crate::http::Timing::default(),
3551        final_url: String::new(),
3552        tls: None,
3553    })
3554}
3555
3556/// Streaming counterpart of [`run_one_request`]: response DATA is written to
3557/// `sink` as it arrives rather than buffered (see
3558/// [`Connection::drive_until_stream_done_to`]).
3559fn run_one_request_to<S: Read + Write>(
3560    conn: &mut Connection<S>,
3561    req: &Request,
3562    sink: &mut dyn Write,
3563    on_head: Option<crate::http::HeadObserver<'_>>,
3564    trace: &mut dyn Write,
3565) -> Result<Response> {
3566    let stream_id = conn.open_stream()?;
3567    trace_request(req, trace);
3568    conn.send_request_on(stream_id, req)?;
3569    let stream = conn.drive_until_stream_done_to(stream_id, Some(sink), on_head)?;
3570    conn.prune_completed_streams();
3571    let mut resp = build_response_from_stream_streaming(stream, sink, req.decompress, trace)?;
3572    resp.tls = conn.tls_info.clone();
3573    Ok(resp)
3574}
3575
3576/// Stream an HTTP/2 response body straight to `sink` instead of buffering it.
3577/// Always cold-dials (the streaming path does not pool) and closes the
3578/// connection afterward; the returned [`Response`] carries an empty `body`.
3579pub fn send_to(
3580    req: Request,
3581    sink: &mut dyn Write,
3582    on_head: Option<crate::http::HeadObserver<'_>>,
3583    trace: &mut dyn Write,
3584) -> Result<Response> {
3585    if req.url.scheme != "https" {
3586        return Err(Error::UnsupportedScheme(format!(
3587            "http/2 over {} not supported",
3588            req.url.scheme
3589        )));
3590    }
3591    let (mut fresh, _cancel_guard) = dial_h2(&req, trace)?;
3592    let mut resp = run_one_request_to(&mut fresh, &req, sink, on_head, trace)?;
3593    apply_dial_timing(&mut resp, &fresh);
3594    let _ = writeln!(trace, "* Connection closed");
3595    Ok(resp)
3596}
3597
3598/// Copy a freshly-dialed connection's per-phase timing onto its first response.
3599/// (Pooled reuse leaves these phases unset, matching curl's reuse semantics.)
3600fn apply_dial_timing<S: Read + Write>(resp: &mut Response, conn: &Connection<S>) {
3601    resp.timing.namelookup = conn.dial_timing.namelookup;
3602    resp.timing.connect = conn.dial_timing.connect;
3603    resp.timing.appconnect = conn.dial_timing.appconnect;
3604    resp.timing.pretransfer = conn.dial_timing.pretransfer;
3605}
3606
3607/// Build the HPACK-encoded header block for the request: pseudo-headers in the
3608/// required order (RFC 9113 §8.3.1), then lowercased user headers (skipping
3609/// the connection-specific ones HTTP/2 forbids per §8.2.2). The `encoder`
3610/// is borrowed mutably so its dynamic table tracks every header we emit
3611/// with incremental indexing — keeping our table aligned with the peer's.
3612/// Compute the exact ordered list of header fields rsurl will put on the wire
3613/// for `req`, split into the four pseudo-headers (`:method`, `:scheme`,
3614/// `:authority`, `:path`) and the regular `(name, value)` fields that follow.
3615///
3616/// This is the single source of truth for what gets encoded into the HEADERS
3617/// block, so the verbose `-v` trace can reproduce the request exactly without
3618/// hardcoding (it reads the same list the encoder consumes).
3619fn request_header_fields(req: &Request) -> (RequestPseudo, Vec<(String, String)>) {
3620    let authority = if req.url.port == 443 && req.url.scheme == "https" {
3621        req.url.host.clone()
3622    } else {
3623        format!("{}:{}", req.url.host, req.url.port)
3624    };
3625    let pseudo = RequestPseudo {
3626        method: crate::http::effective_method(req),
3627        scheme: req.url.scheme.clone(),
3628        authority,
3629        path: req.url.path.clone(),
3630    };
3631
3632    let mut fields: Vec<(String, String)> = Vec::new();
3633    let mut have_ua = false;
3634    let mut have_accept = false;
3635    let mut have_accept_enc = false;
3636    let mut have_auth = false;
3637    for (k, v) in &req.headers {
3638        if is_connection_specific_header(k) || k.eq_ignore_ascii_case("host") {
3639            continue;
3640        }
3641        let lk = k.to_ascii_lowercase();
3642        if lk == "user-agent" {
3643            have_ua = true;
3644        }
3645        if lk == "accept" {
3646            have_accept = true;
3647        }
3648        if lk == "accept-encoding" {
3649            have_accept_enc = true;
3650        }
3651        if lk == "authorization" {
3652            have_auth = true;
3653        }
3654        fields.push((lk, v.clone()));
3655    }
3656    // Automatic request headers, suppressed in strict mode (the caller's set is
3657    // sent verbatim); see [`crate::Request::strict_headers`].
3658    if !req.strict_headers {
3659        if !have_auth {
3660            if let Some(creds) = crate::http::effective_basic_auth(req) {
3661                fields.push(("authorization".to_string(), format!("Basic {creds}")));
3662            }
3663        }
3664        if !have_ua {
3665            fields.push((
3666                "user-agent".to_string(),
3667                concat!("rsurl/", env!("CARGO_PKG_VERSION")).to_string(),
3668            ));
3669        }
3670        if !have_accept {
3671            fields.push(("accept".to_string(), "*/*".to_string()));
3672        }
3673        if !have_accept_enc {
3674            // Same default as the HTTP/1.1 writer — rsurl always decodes these
3675            // on the way back (see `crate::compress`). The full value is HPACK
3676            // static index 16, so this round-trips with minimum bytes on wire.
3677            fields.push(("accept-encoding".to_string(), "gzip, deflate".to_string()));
3678        }
3679    }
3680    if !req.body.is_empty() {
3681        fields.push(("content-length".to_string(), req.body.len().to_string()));
3682    }
3683    (pseudo, fields)
3684}
3685
3686/// The four HTTP/2 request pseudo-headers, in send order.
3687struct RequestPseudo {
3688    method: String,
3689    scheme: String,
3690    authority: String,
3691    path: String,
3692}
3693
3694fn build_header_block(encoder: &mut Encoder, req: &Request) -> Vec<u8> {
3695    let mut out = Vec::new();
3696    let (pseudo, fields) = request_header_fields(req);
3697
3698    // Pseudo-headers must come first, in this order: :method, :scheme,
3699    // :authority, :path.
3700    encoder.encode_header(&mut out, ":method", &pseudo.method);
3701    encoder.encode_header(&mut out, ":scheme", &pseudo.scheme);
3702    encoder.encode_header(&mut out, ":authority", &pseudo.authority);
3703    encoder.encode_header(&mut out, ":path", &pseudo.path);
3704
3705    // Regular headers: lowercased name, banned ones already filtered out.
3706    for (k, v) in &fields {
3707        encoder.encode_header(&mut out, k, v);
3708    }
3709    out
3710}
3711
3712/// Emit the curl-style `> ` request trace for an HTTP/2 request, mirroring the
3713/// HTTP/1.1 writer's format: a request line, a `Host:` line synthesised from
3714/// `:authority`, then each regular header field, then a closing blank `> `.
3715/// Reads from [`request_header_fields`] so the trace reflects exactly what the
3716/// HEADERS block carries.
3717fn trace_request(req: &Request, trace: &mut dyn Write) {
3718    let (pseudo, fields) = request_header_fields(req);
3719    let _ = writeln!(trace, "> {} {} HTTP/2", pseudo.method, pseudo.path);
3720    let _ = writeln!(trace, "> Host: {}", pseudo.authority);
3721    for (k, v) in &fields {
3722        let _ = writeln!(trace, "> {k}: {v}");
3723    }
3724    let _ = writeln!(trace, "> ");
3725}
3726
3727fn is_connection_specific_header(name: &str) -> bool {
3728    // RFC 9113 §8.2.2: connection-specific header fields MUST NOT be sent.
3729    matches!(
3730        name.to_ascii_lowercase().as_str(),
3731        "connection" | "proxy-connection" | "keep-alive" | "transfer-encoding" | "upgrade" | "te" // unless value is exactly "trailers"; we conservatively drop.
3732    )
3733}
3734
3735/// Like [`trace_request`] but labels every `>` line with `[stream N]` so the
3736/// interleaved request lines of a multiplexed batch stay attributable.
3737fn trace_request_labelled(req: &Request, id: u32, trace: &mut dyn Write) {
3738    let (pseudo, fields) = request_header_fields(req);
3739    let _ = writeln!(
3740        trace,
3741        "> [stream {id}] {} {} HTTP/2",
3742        pseudo.method, pseudo.path
3743    );
3744    let _ = writeln!(trace, "> [stream {id}] Host: {}", pseudo.authority);
3745    for (k, v) in &fields {
3746        let _ = writeln!(trace, "> [stream {id}] {k}: {v}");
3747    }
3748    let _ = writeln!(trace, "> [stream {id}] ");
3749}
3750
3751/// Collapse the per-request `Option<Result<...>>` slots into a `Vec<Result<...>>`.
3752/// Every slot must be filled by the time the driver returns; an unfilled slot
3753/// is an internal bug, surfaced as a `BadResponse` rather than a panic.
3754fn collect_results(results: Vec<Option<Result<Response>>>) -> Vec<Result<Response>> {
3755    results
3756        .into_iter()
3757        .map(|slot| {
3758            slot.unwrap_or_else(|| {
3759                Err(Error::BadResponse(
3760                    "internal: multiplexed request produced no result".into(),
3761                ))
3762            })
3763        })
3764        .collect()
3765}
3766
3767/// Best-effort clone of an [`Error`] so a single connection-level failure can
3768/// be reported on every outstanding request. `Error` isn't `Clone` because it
3769/// wraps `io::Error`; we reconstruct an equivalent value (preserving the
3770/// `io::ErrorKind` for the `Io` case) so callers still get a faithful kind and
3771/// message.
3772fn clone_error(e: &Error) -> Error {
3773    match e {
3774        Error::InvalidUrl(s) => Error::InvalidUrl(s.clone()),
3775        Error::UnsupportedScheme(s) => Error::UnsupportedScheme(s.clone()),
3776        Error::Io(io_err) => Error::Io(io::Error::new(io_err.kind(), io_err.to_string())),
3777        Error::BadResponse(s) => Error::BadResponse(s.clone()),
3778        Error::UnexpectedEof => Error::UnexpectedEof,
3779        Error::H2NotNegotiated => Error::H2NotNegotiated,
3780        Error::Ssh(s) => Error::Ssh(s.clone()),
3781        Error::Decode(s) => Error::Decode(s.clone()),
3782        Error::Status { code, reason } => Error::Status {
3783            code: *code,
3784            reason: reason.clone(),
3785        },
3786        Error::Cancelled => Error::Cancelled,
3787    }
3788}
3789
3790/// Issue `reqs` concurrently over a SINGLE HTTP/2 connection and return one
3791/// result per request, in input order.
3792///
3793/// All requests MUST share the same origin (scheme/host/port) and be
3794/// `https://` (HTTP/2 over TLS). This is the precondition for multiplexing —
3795/// the whole point is one connection. Violations are handled gracefully rather
3796/// than panicking:
3797///
3798/// - An empty `reqs` returns an empty `Vec`.
3799/// - A non-`https` request, or any request whose origin differs from the
3800///   first, makes the batch fall back to issuing **every** request
3801///   sequentially via [`send`] (each on its own pooled connection). The
3802///   results are still correct and in order; you just don't get multiplexing.
3803/// - If the common origin is not pool-eligible (i.e. `-k` /
3804///   `--insecure` or a custom `--cacert`), we likewise fall back to
3805///   sequential [`send`] — the same TLS-posture isolation rule the pool
3806///   enforces (a verify-off session must never be reused for a verify-on
3807///   caller).
3808/// - If the server doesn't negotiate ALPN `h2` on the shared connection, we
3809///   fall back to sequential [`send`] (which itself does the h2→h1.1 dance).
3810///
3811/// On the happy path: one TCP+TLS handshake, N concurrent streams, interleaved
3812/// frame I/O, demultiplexed responses. A single stream's `RST_STREAM` /
3813/// per-stream protocol error fails only that request; the rest still complete.
3814/// A connection-level failure (transport error, GOAWAY beyond a stream's id)
3815/// fails the affected subset. The successful connection is returned to the pool
3816/// when still usable.
3817pub fn send_multiplexed(reqs: Vec<Request>, trace: &mut dyn Write) -> Vec<Result<Response>> {
3818    if reqs.is_empty() {
3819        return Vec::new();
3820    }
3821
3822    // Determine the common origin and whether the batch is multiplex-eligible.
3823    let first = &reqs[0];
3824    let same_origin_https = first.url.scheme == "https"
3825        && reqs.iter().all(|r| {
3826            r.url.scheme == first.url.scheme
3827                && r.url.host == first.url.host
3828                && r.url.port == first.url.port
3829        });
3830    let all_eligible = reqs.iter().all(pool_eligible);
3831
3832    if !same_origin_https || !all_eligible {
3833        // Preconditions not met — fall back to issuing each request on its own
3834        // (pooled) connection, sequentially. Still correct, just not multiplexed.
3835        let _ = writeln!(
3836            trace,
3837            "* multiplexing preconditions not met (mixed origin / non-https / non-pool-eligible TLS); issuing requests sequentially"
3838        );
3839        return reqs.into_iter().map(|r| send(r, trace)).collect();
3840    }
3841
3842    let key = PoolKey::from_request(first);
3843
3844    // Try a pooled connection first; fall back to a cold dial. We do NOT pump
3845    // the batch over a pooled conn that turns out unusable mid-flight — instead
3846    // we cold-dial a fresh one and run the whole batch there (a half-consumed
3847    // batch is hard to reason about; a clean re-run is simpler and correct
3848    // because none of these requests have been observed as sent yet).
3849    let pooled = {
3850        let mut guard = global_pool().lock().unwrap_or_else(|e| e.into_inner());
3851        guard.checkout(&key)
3852    };
3853    if let Some(arc) = pooled {
3854        let mut conn_guard = arc.lock().unwrap_or_else(|e| e.into_inner());
3855        if conn_guard.is_usable() {
3856            let _ = writeln!(
3857                trace,
3858                "* Reusing existing connection from pool (multiplexed)"
3859            );
3860            let results = conn_guard.run_multiplexed(&reqs, trace);
3861            // Re-pool only if every request completed without disturbing the
3862            // wire (no error result) and the conn is still structurally usable.
3863            let clean = results.iter().all(Result::is_ok) && conn_guard.is_usable();
3864            drop(conn_guard);
3865            if clean {
3866                let mut guard = global_pool().lock().unwrap_or_else(|e| e.into_inner());
3867                guard.release(key, arc);
3868                let _ = writeln!(trace, "* Connection kept alive (pooled)");
3869            } else {
3870                let _ = writeln!(trace, "* Connection closed");
3871            }
3872            return results;
3873        }
3874        // Unusable on checkout: drop it and cold-dial below.
3875        drop(conn_guard);
3876        let _ = writeln!(
3877            trace,
3878            "* Pooled connection unusable (connection closed); reconnecting"
3879        );
3880    }
3881
3882    // Cold-dial path.
3883    let (mut fresh, _cancel_guard) = match dial_h2(first, trace) {
3884        Ok(c) => c,
3885        Err(e) => {
3886            // The shared handshake failed (e.g. ALPN didn't select h2). Fall
3887            // back to sequential `send` so each request gets the standard
3888            // h2→h1.1 negotiation rather than failing the whole batch.
3889            let _ = writeln!(
3890                trace,
3891                "* HTTP/2 connection for multiplexing failed ({e}); issuing requests sequentially"
3892            );
3893            return reqs.into_iter().map(|r| send(r, trace)).collect();
3894        }
3895    };
3896    let results = fresh.run_multiplexed(&reqs, trace);
3897    let clean = results.iter().all(Result::is_ok) && fresh.is_usable();
3898    if clean {
3899        let arc = Arc::new(Mutex::new(fresh));
3900        let mut guard = global_pool().lock().unwrap_or_else(|e| e.into_inner());
3901        guard.release(key, arc);
3902        let _ = writeln!(trace, "* Connection kept alive (pooled)");
3903    } else {
3904        let _ = writeln!(trace, "* Connection closed");
3905    }
3906    results
3907}
3908
3909// ---------------------------------------------------------------------------
3910// Tests.
3911// ---------------------------------------------------------------------------
3912
3913#[cfg(test)]
3914mod tests {
3915    use super::*;
3916    use std::io::Cursor;
3917
3918    #[test]
3919    fn int_encode_small() {
3920        // RFC 7541 §C.1.1: 10 with a 5-bit prefix fits in one byte.
3921        assert_eq!(encode_int(10, 5), vec![10]);
3922    }
3923
3924    #[test]
3925    fn int_encode_large() {
3926        // RFC 7541 §C.1.2: 1337 with a 5-bit prefix.
3927        assert_eq!(encode_int(1337, 5), vec![0x1f, 0x9a, 0x0a]);
3928    }
3929
3930    #[test]
3931    fn int_encode_eight_bit() {
3932        // RFC 7541 §C.1.3: 42 with an 8-bit prefix is just 42.
3933        assert_eq!(encode_int(42, 8), vec![42]);
3934    }
3935
3936    #[test]
3937    fn int_decode_round_trips() {
3938        for &(v, p) in &[
3939            (0u64, 5),
3940            (10, 5),
3941            (30, 5),
3942            (31, 5),
3943            (1337, 5),
3944            (1, 8),
3945            (255, 8),
3946        ] {
3947            let enc = encode_int(v, p);
3948            let (dec, n) = decode_int(&enc, p).unwrap();
3949            assert_eq!(dec, v, "value {v} with {p}-bit prefix");
3950            assert_eq!(n, enc.len());
3951        }
3952    }
3953
3954    #[test]
3955    fn int_decode_truncated_errors() {
3956        // 0x1f means "the integer continues" with a 5-bit prefix.
3957        assert!(decode_int(&[0x1f], 5).is_err());
3958        assert!(decode_int(&[0x1f, 0x80], 5).is_err());
3959    }
3960
3961    #[test]
3962    fn static_table_method_get() {
3963        // ":method GET" is entry 2 in the static table.
3964        assert_eq!(static_full_index(":method", "GET"), Some(2));
3965    }
3966
3967    #[test]
3968    fn static_table_method_post() {
3969        assert_eq!(static_full_index(":method", "POST"), Some(3));
3970    }
3971
3972    #[test]
3973    fn static_table_name_only() {
3974        assert_eq!(static_name_index(":status"), Some(8));
3975        assert_eq!(static_name_index("user-agent"), Some(58));
3976        assert_eq!(static_name_index("does-not-exist"), None);
3977    }
3978
3979    #[test]
3980    fn static_table_length() {
3981        assert_eq!(STATIC_TABLE.len(), 61);
3982    }
3983
3984    #[test]
3985    fn frame_round_trip_empty_settings() {
3986        let f = Frame {
3987            typ: F_SETTINGS,
3988            flags: 0,
3989            stream_id: 0,
3990            payload: Vec::new(),
3991        };
3992        let mut buf = Vec::new();
3993        write_frame(&mut buf, &f).unwrap();
3994        assert_eq!(buf.len(), 9);
3995        let mut cur = Cursor::new(buf);
3996        let g = read_frame(&mut cur).unwrap();
3997        assert_eq!(g, f);
3998    }
3999
4000    #[test]
4001    fn frame_round_trip_headers_with_payload() {
4002        let f = Frame {
4003            typ: F_HEADERS,
4004            flags: FLAG_END_STREAM | FLAG_END_HEADERS,
4005            stream_id: 1,
4006            payload: vec![
4007                0x82, 0x86, 0x84, 0x41, 0x88, 0xf1, 0xe3, 0xc2, 0xe5, 0xf2, 0x3a, 0x6b, 0xa0, 0xab,
4008                0x90, 0xf4, 0xff,
4009            ],
4010        };
4011        let mut buf = Vec::new();
4012        write_frame(&mut buf, &f).unwrap();
4013        let mut cur = Cursor::new(buf);
4014        let g = read_frame(&mut cur).unwrap();
4015        assert_eq!(g, f);
4016        assert_eq!(g.flags, 0x05);
4017    }
4018
4019    #[test]
4020    fn frame_stream_id_high_bit_masked_on_read() {
4021        // Set the R bit (top bit of byte 5). RFC 9113 says receivers MUST ignore it.
4022        let buf = vec![0, 0, 0, F_DATA, 0, 0x80, 0, 0, 1];
4023        let mut cur = Cursor::new(buf);
4024        let f = read_frame(&mut cur).unwrap();
4025        assert_eq!(f.stream_id, 1);
4026    }
4027
4028    #[test]
4029    fn hpack_encode_indexed_method() {
4030        // Static index 2 = (":method", "GET") — high bit set + index = 0x82.
4031        // The indexed-field form (§6.1) doesn't touch the dynamic table.
4032        let mut enc = Encoder::new();
4033        let mut out = Vec::new();
4034        enc.encode_header(&mut out, ":method", "GET");
4035        assert_eq!(out, vec![0x82]);
4036        assert!(enc.dyn_table.is_empty());
4037    }
4038
4039    #[test]
4040    fn hpack_encode_literal_with_indexed_name() {
4041        // ":path" is static name index 4. The encoder uses literal-with-
4042        // incremental-indexing + indexed name (`01xxxxxx` = 0x40 + idx), so
4043        // the first byte is 0x44. The value "/foo" picks whichever is
4044        // shorter between raw and Huffman; we just verify the decoder
4045        // round-trips and the entry landed in the dynamic table.
4046        let mut enc = Encoder::new();
4047        let mut out = Vec::new();
4048        enc.encode_header(&mut out, ":path", "/foo");
4049        assert_eq!(out[0], 0x44);
4050        let mut dec = Decoder::new();
4051        let got = dec.decode_block(&out).unwrap();
4052        assert_eq!(got, vec![(":path".into(), "/foo".into())]);
4053        assert_eq!(enc.dyn_table.len(), 1);
4054        assert_eq!(enc.dyn_table[0], (":path".to_string(), "/foo".to_string()));
4055    }
4056
4057    #[test]
4058    fn hpack_encode_literal_full() {
4059        // "x-custom" is in neither table → literal-with-incremental-indexing
4060        // + literal name (0x40 marker), then two length-prefixed strings.
4061        let mut enc = Encoder::new();
4062        let mut out = Vec::new();
4063        enc.encode_header(&mut out, "x-custom", "yes");
4064        assert_eq!(out[0], 0x40);
4065        let mut dec = Decoder::new();
4066        let got = dec.decode_block(&out).unwrap();
4067        assert_eq!(got, vec![("x-custom".into(), "yes".into())]);
4068        assert_eq!(enc.dyn_table[0], ("x-custom".into(), "yes".into()));
4069    }
4070
4071    #[test]
4072    fn hpack_decode_round_trip_pseudo_headers() {
4073        let mut enc = Encoder::new();
4074        let mut block = Vec::new();
4075        enc.encode_header(&mut block, ":method", "GET");
4076        enc.encode_header(&mut block, ":scheme", "https");
4077        enc.encode_header(&mut block, ":authority", "example.com");
4078        enc.encode_header(&mut block, ":path", "/");
4079        let mut dec = Decoder::new();
4080        let got = dec.decode_block(&block).unwrap();
4081        assert_eq!(got.len(), 4);
4082        assert_eq!(got[0], (":method".into(), "GET".into()));
4083        assert_eq!(got[1], (":scheme".into(), "https".into()));
4084        assert_eq!(got[2], (":authority".into(), "example.com".into()));
4085        assert_eq!(got[3], (":path".into(), "/".into()));
4086    }
4087
4088    #[test]
4089    fn hpack_decode_indexed_static() {
4090        // 0x82 = indexed header field, static index 2 = (":method", "GET").
4091        let mut dec = Decoder::new();
4092        let got = dec.decode_block(&[0x82]).unwrap();
4093        assert_eq!(got, vec![(":method".into(), "GET".into())]);
4094    }
4095
4096    #[test]
4097    fn hpack_decode_literal_with_incremental_indexing() {
4098        // RFC 7541 §C.2.1: encoding of "custom-key: custom-header" with
4099        // incremental indexing, literal name.
4100        let buf: Vec<u8> = vec![
4101            0x40, 0x0a, b'c', b'u', b's', b't', b'o', b'm', b'-', b'k', b'e', b'y', 0x0d, b'c',
4102            b'u', b's', b't', b'o', b'm', b'-', b'h', b'e', b'a', b'd', b'e', b'r',
4103        ];
4104        let mut dec = Decoder::new();
4105        let got = dec.decode_block(&buf).unwrap();
4106        assert_eq!(got, vec![("custom-key".into(), "custom-header".into())]);
4107        // And the dynamic table should now hold the new entry.
4108        assert_eq!(dec.dyn_table.len(), 1);
4109    }
4110
4111    /// Build a literal-without-indexing block (0x00 marker) with a raw
4112    /// (non-Huffman) literal name and value. Used to drive forbidden-octet
4113    /// rejection tests directly at the decode boundary.
4114    fn raw_literal_block(name: &[u8], value: &[u8]) -> Vec<u8> {
4115        let mut buf = vec![0x00u8];
4116        buf.push(name.len() as u8); // 7-bit length, high bit 0 = raw
4117        buf.extend_from_slice(name);
4118        buf.push(value.len() as u8);
4119        buf.extend_from_slice(value);
4120        buf
4121    }
4122
4123    #[test]
4124    fn hpack_decode_rejects_crlf_in_value() {
4125        // x: "evil\r\nset-cookie: x=1" — classic response-splitting payload.
4126        let block = raw_literal_block(b"x-h", b"evil\r\nset-cookie: x=1");
4127        let mut dec = Decoder::new();
4128        let err = dec.decode_block(&block).unwrap_err();
4129        assert!(matches!(err, Error::BadResponse(_)), "got {err:?}");
4130    }
4131
4132    #[test]
4133    fn hpack_decode_rejects_lf_in_value() {
4134        let block = raw_literal_block(b"x-h", b"a\nb");
4135        let mut dec = Decoder::new();
4136        assert!(matches!(
4137            dec.decode_block(&block).unwrap_err(),
4138            Error::BadResponse(_)
4139        ));
4140    }
4141
4142    #[test]
4143    fn hpack_decode_rejects_nul_in_value() {
4144        let block = raw_literal_block(b"x-h", b"a\x00b");
4145        let mut dec = Decoder::new();
4146        assert!(matches!(
4147            dec.decode_block(&block).unwrap_err(),
4148            Error::BadResponse(_)
4149        ));
4150    }
4151
4152    #[test]
4153    fn hpack_decode_rejects_uppercase_name() {
4154        let block = raw_literal_block(b"X-Bad", b"ok");
4155        let mut dec = Decoder::new();
4156        assert!(matches!(
4157            dec.decode_block(&block).unwrap_err(),
4158            Error::BadResponse(_)
4159        ));
4160    }
4161
4162    #[test]
4163    fn hpack_decode_rejects_empty_name() {
4164        let block = raw_literal_block(b"", b"ok");
4165        let mut dec = Decoder::new();
4166        assert!(matches!(
4167            dec.decode_block(&block).unwrap_err(),
4168            Error::BadResponse(_)
4169        ));
4170    }
4171
4172    #[test]
4173    fn hpack_decode_accepts_normal_header_and_pseudo() {
4174        // Ordinary header with spaces/tabs in the value, plus a pseudo-header.
4175        let mut block = raw_literal_block(b"content-type", b"text/html; charset=utf-8");
4176        block.extend(raw_literal_block(b":status", b"200"));
4177        let mut dec = Decoder::new();
4178        let got = dec.decode_block(&block).unwrap();
4179        assert_eq!(
4180            got[0],
4181            ("content-type".into(), "text/html; charset=utf-8".into())
4182        );
4183        assert_eq!(got[1], (":status".into(), "200".into()));
4184        // A tab in the value is allowed (only NUL/CR/LF are forbidden).
4185        let tabbed = raw_literal_block(b"x-h", b"a\tb");
4186        let mut dec2 = Decoder::new();
4187        assert!(dec2.decode_block(&tabbed).is_ok());
4188    }
4189
4190    #[test]
4191    fn huffman_decode_c4_1() {
4192        // RFC 7541 §C.4.1: "www.example.com" Huffman-coded.
4193        let coded = [
4194            0xf1, 0xe3, 0xc2, 0xe5, 0xf2, 0x3a, 0x6b, 0xa0, 0xab, 0x90, 0xf4, 0xff,
4195        ];
4196        let out = huffman_decode(&coded).unwrap();
4197        assert_eq!(out, b"www.example.com");
4198    }
4199
4200    #[test]
4201    fn huffman_decode_c4_2() {
4202        // RFC 7541 §C.4.2: "no-cache" Huffman-coded.
4203        let coded = [0xa8, 0xeb, 0x10, 0x64, 0x9c, 0xbf];
4204        let out = huffman_decode(&coded).unwrap();
4205        assert_eq!(out, b"no-cache");
4206    }
4207
4208    #[test]
4209    fn huffman_decode_c4_3() {
4210        // RFC 7541 §C.4.3: "custom-key" Huffman-coded.
4211        let coded = [0x25, 0xa8, 0x49, 0xe9, 0x5b, 0xa9, 0x7d, 0x7f];
4212        let out = huffman_decode(&coded).unwrap();
4213        assert_eq!(out, b"custom-key");
4214    }
4215
4216    #[test]
4217    fn huffman_decode_rejects_short_padding() {
4218        // A single byte whose padding bits aren't all 1s is invalid.
4219        // 0x00 alone has 8 bits, all zero — must be rejected.
4220        assert!(huffman_decode(&[0x00]).is_err());
4221    }
4222
4223    // -----------------------------------------------------------------
4224    // Huffman encoder (RFC 7541 §5.2).
4225    // -----------------------------------------------------------------
4226
4227    #[test]
4228    fn huffman_encode_padding_bits() {
4229        // 'a' (Huffman index 97) encodes to (code=0x3, len=5). Left-shifted
4230        // into the top 5 bits of a byte: 0b00011_000 = 0x18. Padded with 3
4231        // trailing 1-bits: 0b00011_111 = 0x1f.
4232        let out = huffman_encode(b"a");
4233        assert_eq!(out, vec![0x1f]);
4234    }
4235
4236    #[test]
4237    fn huffman_encode_appendix_c_www_example_com() {
4238        // RFC 7541 §C.4.1: "www.example.com".
4239        let out = huffman_encode(b"www.example.com");
4240        assert_eq!(
4241            out,
4242            vec![0xf1, 0xe3, 0xc2, 0xe5, 0xf2, 0x3a, 0x6b, 0xa0, 0xab, 0x90, 0xf4, 0xff,]
4243        );
4244    }
4245
4246    #[test]
4247    fn huffman_encode_appendix_c_no_cache() {
4248        // RFC 7541 §C.4.2: "no-cache".
4249        let out = huffman_encode(b"no-cache");
4250        assert_eq!(out, vec![0xa8, 0xeb, 0x10, 0x64, 0x9c, 0xbf]);
4251    }
4252
4253    #[test]
4254    fn huffman_encode_appendix_c_custom_key() {
4255        // RFC 7541 §C.4.3: "custom-key".
4256        let out = huffman_encode(b"custom-key");
4257        assert_eq!(out, vec![0x25, 0xa8, 0x49, 0xe9, 0x5b, 0xa9, 0x7d, 0x7f]);
4258    }
4259
4260    #[test]
4261    fn huffman_encode_appendix_c_custom_value() {
4262        // RFC 7541 §C.4.3: "custom-value".
4263        let out = huffman_encode(b"custom-value");
4264        assert_eq!(
4265            out,
4266            vec![0x25, 0xa8, 0x49, 0xe9, 0x5b, 0xb8, 0xe8, 0xb4, 0xbf]
4267        );
4268    }
4269
4270    #[test]
4271    fn huffman_encode_round_trips_through_decoder() {
4272        // Defensive: any byte sequence we Huffman-encode must decode back
4273        // to itself. Catches bit-cursor / padding bugs.
4274        for s in &[
4275            "",
4276            "a",
4277            "ab",
4278            "abc",
4279            "Hello, world!",
4280            "the quick brown fox jumps",
4281            "/foo/bar/baz",
4282        ] {
4283            let bytes = s.as_bytes();
4284            if bytes.is_empty() {
4285                // huffman_decode rejects empty input only when padding is
4286                // nonzero; empty in / empty out trivially round-trips.
4287                let enc = huffman_encode(bytes);
4288                assert!(enc.is_empty());
4289                continue;
4290            }
4291            let enc = huffman_encode(bytes);
4292            let dec = huffman_decode(&enc).unwrap();
4293            assert_eq!(dec, bytes, "round-trip mismatch for {s:?}");
4294        }
4295    }
4296
4297    #[test]
4298    fn encode_literal_chooses_huffman_when_shorter() {
4299        // 100 'a' bytes: each 'a' is 5 bits → 500 bits = 63 bytes Huffman.
4300        // Raw is 100 bytes. Huffman wins; high bit of length prefix is set.
4301        let mut out = Vec::new();
4302        let s: String = "a".repeat(100);
4303        encode_literal_string(&mut out, &s);
4304        assert_eq!(out[0] & 0x80, 0x80, "Huffman bit should be set");
4305    }
4306
4307    #[test]
4308    fn encode_literal_chooses_raw_when_huffman_longer() {
4309        // 0xff Huffman-encodes to 27 bits. 100 copies = 2700 bits ≈ 338
4310        // bytes — far worse than the raw 100. We pick raw; high bit cleared.
4311        let mut out = Vec::new();
4312        // Hold the string in a Vec<u8> so we don't have to construct an
4313        // invalid UTF-8 &str. encode_literal_string takes &str so we cheat
4314        // through Latin-1 by passing characters that round-trip to bytes.
4315        // Actually `as_bytes()` is called inside the function, so we use a
4316        // helper that operates on bytes directly.
4317        let bytes: Vec<u8> = vec![0xff; 100];
4318        // encode_literal_string takes &str; we construct a String of the
4319        // same length via Latin-1 chars. Char `\u{00FF}` is two bytes in
4320        // UTF-8, so use printable ASCII whose Huffman is also worse than
4321        // raw: '|' (0x7c) is 28 bits each.
4322        // Easier: call the underlying primitives directly.
4323        let huff = huffman_encode(&bytes);
4324        assert!(
4325            huff.len() > bytes.len(),
4326            "0xff Huffman should be longer than raw"
4327        );
4328        // Now drive `encode_literal_string` via a printable ASCII string
4329        // whose Huffman code is also wider than 8 bits per symbol. '|'
4330        // (Huffman entry: 28 bits) qualifies.
4331        let s: String = "|".repeat(50);
4332        out.clear();
4333        encode_literal_string(&mut out, &s);
4334        assert_eq!(out[0] & 0x80, 0x00, "Huffman bit should be cleared");
4335        assert_eq!(out[0] as usize & 0x7f, 50);
4336        assert_eq!(&out[1..], s.as_bytes());
4337    }
4338
4339    // -----------------------------------------------------------------
4340    // HPACK encoder dynamic-table insertion (RFC 7541 §6.2.1).
4341    // -----------------------------------------------------------------
4342
4343    #[test]
4344    fn encoder_inserts_into_dyn_table_on_incremental_indexing() {
4345        let mut enc = Encoder::new();
4346        let mut out = Vec::new();
4347        enc.encode_header(&mut out, "x-custom", "value1");
4348        assert_eq!(enc.dyn_table.len(), 1);
4349        assert_eq!(
4350            enc.dyn_table[0],
4351            ("x-custom".to_string(), "value1".to_string())
4352        );
4353        assert_eq!(enc.dyn_table_size, "x-custom".len() + "value1".len() + 32);
4354    }
4355
4356    #[test]
4357    fn encoder_evicts_to_fit_max_size() {
4358        // Each entry has overhead 32 + name + value. Two entries of length
4359        // (name=4, value=4) cost 40 bytes each = 80 total. Cap at 64 forces
4360        // the older one out when the second arrives.
4361        let mut enc = Encoder::new();
4362        enc.max_dyn_table_size = 64;
4363        let mut out = Vec::new();
4364        enc.encode_header(&mut out, "n1aa", "v1aa");
4365        enc.encode_header(&mut out, "n2aa", "v2aa");
4366        assert_eq!(enc.dyn_table.len(), 1, "only the newest should remain");
4367        assert_eq!(enc.dyn_table[0], ("n2aa".to_string(), "v2aa".to_string()));
4368        assert_eq!(enc.dyn_table_size, 40);
4369    }
4370
4371    #[test]
4372    fn encoder_emits_size_update_signal_on_next_encode_after_setting_change() {
4373        let mut enc = Encoder::new();
4374        enc.set_peer_max_table_size(1024);
4375        let mut out = Vec::new();
4376        enc.encode_header(&mut out, ":method", "GET");
4377        // 0x20 prefix + 5-bit integer encoding of 1024.
4378        // 1024 >= 31 → first byte = 0x20 | 0x1f = 0x3f; remainder 993 =
4379        // 0xe1, 0x07 (varint). Then `:method GET` is indexed = 0x82.
4380        assert_eq!(out, vec![0x3f, 0xe1, 0x07, 0x82]);
4381        // Signal is consumed; a subsequent call MUST NOT re-emit it.
4382        out.clear();
4383        enc.encode_header(&mut out, ":method", "GET");
4384        assert_eq!(out, vec![0x82]);
4385    }
4386
4387    #[test]
4388    fn encoder_uses_dynamic_index_for_repeat() {
4389        let mut enc = Encoder::new();
4390        let mut out = Vec::new();
4391        enc.encode_header(&mut out, "x", "y");
4392        out.clear();
4393        enc.encode_header(&mut out, "x", "y");
4394        // index = static (61) + 1 = 62, high bit set → 0x80 | 62 = 0xbe.
4395        assert_eq!(out, vec![0xbe]);
4396    }
4397
4398    #[test]
4399    fn encoder_uses_indexed_name_from_dyn_table() {
4400        let mut enc = Encoder::new();
4401        let mut out = Vec::new();
4402        enc.encode_header(&mut out, "x-foo", "v1");
4403        // After insertion: dyn_table[0] = ("x-foo", "v1") at HPACK index 62.
4404        out.clear();
4405        enc.encode_header(&mut out, "x-foo", "v2");
4406        // Literal-with-incremental-indexing, indexed name (6-bit): 0x40 | 62 = 0x7e.
4407        assert_eq!(out[0], 0x7e);
4408        // And both entries should be in the dyn table now (newest first).
4409        assert_eq!(enc.dyn_table.len(), 2);
4410        assert_eq!(enc.dyn_table[0].1, "v2");
4411        assert_eq!(enc.dyn_table[1].1, "v1");
4412    }
4413
4414    #[test]
4415    fn encode_decode_round_trip() {
4416        // A handful of mixed headers — static-table hits, repeats (which
4417        // collapse to indexed dynamic refs), and new entries — must
4418        // round-trip exactly through the decoder.
4419        let mut enc = Encoder::new();
4420        let mut dec = Decoder::new();
4421        let inputs: Vec<(&str, &str)> = vec![
4422            (":method", "GET"),
4423            (":scheme", "https"),
4424            (":authority", "example.com"),
4425            (":path", "/foo"),
4426            ("user-agent", "rsurl/test"),
4427            ("accept", "*/*"),
4428            ("x-custom", "hello world"),
4429            ("user-agent", "rsurl/test"), // repeat → indexed dyn ref
4430            ("x-custom", "different"),    // same name, new value
4431        ];
4432        let mut buf = Vec::new();
4433        for (n, v) in &inputs {
4434            enc.encode_header(&mut buf, n, v);
4435        }
4436        let got = dec.decode_block(&buf).unwrap();
4437        let expected: Vec<(String, String)> = inputs
4438            .into_iter()
4439            .map(|(n, v)| (n.to_string(), v.to_string()))
4440            .collect();
4441        assert_eq!(got, expected);
4442    }
4443
4444    #[test]
4445    fn encoder_size_update_evicts_oversize_entries_immediately() {
4446        // Insert two entries (total ~80 bytes), then shrink the cap to 50.
4447        // The older one must be evicted right away, even before the next
4448        // encode_header call.
4449        let mut enc = Encoder::new();
4450        let mut out = Vec::new();
4451        enc.encode_header(&mut out, "n1aa", "v1aa"); // 40 bytes
4452        enc.encode_header(&mut out, "n2aa", "v2aa"); // 40 bytes
4453        assert_eq!(enc.dyn_table.len(), 2);
4454        enc.set_peer_max_table_size(50);
4455        assert_eq!(enc.dyn_table.len(), 1);
4456        assert_eq!(enc.dyn_table[0].0, "n2aa");
4457    }
4458
4459    #[test]
4460    fn hpack_decode_huffman_literal_value() {
4461        // RFC 7541 §C.4.1 second header: (":path", "/sample/path") with
4462        // literal name index 4 + Huffman-coded value. But easier: build
4463        // a header field "custom-key: custom-value" with both Huffman.
4464        // We synthesize: 0x40 (literal incremental, new name) + Huffman
4465        // strings for "custom-key" and "custom-value".
4466        //
4467        // For confidence we just test that a known-good RFC vector decodes:
4468        // C.6.1 first response header. Use the simpler approach of encoding
4469        // "/sample/path" Huffman behind a literal-without-indexing name=:path.
4470        //
4471        // Per RFC §C.4.2's encoding, ":path /sample/path" with Huffman value
4472        // and indexed name 4 = `0x44 0x8c <huffman bytes>`.
4473        // We computed the Huffman bytes elsewhere; just verify decoding works
4474        // on the vector printed in the RFC.
4475        let buf = vec![
4476            0x44, 0x8c, 0x60, 0xd4, 0x85, 0x31, 0x68, 0xdf, 0x1c, 0x6f, 0xa2, 0xa6, 0xfd, 0x95,
4477            0xb6, 0x88,
4478        ];
4479        // This vector is hand-crafted to be illustrative; we accept either a
4480        // successful decode (preferred) or a clean error. The point of this
4481        // test is to make sure the decoder doesn't panic on adversarial input.
4482        let _ = Decoder::new().decode_block(&buf);
4483    }
4484
4485    #[test]
4486    fn build_header_block_includes_pseudo() {
4487        let req = Request::new("GET", "https://example.com/foo").unwrap();
4488        let mut enc = Encoder::new();
4489        let block = build_header_block(&mut enc, &req);
4490        let mut dec = Decoder::new();
4491        let headers = dec.decode_block(&block).unwrap();
4492        let kv: Vec<(&str, &str)> = headers
4493            .iter()
4494            .map(|(k, v)| (k.as_str(), v.as_str()))
4495            .collect();
4496        assert!(kv.contains(&(":method", "GET")));
4497        assert!(kv.contains(&(":scheme", "https")));
4498        assert!(kv.contains(&(":authority", "example.com")));
4499        assert!(kv.contains(&(":path", "/foo")));
4500        assert!(kv.iter().any(|(k, _)| *k == "user-agent"));
4501        assert!(kv.iter().any(|(k, _)| *k == "accept"));
4502    }
4503
4504    #[test]
4505    fn build_header_block_strips_banned_headers() {
4506        let req = Request::new("GET", "https://example.com/")
4507            .unwrap()
4508            .header("Connection", "close")
4509            .header("Host", "evil.example")
4510            .header("X-Allowed", "yes");
4511        let mut enc = Encoder::new();
4512        let block = build_header_block(&mut enc, &req);
4513        let mut dec = Decoder::new();
4514        let headers = dec.decode_block(&block).unwrap();
4515        let names: Vec<&str> = headers.iter().map(|(k, _)| k.as_str()).collect();
4516        assert!(!names.contains(&"connection"));
4517        assert!(!names.contains(&"host"));
4518        assert!(names.contains(&"x-allowed"));
4519    }
4520
4521    #[test]
4522    fn build_header_block_authority_includes_nonstandard_port() {
4523        let req = Request::new("GET", "https://example.com:8443/").unwrap();
4524        let mut enc = Encoder::new();
4525        let block = build_header_block(&mut enc, &req);
4526        let mut dec = Decoder::new();
4527        let headers = dec.decode_block(&block).unwrap();
4528        let auth = headers.iter().find(|(k, _)| k == ":authority").unwrap();
4529        assert_eq!(auth.1, "example.com:8443");
4530    }
4531
4532    #[test]
4533    fn decoder_dynamic_table_size_update_caps_to_4096() {
4534        // 0x20 = size update with 5-bit prefix, value 0 → cap goes to 0.
4535        let mut dec = Decoder::new();
4536        dec.decode_block(&[0x20]).unwrap();
4537        assert_eq!(dec.dyn_table_cap, 0);
4538    }
4539
4540    #[test]
4541    fn decoder_rejects_oversize_index() {
4542        let mut dec = Decoder::new();
4543        // 0xff 0x01 = indexed, value 127+1 = 128. We have 61 static + 0 dynamic.
4544        let err = dec.decode_block(&[0xff, 0x01]).unwrap_err();
4545        match err {
4546            Error::BadResponse(_) => {}
4547            other => panic!("expected BadResponse, got {other:?}"),
4548        }
4549    }
4550
4551    // -----------------------------------------------------------------
4552    // SETTINGS application (RFC 9113 §6.5).
4553    // -----------------------------------------------------------------
4554
4555    /// Build a SETTINGS payload from a list of (id, value) pairs.
4556    fn settings_payload(entries: &[(u16, u32)]) -> Vec<u8> {
4557        let mut out = Vec::with_capacity(entries.len() * 6);
4558        for (id, val) in entries {
4559            out.extend_from_slice(&id.to_be_bytes());
4560            out.extend_from_slice(&val.to_be_bytes());
4561        }
4562        out
4563    }
4564
4565    #[test]
4566    fn peer_settings_defaults_match_rfc() {
4567        let p = PeerSettings::default();
4568        assert_eq!(p.header_table_size, 4096);
4569        assert!(p.enable_push);
4570        assert_eq!(p.max_concurrent_streams, u32::MAX);
4571        assert_eq!(p.initial_window_size, 65_535);
4572        assert_eq!(p.max_frame_size, 16_384);
4573        assert_eq!(p.max_header_list_size, u32::MAX);
4574    }
4575
4576    #[test]
4577    fn peer_settings_apply_updates_known_identifiers() {
4578        let mut p = PeerSettings::default();
4579        let payload = settings_payload(&[
4580            (S_HEADER_TABLE_SIZE, 8192),
4581            (S_INITIAL_WINDOW_SIZE, 131_072),
4582            (S_MAX_FRAME_SIZE, 32_768),
4583        ]);
4584        p.apply_settings_payload(&payload).unwrap();
4585        assert_eq!(p.header_table_size, 8192);
4586        assert_eq!(p.initial_window_size, 131_072);
4587        assert_eq!(p.max_frame_size, 32_768);
4588        // Untouched parameters stay at defaults.
4589        assert!(p.enable_push);
4590        assert_eq!(p.max_concurrent_streams, u32::MAX);
4591        assert_eq!(p.max_header_list_size, u32::MAX);
4592    }
4593
4594    #[test]
4595    fn peer_settings_ignores_unknown_identifier() {
4596        let mut p = PeerSettings::default();
4597        let before = p.clone();
4598        let payload = settings_payload(&[(0xFFFF, 42)]);
4599        p.apply_settings_payload(&payload).unwrap();
4600        assert_eq!(p, before);
4601    }
4602
4603    #[test]
4604    fn peer_settings_rejects_bad_enable_push() {
4605        let mut p = PeerSettings::default();
4606        let payload = settings_payload(&[(S_ENABLE_PUSH, 2)]);
4607        let err = p.apply_settings_payload(&payload).unwrap_err();
4608        match err {
4609            Error::BadResponse(_) => {}
4610            other => panic!("expected BadResponse, got {other:?}"),
4611        }
4612    }
4613
4614    #[test]
4615    fn peer_settings_rejects_oversize_window() {
4616        let mut p = PeerSettings::default();
4617        // 2^31 exactly is one past the max.
4618        let payload = settings_payload(&[(S_INITIAL_WINDOW_SIZE, 0x8000_0000)]);
4619        let err = p.apply_settings_payload(&payload).unwrap_err();
4620        match err {
4621            Error::BadResponse(_) => {}
4622            other => panic!("expected BadResponse, got {other:?}"),
4623        }
4624    }
4625
4626    #[test]
4627    fn peer_settings_rejects_undersize_max_frame() {
4628        let mut p = PeerSettings::default();
4629        let payload = settings_payload(&[(S_MAX_FRAME_SIZE, 16_383)]);
4630        let err = p.apply_settings_payload(&payload).unwrap_err();
4631        match err {
4632            Error::BadResponse(_) => {}
4633            other => panic!("expected BadResponse, got {other:?}"),
4634        }
4635    }
4636
4637    #[test]
4638    fn peer_settings_rejects_truncated_payload() {
4639        let mut p = PeerSettings::default();
4640        let payload = vec![0u8; 5];
4641        let err = p.apply_settings_payload(&payload).unwrap_err();
4642        match err {
4643            Error::BadResponse(_) => {}
4644            other => panic!("expected BadResponse, got {other:?}"),
4645        }
4646    }
4647
4648    #[test]
4649    fn peer_settings_enable_push_zero_disables() {
4650        // We send ENABLE_PUSH=0 ourselves; verify the parser handles it both ways.
4651        let mut p = PeerSettings::default();
4652        p.apply_settings_payload(&settings_payload(&[(S_ENABLE_PUSH, 0)]))
4653            .unwrap();
4654        assert!(!p.enable_push);
4655        p.apply_settings_payload(&settings_payload(&[(S_ENABLE_PUSH, 1)]))
4656            .unwrap();
4657        assert!(p.enable_push);
4658    }
4659
4660    #[test]
4661    fn peer_settings_max_frame_size_boundaries() {
4662        // 16384 and 16777215 are inclusive bounds.
4663        let mut p = PeerSettings::default();
4664        p.apply_settings_payload(&settings_payload(&[(S_MAX_FRAME_SIZE, 16_384)]))
4665            .unwrap();
4666        assert_eq!(p.max_frame_size, 16_384);
4667        p.apply_settings_payload(&settings_payload(&[(S_MAX_FRAME_SIZE, 16_777_215)]))
4668            .unwrap();
4669        assert_eq!(p.max_frame_size, 16_777_215);
4670        // One past the max should fail.
4671        let err = p
4672            .apply_settings_payload(&settings_payload(&[(S_MAX_FRAME_SIZE, 16_777_216)]))
4673            .unwrap_err();
4674        assert!(matches!(err, Error::BadResponse(_)));
4675    }
4676
4677    // -----------------------------------------------------------------
4678    // Flow control (RFC 9113 §6.9).
4679    // -----------------------------------------------------------------
4680
4681    #[test]
4682    fn send_window_defaults_match_rfc() {
4683        // Conn and stream send windows both start at 65_535 (RFC 9113 §6.9.2).
4684        let c = ConnSendWindow::new();
4685        assert_eq!(c.available, 65_535);
4686        let s = StreamSendWindow::new(65_535);
4687        assert_eq!(s.available, 65_535);
4688        assert_eq!(s.initial_peer_window, 65_535);
4689    }
4690
4691    #[test]
4692    fn send_window_decrements_after_data() {
4693        // Both halves drop independently by exactly `n` on `consume`.
4694        let mut c = ConnSendWindow::new();
4695        let mut s = StreamSendWindow::new(65_535);
4696        c.consume(1000);
4697        s.consume(1000);
4698        assert_eq!(c.available, 64_535);
4699        assert_eq!(s.available, 64_535);
4700        c.consume(64_535);
4701        s.consume(64_535);
4702        assert_eq!(c.available, 0);
4703        assert_eq!(s.available, 0);
4704    }
4705
4706    #[test]
4707    fn window_update_zero_increment_is_error() {
4708        // RFC 9113 §6.9.1: zero increment is PROTOCOL_ERROR on a stream and
4709        // FLOW_CONTROL_ERROR on the connection. Both reject it.
4710        let zero_payload = [0u8; 4];
4711        let inc = parse_window_update(&zero_payload).unwrap();
4712        assert_eq!(inc, 0);
4713        let mut c = ConnSendWindow::new();
4714        assert!(matches!(
4715            c.apply_window_update(inc),
4716            Err(Error::BadResponse(_))
4717        ));
4718        let mut s = StreamSendWindow::new(65_535);
4719        assert!(matches!(
4720            s.apply_window_update(inc),
4721            Err(Error::BadResponse(_))
4722        ));
4723    }
4724
4725    #[test]
4726    fn window_update_overflow_is_error() {
4727        // Current window = 2^31 - 1, increment 1 → would push to 2^31; that's
4728        // a FLOW_CONTROL_ERROR (RFC 9113 §6.9.1).
4729        let mut c = ConnSendWindow::new();
4730        c.available = WINDOW_MAX;
4731        assert!(matches!(
4732            c.apply_window_update(1),
4733            Err(Error::BadResponse(_))
4734        ));
4735        let mut s = StreamSendWindow::new(65_535);
4736        s.available = WINDOW_MAX;
4737        assert!(matches!(
4738            s.apply_window_update(1),
4739            Err(Error::BadResponse(_))
4740        ));
4741    }
4742
4743    #[test]
4744    fn window_update_high_bit_ignored_on_parse() {
4745        // The high bit of the 4-byte payload is reserved (R bit) and MUST be
4746        // ignored on receipt. Pass a payload with R=1 and increment=1.
4747        let payload = [0x80, 0x00, 0x00, 0x01];
4748        let inc = parse_window_update(&payload).unwrap();
4749        assert_eq!(inc, 1);
4750    }
4751
4752    #[test]
4753    fn window_update_wrong_length_is_error() {
4754        // Payload must be exactly 4 bytes (FRAME_SIZE_ERROR per RFC 9113 §6.9).
4755        assert!(matches!(
4756            parse_window_update(&[0u8; 3]),
4757            Err(Error::BadResponse(_))
4758        ));
4759        assert!(matches!(
4760            parse_window_update(&[0u8; 5]),
4761            Err(Error::BadResponse(_))
4762        ));
4763    }
4764
4765    #[test]
4766    fn initial_window_size_delta_adjusts_stream_send_window() {
4767        // Peer doubles INITIAL_WINDOW_SIZE: 65535 → 131072. Existing stream's
4768        // send window grows by exactly that delta. Conn window is independent.
4769        let mut s = StreamSendWindow::new(65_535);
4770        s.apply_initial_window_change(131_072).unwrap();
4771        assert_eq!(s.available, 65_535 + (131_072 - 65_535));
4772        assert_eq!(s.initial_peer_window, 131_072);
4773        // A subsequent shrink applies relative to the new initial, not the
4774        // RFC default.
4775        s.apply_initial_window_change(0).unwrap();
4776        // delta = 0 - 131072 = -131072; stream was 131072, becomes 0.
4777        assert_eq!(s.available, 0);
4778        assert_eq!(s.initial_peer_window, 0);
4779    }
4780
4781    #[test]
4782    fn initial_window_size_delta_overflow_is_error() {
4783        // Stream send window already at 2^31-1, then SETTINGS announces a
4784        // positive INITIAL_WINDOW_SIZE delta → result exceeds 2^31-1, which
4785        // is a FLOW_CONTROL_ERROR (RFC 9113 §6.9.2).
4786        let mut s = StreamSendWindow::new(65_535);
4787        s.available = WINDOW_MAX;
4788        let err = s.apply_initial_window_change(65_536).unwrap_err();
4789        assert!(matches!(err, Error::BadResponse(_)));
4790    }
4791
4792    #[test]
4793    fn initial_window_size_delta_allows_negative_window() {
4794        // §6.9.2 explicitly permits the window to go negative when the peer
4795        // shrinks INITIAL_WINDOW_SIZE below the bytes already in flight.
4796        let mut s = StreamSendWindow::new(65_535);
4797        s.available = 100;
4798        s.apply_initial_window_change(0).unwrap();
4799        // delta = 0 - 65535 = -65535; 100 + (-65535) = -65435.
4800        assert_eq!(s.available, -65_435);
4801    }
4802
4803    #[test]
4804    fn recv_window_new_sets_available_and_initial() {
4805        let c = ConnRecvWindow::new(OUR_INITIAL_WINDOW);
4806        assert_eq!(c.available, OUR_INITIAL_WINDOW);
4807        assert_eq!(c.initial, OUR_INITIAL_WINDOW);
4808        let s = StreamRecvWindow::new(DEFAULT_RECV_WINDOW as i64);
4809        assert_eq!(s.available, DEFAULT_RECV_WINDOW as i64);
4810        assert_eq!(s.initial, DEFAULT_RECV_WINDOW as i64);
4811    }
4812
4813    #[test]
4814    fn recv_window_no_replenish_above_half() {
4815        // Consume slightly less than half the initial window — no
4816        // WINDOW_UPDATE should be produced.
4817        let mut c = ConnRecvWindow::new(OUR_INITIAL_WINDOW);
4818        c.consume(1000);
4819        assert!(c.replenish().is_none());
4820        assert_eq!(c.available, OUR_INITIAL_WINDOW - 1000);
4821
4822        let mut s = StreamRecvWindow::new(OUR_INITIAL_WINDOW);
4823        s.consume(1000);
4824        assert!(s.replenish(1).is_none());
4825        assert_eq!(s.available, OUR_INITIAL_WINDOW - 1000);
4826    }
4827
4828    #[test]
4829    fn recv_window_replenishes_when_below_half() {
4830        // Two DATA-sized consumes (20_000 each) bring the window to 25_535 —
4831        // below the 32_767 threshold. Replenish must emit one WINDOW_UPDATE
4832        // restoring the running window to `initial`.
4833        let mut c = ConnRecvWindow::new(OUR_INITIAL_WINDOW);
4834        c.consume(20_000);
4835        c.consume(20_000);
4836        assert_eq!(c.available, 25_535);
4837        let f = c.replenish().expect("conn window expected replenish");
4838        assert_eq!(f.typ, F_WINDOW_UPDATE);
4839        assert_eq!(f.stream_id, 0);
4840        let inc = parse_window_update(&f.payload).unwrap();
4841        assert_eq!(inc, (OUR_INITIAL_WINDOW - 25_535) as u32);
4842        assert_eq!(c.available, OUR_INITIAL_WINDOW);
4843        // Idempotent: subsequent replenish at full is a no-op.
4844        assert!(c.replenish().is_none());
4845
4846        let mut s = StreamRecvWindow::new(OUR_INITIAL_WINDOW);
4847        s.consume(40_000);
4848        let f = s.replenish(7).expect("stream window expected replenish");
4849        assert_eq!(f.typ, F_WINDOW_UPDATE);
4850        assert_eq!(f.stream_id, 7);
4851        let inc = parse_window_update(&f.payload).unwrap();
4852        assert_eq!(inc, 40_000);
4853        assert_eq!(s.available, OUR_INITIAL_WINDOW);
4854    }
4855
4856    #[test]
4857    fn new_advertises_recv_window_and_bumps_conn_window() {
4858        // Bug fix: the client must raise both receive windows above the 64 KiB
4859        // RFC default, else h2 download throughput is capped at window/RTT.
4860        let recv = 8 * 1024 * 1024u32;
4861        let conn = Connection::new(FakeTls::new(), recv).unwrap();
4862        let out = conn.tls.wire_out.clone();
4863        assert!(out.starts_with(PREFACE), "client preface comes first");
4864        let mut cur = Cursor::new(out[PREFACE.len()..].to_vec());
4865
4866        // SETTINGS carries SETTINGS_INITIAL_WINDOW_SIZE = recv (per stream).
4867        let settings = read_frame(&mut cur).unwrap();
4868        assert_eq!(settings.typ, F_SETTINGS);
4869        let iws = settings
4870            .payload
4871            .chunks_exact(6)
4872            .find(|c| u16::from_be_bytes([c[0], c[1]]) == S_INITIAL_WINDOW_SIZE)
4873            .map(|c| u32::from_be_bytes([c[2], c[3], c[4], c[5]]));
4874        assert_eq!(iws, Some(recv));
4875
4876        // A stream-0 WINDOW_UPDATE raises the connection window to the same size.
4877        let wu = read_frame(&mut cur).unwrap();
4878        assert_eq!(wu.typ, F_WINDOW_UPDATE);
4879        assert_eq!(wu.stream_id, 0);
4880        assert_eq!(
4881            parse_window_update(&wu.payload).unwrap(),
4882            recv - OUR_INITIAL_WINDOW as u32
4883        );
4884
4885        // Post-state windows reflect the advertised size.
4886        assert_eq!(conn.conn_recv_window.available, recv as i64);
4887        assert_eq!(conn.our_recv_window, recv as i64);
4888    }
4889
4890    #[test]
4891    fn new_at_rfc_default_emits_no_conn_window_update() {
4892        // A recv window equal to the RFC default means a zero connection bump,
4893        // which is a protocol error — so no WINDOW_UPDATE must be sent.
4894        let conn = Connection::new(FakeTls::new(), OUR_INITIAL_WINDOW as u32).unwrap();
4895        let mut cur = Cursor::new(conn.tls.wire_out[PREFACE.len()..].to_vec());
4896        assert_eq!(read_frame(&mut cur).unwrap().typ, F_SETTINGS);
4897        assert!(read_frame(&mut cur).is_err(), "no frame after SETTINGS");
4898    }
4899
4900    #[test]
4901    fn window_update_frame_payload_shape() {
4902        // The frame helper must produce a 4-byte payload with the R bit
4903        // cleared and the increment in network byte order.
4904        let f = window_update_frame(7, 0x0102_0304);
4905        assert_eq!(f.typ, F_WINDOW_UPDATE);
4906        assert_eq!(f.flags, 0);
4907        assert_eq!(f.stream_id, 7);
4908        assert_eq!(f.payload, vec![0x01, 0x02, 0x03, 0x04]);
4909    }
4910
4911    // -----------------------------------------------------------------
4912    // CONTINUATION + DATA fragmentation on send (RFC 9113 §6.1 / §6.10).
4913    // -----------------------------------------------------------------
4914
4915    #[test]
4916    fn fragment_header_block_into_continuation() {
4917        // Build a synthetic header block of `max * 2 + 7` bytes and split it
4918        // with end_stream=false (i.e. we will follow up with a DATA body).
4919        // Expected: HEADERS + CONTINUATION + CONTINUATION; END_HEADERS only
4920        // on the last; END_STREAM nowhere (because has_body == true).
4921        let max: usize = 16_384;
4922        let payload_len = max * 2 + 7;
4923        let block: Vec<u8> = (0..payload_len).map(|i| (i & 0xff) as u8).collect();
4924
4925        let frames = fragment_header_block(1, &block, max, /*end_stream=*/ false);
4926        assert_eq!(frames.len(), 3, "expected HEADERS + 2 CONTINUATION");
4927
4928        // Frame 0: HEADERS, full chunk, no END_HEADERS, no END_STREAM.
4929        assert_eq!(frames[0].typ, F_HEADERS);
4930        assert_eq!(frames[0].stream_id, 1);
4931        assert_eq!(frames[0].payload.len(), max);
4932        assert_eq!(frames[0].flags & FLAG_END_HEADERS, 0);
4933        assert_eq!(frames[0].flags & FLAG_END_STREAM, 0);
4934
4935        // Frame 1: CONTINUATION, full chunk, no flags.
4936        assert_eq!(frames[1].typ, F_CONTINUATION);
4937        assert_eq!(frames[1].stream_id, 1);
4938        assert_eq!(frames[1].payload.len(), max);
4939        assert_eq!(frames[1].flags, 0);
4940
4941        // Frame 2: CONTINUATION, tail (7 bytes), END_HEADERS set.
4942        assert_eq!(frames[2].typ, F_CONTINUATION);
4943        assert_eq!(frames[2].stream_id, 1);
4944        assert_eq!(frames[2].payload.len(), 7);
4945        assert_eq!(frames[2].flags, FLAG_END_HEADERS);
4946
4947        // Reassembling all three payloads must reproduce the original block.
4948        let mut reassembled = Vec::with_capacity(payload_len);
4949        for f in &frames {
4950            reassembled.extend_from_slice(&f.payload);
4951        }
4952        assert_eq!(reassembled, block);
4953
4954        // Now flip end_stream=true (no body); END_STREAM lands on the
4955        // HEADERS frame, not on the final CONTINUATION (per RFC 9113 §6.10).
4956        let frames = fragment_header_block(1, &block, max, /*end_stream=*/ true);
4957        assert_eq!(frames[0].flags & FLAG_END_STREAM, FLAG_END_STREAM);
4958        assert_eq!(frames[2].flags & FLAG_END_STREAM, 0);
4959        assert_eq!(frames[2].flags & FLAG_END_HEADERS, FLAG_END_HEADERS);
4960    }
4961
4962    #[test]
4963    fn fragment_header_block_exact_fit() {
4964        // A block of exactly max_frame_size bytes is a single HEADERS frame
4965        // with END_HEADERS set and no CONTINUATION needed.
4966        let max: usize = 16_384;
4967        let block: Vec<u8> = vec![0xab; max];
4968
4969        // Case 1: has body → no END_STREAM on the HEADERS frame.
4970        let frames = fragment_header_block(1, &block, max, /*end_stream=*/ false);
4971        assert_eq!(frames.len(), 1);
4972        assert_eq!(frames[0].typ, F_HEADERS);
4973        assert_eq!(frames[0].stream_id, 1);
4974        assert_eq!(frames[0].payload.len(), max);
4975        assert_eq!(frames[0].flags & FLAG_END_HEADERS, FLAG_END_HEADERS);
4976        assert_eq!(frames[0].flags & FLAG_END_STREAM, 0);
4977
4978        // Case 2: no body → END_STREAM on the HEADERS frame.
4979        let frames = fragment_header_block(1, &block, max, /*end_stream=*/ true);
4980        assert_eq!(frames.len(), 1);
4981        assert_eq!(
4982            frames[0].flags,
4983            FLAG_END_HEADERS | FLAG_END_STREAM,
4984            "exact-fit HEADERS with no body should have END_HEADERS|END_STREAM"
4985        );
4986    }
4987
4988    #[test]
4989    fn fragment_header_block_empty() {
4990        // Empty header block → single HEADERS frame with empty payload and
4991        // END_HEADERS set (and END_STREAM if there's no body).
4992        let frames = fragment_header_block(1, &[], 16_384, /*end_stream=*/ true);
4993        assert_eq!(frames.len(), 1);
4994        assert_eq!(frames[0].typ, F_HEADERS);
4995        assert!(frames[0].payload.is_empty());
4996        assert_eq!(frames[0].flags, FLAG_END_HEADERS | FLAG_END_STREAM);
4997    }
4998
4999    #[test]
5000    fn fragment_header_block_small_under_cap() {
5001        // A small block (well under the cap) → single HEADERS frame holding
5002        // the whole block; END_HEADERS set.
5003        let block = vec![0x82, 0x86, 0x84]; // three indexed headers
5004        let frames = fragment_header_block(1, &block, 16_384, /*end_stream=*/ false);
5005        assert_eq!(frames.len(), 1);
5006        assert_eq!(frames[0].typ, F_HEADERS);
5007        assert_eq!(frames[0].payload, block);
5008        assert_eq!(frames[0].flags, FLAG_END_HEADERS);
5009    }
5010
5011    #[test]
5012    fn next_data_chunk_size_clamps_to_min_of_three() {
5013        // Returns the smallest of (max_frame_size, available, remaining).
5014        assert_eq!(next_data_chunk_size(16_384, 65_535, 100), 100);
5015        assert_eq!(next_data_chunk_size(16_384, 65_535, 1_000_000), 16_384);
5016        assert_eq!(next_data_chunk_size(16_384, 1_000, 1_000_000), 1_000);
5017        assert_eq!(next_data_chunk_size(16_384, 5_000, 8_000), 5_000);
5018    }
5019
5020    #[test]
5021    fn next_data_chunk_size_zero_when_window_depleted() {
5022        // available <= 0 must yield 0 so the caller knows to block on a
5023        // WINDOW_UPDATE before issuing the next DATA chunk.
5024        assert_eq!(next_data_chunk_size(16_384, 0, 100), 0);
5025        assert_eq!(next_data_chunk_size(16_384, -1, 100), 0);
5026        assert_eq!(next_data_chunk_size(16_384, -65_535, 100), 0);
5027    }
5028
5029    #[test]
5030    fn fragment_data_into_chunks() {
5031        // Synthetic body fragmentation mirroring the body-send loop's
5032        // chunking logic, but without I/O: split a body using `available` as
5033        // an unchanging window (no WINDOW_UPDATE replenishment in this test).
5034        // We assert: every chunk is ≤ max_frame_size, ≤ available, the chunks
5035        // reassemble to the original body, and only the final frame has
5036        // FLAG_END_STREAM set.
5037        fn fragment(body: &[u8], max_frame_size: usize, mut available: i64) -> Vec<Frame> {
5038            let mut out = Vec::new();
5039            let mut remaining = body;
5040            while !remaining.is_empty() {
5041                let n = next_data_chunk_size(max_frame_size, available, remaining.len());
5042                if n == 0 {
5043                    break; // stalled — caller would have to wait for WINDOW_UPDATE
5044                }
5045                let chunk = &remaining[..n];
5046                remaining = &remaining[n..];
5047                let is_last = remaining.is_empty();
5048                out.push(Frame {
5049                    typ: F_DATA,
5050                    flags: if is_last { FLAG_END_STREAM } else { 0 },
5051                    stream_id: 1,
5052                    payload: chunk.to_vec(),
5053                });
5054                available -= n as i64;
5055            }
5056            out
5057        }
5058
5059        // Case 1: body fits well within window, just exceeds max_frame_size.
5060        let body: Vec<u8> = (0..50_000u32).map(|i| (i & 0xff) as u8).collect();
5061        let frames = fragment(&body, 16_384, 65_535);
5062        // 50_000 / 16_384 = 3 full + 1 partial = 4 frames.
5063        assert_eq!(frames.len(), 4);
5064        assert_eq!(frames[0].payload.len(), 16_384);
5065        assert_eq!(frames[1].payload.len(), 16_384);
5066        assert_eq!(frames[2].payload.len(), 16_384);
5067        assert_eq!(frames[3].payload.len(), 50_000 - 3 * 16_384);
5068        // END_STREAM only on the final frame.
5069        assert_eq!(frames[0].flags, 0);
5070        assert_eq!(frames[1].flags, 0);
5071        assert_eq!(frames[2].flags, 0);
5072        assert_eq!(frames[3].flags, FLAG_END_STREAM);
5073        // Reassembly matches the original body.
5074        let mut roundtrip = Vec::with_capacity(body.len());
5075        for f in &frames {
5076            roundtrip.extend_from_slice(&f.payload);
5077        }
5078        assert_eq!(roundtrip, body);
5079
5080        // Case 2: window smaller than max_frame_size — chunks shrink to fit.
5081        let frames = fragment(&body, 16_384, 4_000);
5082        // First chunk capped to 4_000; available depletes after; loop stalls.
5083        // (Real code would WINDOW_UPDATE-wait, but this in-test fragmenter
5084        // mimics that with `available -= n` and `n == 0 → break`.)
5085        assert_eq!(frames.len(), 1);
5086        assert_eq!(frames[0].payload.len(), 4_000);
5087        // Not the last chunk in absolute terms — only one was emitted.
5088        assert_eq!(frames[0].flags, 0);
5089
5090        // Case 3: body exactly equals max_frame_size — one frame, END_STREAM.
5091        let body = vec![0xab; 16_384];
5092        let frames = fragment(&body, 16_384, 65_535);
5093        assert_eq!(frames.len(), 1);
5094        assert_eq!(frames[0].payload.len(), 16_384);
5095        assert_eq!(frames[0].flags, FLAG_END_STREAM);
5096
5097        // Case 4: empty body — no frames at all (caller skips the loop).
5098        let frames = fragment(&[], 16_384, 65_535);
5099        assert!(frames.is_empty());
5100    }
5101
5102    // -----------------------------------------------------------------
5103    // Connection / stream dispatch (RFC 9113 §5.1, §6.10).
5104    // -----------------------------------------------------------------
5105
5106    /// In-memory Read+Write impl. `wire_in` is what the test feeds *to* the
5107    /// `Connection` (frames the peer would send), `wire_out` is what the
5108    /// `Connection` wrote *to* the peer.
5109    struct FakeTls {
5110        wire_in: Cursor<Vec<u8>>,
5111        wire_out: Vec<u8>,
5112    }
5113
5114    impl FakeTls {
5115        fn new() -> Self {
5116            FakeTls {
5117                wire_in: Cursor::new(Vec::new()),
5118                wire_out: Vec::new(),
5119            }
5120        }
5121    }
5122
5123    impl Read for FakeTls {
5124        fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
5125            self.wire_in.read(buf)
5126        }
5127    }
5128    impl Write for FakeTls {
5129        fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
5130            self.wire_out.extend_from_slice(buf);
5131            Ok(buf.len())
5132        }
5133        fn flush(&mut self) -> io::Result<()> {
5134            Ok(())
5135        }
5136    }
5137
5138    /// Build a `Connection` over a fresh `FakeTls` without going through the
5139    /// real `new()` path (we don't want to consume the preface bytes from
5140    /// `wire_out` in every test). All defaults match the `Connection::new`
5141    /// post-state on a clean handshake.
5142    fn fake_conn() -> Connection<FakeTls> {
5143        Connection {
5144            tls: FakeTls::new(),
5145            peer: PeerSettings::default(),
5146            conn_send_window: ConnSendWindow::new(),
5147            conn_recv_window: ConnRecvWindow::new(OUR_INITIAL_WINDOW),
5148            our_recv_window: OUR_INITIAL_WINDOW,
5149            decoder: Decoder::new(),
5150            encoder: Encoder::new(),
5151            streams: HashMap::new(),
5152            next_stream_id: 1,
5153            goaway_received: None,
5154            expecting_continuation: None,
5155            budget: FloodBudget::default(),
5156            made_progress: false,
5157            tls_info: None,
5158            dial_timing: crate::http::Timing::default(),
5159        }
5160    }
5161
5162    #[test]
5163    fn priority_weight_byte_maps_hints() {
5164        use crate::http::Priority;
5165        assert_eq!(priority_weight_byte(Priority::High), Some(255));
5166        assert_eq!(priority_weight_byte(Priority::Normal), None);
5167        assert_eq!(priority_weight_byte(Priority::Low), Some(0));
5168    }
5169
5170    #[test]
5171    fn priority_hint_emits_priority_frame() {
5172        use crate::http::Priority;
5173        let mut conn = fake_conn();
5174        conn.send_priority_hint(1, Priority::High).unwrap();
5175        let out = &conn.tls.wire_out;
5176        // 9-byte frame header + 5-byte PRIORITY payload.
5177        assert_eq!(out.len(), 14, "expected one PRIORITY frame");
5178        assert_eq!(out[3], F_PRIORITY, "frame type should be PRIORITY");
5179        assert_eq!(out[13], 255, "weight byte should be max for High");
5180        // Normal priority emits no frame (default weight).
5181        let mut c2 = fake_conn();
5182        c2.send_priority_hint(3, Priority::Normal).unwrap();
5183        assert!(
5184            c2.tls.wire_out.is_empty(),
5185            "Normal should send no PRIORITY frame"
5186        );
5187    }
5188
5189    #[test]
5190    fn apply_dial_timing_copies_phases() {
5191        use std::time::Duration;
5192        let mut conn = fake_conn();
5193        conn.dial_timing = crate::http::Timing {
5194            namelookup: Some(Duration::from_millis(1)),
5195            connect: Some(Duration::from_millis(2)),
5196            appconnect: Some(Duration::from_millis(3)),
5197            pretransfer: Some(Duration::from_millis(3)),
5198            ..Default::default()
5199        };
5200        let mut resp = crate::http::Response {
5201            status: 200,
5202            reason: String::new(),
5203            version: "HTTP/2".into(),
5204            headers: Vec::new(),
5205            body: Vec::new(),
5206            timing: crate::http::Timing::default(),
5207            final_url: String::new(),
5208            tls: None,
5209        };
5210        apply_dial_timing(&mut resp, &conn);
5211        assert_eq!(resp.timing.namelookup, Some(Duration::from_millis(1)));
5212        assert_eq!(resp.timing.connect, Some(Duration::from_millis(2)));
5213        assert_eq!(resp.timing.appconnect, Some(Duration::from_millis(3)));
5214        assert_eq!(resp.timing.pretransfer, Some(Duration::from_millis(3)));
5215    }
5216
5217    #[test]
5218    fn connection_process_settings_acks_and_applies() {
5219        // Synthetic SETTINGS frame: bump MAX_FRAME_SIZE to 32 KiB and
5220        // INITIAL_WINDOW_SIZE to 131_072. process_frame must:
5221        // 1. update conn.peer to reflect the new values,
5222        // 2. shift every existing stream's send window by the
5223        //    INITIAL_WINDOW_SIZE delta (here, none exist),
5224        // 3. write a SETTINGS ACK frame back to the (fake) TLS sink.
5225        let payload =
5226            settings_payload(&[(S_MAX_FRAME_SIZE, 32_768), (S_INITIAL_WINDOW_SIZE, 131_072)]);
5227        let frame = Frame {
5228            typ: F_SETTINGS,
5229            flags: 0,
5230            stream_id: 0,
5231            payload,
5232        };
5233        let mut conn = fake_conn();
5234        let outcome = conn.process_frame(frame, None).unwrap();
5235        assert_eq!(outcome, DispatchOutcome::Continue);
5236        assert_eq!(conn.peer.max_frame_size, 32_768);
5237        assert_eq!(conn.peer.initial_window_size, 131_072);
5238        assert_eq!(conn.conn_send_window.available, 65_535); // untouched
5239
5240        // The ACK frame must be on the wire.
5241        assert_eq!(conn.tls.wire_out.len(), 9);
5242        let mut cur = Cursor::new(conn.tls.wire_out.clone());
5243        let ack = read_frame(&mut cur).unwrap();
5244        assert_eq!(ack.typ, F_SETTINGS);
5245        assert_eq!(ack.flags, FLAG_ACK);
5246        assert_eq!(ack.stream_id, 0);
5247        assert!(ack.payload.is_empty());
5248    }
5249
5250    #[test]
5251    fn connection_process_window_update_replenishes_send_window() {
5252        // A WINDOW_UPDATE for an open stream grows the stream send window;
5253        // a WINDOW_UPDATE on stream 0 grows the conn window.
5254        let mut conn = fake_conn();
5255        let id = conn.open_stream().unwrap();
5256        conn.process_frame(window_update_frame(id, 10_000), None)
5257            .unwrap();
5258        assert_eq!(
5259            conn.streams.get(&id).unwrap().send_window.available,
5260            65_535 + 10_000
5261        );
5262        assert_eq!(conn.conn_send_window.available, 65_535);
5263
5264        conn.process_frame(window_update_frame(0, 5_000), None)
5265            .unwrap();
5266        assert_eq!(conn.conn_send_window.available, 65_535 + 5_000);
5267    }
5268
5269    // ---- stream state machine -----
5270
5271    #[test]
5272    fn stream_state_open_to_half_closed_local_on_end_stream_send() {
5273        // From Open, sending DATA with end_stream advances to HalfClosedLocal.
5274        let s = StreamState::Open;
5275        assert_eq!(
5276            s.send_data(/*end_stream=*/ true).unwrap(),
5277            StreamState::HalfClosedLocal
5278        );
5279        // Without END_STREAM the state stays Open.
5280        assert_eq!(
5281            StreamState::Open.send_data(false).unwrap(),
5282            StreamState::Open
5283        );
5284    }
5285
5286    #[test]
5287    fn stream_state_recv_data_in_idle_is_error() {
5288        // Idle streams cannot receive DATA — that's a §5.1 violation.
5289        let err = StreamState::Idle.recv_data(false).unwrap_err();
5290        assert!(matches!(err, Error::BadResponse(_)));
5291    }
5292
5293    #[test]
5294    fn stream_state_recv_headers_on_closed_stream_is_ignored() {
5295        // Closed → Closed; no panic, no error. The peer is allowed to send a
5296        // late trailer block; the decoder will still process it for HPACK
5297        // dynamic-table consistency but we won't surface the headers.
5298        assert_eq!(
5299            StreamState::Closed.recv_headers(true).unwrap(),
5300            StreamState::Closed
5301        );
5302    }
5303
5304    // ---- stream id allocation -----
5305
5306    #[test]
5307    fn next_stream_id_allocates_odd_only() {
5308        // §5.1.1: client-initiated streams are odd-numbered and strictly
5309        // increasing. Open four; ids must be 1, 3, 5, 7.
5310        let mut conn = fake_conn();
5311        let ids: Vec<u32> = (0..4).map(|_| conn.open_stream().unwrap()).collect();
5312        assert_eq!(ids, vec![1, 3, 5, 7]);
5313    }
5314
5315    #[test]
5316    fn open_stream_refuses_at_max_concurrent() {
5317        let mut conn = fake_conn();
5318        conn.peer.max_concurrent_streams = 2;
5319        assert!(conn.open_stream().is_ok());
5320        assert!(conn.open_stream().is_ok());
5321        let err = conn.open_stream().unwrap_err();
5322        assert!(matches!(err, Error::BadResponse(_)));
5323    }
5324
5325    #[test]
5326    fn open_stream_refuses_after_goaway() {
5327        // GOAWAY with last-stream-id=3: ids 1 and 3 can still be allocated,
5328        // but the id=5 attempt errors.
5329        let mut conn = fake_conn();
5330        conn.goaway_received = Some(3);
5331        assert_eq!(conn.open_stream().unwrap(), 1);
5332        assert_eq!(conn.open_stream().unwrap(), 3);
5333        let err = conn.open_stream().unwrap_err();
5334        assert!(matches!(err, Error::BadResponse(_)));
5335    }
5336
5337    // ---- per-frame dispatch / multiplexing -----
5338
5339    /// Synthesize a HEADERS frame for stream `id` carrying just `:status 200`.
5340    fn synth_status_200_headers(id: u32, end_stream: bool) -> Frame {
5341        // 0x88 = indexed header field, static index 8 = (":status", "200").
5342        let payload = vec![0x88];
5343        let mut flags = FLAG_END_HEADERS;
5344        if end_stream {
5345            flags |= FLAG_END_STREAM;
5346        }
5347        Frame {
5348            typ: F_HEADERS,
5349            flags,
5350            stream_id: id,
5351            payload,
5352        }
5353    }
5354
5355    fn synth_data(id: u32, body: &[u8], end_stream: bool) -> Frame {
5356        Frame {
5357            typ: F_DATA,
5358            flags: if end_stream { FLAG_END_STREAM } else { 0 },
5359            stream_id: id,
5360            payload: body.to_vec(),
5361        }
5362    }
5363
5364    #[test]
5365    fn dispatch_frame_routes_to_correct_stream() {
5366        // Open two streams; feed interleaved HEADERS + DATA for each;
5367        // each stream's body must accumulate only its own bytes.
5368        let mut conn = fake_conn();
5369        let id_a = conn.open_stream().unwrap();
5370        let id_b = conn.open_stream().unwrap();
5371        // Manually move both streams into Open (as if we'd just sent HEADERS).
5372        conn.streams.get_mut(&id_a).unwrap().state = StreamState::Open;
5373        conn.streams.get_mut(&id_b).unwrap().state = StreamState::Open;
5374
5375        conn.process_frame(synth_status_200_headers(id_a, false), None)
5376            .unwrap();
5377        conn.process_frame(synth_status_200_headers(id_b, false), None)
5378            .unwrap();
5379        conn.process_frame(synth_data(id_a, b"aaa", false), None)
5380            .unwrap();
5381        conn.process_frame(synth_data(id_b, b"bbbb", false), None)
5382            .unwrap();
5383        conn.process_frame(synth_data(id_a, b"AAA", true), None)
5384            .unwrap();
5385        conn.process_frame(synth_data(id_b, b"BBBB", true), None)
5386            .unwrap();
5387
5388        assert_eq!(conn.streams.get(&id_a).unwrap().body, b"aaaAAA");
5389        assert_eq!(conn.streams.get(&id_b).unwrap().body, b"bbbbBBBB");
5390    }
5391
5392    #[test]
5393    fn dispatch_data_on_unknown_stream_is_silently_dropped() {
5394        // DATA on a stream id we never opened: per RFC 9113 §5.1 we may
5395        // ignore. No error surfaces and nothing is accumulated.
5396        let mut conn = fake_conn();
5397        let outcome = conn
5398            .process_frame(synth_data(7, b"orphaned", false), None)
5399            .unwrap();
5400        assert_eq!(outcome, DispatchOutcome::Continue);
5401        // No stream was registered, so no body anywhere.
5402        assert!(conn.streams.is_empty());
5403        // Conn recv window has still been charged (the bytes did cross the
5404        // shared budget), then possibly replenished — verify the latter holds.
5405        assert!(conn.conn_recv_window.available <= OUR_INITIAL_WINDOW);
5406    }
5407
5408    #[test]
5409    fn inbound_data_exceeding_conn_window_is_flow_control_error() {
5410        // RFC 9113 §6.9.1: a single DATA frame larger than the advertised
5411        // connection receive window must be rejected as a flow-control error.
5412        let mut conn = fake_conn();
5413        let id = conn.open_stream().unwrap();
5414        conn.streams.get_mut(&id).unwrap().state = StreamState::Open;
5415        conn.process_frame(synth_status_200_headers(id, false), None)
5416            .unwrap();
5417
5418        // OUR_INITIAL_WINDOW + 1 bytes overruns the 65_535 conn window.
5419        let overrun = vec![0u8; OUR_INITIAL_WINDOW as usize + 1];
5420        let err = conn
5421            .process_frame(synth_data(id, &overrun, false), None)
5422            .unwrap_err();
5423        match err {
5424            Error::BadResponse(m) => assert!(
5425                m.contains("flow-control window exceeded"),
5426                "unexpected message: {m}"
5427            ),
5428            other => panic!("expected BadResponse, got {other:?}"),
5429        }
5430    }
5431
5432    #[test]
5433    fn inbound_data_exceeding_stream_window_is_flow_control_error() {
5434        // Same overrun but isolated to the per-stream window: inflate the
5435        // connection window so only the stream window goes negative, proving
5436        // the per-stream check fires independently.
5437        let mut conn = fake_conn();
5438        let id = conn.open_stream().unwrap();
5439        conn.streams.get_mut(&id).unwrap().state = StreamState::Open;
5440        conn.process_frame(synth_status_200_headers(id, false), None)
5441            .unwrap();
5442
5443        // Give the conn window plenty of room so it stays >= 0.
5444        conn.conn_recv_window.available = i64::from(u32::MAX);
5445
5446        let overrun = vec![0u8; OUR_INITIAL_WINDOW as usize + 1];
5447        let err = conn
5448            .process_frame(synth_data(id, &overrun, false), None)
5449            .unwrap_err();
5450        match err {
5451            Error::BadResponse(m) => assert!(
5452                m.contains("flow-control window exceeded"),
5453                "unexpected message: {m}"
5454            ),
5455            other => panic!("expected BadResponse, got {other:?}"),
5456        }
5457        // The stream window must have actually gone negative.
5458        assert!(conn.streams.get(&id).unwrap().recv_window.available < 0);
5459    }
5460
5461    #[test]
5462    fn inbound_data_filling_window_exactly_is_accepted() {
5463        // A frame that drives the window to exactly 0 is legitimate and must
5464        // NOT be rejected (only strictly-negative is an overrun).
5465        let mut conn = fake_conn();
5466        let id = conn.open_stream().unwrap();
5467        conn.streams.get_mut(&id).unwrap().state = StreamState::Open;
5468        conn.process_frame(synth_status_200_headers(id, false), None)
5469            .unwrap();
5470
5471        let exact = vec![0u8; OUR_INITIAL_WINDOW as usize];
5472        // Must succeed; both windows reach exactly 0 before replenish runs.
5473        conn.process_frame(synth_data(id, &exact, false), None)
5474            .unwrap();
5475        assert_eq!(conn.streams.get(&id).unwrap().body.len(), exact.len());
5476    }
5477
5478    #[test]
5479    fn dispatch_continuation_on_wrong_stream_is_protocol_error() {
5480        // Stream 1 mid-headers (no END_HEADERS), then CONTINUATION on stream 3
5481        // → §6.10 violation, surfaced as BadResponse.
5482        let mut conn = fake_conn();
5483        let id1 = conn.open_stream().unwrap();
5484        let id3 = conn.open_stream().unwrap();
5485        assert_eq!(id1, 1);
5486        assert_eq!(id3, 3);
5487        conn.streams.get_mut(&id1).unwrap().state = StreamState::Open;
5488        conn.streams.get_mut(&id3).unwrap().state = StreamState::Open;
5489
5490        // HEADERS on 1 without END_HEADERS.
5491        let frame = Frame {
5492            typ: F_HEADERS,
5493            flags: 0, // no END_HEADERS, no END_STREAM
5494            stream_id: id1,
5495            payload: vec![0x88], // partial — but the gate triggers before HPACK
5496        };
5497        conn.process_frame(frame, None).unwrap();
5498        assert_eq!(conn.expecting_continuation, Some(id1));
5499
5500        // CONTINUATION on stream 3 must error.
5501        let bad = Frame {
5502            typ: F_CONTINUATION,
5503            flags: FLAG_END_HEADERS,
5504            stream_id: id3,
5505            payload: vec![],
5506        };
5507        let err = conn.process_frame(bad, None).unwrap_err();
5508        assert!(matches!(err, Error::BadResponse(_)));
5509    }
5510
5511    #[test]
5512    fn data_frames_past_body_cap_are_rejected() {
5513        // A server that streams DATA past MAX_RESPONSE_BYTES must be stopped
5514        // with BadResponse rather than allowed to exhaust memory.
5515        let mut conn = fake_conn();
5516        let id = conn.open_stream().unwrap();
5517        conn.streams.get_mut(&id).unwrap().state = StreamState::Open;
5518        // Seed the body right up against the ceiling, then push one more frame
5519        // that tips it over.
5520        conn.streams.get_mut(&id).unwrap().body = vec![0u8; MAX_RESPONSE_BYTES - 2];
5521        let err = conn
5522            .process_frame(synth_data(id, b"abc", false), None)
5523            .unwrap_err();
5524        assert!(matches!(err, Error::BadResponse(_)));
5525        // The over-limit payload must not have been appended.
5526        assert_eq!(
5527            conn.streams.get(&id).unwrap().body.len(),
5528            MAX_RESPONSE_BYTES - 2
5529        );
5530    }
5531
5532    #[test]
5533    fn empty_data_flood_is_bounded() {
5534        // The empty-DATA spin: a 0-byte DATA frame with no END_STREAM bills
5535        // consume(0), appends nothing (MAX_RESPONSE_BYTES never trips) and
5536        // leaves the stream Open. Without a no-progress guard the frame loop
5537        // would accept these forever. We must abort after
5538        // MAX_NO_PROGRESS_FRAMES.
5539        let mut conn = fake_conn();
5540        let id = conn.open_stream().unwrap();
5541        conn.streams.get_mut(&id).unwrap().state = StreamState::Open;
5542        conn.process_frame(synth_status_200_headers(id, false), None)
5543            .unwrap();
5544        // The HEADERS above completed a block → progress, so the streak starts
5545        // fresh. Feed empty DATA frames until the guard fires.
5546        let mut err = None;
5547        for _ in 0..(MAX_NO_PROGRESS_FRAMES as usize + 10) {
5548            match conn.process_frame(synth_data(id, b"", false), None) {
5549                Ok(_) => {}
5550                Err(e) => {
5551                    err = Some(e);
5552                    break;
5553                }
5554            }
5555        }
5556        let err = err.expect("empty-DATA flood was not bounded");
5557        match err {
5558            Error::BadResponse(m) => {
5559                assert!(m.contains("no forward progress"), "unexpected message: {m}")
5560            }
5561            other => panic!("expected BadResponse, got {other:?}"),
5562        }
5563        // The stream is still Open with an empty body — proving the abort came
5564        // from the flood guard, not from any flow-control / body-cap path.
5565        assert_eq!(conn.streams.get(&id).unwrap().body.len(), 0);
5566        assert_eq!(conn.streams.get(&id).unwrap().state, StreamState::Open);
5567    }
5568
5569    #[test]
5570    fn process_data_streams_body_to_sink() {
5571        // With a sink and an un-encoded 200 response, DATA payloads are written
5572        // straight to the sink and never buffered in `body`.
5573        let mut conn = fake_conn();
5574        let id = conn.open_stream().unwrap();
5575        conn.streams.get_mut(&id).unwrap().state = StreamState::Open;
5576        conn.process_frame(synth_status_200_headers(id, false), None)
5577            .unwrap();
5578        let mut sink: Vec<u8> = Vec::new();
5579        conn.process_frame(synth_data(id, b"hello ", false), Some(&mut sink))
5580            .unwrap();
5581        conn.process_frame(synth_data(id, b"world", true), Some(&mut sink))
5582            .unwrap();
5583        assert_eq!(sink, b"hello world");
5584        let s = conn.streams.get(&id).unwrap();
5585        assert_eq!(s.body.len(), 0, "streamed body must not be buffered");
5586        assert_eq!(s.streamed_len, 11);
5587    }
5588
5589    #[test]
5590    fn body_byte_resets_no_progress_counter() {
5591        // A single real DATA byte must reset the no-progress streak so a server
5592        // that legitimately interleaves small bodies with other frames is never
5593        // tripped. Bring the counter near the ceiling, then a 1-byte DATA frame
5594        // should let us go another full window of no-progress frames.
5595        let mut conn = fake_conn();
5596        let id = conn.open_stream().unwrap();
5597        conn.streams.get_mut(&id).unwrap().state = StreamState::Open;
5598        conn.process_frame(synth_status_200_headers(id, false), None)
5599            .unwrap();
5600
5601        for _ in 0..(MAX_NO_PROGRESS_FRAMES - 1) {
5602            conn.process_frame(synth_data(id, b"", false), None)
5603                .unwrap();
5604        }
5605        assert_eq!(conn.budget.no_progress, MAX_NO_PROGRESS_FRAMES - 1);
5606        // One real byte resets the streak.
5607        conn.process_frame(synth_data(id, b"x", false), None)
5608            .unwrap();
5609        assert_eq!(conn.budget.no_progress, 0);
5610        assert_eq!(conn.streams.get(&id).unwrap().body, b"x");
5611    }
5612
5613    #[test]
5614    fn settings_flood_is_bounded() {
5615        // Every non-ACK SETTINGS forces an ACK write+flush. An unbounded stream
5616        // must be rejected after MAX_SETTINGS_FRAMES. Use an empty payload so
5617        // each frame is a trivially-valid no-op reconfigure.
5618        let mut conn = fake_conn();
5619        let mut err = None;
5620        for _ in 0..(MAX_SETTINGS_FRAMES as usize + 10) {
5621            let f = Frame {
5622                typ: F_SETTINGS,
5623                flags: 0,
5624                stream_id: 0,
5625                payload: Vec::new(),
5626            };
5627            if let Err(e) = conn.process_frame(f, None) {
5628                err = Some(e);
5629                break;
5630            }
5631        }
5632        match err.expect("SETTINGS flood was not bounded") {
5633            Error::BadResponse(m) => {
5634                assert!(m.contains("SETTINGS"), "unexpected message: {m}")
5635            }
5636            other => panic!("expected BadResponse, got {other:?}"),
5637        }
5638    }
5639
5640    #[test]
5641    fn ping_flood_is_bounded() {
5642        // Every non-ACK PING forces a PONG write+flush; bound it.
5643        let mut conn = fake_conn();
5644        let mut err = None;
5645        for _ in 0..(MAX_PING_FRAMES as usize + 10) {
5646            let f = Frame {
5647                typ: F_PING,
5648                flags: 0,
5649                stream_id: 0,
5650                payload: vec![0u8; 8],
5651            };
5652            if let Err(e) = conn.process_frame(f, None) {
5653                err = Some(e);
5654                break;
5655            }
5656        }
5657        match err.expect("PING flood was not bounded") {
5658            Error::BadResponse(m) => assert!(m.contains("PING"), "unexpected message: {m}"),
5659            other => panic!("expected BadResponse, got {other:?}"),
5660        }
5661    }
5662
5663    #[test]
5664    fn rst_stream_flood_is_bounded() {
5665        // Rapid-Reset (CVE-2023-44487): RST_STREAM on unknown streams is
5666        // individually harmless (ignored) but must carry an aggregate budget so
5667        // a hostile server cannot churn us indefinitely. Target unknown stream
5668        // ids so each frame returns Ok(Continue) until the budget trips.
5669        let mut conn = fake_conn();
5670        let mut err = None;
5671        for i in 0..(MAX_RST_STREAM_FRAMES as usize + 10) {
5672            // Unknown odd stream id (never opened) → process_rst returns
5673            // Continue; only the flood budget can stop the loop.
5674            let f = synth_rst((2 * i as u32) + 1001, 0);
5675            if let Err(e) = conn.process_frame(f, None) {
5676                err = Some(e);
5677                break;
5678            }
5679        }
5680        match err.expect("RST_STREAM flood was not bounded") {
5681            Error::BadResponse(m) => {
5682                assert!(m.contains("RST_STREAM"), "unexpected message: {m}")
5683            }
5684            other => panic!("expected BadResponse, got {other:?}"),
5685        }
5686    }
5687
5688    #[test]
5689    fn continuation_flood_is_bounded() {
5690        // HEADERS without END_HEADERS followed by a stream of CONTINUATION
5691        // frames must not grow headers_buf without bound (CVE-2024-27316).
5692        let mut conn = fake_conn();
5693        let id = conn.open_stream().unwrap();
5694        conn.streams.get_mut(&id).unwrap().state = StreamState::Open;
5695        // HEADERS, no END_HEADERS — opens the continuation window. Payload is
5696        // raw HPACK bytes that we never decode (we error before END_HEADERS).
5697        conn.process_frame(
5698            Frame {
5699                typ: F_HEADERS,
5700                flags: 0,
5701                stream_id: id,
5702                payload: vec![0u8; 8 * 1024],
5703            },
5704            None,
5705        )
5706        .unwrap();
5707        // Keep feeding CONTINUATION fragments with no END_HEADERS; eventually
5708        // the aggregate buffer cap fires.
5709        let chunk = vec![0u8; 16 * 1024];
5710        let mut hit_cap = false;
5711        for _ in 0..(MAX_HEADERS_BUF / chunk.len() + 4) {
5712            let r = conn.process_frame(
5713                Frame {
5714                    typ: F_CONTINUATION,
5715                    flags: 0,
5716                    stream_id: id,
5717                    payload: chunk.clone(),
5718                },
5719                None,
5720            );
5721            if let Err(Error::BadResponse(_)) = r {
5722                hit_cap = true;
5723                break;
5724            }
5725            r.unwrap();
5726        }
5727        assert!(hit_cap, "CONTINUATION flood was not bounded");
5728        assert!(conn.streams.get(&id).unwrap().headers_buf.len() <= MAX_HEADERS_BUF);
5729    }
5730
5731    #[test]
5732    fn hpack_decompression_bomb_is_rejected() {
5733        // A small compressed block that expands to a huge decoded header list
5734        // must be rejected. We craft many literal-with-incremental-indexing
5735        // entries with a long value; the static dynamic-table eviction means
5736        // the *block* stays modest while the decoded list keeps growing.
5737        let mut dec = Decoder::new();
5738        let mut block: Vec<u8> = Vec::new();
5739        // Each entry: 0x40 (literal, incremental index, name idx 0) + name +
5740        // value. Use a 1-byte name and a long-ish value; repeat until the
5741        // decoded list_size accounting must exceed MAX_DECODED_HEADER_LIST.
5742        let name = b"a";
5743        let value = vec![b'x'; 4096];
5744        // Encode one entry of this shape.
5745        let mut entry = Vec::new();
5746        entry.push(0x40); // literal w/ incremental indexing, name index 0
5747        entry.push(name.len() as u8); // H=0, 7-bit length
5748        entry.extend_from_slice(name);
5749        // value length 4096 needs the multi-byte 7-bit-prefix int encoding.
5750        // 4096 = 127 + 3969 → prefix 0x7f, then 3969 as continuation bytes.
5751        encode_int_local(value.len() as u64, 7, 0x00, &mut entry);
5752        entry.extend_from_slice(&value);
5753        // ~128 entries × (4096+32) ≈ 528 KiB decoded, well over the 256 KiB cap.
5754        for _ in 0..200 {
5755            block.extend_from_slice(&entry);
5756        }
5757        let err = dec.decode_block(&block).unwrap_err();
5758        assert!(matches!(err, Error::BadResponse(_)));
5759    }
5760
5761    /// Minimal HPACK integer encoder for tests (mirrors `decode_int`).
5762    fn encode_int_local(mut value: u64, prefix_bits: u8, first_byte_high: u8, out: &mut Vec<u8>) {
5763        let max_prefix = (1u64 << prefix_bits) - 1;
5764        if value < max_prefix {
5765            out.push(first_byte_high | value as u8);
5766            return;
5767        }
5768        out.push(first_byte_high | max_prefix as u8);
5769        value -= max_prefix;
5770        while value >= 128 {
5771            out.push(((value & 0x7f) as u8) | 0x80);
5772            value >>= 7;
5773        }
5774        out.push(value as u8);
5775    }
5776
5777    // -----------------------------------------------------------------
5778    // Connection pool. All pool tests use `PoolInner::<FakeTls>` built
5779    // directly — they do NOT touch the process-global `POOL` static, so they
5780    // are isolated from one another and from any production code.
5781    // -----------------------------------------------------------------
5782
5783    fn fake_arc_conn() -> Arc<Mutex<Connection<FakeTls>>> {
5784        Arc::new(Mutex::new(fake_conn()))
5785    }
5786
5787    fn url_key(url: &str) -> PoolKey {
5788        let req = Request::new("GET", url).unwrap();
5789        PoolKey::from_request(&req)
5790    }
5791
5792    #[test]
5793    fn pool_key_round_trip() {
5794        // Same URL → equal keys; differing scheme/host/port → distinct.
5795        let a = url_key("https://example.com/a");
5796        let b = url_key("https://example.com/b"); // path differs only
5797        assert_eq!(a, b);
5798
5799        let c = url_key("https://example.com:8443/a");
5800        assert_ne!(a, c, "port differs");
5801
5802        let d = url_key("https://other.example/a");
5803        assert_ne!(a, d, "host differs");
5804    }
5805
5806    #[test]
5807    fn pool_checkout_empty_returns_none() {
5808        let mut pool: PoolInner<FakeTls> = PoolInner::new();
5809        let k = url_key("https://example.com/");
5810        assert!(pool.checkout(&k).is_none());
5811    }
5812
5813    #[test]
5814    fn pool_release_then_checkout_returns_same_conn() {
5815        // Release one Arc, check it back out, assert it's the same allocation.
5816        let mut pool: PoolInner<FakeTls> = PoolInner::new();
5817        let k = url_key("https://example.com/");
5818        let arc = fake_arc_conn();
5819        let raw_in = Arc::as_ptr(&arc) as usize;
5820        pool.release(k.clone(), arc);
5821
5822        let got = pool.checkout(&k).expect("checkout after release");
5823        let raw_out = Arc::as_ptr(&got) as usize;
5824        assert_eq!(raw_in, raw_out, "pool returned a different Arc");
5825
5826        // Bucket should have been removed once empty.
5827        assert!(pool.checkout(&k).is_none());
5828    }
5829
5830    #[test]
5831    fn pool_per_key_cap_drops_overflow() {
5832        // Release per_key_cap + 2 conns to a single key; only CAP survive.
5833        // Serialized with the other cap tests; defaults restored first.
5834        let _g = crate::pool::CAP_TEST_LOCK
5835            .lock()
5836            .unwrap_or_else(|e| e.into_inner());
5837        crate::pool::configure(4, 32);
5838        let cap = crate::pool::per_key_cap();
5839        let mut pool: PoolInner<FakeTls> = PoolInner::new();
5840        let k = url_key("https://example.com/");
5841        for _ in 0..(cap + 2) {
5842            pool.release(k.clone(), fake_arc_conn());
5843        }
5844        let mut popped = 0;
5845        while pool.checkout(&k).is_some() {
5846            popped += 1;
5847        }
5848        assert_eq!(popped, cap);
5849    }
5850
5851    #[test]
5852    fn pool_global_cap_drops_overflow() {
5853        // Spread releases across many distinct keys so the per-key cap
5854        // never bites — only the global cap can.
5855        let _g = crate::pool::CAP_TEST_LOCK
5856            .lock()
5857            .unwrap_or_else(|e| e.into_inner());
5858        crate::pool::configure(4, 32);
5859        let cap = crate::pool::global_cap();
5860        let mut pool: PoolInner<FakeTls> = PoolInner::new();
5861        for i in 0..(cap * 2) {
5862            let k = url_key(&format!("https://h{i}.example/"));
5863            pool.release(k, fake_arc_conn());
5864        }
5865        assert!(
5866            pool.total_len() <= cap,
5867            "pool grew past global cap: {} > {}",
5868            pool.total_len(),
5869            cap
5870        );
5871        // And we should be exactly at the cap (we never evict, so we should
5872        // have stopped accepting at the global cap).
5873        assert_eq!(pool.total_len(), cap);
5874    }
5875
5876    #[test]
5877    fn connection_is_usable_false_after_goaway() {
5878        let mut conn = fake_conn();
5879        conn.goaway_received = Some(0);
5880        assert!(
5881            conn.streams.is_empty(),
5882            "precondition: fresh conn has no streams"
5883        );
5884        assert!(!conn.is_usable());
5885    }
5886
5887    #[test]
5888    fn connection_is_usable_true_initially() {
5889        let conn = fake_conn();
5890        assert!(conn.is_usable());
5891    }
5892
5893    // -----------------------------------------------------------------
5894    // Sequential connection reuse: run more than one request/response over a
5895    // single `Connection`, exactly as the pool does on a hit. Stream ids must
5896    // advance 1 → 3 → 5, response bodies must be demultiplexed correctly, the
5897    // connection must stay `is_usable` between requests, and the `streams` map
5898    // must be pruned back to empty after each one.
5899    // -----------------------------------------------------------------
5900
5901    /// A complete server response for `id`: HEADERS(:status 200, END_HEADERS)
5902    /// then DATA(`body`, END_STREAM).
5903    fn synth_full_response(id: u32, body: &[u8]) -> Vec<Frame> {
5904        vec![
5905            synth_status_200_headers(id, /*end_stream=*/ false),
5906            synth_data(id, body, /*end_stream=*/ true),
5907        ]
5908    }
5909
5910    fn h2_get(url: &str) -> Request {
5911        Request::new("GET", url).unwrap()
5912    }
5913
5914    #[test]
5915    fn sequential_reuse_advances_stream_ids_and_demuxes_bodies() {
5916        // Pre-seed three full responses on streams 1, 3, 5 — the ids the
5917        // client must allocate across three sequential requests on one conn.
5918        let mut inbound = Vec::new();
5919        inbound.extend(synth_full_response(1, b"first"));
5920        inbound.extend(synth_full_response(3, b"second"));
5921        inbound.extend(synth_full_response(5, b"third"));
5922        let mut conn = fake_conn_with_inbound(&inbound);
5923
5924        let req = h2_get("https://example.com/");
5925
5926        // Request #1 → stream 1.
5927        assert_eq!(conn.next_stream_id, 1);
5928        let r1 = run_one_request(&mut conn, &req, &mut std::io::sink()).unwrap();
5929        assert_eq!(r1.status, 200);
5930        assert_eq!(r1.body, b"first");
5931        // Stream pruned, conn ready for the next id, still poolable.
5932        assert!(conn.streams.is_empty(), "stream 1 not reaped after reuse");
5933        assert_eq!(conn.next_stream_id, 3);
5934        assert!(conn.is_usable());
5935
5936        // Request #2 → stream 3.
5937        let r2 = run_one_request(&mut conn, &req, &mut std::io::sink()).unwrap();
5938        assert_eq!(r2.status, 200);
5939        assert_eq!(r2.body, b"second");
5940        assert!(conn.streams.is_empty());
5941        assert_eq!(conn.next_stream_id, 5);
5942        assert!(conn.is_usable());
5943
5944        // Request #3 → stream 5.
5945        let r3 = run_one_request(&mut conn, &req, &mut std::io::sink()).unwrap();
5946        assert_eq!(r3.body, b"third");
5947        assert_eq!(conn.next_stream_id, 7);
5948        assert!(conn.is_usable());
5949
5950        // Confirm the HEADERS the client actually wrote carried stream ids
5951        // 1, 3, 5 in order — i.e. id progression went out on the wire, not
5952        // just in the local counter.
5953        let header_ids: Vec<u32> = drain_wire_out(&conn)
5954            .into_iter()
5955            .filter(|f| f.typ == F_HEADERS)
5956            .map(|f| f.stream_id)
5957            .collect();
5958        assert_eq!(header_ids, vec![1, 3, 5]);
5959    }
5960
5961    #[test]
5962    fn run_one_request_emits_curl_style_verbose_trace() {
5963        // Server response: HEADERS(:status 200, content-type: text/plain)
5964        // then DATA("hello world", END_STREAM). Drive one request through
5965        // `run_one_request` with a `Vec<u8>` trace sink and assert it carries
5966        // the curl-style `>` request lines, the `< HTTP/2 200` status line,
5967        // the response header line, and the `* Received N body bytes` line.
5968        let mut hdr_payload = Vec::new();
5969        let mut enc = Encoder::new();
5970        enc.encode_header(&mut hdr_payload, ":status", "200");
5971        enc.encode_header(&mut hdr_payload, "content-type", "text/plain");
5972        let headers_frame = Frame {
5973            typ: F_HEADERS,
5974            flags: FLAG_END_HEADERS,
5975            stream_id: 1,
5976            payload: hdr_payload,
5977        };
5978        let inbound = vec![headers_frame, synth_data(1, b"hello world", true)];
5979        let mut conn = fake_conn_with_inbound(&inbound);
5980
5981        let req = h2_get("https://example.com/path");
5982        let mut trace: Vec<u8> = Vec::new();
5983        let resp = run_one_request(&mut conn, &req, &mut trace).unwrap();
5984        assert_eq!(resp.status, 200);
5985        assert_eq!(resp.body, b"hello world");
5986
5987        let t = String::from_utf8(trace).expect("trace is utf-8");
5988        // Request line + a synthesised Host line + a default header field.
5989        assert!(
5990            t.contains("> GET /path HTTP/2"),
5991            "missing request line in trace:\n{t}"
5992        );
5993        assert!(
5994            t.contains("> Host: example.com"),
5995            "missing Host line in trace:\n{t}"
5996        );
5997        assert!(
5998            t.contains("> accept: */*"),
5999            "missing default accept header in trace:\n{t}"
6000        );
6001        // Response status + header + body-byte notice.
6002        assert!(
6003            t.contains("< HTTP/2 200"),
6004            "missing response status line in trace:\n{t}"
6005        );
6006        assert!(
6007            t.contains("< content-type: text/plain"),
6008            "missing response header line in trace:\n{t}"
6009        );
6010        assert!(
6011            t.contains("* Received 11 body bytes"),
6012            "missing received-bytes notice in trace:\n{t}"
6013        );
6014    }
6015
6016    #[test]
6017    fn goaway_between_requests_marks_connection_non_reusable() {
6018        // First request succeeds; the peer then sends GOAWAY (last-stream-id
6019        // = 1) before we issue a second. is_usable must flip to false so the
6020        // pool drops the connection instead of handing it back out.
6021        let mut inbound = Vec::new();
6022        inbound.extend(synth_full_response(1, b"ok"));
6023        // GOAWAY(last_stream_id=1, NO_ERROR) on stream 0.
6024        let mut goaway_payload = Vec::new();
6025        goaway_payload.extend_from_slice(&1u32.to_be_bytes()); // last-stream-id
6026        goaway_payload.extend_from_slice(&0u32.to_be_bytes()); // error code
6027        inbound.push(Frame {
6028            typ: F_GOAWAY,
6029            flags: 0,
6030            stream_id: 0,
6031            payload: goaway_payload,
6032        });
6033        let mut conn = fake_conn_with_inbound(&inbound);
6034
6035        let req = h2_get("https://example.com/");
6036        let r1 = run_one_request(&mut conn, &req, &mut std::io::sink()).unwrap();
6037        assert_eq!(r1.body, b"ok");
6038        assert!(conn.is_usable(), "no GOAWAY seen yet — still reusable");
6039
6040        // Consume the GOAWAY that's sitting in the inbound buffer.
6041        let outcome = conn.read_and_dispatch(None).unwrap();
6042        assert_eq!(outcome, DispatchOutcome::Continue);
6043        assert_eq!(conn.goaway_received, Some(1));
6044        assert!(
6045            !conn.is_usable(),
6046            "GOAWAY must make the connection non-reusable"
6047        );
6048    }
6049
6050    #[test]
6051    fn prune_completed_streams_drops_terminal_entries_only() {
6052        // A connection that finished one stream while another is still open
6053        // must keep the open one and reap the closed one.
6054        let mut conn = fake_conn();
6055        let open_id = conn.open_stream().unwrap();
6056        let done_id = conn.open_stream().unwrap();
6057        // Mark `done_id` fully received and closed; leave `open_id` mid-flight.
6058        {
6059            let s = conn.streams.get_mut(&done_id).unwrap();
6060            s.state = StreamState::Closed;
6061            s.response_headers = Some(vec![(":status".into(), "200".into())]);
6062            s.end_stream_recv = true;
6063        }
6064        conn.streams.get_mut(&open_id).unwrap().state = StreamState::Open;
6065
6066        conn.prune_completed_streams();
6067        assert!(
6068            conn.streams.contains_key(&open_id),
6069            "open stream was reaped"
6070        );
6071        assert!(
6072            !conn.streams.contains_key(&done_id),
6073            "closed stream was not reaped"
6074        );
6075    }
6076
6077    #[test]
6078    fn initial_window_size_delta_applies_to_all_streams() {
6079        // Open two streams (both at the default 65_535 send window).
6080        // A SETTINGS bump to INITIAL_WINDOW_SIZE = 131_072 must shift both
6081        // stream send windows by +65_537; the conn send window is unchanged.
6082        let mut conn = fake_conn();
6083        let id1 = conn.open_stream().unwrap();
6084        let id2 = conn.open_stream().unwrap();
6085
6086        let payload = settings_payload(&[(S_INITIAL_WINDOW_SIZE, 131_072)]);
6087        let frame = Frame {
6088            typ: F_SETTINGS,
6089            flags: 0,
6090            stream_id: 0,
6091            payload,
6092        };
6093        conn.process_frame(frame, None).unwrap();
6094
6095        let expect = 65_535 + (131_072 - 65_535);
6096        assert_eq!(
6097            conn.streams.get(&id1).unwrap().send_window.available,
6098            expect
6099        );
6100        assert_eq!(
6101            conn.streams.get(&id2).unwrap().send_window.available,
6102            expect
6103        );
6104        assert_eq!(conn.conn_send_window.available, 65_535);
6105    }
6106
6107    // -----------------------------------------------------------------
6108    // End-to-end flow control over the I/O loop (RFC 9113 §5.2 / §6.9).
6109    // These drive `send_request_on` / `process_frame` against a `FakeTls`
6110    // whose `wire_in` is pre-seeded with the frames the peer would send,
6111    // and inspect the bytes the `Connection` wrote to `wire_out`.
6112    // -----------------------------------------------------------------
6113
6114    /// Build a `Connection<FakeTls>` whose inbound wire is pre-seeded with the
6115    /// concatenation of `frames` (the order the peer would send them in).
6116    fn fake_conn_with_inbound(frames: &[Frame]) -> Connection<FakeTls> {
6117        let mut bytes = Vec::new();
6118        for f in frames {
6119            write_frame(&mut bytes, f).unwrap();
6120        }
6121        let mut conn = fake_conn();
6122        conn.tls.wire_in = Cursor::new(bytes);
6123        conn
6124    }
6125
6126    /// Decode every frame the `Connection` wrote to its peer.
6127    fn drain_wire_out(conn: &Connection<FakeTls>) -> Vec<Frame> {
6128        let mut cur = Cursor::new(conn.tls.wire_out.clone());
6129        let mut out = Vec::new();
6130        while (cur.position() as usize) < conn.tls.wire_out.len() {
6131            out.push(read_frame(&mut cur).unwrap());
6132        }
6133        out
6134    }
6135
6136    fn h2_request_with_body(body: Vec<u8>) -> Request {
6137        let mut req = Request::new("POST", "https://example.com/upload").unwrap();
6138        req.body = body;
6139        req
6140    }
6141
6142    #[test]
6143    fn send_body_splits_across_window_updates() {
6144        // Peer advertises a tiny INITIAL_WINDOW_SIZE (5 octets). A 12-byte
6145        // request body therefore cannot be sent in one go: the stream send
6146        // window only allows 5 bytes, then the send loop must block until the
6147        // peer grants more with WINDOW_UPDATE. We seed two stream-level
6148        // WINDOW_UPDATE(+5) frames so the loop can drain the whole body across
6149        // three DATA frames (5 + 5 + 2).
6150        let body = (0..12u8).collect::<Vec<u8>>();
6151        let req = h2_request_with_body(body.clone());
6152
6153        // The send loop will read these when its window hits zero.
6154        let inbound = vec![window_update_frame(1, 5), window_update_frame(1, 5)];
6155        let mut conn = fake_conn_with_inbound(&inbound);
6156        // Shrink the peer's initial window *before* opening the stream so the
6157        // new stream picks up the small send window.
6158        conn.peer.initial_window_size = 5;
6159
6160        let id = conn.open_stream().unwrap();
6161        assert_eq!(conn.streams.get(&id).unwrap().send_window.available, 5);
6162
6163        conn.send_request_on(id, &req).unwrap();
6164
6165        // Pull apart what we wrote: one or more HEADERS frames, then DATA.
6166        let frames = drain_wire_out(&conn);
6167        let data: Vec<&Frame> = frames.iter().filter(|f| f.typ == F_DATA).collect();
6168        assert_eq!(
6169            data.len(),
6170            3,
6171            "12-byte body under a 5-octet window must split into 5+5+2"
6172        );
6173        assert_eq!(data[0].payload.len(), 5);
6174        assert_eq!(data[1].payload.len(), 5);
6175        assert_eq!(data[2].payload.len(), 2);
6176        // END_STREAM only on the final DATA frame.
6177        assert_eq!(data[0].flags & FLAG_END_STREAM, 0);
6178        assert_eq!(data[1].flags & FLAG_END_STREAM, 0);
6179        assert_eq!(data[2].flags & FLAG_END_STREAM, FLAG_END_STREAM);
6180        // Reassembled DATA equals the original body.
6181        let mut reassembled = Vec::new();
6182        for d in &data {
6183            reassembled.extend_from_slice(&d.payload);
6184        }
6185        assert_eq!(reassembled, body);
6186
6187        // Both windows were charged for the full body: stream window back to 0
6188        // (5 + 5 granted, 12 consumed = -2... but the third chunk only fired
6189        // after the second grant left 5, consuming 2 → 3 remaining).
6190        let s = conn.streams.get(&id).unwrap();
6191        assert_eq!(s.send_window.available, 3, "5+5 granted, 12 consumed");
6192        assert_eq!(conn.conn_send_window.available, 65_535 - 12);
6193    }
6194
6195    #[test]
6196    fn send_body_blocks_on_conn_window_too() {
6197        // Here the per-stream window is huge but the *connection* window is the
6198        // binding constraint. Set the conn send window to 4 and seed a conn
6199        // WINDOW_UPDATE(+8) on stream 0; a 10-byte body must go 4, then (after
6200        // the grant) 6.
6201        let body = (0..10u8).collect::<Vec<u8>>();
6202        let req = h2_request_with_body(body.clone());
6203
6204        let inbound = vec![window_update_frame(0, 8)];
6205        let mut conn = fake_conn_with_inbound(&inbound);
6206        conn.conn_send_window.available = 4;
6207
6208        let id = conn.open_stream().unwrap();
6209        conn.send_request_on(id, &req).unwrap();
6210
6211        let frames = drain_wire_out(&conn);
6212        let data: Vec<&Frame> = frames.iter().filter(|f| f.typ == F_DATA).collect();
6213        assert_eq!(data.len(), 2, "conn window of 4 then +8 splits 10 into 4+6");
6214        assert_eq!(data[0].payload.len(), 4);
6215        assert_eq!(data[1].payload.len(), 6);
6216        assert_eq!(data[1].flags & FLAG_END_STREAM, FLAG_END_STREAM);
6217        // 4 + 8 granted = 12, consumed 10 → 2 left.
6218        assert_eq!(conn.conn_send_window.available, 2);
6219    }
6220
6221    #[test]
6222    fn recv_data_replenishes_window_on_the_wire() {
6223        // Drive enough inbound DATA past the half-window threshold and confirm
6224        // the Connection writes WINDOW_UPDATE frames (stream + connection) back
6225        // to the peer — i.e. replenishment is tied to actual consumption, not
6226        // emitted unconditionally.
6227        let mut conn = fake_conn();
6228        let id = conn.open_stream().unwrap();
6229        conn.streams.get_mut(&id).unwrap().state = StreamState::Open;
6230
6231        // A single 40_000-byte DATA frame drops both windows from 65_535 to
6232        // 25_535 — below the 32_767 half-threshold — so both replenish.
6233        let big = vec![0xa5u8; 40_000];
6234        conn.process_frame(synth_data(id, &big, false), None)
6235            .unwrap();
6236
6237        let out = drain_wire_out(&conn);
6238        let updates: Vec<&Frame> = out.iter().filter(|f| f.typ == F_WINDOW_UPDATE).collect();
6239        assert_eq!(
6240            updates.len(),
6241            2,
6242            "one conn-level and one stream-level WINDOW_UPDATE expected"
6243        );
6244        let conn_update = updates.iter().find(|f| f.stream_id == 0).unwrap();
6245        let stream_update = updates.iter().find(|f| f.stream_id == id).unwrap();
6246        // Each grant restores the consumed 40_000 octets.
6247        assert_eq!(parse_window_update(&conn_update.payload).unwrap(), 40_000);
6248        assert_eq!(parse_window_update(&stream_update.payload).unwrap(), 40_000);
6249        // Running windows are back to full after the grant.
6250        assert_eq!(conn.conn_recv_window.available, OUR_INITIAL_WINDOW);
6251        assert_eq!(
6252            conn.streams.get(&id).unwrap().recv_window.available,
6253            OUR_INITIAL_WINDOW
6254        );
6255    }
6256
6257    #[test]
6258    fn recv_small_data_does_not_replenish() {
6259        // A small DATA frame that leaves both windows above the half-threshold
6260        // must NOT trigger any WINDOW_UPDATE (the prior unconditional replenish
6261        // was the security-audit finding this guards against).
6262        let mut conn = fake_conn();
6263        let id = conn.open_stream().unwrap();
6264        conn.streams.get_mut(&id).unwrap().state = StreamState::Open;
6265
6266        conn.process_frame(synth_data(id, b"hello", false), None)
6267            .unwrap();
6268        let out = drain_wire_out(&conn);
6269        assert!(
6270            out.iter().all(|f| f.typ != F_WINDOW_UPDATE),
6271            "no WINDOW_UPDATE should be emitted while windows stay above half"
6272        );
6273        assert_eq!(conn.conn_recv_window.available, OUR_INITIAL_WINDOW - 5);
6274        assert_eq!(
6275            conn.streams.get(&id).unwrap().recv_window.available,
6276            OUR_INITIAL_WINDOW - 5
6277        );
6278    }
6279
6280    #[test]
6281    fn dispatch_zero_increment_window_update_conn_is_error() {
6282        // §6.9: a WINDOW_UPDATE with a 0 increment on stream 0 is a connection
6283        // error. Driving it through the full dispatch ladder must surface it.
6284        let mut conn = fake_conn();
6285        let frame = window_update_frame(0, 0);
6286        let err = conn.process_frame(frame, None).unwrap_err();
6287        assert!(matches!(err, Error::BadResponse(_)));
6288    }
6289
6290    #[test]
6291    fn dispatch_zero_increment_window_update_stream_is_error() {
6292        // §6.9: a WINDOW_UPDATE with a 0 increment on a live stream is a stream
6293        // error; through the dispatch ladder it surfaces as BadResponse.
6294        let mut conn = fake_conn();
6295        let id = conn.open_stream().unwrap();
6296        let frame = window_update_frame(id, 0);
6297        let err = conn.process_frame(frame, None).unwrap_err();
6298        assert!(matches!(err, Error::BadResponse(_)));
6299    }
6300
6301    #[test]
6302    fn dispatch_window_update_overflow_conn_is_error() {
6303        // §6.9.1: a WINDOW_UPDATE pushing the connection send window past
6304        // 2^31-1 is a FLOW_CONTROL_ERROR. Prime the window near the ceiling and
6305        // drive an oversized grant through dispatch.
6306        let mut conn = fake_conn();
6307        conn.conn_send_window.available = WINDOW_MAX - 1;
6308        let err = conn
6309            .process_frame(window_update_frame(0, 5), None)
6310            .unwrap_err();
6311        assert!(matches!(err, Error::BadResponse(_)));
6312    }
6313
6314    #[test]
6315    fn dispatch_window_update_overflow_stream_is_error() {
6316        // §6.9.1 on a stream: same overflow rule via the stream dispatch path.
6317        let mut conn = fake_conn();
6318        let id = conn.open_stream().unwrap();
6319        conn.streams.get_mut(&id).unwrap().send_window.available = WINDOW_MAX - 1;
6320        let err = conn
6321            .process_frame(window_update_frame(id, 5), None)
6322            .unwrap_err();
6323        assert!(matches!(err, Error::BadResponse(_)));
6324    }
6325
6326    #[test]
6327    fn settings_initial_window_change_lets_stalled_send_proceed() {
6328        // A stream opened under a 0-octet initial window cannot send any body
6329        // until the peer enlarges the window. Here the peer raises
6330        // INITIAL_WINDOW_SIZE mid-connection (§6.9.2), which retroactively
6331        // grows the existing stream's send window and unblocks the send loop.
6332        let body = vec![0x11u8; 6];
6333        let req = h2_request_with_body(body.clone());
6334
6335        // The send loop will read this SETTINGS frame when stalled at window 0;
6336        // it bumps INITIAL_WINDOW_SIZE to 100, applying a +100 delta to the
6337        // already-open stream. We must also seed its ACK consumption — the loop
6338        // writes an ACK, which is fine (it goes to wire_out, not wire_in).
6339        let settings = Frame {
6340            typ: F_SETTINGS,
6341            flags: 0,
6342            stream_id: 0,
6343            payload: settings_payload(&[(S_INITIAL_WINDOW_SIZE, 100)]),
6344        };
6345        let mut conn = fake_conn_with_inbound(&[settings]);
6346        conn.peer.initial_window_size = 0;
6347
6348        let id = conn.open_stream().unwrap();
6349        assert_eq!(conn.streams.get(&id).unwrap().send_window.available, 0);
6350
6351        conn.send_request_on(id, &req).unwrap();
6352
6353        let frames = drain_wire_out(&conn);
6354        let data: Vec<&Frame> = frames.iter().filter(|f| f.typ == F_DATA).collect();
6355        assert_eq!(data.len(), 1, "after the delta the whole body fits");
6356        assert_eq!(data[0].payload, body);
6357        assert_eq!(data[0].flags & FLAG_END_STREAM, FLAG_END_STREAM);
6358        // Stream send window: 0 + 100 (delta) - 6 (consumed) = 94.
6359        assert_eq!(conn.streams.get(&id).unwrap().send_window.available, 94);
6360        // The loop also ACKed the SETTINGS frame.
6361        assert!(
6362            frames
6363                .iter()
6364                .any(|f| f.typ == F_SETTINGS && f.flags & FLAG_ACK != 0),
6365            "SETTINGS must be ACKed"
6366        );
6367    }
6368
6369    // -----------------------------------------------------------------
6370    // Concurrent multiplexing (`run_multiplexed`).
6371    //
6372    // These drive several requests over one `Connection<FakeTls>` whose
6373    // inbound wire is pre-seeded with interleaved server frames, then assert
6374    // each request's `Response` is demultiplexed back to the right slot.
6375    // -----------------------------------------------------------------
6376
6377    /// Synthesize an RST_STREAM frame for `id` carrying `code`.
6378    fn synth_rst(id: u32, code: u32) -> Frame {
6379        Frame {
6380            typ: F_RST_STREAM,
6381            flags: 0,
6382            stream_id: id,
6383            payload: code.to_be_bytes().to_vec(),
6384        }
6385    }
6386
6387    #[test]
6388    fn multiplex_two_requests_demuxes_interleaved_frames() {
6389        // Two GET requests → streams 1 and 3. Seed the server's frames
6390        // INTERLEAVED across the two streams: h1-headers, h3-headers,
6391        // h1-data-part, h3-data(END), h1-data-rest(END). The driver must route
6392        // each fragment to its own stream and return the right body to the
6393        // right request regardless of interleave order.
6394        let inbound = vec![
6395            synth_status_200_headers(1, false),
6396            synth_status_200_headers(3, false),
6397            synth_data(1, b"one-", false),
6398            synth_data(3, b"THREE", true),
6399            synth_data(1, b"part", true),
6400        ];
6401        let mut conn = fake_conn_with_inbound(&inbound);
6402
6403        let reqs = vec![
6404            h2_get("https://example.com/a"),
6405            h2_get("https://example.com/b"),
6406        ];
6407        let results = conn.run_multiplexed(&reqs, &mut std::io::sink());
6408        assert_eq!(results.len(), 2);
6409
6410        let r0 = results[0].as_ref().expect("req 0 ok");
6411        let r1 = results[1].as_ref().expect("req 1 ok");
6412        assert_eq!(r0.status, 200);
6413        assert_eq!(r0.body, b"one-part", "stream 1 body");
6414        assert_eq!(r1.status, 200);
6415        assert_eq!(r1.body, b"THREE", "stream 3 body");
6416
6417        // Both HEADERS the client wrote went out on streams 1 and 3.
6418        let header_ids: Vec<u32> = drain_wire_out(&conn)
6419            .into_iter()
6420            .filter(|f| f.typ == F_HEADERS)
6421            .map(|f| f.stream_id)
6422            .collect();
6423        assert_eq!(header_ids, vec![1, 3]);
6424        // The streams were reaped.
6425        assert!(conn.streams.is_empty());
6426    }
6427
6428    #[test]
6429    fn multiplex_reversed_interleave_still_demuxes() {
6430        // Same as above but the server completes stream 3 entirely before it
6431        // even opens stream 1's body — order independence.
6432        let inbound = vec![
6433            synth_status_200_headers(3, false),
6434            synth_data(3, b"bbb", true),
6435            synth_status_200_headers(1, false),
6436            synth_data(1, b"aaaa", true),
6437        ];
6438        let mut conn = fake_conn_with_inbound(&inbound);
6439        let reqs = vec![
6440            h2_get("https://example.com/a"),
6441            h2_get("https://example.com/b"),
6442        ];
6443        let results = conn.run_multiplexed(&reqs, &mut std::io::sink());
6444        assert_eq!(results[0].as_ref().unwrap().body, b"aaaa");
6445        assert_eq!(results[1].as_ref().unwrap().body, b"bbb");
6446    }
6447
6448    #[test]
6449    fn multiplex_queues_third_request_at_max_concurrent_two() {
6450        // MAX_CONCURRENT_STREAMS = 2: only streams 1 and 3 may be open at once.
6451        // The third request must wait until one of the first two completes,
6452        // then open on stream 5. We seed the responses so stream 1 finishes
6453        // first (freeing a slot for stream 5), then 3, then 5.
6454        let inbound = vec![
6455            // Stream 1 completes first.
6456            synth_status_200_headers(1, false),
6457            synth_data(1, b"first", true),
6458            // Then stream 3.
6459            synth_status_200_headers(3, false),
6460            synth_data(3, b"second", true),
6461            // Stream 5 (the queued one) only opens after 1 frees a slot.
6462            synth_status_200_headers(5, false),
6463            synth_data(5, b"third", true),
6464        ];
6465        let mut conn = fake_conn_with_inbound(&inbound);
6466        conn.peer.max_concurrent_streams = 2;
6467
6468        let reqs = vec![
6469            h2_get("https://example.com/1"),
6470            h2_get("https://example.com/2"),
6471            h2_get("https://example.com/3"),
6472        ];
6473        let results = conn.run_multiplexed(&reqs, &mut std::io::sink());
6474        assert_eq!(results.len(), 3);
6475        assert_eq!(results[0].as_ref().unwrap().body, b"first");
6476        assert_eq!(results[1].as_ref().unwrap().body, b"second");
6477        assert_eq!(results[2].as_ref().unwrap().body, b"third");
6478
6479        // The client must have written HEADERS on streams 1, 3 first, and
6480        // only later on 5 — i.e. no more than two were open before stream 1
6481        // completed. We assert the *order* of HEADERS writes: 1, 3, then 5.
6482        let header_ids: Vec<u32> = drain_wire_out(&conn)
6483            .into_iter()
6484            .filter(|f| f.typ == F_HEADERS)
6485            .map(|f| f.stream_id)
6486            .collect();
6487        assert_eq!(
6488            header_ids,
6489            vec![1, 3, 5],
6490            "stream 5 must be opened only after a slot freed"
6491        );
6492    }
6493
6494    #[test]
6495    fn multiplex_one_stream_rst_others_succeed() {
6496        // Stream 1 is reset by the server; stream 3 completes normally. The
6497        // reset request gets an Err, the other its Response — the RST must not
6498        // kill the whole batch.
6499        let inbound = vec![
6500            synth_status_200_headers(3, false),
6501            synth_rst(1, 0x8), // CANCEL
6502            synth_data(3, b"alive", true),
6503        ];
6504        let mut conn = fake_conn_with_inbound(&inbound);
6505        let reqs = vec![
6506            h2_get("https://example.com/doomed"),
6507            h2_get("https://example.com/ok"),
6508        ];
6509        let results = conn.run_multiplexed(&reqs, &mut std::io::sink());
6510        assert_eq!(results.len(), 2);
6511        assert!(
6512            matches!(results[0], Err(Error::BadResponse(_))),
6513            "reset stream must yield an error, got {:?}",
6514            results[0]
6515        );
6516        let ok = results[1].as_ref().expect("stream 3 should succeed");
6517        assert_eq!(ok.body, b"alive");
6518    }
6519
6520    #[test]
6521    fn multiplex_flow_control_no_head_of_line_block() {
6522        // A tiny INITIAL_WINDOW_SIZE (4 octets) forces request bodies to be
6523        // split. Two POSTs with 10-byte bodies each: stream 1 can only put 4
6524        // bytes out before stalling, but stream 3 must still make progress (and
6525        // vice versa) — the non-blocking pump interleaves them. After the
6526        // server grants WINDOW_UPDATEs, both bodies finish and both responses
6527        // come back.
6528        let mut req1 = Request::new("POST", "https://example.com/u1").unwrap();
6529        req1.body = (0..10u8).collect();
6530        let mut req3 = Request::new("POST", "https://example.com/u3").unwrap();
6531        req3.body = (100..110u8).collect();
6532
6533        // Inbound: first the per-stream WINDOW_UPDATEs that unblock the bodies
6534        // (interleaved across both streams), then the responses. The driver
6535        // pumps sends after each inbound frame, so the grants let both bodies
6536        // drain without one blocking the other.
6537        let inbound = vec![
6538            window_update_frame(1, 6),  // stream 1: 4 + 6 = 10 → done
6539            window_update_frame(3, 6),  // stream 3: 4 + 6 = 10 → done
6540            window_update_frame(0, 12), // conn: enough for both remainders
6541            synth_status_200_headers(1, false),
6542            synth_data(1, b"r1", true),
6543            synth_status_200_headers(3, false),
6544            synth_data(3, b"r3", true),
6545        ];
6546        let mut conn = fake_conn_with_inbound(&inbound);
6547        // Small per-stream send window; conn window large enough initially for
6548        // the first 4+4 octets (8 < 65535).
6549        conn.peer.initial_window_size = 4;
6550
6551        let reqs = vec![req1.clone(), req3.clone()];
6552        let results = conn.run_multiplexed(&reqs, &mut std::io::sink());
6553        assert_eq!(results.len(), 2);
6554        assert_eq!(results[0].as_ref().unwrap().body, b"r1");
6555        assert_eq!(results[1].as_ref().unwrap().body, b"r3");
6556
6557        // Verify both bodies went out fully and interleaved: the first DATA on
6558        // each stream was 4 bytes (the initial window), proving neither waited
6559        // for the other to finish before starting.
6560        let data: Vec<Frame> = drain_wire_out(&conn)
6561            .into_iter()
6562            .filter(|f| f.typ == F_DATA)
6563            .collect();
6564        // Reassemble per-stream payloads and confirm completeness.
6565        let mut s1 = Vec::new();
6566        let mut s3 = Vec::new();
6567        for f in &data {
6568            if f.stream_id == 1 {
6569                s1.extend_from_slice(&f.payload);
6570            } else if f.stream_id == 3 {
6571                s3.extend_from_slice(&f.payload);
6572            }
6573        }
6574        assert_eq!(s1, req1.body);
6575        assert_eq!(s3, req3.body);
6576        // The first chunk on stream 1 was capped to the 4-octet window.
6577        let first_s1 = data.iter().find(|f| f.stream_id == 1).unwrap();
6578        assert_eq!(
6579            first_s1.payload.len(),
6580            4,
6581            "stream 1 first DATA capped to window"
6582        );
6583        let first_s3 = data.iter().find(|f| f.stream_id == 3).unwrap();
6584        assert_eq!(
6585            first_s3.payload.len(),
6586            4,
6587            "stream 3 first DATA capped to window"
6588        );
6589    }
6590
6591    #[test]
6592    fn multiplex_goaway_fails_high_streams_lower_completes() {
6593        // MAX_CONCURRENT_STREAMS=3 so streams 1, 3, 5 all open up front.
6594        // The server completes stream 1, then sends GOAWAY(last-stream-id=3):
6595        // stream 3 may still finish, but stream 5 (id > 3) is abandoned and
6596        // must fail. Stream 1 already completed.
6597        let mut goaway_payload = Vec::new();
6598        goaway_payload.extend_from_slice(&3u32.to_be_bytes()); // last-stream-id = 3
6599        goaway_payload.extend_from_slice(&0u32.to_be_bytes()); // NO_ERROR
6600        let goaway = Frame {
6601            typ: F_GOAWAY,
6602            flags: 0,
6603            stream_id: 0,
6604            payload: goaway_payload,
6605        };
6606        let inbound = vec![
6607            synth_status_200_headers(1, false),
6608            synth_data(1, b"one", true),
6609            goaway,
6610            synth_status_200_headers(3, false),
6611            synth_data(3, b"three", true),
6612        ];
6613        let mut conn = fake_conn_with_inbound(&inbound);
6614        conn.peer.max_concurrent_streams = 3;
6615
6616        let reqs = vec![
6617            h2_get("https://example.com/1"),
6618            h2_get("https://example.com/3"),
6619            h2_get("https://example.com/5"),
6620        ];
6621        let results = conn.run_multiplexed(&reqs, &mut std::io::sink());
6622        assert_eq!(results.len(), 3);
6623        assert_eq!(
6624            results[0].as_ref().unwrap().body,
6625            b"one",
6626            "stream 1 completes"
6627        );
6628        assert_eq!(
6629            results[1].as_ref().unwrap().body,
6630            b"three",
6631            "stream 3 (<= last-stream-id) completes"
6632        );
6633        assert!(
6634            matches!(results[2], Err(Error::BadResponse(_))),
6635            "stream 5 (> last-stream-id) must be abandoned, got {:?}",
6636            results[2]
6637        );
6638    }
6639
6640    #[test]
6641    fn multiplex_verbose_trace_labels_streams() {
6642        // The -v trace must label request/response lines per stream id so the
6643        // interleaved output stays readable.
6644        let inbound = vec![
6645            synth_status_200_headers(1, false),
6646            synth_data(1, b"x", true),
6647            synth_status_200_headers(3, false),
6648            synth_data(3, b"y", true),
6649        ];
6650        let mut conn = fake_conn_with_inbound(&inbound);
6651        let reqs = vec![
6652            h2_get("https://example.com/a"),
6653            h2_get("https://example.com/b"),
6654        ];
6655        let mut trace: Vec<u8> = Vec::new();
6656        let _ = conn.run_multiplexed(&reqs, &mut trace);
6657        let t = String::from_utf8(trace).unwrap();
6658        assert!(t.contains("> [stream 1] GET /a HTTP/2"), "trace:\n{t}");
6659        assert!(t.contains("> [stream 3] GET /b HTTP/2"), "trace:\n{t}");
6660        assert!(t.contains("< [stream 1] HTTP/2 200"), "trace:\n{t}");
6661        assert!(t.contains("< [stream 3] HTTP/2 200"), "trace:\n{t}");
6662    }
6663
6664    #[test]
6665    fn send_multiplexed_empty_returns_empty() {
6666        let mut sink = std::io::sink();
6667        let out = send_multiplexed(Vec::new(), &mut sink);
6668        assert!(out.is_empty());
6669    }
6670}