Skip to main content

sozu_lib/protocol/mux/
parser.rs

1//! H2 frame parser (nom-based).
2//!
3//! Decodes every RFC 9113 §6 frame type plus RFC 9218 `PRIORITY_UPDATE` and
4//! unknown extension frames; rejects malformed framing with the matching
5//! `H2Error` so the connection can react with GOAWAY / RST_STREAM. Mostly
6//! zero-allocation, with bounded `Vec<u8>` allocations on `priority_update`
7//! and a SETTINGS-list cap (cf. `priority_update_frame` and the SETTINGS
8//! allocation cap test). Frame-size invariants are enforced via
9//! `ensure_frame_size!`.
10
11use std::convert::From;
12
13use kawa::repr::Slice;
14use nom::{
15    Err, IResult, Parser,
16    bytes::complete::{tag, take},
17    combinator::{complete, map},
18    error::{ErrorKind, ParseError},
19    multi::many0,
20    number::complete::{be_u8, be_u16, be_u24, be_u32},
21};
22use sozu_command::logging::ansi_palette;
23
24/// Module-level prefix for nom-based H2 frame parser diagnostics. The parser
25/// has no session in scope, so a single `MUX-PARSER` label is used, colored
26/// bold bright-white (uniform across every protocol) when the logger supports
27/// ANSI.
28macro_rules! log_module_context {
29    () => {{
30        let (open, reset, _, _, _) = ansi_palette();
31        format!("{open}MUX-PARSER{reset}\t >>>", open = open, reset = reset)
32    }};
33}
34
35// ── RFC 9113 Wire Format Constants ──────────────────────────────────────────
36
37/// H2 frame header size in bytes (RFC 9113 §4.1)
38pub const FRAME_HEADER_SIZE: usize = 9;
39
40/// Mask to extract 31-bit stream ID, clearing the reserved MSB (RFC 9113 §4.1)
41pub const STREAM_ID_MASK: u32 = 0x7FFFFFFF;
42
43// Frame flags (RFC 9113 §6)
44/// END_STREAM flag — signals last frame for this stream (§6.1, §6.2)
45pub const FLAG_END_STREAM: u8 = 0x1;
46/// END_HEADERS flag — signals last header block fragment (§6.2, §6.10)
47pub const FLAG_END_HEADERS: u8 = 0x4;
48/// PADDED flag — indicates padding is present (§6.1, §6.2)
49pub const FLAG_PADDED: u8 = 0x8;
50/// PRIORITY flag on HEADERS — stream dependency follows (§6.2)
51pub const FLAG_PRIORITY: u8 = 0x20;
52/// ACK flag on SETTINGS/PING (§6.5, §6.7)
53pub const FLAG_ACK: u8 = 0x1;
54
55// Fixed-size frame payload lengths (RFC 9113)
56pub const PRIORITY_PAYLOAD_SIZE: u32 = 5;
57pub const RST_STREAM_PAYLOAD_SIZE: u32 = 4;
58pub const SETTINGS_ENTRY_SIZE: u32 = 6;
59pub const PING_PAYLOAD_SIZE: u32 = 8;
60pub const WINDOW_UPDATE_PAYLOAD_SIZE: u32 = 4;
61pub const GOAWAY_PAYLOAD_SIZE: u32 = 8;
62
63// SETTINGS identifiers (RFC 9113 §6.5.1, RFC 8441, RFC 9218)
64pub const SETTINGS_HEADER_TABLE_SIZE: u16 = 1;
65pub const SETTINGS_ENABLE_PUSH: u16 = 2;
66pub const SETTINGS_MAX_CONCURRENT_STREAMS: u16 = 3;
67pub const SETTINGS_INITIAL_WINDOW_SIZE: u16 = 4;
68pub const SETTINGS_MAX_FRAME_SIZE: u16 = 5;
69pub const SETTINGS_MAX_HEADER_LIST_SIZE: u16 = 6;
70pub const SETTINGS_ENABLE_CONNECT_PROTOCOL: u16 = 8;
71pub const SETTINGS_NO_RFC7540_PRIORITIES: u16 = 9;
72/// Number of settings entries we send in our SETTINGS frame
73pub const SETTINGS_COUNT: u32 = 8;
74
75// ─────────────────────────────────────────────────────────────────────────────
76
77#[derive(Clone, Debug, PartialEq)]
78pub struct FrameHeader {
79    pub payload_len: u32,
80    pub frame_type: FrameType,
81    pub flags: u8,
82    pub stream_id: u32,
83}
84
85#[derive(Clone, Copy, Debug, PartialEq)]
86pub enum FrameType {
87    Data,
88    Headers,
89    Priority,
90    RstStream,
91    Settings,
92    PushPromise,
93    Ping,
94    GoAway,
95    WindowUpdate,
96    Continuation,
97    /// RFC 9218 §7.1 — PRIORITY_UPDATE frame (type 0x10). Carries a new
98    /// priority signal for a prioritized stream, replacing the deprecated
99    /// `Priority` frame at the connection level.
100    PriorityUpdate,
101    /// Frame of unknown type per RFC 9113 §5.5. Implementations MUST ignore
102    /// and discard these frames so future H2 extensions do not break
103    /// interoperability. The associated `u8` is the raw type byte for
104    /// diagnostics only.
105    Unknown(u8),
106}
107
108impl std::str::FromStr for H2Error {
109    type Err = ();
110
111    fn from_str(s: &str) -> Result<Self, ()> {
112        match s {
113            "NO_ERROR" => Ok(H2Error::NoError),
114            "PROTOCOL_ERROR" => Ok(H2Error::ProtocolError),
115            "INTERNAL_ERROR" => Ok(H2Error::InternalError),
116            "FLOW_CONTROL_ERROR" => Ok(H2Error::FlowControlError),
117            "SETTINGS_TIMEOUT" => Ok(H2Error::SettingsTimeout),
118            "STREAM_CLOSED" => Ok(H2Error::StreamClosed),
119            "FRAME_SIZE_ERROR" => Ok(H2Error::FrameSizeError),
120            "REFUSED_STREAM" => Ok(H2Error::RefusedStream),
121            "CANCEL" => Ok(H2Error::Cancel),
122            "COMPRESSION_ERROR" => Ok(H2Error::CompressionError),
123            "CONNECT_ERROR" => Ok(H2Error::ConnectError),
124            "ENHANCE_YOUR_CALM" => Ok(H2Error::EnhanceYourCalm),
125            "INADEQUATE_SECURITY" => Ok(H2Error::InadequateSecurity),
126            "HTTP_1_1_REQUIRED" => Ok(H2Error::HTTP11Required),
127            _ => Err(()),
128        }
129    }
130}
131
132#[derive(Clone, Debug, PartialEq)]
133pub struct ParserError<'a> {
134    pub input: &'a [u8],
135    pub kind: ParserErrorKind,
136}
137
138#[derive(Clone, Debug, PartialEq)]
139pub enum ParserErrorKind {
140    Nom(ErrorKind),
141    H2(H2Error),
142}
143
144#[derive(Clone, Copy, Debug, PartialEq)]
145#[repr(u32)]
146pub enum H2Error {
147    NoError = 0x0,
148    ProtocolError = 0x1,
149    InternalError = 0x2,
150    FlowControlError = 0x3,
151    SettingsTimeout = 0x4,
152    StreamClosed = 0x5,
153    FrameSizeError = 0x6,
154    RefusedStream = 0x7,
155    Cancel = 0x8,
156    CompressionError = 0x9,
157    ConnectError = 0xa,
158    EnhanceYourCalm = 0xb,
159    InadequateSecurity = 0xc,
160    HTTP11Required = 0xd,
161}
162
163impl TryFrom<u32> for H2Error {
164    type Error = u32;
165
166    fn try_from(code: u32) -> Result<Self, u32> {
167        match code {
168            0x0 => Ok(H2Error::NoError),
169            0x1 => Ok(H2Error::ProtocolError),
170            0x2 => Ok(H2Error::InternalError),
171            0x3 => Ok(H2Error::FlowControlError),
172            0x4 => Ok(H2Error::SettingsTimeout),
173            0x5 => Ok(H2Error::StreamClosed),
174            0x6 => Ok(H2Error::FrameSizeError),
175            0x7 => Ok(H2Error::RefusedStream),
176            0x8 => Ok(H2Error::Cancel),
177            0x9 => Ok(H2Error::CompressionError),
178            0xa => Ok(H2Error::ConnectError),
179            0xb => Ok(H2Error::EnhanceYourCalm),
180            0xc => Ok(H2Error::InadequateSecurity),
181            0xd => Ok(H2Error::HTTP11Required),
182            other => Err(other),
183        }
184    }
185}
186
187impl H2Error {
188    /// Returns the RFC 7540 §7 error name as a static string.
189    pub const fn as_str(&self) -> &'static str {
190        match self {
191            H2Error::NoError => "NO_ERROR",
192            H2Error::ProtocolError => "PROTOCOL_ERROR",
193            H2Error::InternalError => "INTERNAL_ERROR",
194            H2Error::FlowControlError => "FLOW_CONTROL_ERROR",
195            H2Error::SettingsTimeout => "SETTINGS_TIMEOUT",
196            H2Error::StreamClosed => "STREAM_CLOSED",
197            H2Error::FrameSizeError => "FRAME_SIZE_ERROR",
198            H2Error::RefusedStream => "REFUSED_STREAM",
199            H2Error::Cancel => "CANCEL",
200            H2Error::CompressionError => "COMPRESSION_ERROR",
201            H2Error::ConnectError => "CONNECT_ERROR",
202            H2Error::EnhanceYourCalm => "ENHANCE_YOUR_CALM",
203            H2Error::InadequateSecurity => "INADEQUATE_SECURITY",
204            H2Error::HTTP11Required => "HTTP_1_1_REQUIRED",
205        }
206    }
207}
208
209impl std::fmt::Display for H2Error {
210    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
211        f.write_str(self.as_str())
212    }
213}
214
215impl<'a> ParserError<'a> {
216    pub fn new(input: &'a [u8], error: ParserErrorKind) -> ParserError<'a> {
217        ParserError { input, kind: error }
218    }
219    pub fn new_h2(input: &'a [u8], error: H2Error) -> ParserError<'a> {
220        ParserError {
221            input,
222            kind: ParserErrorKind::H2(error),
223        }
224    }
225}
226
227impl<'a> ParseError<&'a [u8]> for ParserError<'a> {
228    fn from_error_kind(input: &'a [u8], kind: ErrorKind) -> Self {
229        ParserError {
230            input,
231            kind: ParserErrorKind::Nom(kind),
232        }
233    }
234
235    fn append(input: &'a [u8], kind: ErrorKind, _other: Self) -> Self {
236        ParserError {
237            input,
238            kind: ParserErrorKind::Nom(kind),
239        }
240    }
241}
242
243impl<'a> From<(&'a [u8], ErrorKind)> for ParserError<'a> {
244    fn from((input, kind): (&'a [u8], ErrorKind)) -> Self {
245        ParserError {
246            input,
247            kind: ParserErrorKind::Nom(kind),
248        }
249    }
250}
251
252/// The 24-byte HTTP/2 client connection preface magic (RFC 9113 §3.4).
253///
254/// Shared by [`preface`] and the cleartext prior-knowledge sniff
255/// `classify_preface` (`lib/src/protocol/h2c.rs`), so the two cannot
256/// disagree on the bytes that make a connection HTTP/2.
257pub const CLIENT_PREFACE_MAGIC: &[u8; 24] = b"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n";
258
259pub fn preface(i: &[u8]) -> IResult<&[u8], &[u8]> {
260    tag(&CLIENT_PREFACE_MAGIC[..])(i)
261}
262
263/// `if !$cond { return FrameSizeError; }` wrapping macro for the seven
264/// fixed-size H2 frame validators in `frame_body`. Pure dispatch — the
265/// nom Err / ParserError construction stays here so callers read like
266/// the spec citation instead of a four-line ceremony.
267///
268/// `$cond` is the **passes** predicate (e.g. `header.payload_len ==
269/// PING_PAYLOAD_SIZE`); the macro converts the negation into the bail.
270macro_rules! ensure_frame_size {
271    ($input:expr, $cond:expr) => {
272        if !($cond) {
273            return Err(Err::Failure(ParserError::new_h2(
274                $input,
275                H2Error::FrameSizeError,
276            )));
277        }
278    };
279}
280
281// https://httpwg.org/specs/rfc7540.html#rfc.section.4.1
282//
283// Production caller contract:
284// `max_frame_size` MUST be the *negotiated* `settings_max_frame_size` from
285// the peer's latest accepted SETTINGS frame (see `H2Settings` on
286// `ConnectionH2`). It MUST NOT be `DEFAULT_MAX_FRAME_SIZE` — that RFC
287// default (16 KiB) is only correct at connection-preface time, before the
288// peer has had a chance to raise the limit. All four production callers
289// in `lib/src/protocol/mux/h2.rs` (lines 1444, 1704, 1935, 1994) thread
290// `self.local_settings.settings_max_frame_size` correctly. Tests may use
291// `DEFAULT_MAX_FRAME_SIZE` freely because they construct isolated frame
292// buffers that never flow through a real peer handshake.
293pub fn frame_header(
294    input: &[u8],
295    max_frame_size: u32,
296) -> IResult<&[u8], FrameHeader, ParserError<'_>> {
297    let in_len = input.len();
298    let (i, payload_len) = be_u24(input)?;
299    if payload_len > max_frame_size {
300        return Err(Err::Failure(ParserError::new_h2(
301            i,
302            H2Error::FrameSizeError,
303        )));
304    }
305
306    let (i, t) = be_u8(i)?;
307    let frame_type = convert_frame_type(t);
308    let (i, flags) = be_u8(i)?;
309    let (i, raw_stream_id) = be_u32(i)?;
310    let stream_id = raw_stream_id & STREAM_ID_MASK;
311    // Post-conditions of the fixed 9-byte header decode: the parser never grows
312    // its input, it consumes exactly the header it just read, the size bound
313    // this function is contracted to enforce holds, and the reserved high bit is
314    // masked off the stream id.
315    debug_assert!(i.len() <= in_len, "parser must not grow its input");
316    debug_assert_eq!(
317        i.len(),
318        in_len - FRAME_HEADER_SIZE,
319        "frame_header must consume exactly the 9-byte header"
320    );
321    debug_assert!(
322        payload_len <= max_frame_size,
323        "frame_header must enforce the size bound it was given"
324    );
325    debug_assert_eq!(
326        stream_id & !STREAM_ID_MASK,
327        0,
328        "reserved high bit must be masked off the stream id"
329    );
330
331    // RFC 9113 §5.5: unknown frame types MUST be silently discarded. Skip
332    // stream-id parity validation for them — the spec places no constraints on
333    // the stream_id of extension frames, and the h2 state machine simply drops
334    // the payload bytes.
335    let valid_stream_id = match frame_type {
336        FrameType::Data
337        | FrameType::Headers
338        | FrameType::Priority
339        | FrameType::RstStream
340        | FrameType::PushPromise
341        | FrameType::Continuation => stream_id != 0,
342        // RFC 9218 §7.1: PRIORITY_UPDATE is a connection-scoped signal
343        // (the *prioritized* stream ID lives in the payload).
344        FrameType::Settings | FrameType::Ping | FrameType::GoAway | FrameType::PriorityUpdate => {
345            stream_id == 0
346        }
347        FrameType::WindowUpdate | FrameType::Unknown(_) => true,
348    };
349    if !valid_stream_id {
350        error!("{} invalid stream_id: {}", log_module_context!(), stream_id);
351        return Err(Err::Failure(ParserError::new_h2(i, H2Error::ProtocolError)));
352    }
353
354    Ok((
355        i,
356        FrameHeader {
357            payload_len,
358            frame_type,
359            flags,
360            stream_id,
361        },
362    ))
363}
364
365/// Map a raw H2 frame type byte to its [`FrameType`] variant.
366///
367/// Unknown types are mapped to [`FrameType::Unknown`] so the caller can skip
368/// the payload per RFC 9113 §5.5 ("Implementations MUST ignore and discard
369/// frames of unknown type").
370fn convert_frame_type(t: u8) -> FrameType {
371    trace!("{} got frame type: {}", log_module_context!(), t);
372    match t {
373        0 => FrameType::Data,
374        1 => FrameType::Headers,
375        2 => FrameType::Priority,
376        3 => FrameType::RstStream,
377        4 => FrameType::Settings,
378        5 => FrameType::PushPromise,
379        6 => FrameType::Ping,
380        7 => FrameType::GoAway,
381        8 => FrameType::WindowUpdate,
382        9 => FrameType::Continuation,
383        // RFC 9218 §7.1 PRIORITY_UPDATE
384        0x10 => FrameType::PriorityUpdate,
385        other => FrameType::Unknown(other),
386    }
387}
388
389#[derive(Clone, Debug)]
390pub enum Frame {
391    Data(Data),
392    Headers(Headers),
393    Priority(Priority),
394    RstStream(RstStream),
395    Settings(Settings),
396    PushPromise(PushPromise),
397    Ping(Ping),
398    GoAway(GoAway),
399    WindowUpdate(WindowUpdate),
400    Continuation(Continuation),
401    /// RFC 9218 §7.1 PRIORITY_UPDATE — connection-scoped signal that
402    /// re-prioritizes a specific stream. Payload carries the prioritized
403    /// stream ID and the verbatim priority field value (structured field).
404    PriorityUpdate(PriorityUpdate),
405    /// Unknown frame type (RFC 9113 §5.5) — payload already consumed, the
406    /// state machine MUST ignore it.
407    Unknown(u8),
408}
409
410impl Frame {
411    /// The RFC 9113 §6 frame type of a parsed frame.
412    ///
413    /// [`FrameHeader`] carries a `frame_type` field, so a frame that has not
414    /// been parsed yet can name its type; a parsed [`Frame`] could not, and
415    /// callers that needed one re-derived it by matching all twelve variants
416    /// themselves. This is that match, written once.
417    pub fn frame_type(&self) -> FrameType {
418        match self {
419            Frame::Data(_) => FrameType::Data,
420            Frame::Headers(_) => FrameType::Headers,
421            Frame::Priority(_) => FrameType::Priority,
422            Frame::RstStream(_) => FrameType::RstStream,
423            Frame::Settings(_) => FrameType::Settings,
424            Frame::PushPromise(_) => FrameType::PushPromise,
425            Frame::Ping(_) => FrameType::Ping,
426            Frame::GoAway(_) => FrameType::GoAway,
427            Frame::WindowUpdate(_) => FrameType::WindowUpdate,
428            Frame::Continuation(_) => FrameType::Continuation,
429            Frame::PriorityUpdate(_) => FrameType::PriorityUpdate,
430            Frame::Unknown(raw) => FrameType::Unknown(*raw),
431        }
432    }
433}
434
435/// RFC 9218 §7.1 PRIORITY_UPDATE frame payload.
436#[derive(Clone, Debug, PartialEq)]
437pub struct PriorityUpdate {
438    /// Identifier of the stream being re-prioritized (31-bit, reserved high
439    /// bit masked off). `0` MUST be treated as a connection error
440    /// (`PROTOCOL_ERROR`) by the handler.
441    pub prioritized_stream_id: u32,
442    /// Verbatim priority field value (SF-Item / ASCII). The handler passes
443    /// this through the same `parse_rfc9218_priority` helper used for the
444    /// `priority` request header.
445    pub priority_field_value: Vec<u8>,
446}
447
448pub fn frame_body<'a>(
449    i: &'a [u8],
450    header: &FrameHeader,
451) -> IResult<&'a [u8], Frame, ParserError<'a>> {
452    let in_len = i.len();
453    let f = match header.frame_type {
454        FrameType::Data => data_frame(i, header)?,
455        FrameType::Headers => headers_frame(i, header)?,
456        FrameType::Priority => {
457            ensure_frame_size!(i, header.payload_len == PRIORITY_PAYLOAD_SIZE);
458            priority_frame(i, header)?
459        }
460        FrameType::RstStream => {
461            ensure_frame_size!(i, header.payload_len == RST_STREAM_PAYLOAD_SIZE);
462            rst_stream_frame(i, header)?
463        }
464        FrameType::PushPromise => push_promise_frame(i, header)?,
465        FrameType::Continuation => continuation_frame(i, header)?,
466        FrameType::Settings => {
467            // RFC 9113 §6.5: SETTINGS ACK with non-zero payload is FRAME_SIZE_ERROR
468            ensure_frame_size!(
469                i,
470                !(header.flags & FLAG_ACK != 0 && header.payload_len != 0)
471            );
472            ensure_frame_size!(i, header.payload_len.is_multiple_of(SETTINGS_ENTRY_SIZE));
473            settings_frame(i, header)?
474        }
475        FrameType::Ping => {
476            ensure_frame_size!(i, header.payload_len == PING_PAYLOAD_SIZE);
477            ping_frame(i, header)?
478        }
479        FrameType::GoAway => {
480            // RFC 9113 §6.8: GOAWAY payload is at least 8 bytes
481            // (last-stream-id + error-code). Additional debug data may follow.
482            ensure_frame_size!(i, header.payload_len >= GOAWAY_PAYLOAD_SIZE);
483            goaway_frame(i, header)?
484        }
485        FrameType::WindowUpdate => {
486            ensure_frame_size!(i, header.payload_len == WINDOW_UPDATE_PAYLOAD_SIZE);
487            window_update_frame(i, header)?
488        }
489        // RFC 9218 §7.1: PRIORITY_UPDATE payload must be ≥ 4 bytes.
490        FrameType::PriorityUpdate => priority_update_frame(i, header)?,
491        // RFC 9113 §5.5: silently consume unknown frame payloads.
492        FrameType::Unknown(_) => unknown_frame(i, header)?,
493    };
494
495    // Whatever the frame type, decoding the body never grows the input and
496    // never leaves more bytes than there are payload bytes to consume.
497    debug_assert!(
498        f.0.len() <= in_len,
499        "frame body parser must not grow its input"
500    );
501    debug_assert!(
502        f.0.len() <= in_len.saturating_sub(header.payload_len as usize),
503        "frame body must consume at least the declared payload_len bytes"
504    );
505    Ok(f)
506}
507
508#[derive(Clone, Debug)]
509pub struct Data {
510    pub stream_id: u32,
511    pub payload: Slice,
512    pub end_stream: bool,
513}
514
515/// Parse the padding prefix from a frame payload per RFC 9113 §6.1.
516///
517/// If `FLAG_PADDED` is set in `flags`, reads the 1-byte pad length and validates
518/// it does not exceed the remaining data. Returns the content slice (after the
519/// pad-length byte) and the number of padding bytes to trim from the end.
520/// Returns `ProtocolError` when the pad length exceeds available data.
521///
522/// RFC 9113 §6.1: "The Pad Length field MUST be less than the length of the
523/// frame payload". With a frame payload of length `N`, the valid pad_length
524/// range is `0..=N-1`. After reading the 1-byte pad-length field, the
525/// remaining slice has length `N-1`, so pad_length may be at most `N-1`,
526/// i.e. `pad_length <= i.len()`. Only values strictly greater than `i.len()`
527/// are invalid.
528fn strip_padding<'a>(
529    i: &'a [u8],
530    flags: u8,
531    error_input: &'a [u8],
532) -> IResult<&'a [u8], u8, ParserError<'a>> {
533    let in_len = i.len();
534    let (i, pad_length) = if flags & FLAG_PADDED != 0 {
535        let (i, pad_length) = be_u8(i)?;
536        (i, pad_length)
537    } else {
538        (i, 0)
539    };
540    // Reading the pad-length byte (PADDED set) shrinks the slice by exactly one;
541    // the unpadded path leaves it untouched.
542    debug_assert_eq!(
543        i.len(),
544        in_len - (flags & FLAG_PADDED != 0) as usize,
545        "strip_padding consumes the pad-length byte iff PADDED is set"
546    );
547
548    if (pad_length as usize) > i.len() {
549        return Err(Err::Failure(ParserError::new_h2(
550            error_input,
551            H2Error::ProtocolError,
552        )));
553    }
554
555    // Post-condition this function is contracted to enforce (RFC 9113 §6.1):
556    // the returned pad length never exceeds the bytes that remain for it to trim.
557    debug_assert!(
558        pad_length as usize <= i.len(),
559        "strip_padding must reject pad_length larger than the remaining payload"
560    );
561    Ok((i, pad_length))
562}
563
564/// Remove `pad_length` bytes of trailing padding from `i`, returning only
565/// the content portion. Returns `ProtocolError` if the remaining input is
566/// shorter than the declared padding — this is reachable in `headers_frame`
567/// when PADDED and PRIORITY are both set and the 5-byte priority payload
568/// leaves fewer bytes than `pad_length`.
569fn unpad<'a>(
570    i: &'a [u8],
571    pad_length: u8,
572    error_input: &'a [u8],
573) -> Result<&'a [u8], Err<ParserError<'a>>> {
574    let in_len = i.len();
575    let content_len = i
576        .len()
577        .checked_sub(pad_length as usize)
578        .ok_or_else(|| Err::Failure(ParserError::new_h2(error_input, H2Error::ProtocolError)))?;
579    // Trimming padding only ever removes bytes, and removes exactly the declared
580    // padding (content + padding partition the input with no overlap or gap).
581    debug_assert!(
582        content_len <= in_len,
583        "unpad must not grow the content slice"
584    );
585    debug_assert_eq!(
586        content_len + pad_length as usize,
587        in_len,
588        "content and padding must exactly partition the input"
589    );
590    Ok(&i[..content_len])
591}
592
593pub fn data_frame<'a>(
594    input: &'a [u8],
595    header: &FrameHeader,
596) -> IResult<&'a [u8], Frame, ParserError<'a>> {
597    let in_len = input.len();
598    let (remaining, i) = take(header.payload_len)(input)?;
599
600    let (i, pad_length) = strip_padding(i, header.flags, input)?;
601    let payload = unpad(i, pad_length, input)?;
602
603    // `take` consumed exactly the declared payload; the visible content (after
604    // stripping the optional pad-length byte and the trailing padding) can never
605    // exceed it.
606    debug_assert_eq!(
607        remaining.len(),
608        in_len - header.payload_len as usize,
609        "data_frame must consume exactly payload_len bytes"
610    );
611    debug_assert!(
612        payload.len() <= header.payload_len as usize,
613        "data payload must fit within the declared frame payload"
614    );
615    debug_assert!(
616        pad_length as usize <= header.payload_len as usize,
617        "padding must not exceed the frame payload"
618    );
619
620    Ok((
621        remaining,
622        Frame::Data(Data {
623            stream_id: header.stream_id,
624            payload: Slice::new(input, payload),
625            end_stream: header.flags & FLAG_END_STREAM != 0,
626        }),
627    ))
628}
629
630#[derive(Clone, Debug)]
631pub struct Headers {
632    pub stream_id: u32,
633    pub priority: Option<PriorityPart>,
634    pub header_block_fragment: Slice,
635    // pub header_block_fragment: &'a [u8],
636    pub end_stream: bool,
637    pub end_headers: bool,
638}
639
640#[derive(Clone, Debug, PartialEq)]
641pub struct StreamDependency {
642    pub exclusive: bool,
643    pub stream_id: u32,
644}
645
646fn stream_dependency(i: &[u8]) -> IResult<&[u8], StreamDependency, ParserError<'_>> {
647    let (i, stream) = map(be_u32, |i| StreamDependency {
648        exclusive: i & 0x80000000 != 0,
649        stream_id: i & STREAM_ID_MASK,
650    })
651    .parse(i)?;
652    Ok((i, stream))
653}
654
655pub fn headers_frame<'a>(
656    input: &'a [u8],
657    header: &FrameHeader,
658) -> IResult<&'a [u8], Frame, ParserError<'a>> {
659    let in_len = input.len();
660    let (remaining, i) = take(header.payload_len)(input)?;
661
662    let (i, pad_length) = strip_padding(i, header.flags, input)?;
663
664    let (i, priority) = if header.flags & FLAG_PRIORITY != 0 {
665        let (i, stream_dependency) = stream_dependency(i)?;
666        let (i, weight) = be_u8(i)?;
667        (
668            i,
669            Some(PriorityPart::Rfc7540 {
670                stream_dependency,
671                weight,
672            }),
673        )
674    } else {
675        (i, None)
676    };
677
678    let header_block_fragment = unpad(i, pad_length, input)?;
679
680    // `take` consumed exactly the declared payload, and the header block fragment
681    // that survives after stripping pad-length, the optional 5-byte priority
682    // prefix, and trailing padding is necessarily a subslice of it.
683    debug_assert_eq!(
684        remaining.len(),
685        in_len - header.payload_len as usize,
686        "headers_frame must consume exactly payload_len bytes"
687    );
688    debug_assert!(
689        header_block_fragment.len() <= header.payload_len as usize,
690        "header block fragment must fit within the declared frame payload"
691    );
692
693    Ok((
694        remaining,
695        Frame::Headers(Headers {
696            stream_id: header.stream_id,
697            priority,
698            header_block_fragment: Slice::new(input, header_block_fragment),
699            end_stream: header.flags & FLAG_END_STREAM != 0,
700            end_headers: header.flags & FLAG_END_HEADERS != 0,
701        }),
702    ))
703}
704
705#[derive(Clone, Debug, PartialEq)]
706pub enum PriorityPart {
707    Rfc7540 {
708        stream_dependency: StreamDependency,
709        weight: u8,
710    },
711    Rfc9218 {
712        urgency: u8, // should be between 0 and 7 inclusive
713        incremental: bool,
714    },
715}
716
717#[derive(Clone, Debug, PartialEq)]
718pub struct Priority {
719    pub stream_id: u32,
720    pub inner: PriorityPart,
721}
722
723pub fn priority_frame<'a>(
724    input: &'a [u8],
725    header: &FrameHeader,
726) -> IResult<&'a [u8], Frame, ParserError<'a>> {
727    // Size-check in `frame_header` already rejected mismatches, but use
728    // `take(header.payload_len)` to keep the fixed-size parsers structurally
729    // symmetric with variable-size ones: if the upstream length invariant is
730    // ever weakened we still consume exactly the declared payload and the
731    // inner parse fails cleanly instead of reading random trailing bytes.
732    let in_len = input.len();
733    let (i, data) = take(header.payload_len)(input)?;
734    // PRIORITY is a fixed 5-byte frame (RFC 9113 §6.3); the framing layer
735    // rejected mismatches, so the scoped payload is exactly that wide.
736    debug_assert_eq!(
737        i.len(),
738        in_len - header.payload_len as usize,
739        "priority_frame must consume exactly payload_len bytes"
740    );
741    debug_assert_eq!(
742        data.len(),
743        PRIORITY_PAYLOAD_SIZE as usize,
744        "PRIORITY payload must be exactly 5 bytes"
745    );
746    let (_, (stream_dependency, weight)) = (stream_dependency, be_u8).parse(data)?;
747    Ok((
748        i,
749        Frame::Priority(Priority {
750            stream_id: header.stream_id,
751            inner: PriorityPart::Rfc7540 {
752                stream_dependency,
753                weight,
754            },
755        }),
756    ))
757}
758
759#[derive(Clone, Debug, PartialEq)]
760pub struct RstStream {
761    pub stream_id: u32,
762    pub error_code: u32,
763}
764
765pub fn rst_stream_frame<'a>(
766    input: &'a [u8],
767    header: &FrameHeader,
768) -> IResult<&'a [u8], Frame, ParserError<'a>> {
769    // `take(header.payload_len)` keeps the fixed-size parsers symmetric with
770    // variable-size ones (see `priority_frame`). The framing layer already
771    // enforced `payload_len == RST_STREAM_PAYLOAD_SIZE == 4`, so this
772    // consumes exactly the 32-bit error-code field.
773    let in_len = input.len();
774    let (i, data) = take(header.payload_len)(input)?;
775    // RST_STREAM is a fixed 4-byte frame (RFC 9113 §6.4); the framing layer
776    // enforced the size, so the scoped payload holds exactly the error code.
777    debug_assert_eq!(
778        i.len(),
779        in_len - header.payload_len as usize,
780        "rst_stream_frame must consume exactly payload_len bytes"
781    );
782    debug_assert_eq!(
783        data.len(),
784        RST_STREAM_PAYLOAD_SIZE as usize,
785        "RST_STREAM payload must be exactly 4 bytes"
786    );
787    let (_, error_code) = be_u32(data)?;
788    Ok((
789        i,
790        Frame::RstStream(RstStream {
791            stream_id: header.stream_id,
792            error_code,
793        }),
794    ))
795}
796
797#[derive(Clone, Debug, PartialEq)]
798pub struct Settings {
799    pub settings: Vec<Setting>,
800    pub ack: bool,
801}
802
803#[derive(Clone, Debug, PartialEq)]
804pub struct Setting {
805    pub identifier: u16,
806    pub value: u32,
807}
808
809/// Maximum SETTINGS entries accepted in a single SETTINGS frame.
810///
811/// RFC 9113 §6.5 defines 7 identifiers and RFC 7540/9218/RFC 8441 add a
812/// handful more; a well-formed peer never sends more than a dozen. Capping
813/// at 64 leaves generous headroom for future extensions while bounding the
814/// per-frame allocation: without this cap a malicious peer could send a
815/// MAX_FRAME_SIZE payload (16 MiB) composed entirely of six-byte
816/// identifier/value pairs, forcing `settings_frame` to allocate ≈2.8M
817/// `Setting` entries per connection (audit Pass 1 Low #7).
818pub const MAX_SETTINGS_ENTRIES: usize = 64;
819
820pub fn settings_frame<'a>(
821    input: &'a [u8],
822    header: &FrameHeader,
823) -> IResult<&'a [u8], Frame, ParserError<'a>> {
824    // Reject oversized SETTINGS before allocating. An entry is exactly 6
825    // bytes (RFC 9113 §6.5.1 — 16-bit identifier + 32-bit value), so the
826    // count is bounded by `payload_len / 6`.
827    if header.payload_len / SETTINGS_ENTRY_SIZE > MAX_SETTINGS_ENTRIES as u32 {
828        return Err(Err::Failure(ParserError::new_h2(
829            input,
830            H2Error::FrameSizeError,
831        )));
832    }
833
834    let in_len = input.len();
835    let (i, data) = take(header.payload_len)(input)?;
836
837    let (_, settings) = many0(map(complete((be_u16, be_u32)), |(identifier, value)| {
838        Setting { identifier, value }
839    }))
840    .parse(data)?;
841
842    // The framing layer guaranteed `payload_len % 6 == 0` and the cap above
843    // bounded the entry count; the decoded vector must reflect exactly that —
844    // one entry per 6-byte pair, never more than the allocation cap.
845    debug_assert_eq!(
846        i.len(),
847        in_len - header.payload_len as usize,
848        "settings_frame must consume exactly payload_len bytes"
849    );
850    debug_assert_eq!(
851        settings.len(),
852        header.payload_len as usize / SETTINGS_ENTRY_SIZE as usize,
853        "one SETTINGS entry decodes per 6 payload bytes"
854    );
855    debug_assert!(
856        settings.len() <= MAX_SETTINGS_ENTRIES,
857        "settings_frame must honour the MAX_SETTINGS_ENTRIES allocation cap"
858    );
859
860    Ok((
861        i,
862        Frame::Settings(Settings {
863            settings,
864            ack: header.flags & FLAG_ACK != 0,
865        }),
866    ))
867}
868
869/// PushPromise is always rejected with PROTOCOL_ERROR at the wire layer.
870/// Sozu never announces `SETTINGS_ENABLE_PUSH=1`, and RFC 9113 §8.4 requires
871/// that a peer which has not enabled server push treat a received
872/// PUSH_PROMISE as a connection error of type PROTOCOL_ERROR. Rejecting in
873/// the parser is defence-in-depth: any future refactor of `mux/h2.rs` that
874/// forgets to call `handle_push_promise_frame` cannot silently accept push
875/// traffic. The struct is retained so the outer match in `h2.rs` still
876/// compiles, but `push_promise_frame` never constructs it.
877#[derive(Clone, Debug)]
878pub struct PushPromise;
879
880pub fn push_promise_frame<'a>(
881    input: &'a [u8],
882    header: &FrameHeader,
883) -> IResult<&'a [u8], Frame, ParserError<'a>> {
884    // Consume the payload bytes first so the framing layer does not desync
885    // if the caller ever demoted the error, then reject.
886    let (_remaining, _payload) = take(header.payload_len)(input)?;
887    Err(Err::Failure(ParserError::new_h2(
888        input,
889        H2Error::ProtocolError,
890    )))
891}
892
893#[derive(Clone, Debug, PartialEq)]
894pub struct Ping {
895    pub payload: [u8; 8],
896    pub ack: bool,
897}
898
899pub fn ping_frame<'a>(
900    input: &'a [u8],
901    header: &FrameHeader,
902) -> IResult<&'a [u8], Frame, ParserError<'a>> {
903    // `take(header.payload_len)` (rather than `take(8usize)`) keeps the
904    // fixed-size parsers symmetric with variable-size ones. The framing
905    // layer already enforced `payload_len == PING_PAYLOAD_SIZE == 8`.
906    let in_len = input.len();
907    let (i, data) = take(header.payload_len)(input)?;
908
909    // PING is a fixed 8-byte frame (RFC 9113 §6.7). The framing layer enforced
910    // the size, which is the invariant the unchecked `copy_from_slice(&data[..8])`
911    // below relies on — assert it before the copy so a weakened size check would
912    // fire here rather than panic on the slice index.
913    debug_assert_eq!(
914        i.len(),
915        in_len - header.payload_len as usize,
916        "ping_frame must consume exactly payload_len bytes"
917    );
918    debug_assert_eq!(
919        data.len(),
920        PING_PAYLOAD_SIZE as usize,
921        "PING payload must be exactly 8 bytes for the opaque-data copy"
922    );
923
924    let mut p = Ping {
925        payload: [0; 8],
926        ack: header.flags & FLAG_ACK != 0,
927    };
928    p.payload[..8].copy_from_slice(&data[..8]);
929
930    Ok((i, Frame::Ping(p)))
931}
932
933#[derive(Clone, Debug)]
934pub struct GoAway {
935    pub last_stream_id: u32,
936    pub error_code: u32,
937    pub additional_debug_data: Slice,
938}
939
940pub fn goaway_frame<'a>(
941    input: &'a [u8],
942    header: &FrameHeader,
943) -> IResult<&'a [u8], Frame, ParserError<'a>> {
944    let in_len = input.len();
945    let (remaining, i) = take(header.payload_len)(input)?;
946    let (i, raw_last_stream_id) = be_u32(i)?;
947    // RFC 9113 §6.8: reserved bit must be masked (same as frame_header stream_id)
948    let last_stream_id = raw_last_stream_id & STREAM_ID_MASK;
949    let (additional_debug_data, error_code) = be_u32(i)?;
950    // The framing layer guaranteed `payload_len >= 8`; after the two fixed u32
951    // fields the remainder is the optional debug data, exactly `payload_len - 8`
952    // bytes, and the reserved high bit of Last-Stream-ID is cleared.
953    debug_assert_eq!(
954        remaining.len(),
955        in_len - header.payload_len as usize,
956        "goaway_frame must consume exactly payload_len bytes"
957    );
958    debug_assert_eq!(
959        additional_debug_data.len(),
960        header.payload_len as usize - GOAWAY_PAYLOAD_SIZE as usize,
961        "GOAWAY debug data is payload_len minus the 8-byte fixed prefix"
962    );
963    debug_assert_eq!(
964        last_stream_id & !STREAM_ID_MASK,
965        0,
966        "GOAWAY last_stream_id reserved high bit must be masked"
967    );
968    Ok((
969        remaining,
970        Frame::GoAway(GoAway {
971            last_stream_id,
972            error_code,
973            additional_debug_data: Slice::new(input, additional_debug_data),
974        }),
975    ))
976}
977
978#[derive(Clone, Debug, PartialEq)]
979pub struct WindowUpdate {
980    pub stream_id: u32,
981    pub increment: u32,
982}
983
984pub fn window_update_frame<'a>(
985    input: &'a [u8],
986    header: &FrameHeader,
987) -> IResult<&'a [u8], Frame, ParserError<'a>> {
988    // Scope input to payload_len like other fixed-size parsers (rst_stream_frame,
989    // ping_frame). The caller enforces payload_len == 4; the take() ensures a
990    // future relaxation doesn't desynchronize the frame stream.
991    let in_len = input.len();
992    let (i, data) = take(header.payload_len)(input)?;
993    // WINDOW_UPDATE is a fixed 4-byte frame (RFC 9113 §6.9); the framing layer
994    // enforced the size so the scoped payload holds exactly the increment.
995    debug_assert_eq!(
996        i.len(),
997        in_len - header.payload_len as usize,
998        "window_update_frame must consume exactly payload_len bytes"
999    );
1000    debug_assert_eq!(
1001        data.len(),
1002        WINDOW_UPDATE_PAYLOAD_SIZE as usize,
1003        "WINDOW_UPDATE payload must be exactly 4 bytes"
1004    );
1005    let (_, increment) = be_u32(data)?;
1006    let increment = increment & STREAM_ID_MASK;
1007    // The reserved high bit is masked: the increment is a 31-bit value (§6.9).
1008    debug_assert!(
1009        increment <= STREAM_ID_MASK,
1010        "window-size increment must be a 31-bit value"
1011    );
1012
1013    // NOTE: zero-increment validation is intentionally NOT performed here.
1014    // RFC 9113 §6.9 requires different error handling depending on whether the
1015    // WINDOW_UPDATE targets stream 0 (connection error) or a specific stream
1016    // (stream error). That distinction requires the stream_id context, which is
1017    // available in the H2 connection handler (handle_window_update_frame), not
1018    // in the wire-level parser.
1019
1020    Ok((
1021        i,
1022        Frame::WindowUpdate(WindowUpdate {
1023            stream_id: header.stream_id,
1024            increment,
1025        }),
1026    ))
1027}
1028
1029/// Continuation frames are handled inline during HEADERS parsing. The
1030/// wire-level parser does not track connection state (whether the previous
1031/// frame was HEADERS/PUSH_PROMISE with END_HEADERS=0), so standalone
1032/// CONTINUATION is rejected at the h2 state-machine layer
1033/// (`handle_header_state`): when a CONTINUATION frame header is observed
1034/// while the state is `H2State::Header` (i.e. no header block is in
1035/// progress) we emit GOAWAY(PROTOCOL_ERROR) per RFC 9113 §6.10.
1036#[derive(Clone, Debug)]
1037pub struct Continuation;
1038
1039pub fn continuation_frame<'a>(
1040    input: &'a [u8],
1041    header: &FrameHeader,
1042) -> IResult<&'a [u8], Frame, ParserError<'a>> {
1043    // Consume the entire frame payload without storing fields
1044    let in_len = input.len();
1045    let (remaining, _) = take(header.payload_len)(input)?;
1046    debug_assert_eq!(
1047        remaining.len(),
1048        in_len - header.payload_len as usize,
1049        "continuation_frame must consume exactly payload_len bytes"
1050    );
1051    Ok((remaining, Frame::Continuation(Continuation)))
1052}
1053
1054/// Silently consume the payload of a frame whose type byte is not recognised,
1055/// per RFC 9113 §5.5 ("Implementations MUST ignore and discard frames of
1056/// unknown type"). Previously covered RFC 9218 PRIORITY_UPDATE (type 0x10);
1057/// PRIORITY_UPDATE is now parsed through [`priority_update_frame`].
1058pub fn unknown_frame<'a>(
1059    input: &'a [u8],
1060    header: &FrameHeader,
1061) -> IResult<&'a [u8], Frame, ParserError<'a>> {
1062    let in_len = input.len();
1063    let (remaining, _payload) = take(header.payload_len)(input)?;
1064    debug_assert_eq!(
1065        remaining.len(),
1066        in_len - header.payload_len as usize,
1067        "unknown_frame must consume exactly payload_len bytes"
1068    );
1069    let raw = match header.frame_type {
1070        FrameType::Unknown(t) => t,
1071        _ => 0,
1072    };
1073    Ok((remaining, Frame::Unknown(raw)))
1074}
1075
1076/// RFC 9218 §7.1 PRIORITY_UPDATE frame parser. Payload layout:
1077///
1078/// ```text
1079/// +-+-------------------------------------------------------------+
1080/// |R|              Prioritized Stream ID (31)                     |
1081/// +-+-------------------------------------------------------------+
1082/// |                    Priority Field Value (*)                 ...
1083/// +---------------------------------------------------------------+
1084/// ```
1085///
1086/// - 4-byte prioritized stream ID (reserved high bit masked off).
1087/// - Remainder: verbatim priority field value (ASCII / SF-Item).
1088///
1089/// A payload shorter than 4 bytes is `FRAME_SIZE_ERROR` per §7.1. Stream
1090/// ID value `0` is rejected by the handler (connection-level
1091/// `PROTOCOL_ERROR`), not here — keep the parser purely structural.
1092pub fn priority_update_frame<'a>(
1093    input: &'a [u8],
1094    header: &FrameHeader,
1095) -> IResult<&'a [u8], Frame, ParserError<'a>> {
1096    if header.payload_len < PRIORITY_UPDATE_MIN_PAYLOAD {
1097        return Err(Err::Failure(ParserError::new_h2(
1098            input,
1099            H2Error::FrameSizeError,
1100        )));
1101    }
1102    // RFC 9218 §7.1 priority field is an SF-Item (RFC 8941): ASCII-only,
1103    // small. Real-world emissions (`u=N, i, foo=...`) are < 30 bytes.
1104    // Cap at PRIORITY_UPDATE_MAX_VALUE (1024) — the same cap nghttp2 and
1105    // the `h2` Rust crate apply — so an attacker cannot drive
1106    // `value_bytes.to_vec()` to allocate ~16 KiB per frame
1107    // (SETTINGS_MAX_FRAME_SIZE default), turning a structurally-legitimate
1108    // PRIORITY_UPDATE stream into allocator pressure that the
1109    // H2FloodDetector glitch budget may not catch quickly.
1110    let value_len = (header.payload_len - PRIORITY_UPDATE_MIN_PAYLOAD) as usize;
1111    if value_len > PRIORITY_UPDATE_MAX_VALUE {
1112        return Err(Err::Failure(ParserError::new_h2(
1113            input,
1114            H2Error::ProtocolError,
1115        )));
1116    }
1117    let in_len = input.len();
1118    let (remaining, payload) = take(header.payload_len)(input)?;
1119    let (raw_id_bytes, value_bytes) = payload.split_at(PRIORITY_UPDATE_MIN_PAYLOAD as usize);
1120    // The two checks above bound the value length; `split_at` partitions the
1121    // payload into the 4-byte stream-id prefix and the SF-Item value with no
1122    // overlap. Assert the bound this function is contracted to enforce.
1123    debug_assert_eq!(
1124        remaining.len(),
1125        in_len - header.payload_len as usize,
1126        "priority_update_frame must consume exactly payload_len bytes"
1127    );
1128    debug_assert_eq!(
1129        raw_id_bytes.len(),
1130        PRIORITY_UPDATE_MIN_PAYLOAD as usize,
1131        "PRIORITY_UPDATE stream-id prefix must be exactly 4 bytes"
1132    );
1133    debug_assert!(
1134        value_bytes.len() <= PRIORITY_UPDATE_MAX_VALUE,
1135        "priority field value must respect the PRIORITY_UPDATE_MAX_VALUE cap"
1136    );
1137    let prioritized_stream_id = u32::from_be_bytes([
1138        raw_id_bytes[0],
1139        raw_id_bytes[1],
1140        raw_id_bytes[2],
1141        raw_id_bytes[3],
1142    ]) & STREAM_ID_MASK;
1143    debug_assert_eq!(
1144        prioritized_stream_id & !STREAM_ID_MASK,
1145        0,
1146        "prioritized_stream_id reserved high bit must be masked"
1147    );
1148    Ok((
1149        remaining,
1150        Frame::PriorityUpdate(PriorityUpdate {
1151            prioritized_stream_id,
1152            priority_field_value: value_bytes.to_vec(),
1153        }),
1154    ))
1155}
1156
1157/// Minimum payload size for a PRIORITY_UPDATE frame (RFC 9218 §7.1):
1158/// the 4-byte prioritized stream ID. The priority field value is allowed
1159/// to be empty (defaults to the RFC 9218 §4 defaults).
1160pub const PRIORITY_UPDATE_MIN_PAYLOAD: u32 = 4;
1161
1162/// Maximum length of the `priority_field_value` portion of a PRIORITY_UPDATE
1163/// frame (RFC 9218 §7.1). The field is a Structured Field Item (RFC 8941):
1164/// ASCII-only, intended for compact dictionary payloads such as `u=3, i`.
1165/// 1024 bytes mirrors nghttp2's `NGHTTP2_MAX_PRIORITY_VALUE_LEN` and the
1166/// upper-bound the `h2` Rust crate enforces, an order of magnitude tighter
1167/// than `SETTINGS_MAX_FRAME_SIZE` — the attacker is denied a per-frame
1168/// 16 KiB allocation primitive sized in single TCP segments.
1169pub const PRIORITY_UPDATE_MAX_VALUE: usize = 1024;
1170
1171#[cfg(test)]
1172mod tests {
1173    use super::*;
1174
1175    /// Default max frame size per RFC 9113 §6.5.2 (2^14 = 16384)
1176    const DEFAULT_MAX_FRAME_SIZE: u32 = 1 << 14;
1177
1178    // ---- SETTINGS ACK with non-zero payload (C-2 regression) ----
1179
1180    /// RFC 9113 §6.5: a SETTINGS frame with the ACK flag AND a non-empty
1181    /// payload is a FRAME_SIZE_ERROR. The parser now enforces this directly
1182    /// (moved from the mux layer for defense-in-depth).
1183    #[test]
1184    fn test_settings_ack_with_payload_rejected() {
1185        // SETTINGS ACK (flags=0x01) with 6-byte payload (one setting entry)
1186        let input = [
1187            0x00, 0x00, 0x06, // payload_len = 6
1188            0x04, // type = SETTINGS
1189            0x01, // flags = ACK
1190            0x00, 0x00, 0x00, 0x00, // stream_id = 0
1191            // payload: SETTINGS_MAX_CONCURRENT_STREAMS (0x0003) = 100 (0x00000064)
1192            0x00, 0x03, 0x00, 0x00, 0x00, 0x64,
1193        ];
1194
1195        let (remaining, header) =
1196            frame_header(&input, DEFAULT_MAX_FRAME_SIZE).expect("frame header parses cleanly");
1197        assert_eq!(header.frame_type, FrameType::Settings);
1198        assert_eq!(header.flags & FLAG_ACK, FLAG_ACK);
1199        assert_eq!(header.payload_len, 6);
1200
1201        let result = frame_body(remaining, &header);
1202        assert!(
1203            result.is_err(),
1204            "SETTINGS ACK with non-empty payload must be rejected"
1205        );
1206        match result {
1207            Err(nom::Err::Failure(e)) => {
1208                assert_eq!(e.kind, ParserErrorKind::H2(H2Error::FrameSizeError));
1209            }
1210            other => panic!("expected Failure(FrameSizeError), got {other:?}"),
1211        }
1212    }
1213
1214    // ---- SETTINGS ACK with empty payload (valid) ----
1215
1216    /// RFC 9113 §6.5: a SETTINGS ACK with zero-length payload is the only
1217    /// valid form. The parser must accept it and produce ack=true with no
1218    /// settings entries.
1219    #[test]
1220    fn test_settings_ack_empty_accepted() {
1221        let input = [
1222            0x00, 0x00, 0x00, // payload_len = 0
1223            0x04, // type = SETTINGS
1224            0x01, // flags = ACK
1225            0x00, 0x00, 0x00, 0x00, // stream_id = 0
1226        ];
1227
1228        let (remaining, header) =
1229            frame_header(&input, DEFAULT_MAX_FRAME_SIZE).expect("frame header parses cleanly");
1230        assert_eq!(header.frame_type, FrameType::Settings);
1231        assert_eq!(header.flags & FLAG_ACK, FLAG_ACK);
1232
1233        let (_, frame) = frame_body(remaining, &header).expect("frame body parses cleanly");
1234        match frame {
1235            Frame::Settings(settings) => {
1236                assert!(settings.ack, "ACK flag must be set");
1237                assert!(
1238                    settings.settings.is_empty(),
1239                    "should have no settings entries"
1240                );
1241            }
1242            other => panic!("expected Frame::Settings, got {other:?}"),
1243        }
1244    }
1245
1246    // ---- WINDOW_UPDATE with max increment (flow control boundary) ----
1247
1248    /// RFC 9113 §6.9: increment = 2^31-1 (0x7FFFFFFF) is the maximum valid
1249    /// window size increment. The parser must accept it.
1250    #[test]
1251    fn test_window_update_max_increment() {
1252        let input = [
1253            0x00, 0x00, 0x04, // payload_len = 4
1254            0x08, // type = WINDOW_UPDATE
1255            0x00, // flags = 0
1256            0x00, 0x00, 0x00, 0x01, // stream_id = 1
1257            0x7F, 0xFF, 0xFF, 0xFF, // increment = 2^31-1
1258        ];
1259
1260        let (remaining, header) =
1261            frame_header(&input, DEFAULT_MAX_FRAME_SIZE).expect("frame header parses cleanly");
1262        assert_eq!(header.frame_type, FrameType::WindowUpdate);
1263        assert_eq!(header.stream_id, 1);
1264
1265        let (_, frame) = frame_body(remaining, &header).expect("frame body parses cleanly");
1266        match frame {
1267            Frame::WindowUpdate(wu) => {
1268                assert_eq!(wu.increment, 0x7FFFFFFF);
1269                assert_eq!(wu.stream_id, 1);
1270            }
1271            other => panic!("expected Frame::WindowUpdate, got {other:?}"),
1272        }
1273    }
1274
1275    // ---- WINDOW_UPDATE with zero increment (parser accepts, handler differentiates) ----
1276
1277    /// RFC 9113 §6.9: zero-increment WINDOW_UPDATE is now parsed successfully
1278    /// by the wire parser. The connection vs stream error distinction is handled
1279    /// in handle_window_update_frame, not in the parser.
1280    #[test]
1281    fn test_window_update_zero_increment_parsed() {
1282        // Connection-level (stream_id = 0) zero increment
1283        let input = [
1284            0x00, 0x00, 0x04, // payload_len = 4
1285            0x08, // type = WINDOW_UPDATE
1286            0x00, // flags = 0
1287            0x00, 0x00, 0x00, 0x00, // stream_id = 0
1288            0x00, 0x00, 0x00, 0x00, // increment = 0
1289        ];
1290
1291        let (remaining, header) =
1292            frame_header(&input, DEFAULT_MAX_FRAME_SIZE).expect("frame header parses cleanly");
1293        assert_eq!(header.frame_type, FrameType::WindowUpdate);
1294
1295        let (_, frame) = frame_body(remaining, &header).expect("frame body parses cleanly");
1296        match frame {
1297            Frame::WindowUpdate(wu) => {
1298                assert_eq!(wu.stream_id, 0);
1299                assert_eq!(wu.increment, 0);
1300            }
1301            other => panic!("expected Frame::WindowUpdate, got {other:?}"),
1302        }
1303
1304        // Stream-level (stream_id = 3) zero increment
1305        let input2 = [
1306            0x00, 0x00, 0x04, // payload_len = 4
1307            0x08, // type = WINDOW_UPDATE
1308            0x00, // flags = 0
1309            0x00, 0x00, 0x00, 0x03, // stream_id = 3
1310            0x00, 0x00, 0x00, 0x00, // increment = 0
1311        ];
1312
1313        let (remaining2, header2) =
1314            frame_header(&input2, DEFAULT_MAX_FRAME_SIZE).expect("frame header parses cleanly");
1315        let (_, frame2) =
1316            frame_body(remaining2, &header2).expect("second frame body parses cleanly");
1317        match frame2 {
1318            Frame::WindowUpdate(wu) => {
1319                assert_eq!(wu.stream_id, 3);
1320                assert_eq!(wu.increment, 0);
1321            }
1322            other => panic!("expected Frame::WindowUpdate, got {other:?}"),
1323        }
1324    }
1325
1326    // ---- Unknown frame type is silently discarded (RFC 9113 §5.5) ----
1327
1328    /// RFC 9113 §5.5: "Implementations MUST ignore and discard frames of
1329    /// unknown type". The parser maps unknown type bytes to
1330    /// [`FrameType::Unknown`] and consumes the payload without erroring so
1331    /// H2 extensions (e.g. RFC 9218 PRIORITY_UPDATE, type 0x10) stay
1332    /// interoperable.
1333    #[test]
1334    fn test_unknown_frame_type_is_ignored() {
1335        let input = [
1336            0x00, 0x00, 0x04, // payload_len = 4
1337            0xFF, // type = unknown (255)
1338            0x00, // flags = 0
1339            0x00, 0x00, 0x00, 0x00, // stream_id = 0
1340            0xde, 0xad, 0xbe, 0xef, // arbitrary payload bytes
1341        ];
1342
1343        let (remaining, header) = frame_header(&input, DEFAULT_MAX_FRAME_SIZE)
1344            .expect("unknown frame header must parse cleanly");
1345        assert!(matches!(header.frame_type, FrameType::Unknown(0xFF)));
1346        assert_eq!(header.payload_len, 4);
1347
1348        let (after, frame) =
1349            frame_body(remaining, &header).expect("unknown frame body must be consumed");
1350        assert!(after.is_empty(), "payload bytes must be consumed");
1351        match frame {
1352            Frame::Unknown(0xFF) => {}
1353            other => panic!("expected Frame::Unknown(0xFF), got {other:?}"),
1354        }
1355    }
1356
1357    /// RFC 9218 §7.1: PRIORITY_UPDATE with an empty priority field value +
1358    /// prioritized stream ID = 1 is a well-formed frame. The parser yields
1359    /// `Frame::PriorityUpdate` with an empty `priority_field_value` — the
1360    /// handler supplies the RFC 9218 §4 defaults.
1361    #[test]
1362    fn test_priority_update_empty_field_parses() {
1363        let input = [
1364            0x00, 0x00, 0x04, // payload_len = 4
1365            0x10, // type = PRIORITY_UPDATE (0x10)
1366            0x00, // flags = 0
1367            0x00, 0x00, 0x00, 0x00, // stream_id = 0 (connection-scoped)
1368            // prioritized stream ID (big-endian, 31-bit with reserved MSB)
1369            0x00, 0x00, 0x00, 0x01,
1370        ];
1371
1372        let (remaining, header) = frame_header(&input, DEFAULT_MAX_FRAME_SIZE)
1373            .expect("PRIORITY_UPDATE header must parse");
1374        assert!(matches!(header.frame_type, FrameType::PriorityUpdate));
1375        let (_, frame) = frame_body(remaining, &header).expect("body must be consumed");
1376        match frame {
1377            Frame::PriorityUpdate(PriorityUpdate {
1378                prioritized_stream_id,
1379                ref priority_field_value,
1380            }) => {
1381                assert_eq!(prioritized_stream_id, 1);
1382                assert!(priority_field_value.is_empty());
1383            }
1384            other => panic!("expected Frame::PriorityUpdate, got {other:?}"),
1385        }
1386    }
1387
1388    /// RFC 9218 §7.1: PRIORITY_UPDATE with a non-empty SF-Item priority
1389    /// field value. The parser preserves the raw bytes verbatim; the
1390    /// handler re-uses `parse_rfc9218_priority` to extract `(urgency, i)`.
1391    #[test]
1392    fn test_priority_update_with_priority_field_parses() {
1393        let value = b"u=0, i";
1394        let mut input = vec![
1395            0x00,
1396            0x00,
1397            0x04 + value.len() as u8, // payload_len = 4 + value
1398            0x10,                     // type = PRIORITY_UPDATE
1399            0x00,                     // flags = 0
1400            0x00,
1401            0x00,
1402            0x00,
1403            0x00, // connection stream
1404            0x00,
1405            0x00,
1406            0x00,
1407            0x05, // prioritized stream 5
1408        ];
1409        input.extend_from_slice(value);
1410
1411        let (remaining, header) =
1412            frame_header(&input, DEFAULT_MAX_FRAME_SIZE).expect("header parses");
1413        assert!(matches!(header.frame_type, FrameType::PriorityUpdate));
1414        let (_, frame) = frame_body(remaining, &header).expect("body parses");
1415        match frame {
1416            Frame::PriorityUpdate(PriorityUpdate {
1417                prioritized_stream_id,
1418                ref priority_field_value,
1419            }) => {
1420                assert_eq!(prioritized_stream_id, 5);
1421                assert_eq!(priority_field_value.as_slice(), value);
1422            }
1423            other => panic!("expected Frame::PriorityUpdate, got {other:?}"),
1424        }
1425    }
1426
1427    /// RFC 9218 §7.1: PRIORITY_UPDATE MUST carry at least the 4-byte
1428    /// prioritized stream ID. Shorter payloads are `FRAME_SIZE_ERROR`.
1429    #[test]
1430    fn test_priority_update_payload_below_min_is_frame_size_error() {
1431        let input = [
1432            0x00, 0x00, 0x03, // payload_len = 3 (one byte short)
1433            0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1434        ];
1435        let (remaining, header) =
1436            frame_header(&input, DEFAULT_MAX_FRAME_SIZE).expect("header parses");
1437        let result = frame_body(remaining, &header);
1438        match result {
1439            Err(Err::Failure(e)) => {
1440                assert_eq!(e.kind, ParserErrorKind::H2(H2Error::FrameSizeError));
1441            }
1442            other => panic!("expected FRAME_SIZE_ERROR, got {other:?}"),
1443        }
1444    }
1445
1446    /// RFC 9218 §7.1 + nghttp2/h2-Rust convention: cap the
1447    /// `priority_field_value` at PRIORITY_UPDATE_MAX_VALUE (1024) so a
1448    /// peer cannot drive per-frame `Vec<u8>` allocations toward
1449    /// SETTINGS_MAX_FRAME_SIZE (16 KiB) under cover of a structurally-
1450    /// legitimate frame type.
1451    #[test]
1452    fn test_priority_update_oversized_value_is_protocol_error() {
1453        // payload_len = 4 (sid) + 1025 (value) = 1029 → just over the cap.
1454        let payload_len = (PRIORITY_UPDATE_MIN_PAYLOAD as usize) + PRIORITY_UPDATE_MAX_VALUE + 1;
1455        let mut input = Vec::with_capacity(9 + payload_len);
1456        // 24-bit length, big-endian.
1457        input.push(((payload_len >> 16) & 0xff) as u8);
1458        input.push(((payload_len >> 8) & 0xff) as u8);
1459        input.push((payload_len & 0xff) as u8);
1460        input.push(0x10); // type = PRIORITY_UPDATE
1461        input.push(0x00); // flags
1462        input.extend_from_slice(&[0x00, 0x00, 0x00, 0x00]); // stream_id = 0
1463        input.extend_from_slice(&[0x00, 0x00, 0x00, 0x01]); // prioritized_stream_id = 1
1464        input.extend(std::iter::repeat_n(b'a', PRIORITY_UPDATE_MAX_VALUE + 1));
1465        // Use the larger frame size for the header parse so payload_len passes;
1466        // the value-length cap is enforced by `priority_update_frame` itself.
1467        let (remaining, header) =
1468            frame_header(&input, payload_len as u32 + 1).expect("header parses");
1469        match frame_body(remaining, &header) {
1470            Err(Err::Failure(e)) => {
1471                assert_eq!(e.kind, ParserErrorKind::H2(H2Error::ProtocolError));
1472            }
1473            other => {
1474                panic!("expected PROTOCOL_ERROR for oversized PRIORITY_UPDATE value, got {other:?}")
1475            }
1476        }
1477    }
1478
1479    /// Boundary case: priority value at exactly PRIORITY_UPDATE_MAX_VALUE
1480    /// MUST be accepted (off-by-one regression guard).
1481    #[test]
1482    fn test_priority_update_at_max_value_accepted() {
1483        let payload_len = (PRIORITY_UPDATE_MIN_PAYLOAD as usize) + PRIORITY_UPDATE_MAX_VALUE;
1484        let mut input = Vec::with_capacity(9 + payload_len);
1485        input.push(((payload_len >> 16) & 0xff) as u8);
1486        input.push(((payload_len >> 8) & 0xff) as u8);
1487        input.push((payload_len & 0xff) as u8);
1488        input.push(0x10);
1489        input.push(0x00);
1490        input.extend_from_slice(&[0x00, 0x00, 0x00, 0x00]);
1491        input.extend_from_slice(&[0x00, 0x00, 0x00, 0x01]);
1492        input.extend(std::iter::repeat_n(b'a', PRIORITY_UPDATE_MAX_VALUE));
1493        let (remaining, header) =
1494            frame_header(&input, payload_len as u32 + 1).expect("header parses");
1495        match frame_body(remaining, &header) {
1496            Ok((_, Frame::PriorityUpdate(pu))) => {
1497                assert_eq!(pu.priority_field_value.len(), PRIORITY_UPDATE_MAX_VALUE);
1498            }
1499            other => panic!("expected Frame::PriorityUpdate at the cap boundary, got {other:?}"),
1500        }
1501    }
1502
1503    /// RFC 9218 §7.1: PRIORITY_UPDATE MUST be sent on stream 0 (the
1504    /// connection control stream). Any other stream ID is a connection
1505    /// `PROTOCOL_ERROR`, rejected at `frame_header` time via the
1506    /// frame_type ↔ stream_id cross-check.
1507    #[test]
1508    fn test_priority_update_on_non_zero_stream_is_protocol_error() {
1509        let input = [
1510            0x00, 0x00, 0x04, // payload_len = 4
1511            0x10, // type = PRIORITY_UPDATE
1512            0x00, // flags
1513            0x00, 0x00, 0x00, 0x07, // stream_id = 7 (invalid for PRIORITY_UPDATE)
1514            0x00, 0x00, 0x00, 0x01, // prioritized_stream_id
1515        ];
1516        match frame_header(&input, DEFAULT_MAX_FRAME_SIZE) {
1517            Err(Err::Failure(e)) => {
1518                assert_eq!(e.kind, ParserErrorKind::H2(H2Error::ProtocolError));
1519            }
1520            other => panic!("expected PROTOCOL_ERROR, got {other:?}"),
1521        }
1522    }
1523
1524    // ---- SETTINGS with odd payload size (not a multiple of 6) ----
1525
1526    /// RFC 9113 §6.5: a SETTINGS frame payload that is not a multiple of 6
1527    /// octets MUST be treated as a FRAME_SIZE_ERROR.
1528    #[test]
1529    fn test_settings_payload_not_multiple_of_6_rejected() {
1530        let input = [
1531            0x00, 0x00, 0x05, // payload_len = 5 (not a multiple of 6)
1532            0x04, // type = SETTINGS
1533            0x00, // flags = 0 (not ACK)
1534            0x00, 0x00, 0x00, 0x00, // stream_id = 0
1535            0x00, 0x03, 0x00, 0x00, 0x00, // 5 bytes of incomplete setting
1536        ];
1537
1538        let (remaining, header) =
1539            frame_header(&input, DEFAULT_MAX_FRAME_SIZE).expect("frame header parses cleanly");
1540        assert_eq!(header.frame_type, FrameType::Settings);
1541
1542        let result = frame_body(remaining, &header);
1543        assert!(
1544            result.is_err(),
1545            "odd-size SETTINGS payload must be rejected"
1546        );
1547        match result {
1548            Err(nom::Err::Failure(e)) => {
1549                assert_eq!(e.kind, ParserErrorKind::H2(H2Error::FrameSizeError));
1550            }
1551            other => panic!("expected Failure(FrameSizeError), got {other:?}"),
1552        }
1553    }
1554
1555    /// Audit Pass 1 Low #7: cap the number of SETTINGS entries at 64 to
1556    /// prevent a peer from forcing a multi-megabyte `Vec<Setting>` allocation
1557    /// per connection via a MAX_FRAME_SIZE SETTINGS payload full of
1558    /// identifier/value pairs. The first over-cap pair must be rejected.
1559    #[test]
1560    fn test_settings_frame_over_cap_rejected() {
1561        // 65 entries * 6 bytes = 390 bytes payload — one past MAX_SETTINGS_ENTRIES.
1562        let n_entries: u16 = (MAX_SETTINGS_ENTRIES as u16) + 1;
1563        let payload_len = u32::from(n_entries) * SETTINGS_ENTRY_SIZE;
1564        let mut input = Vec::with_capacity(9 + payload_len as usize);
1565        input.extend_from_slice(&payload_len.to_be_bytes()[1..]); // 24-bit length
1566        input.push(0x04); // SETTINGS
1567        input.push(0x00); // flags
1568        input.extend_from_slice(&[0, 0, 0, 0]); // stream_id = 0
1569        for i in 0..n_entries {
1570            input.extend_from_slice(&i.to_be_bytes()); // identifier
1571            input.extend_from_slice(&0u32.to_be_bytes()); // value
1572        }
1573
1574        let (remaining, header) =
1575            frame_header(&input, 16_777_215).expect("frame header parses cleanly");
1576        assert_eq!(header.frame_type, FrameType::Settings);
1577
1578        let result = frame_body(remaining, &header);
1579        assert!(
1580            result.is_err(),
1581            "SETTINGS frame over MAX_SETTINGS_ENTRIES must be rejected"
1582        );
1583        match result {
1584            Err(nom::Err::Failure(e)) => {
1585                assert_eq!(e.kind, ParserErrorKind::H2(H2Error::FrameSizeError));
1586            }
1587            other => panic!("expected Failure(FrameSizeError), got {other:?}"),
1588        }
1589    }
1590
1591    /// Boundary: exactly MAX_SETTINGS_ENTRIES is still accepted.
1592    #[test]
1593    fn test_settings_frame_at_cap_accepted() {
1594        let n_entries: u16 = MAX_SETTINGS_ENTRIES as u16;
1595        let payload_len = u32::from(n_entries) * SETTINGS_ENTRY_SIZE;
1596        let mut input = Vec::with_capacity(9 + payload_len as usize);
1597        input.extend_from_slice(&payload_len.to_be_bytes()[1..]);
1598        input.push(0x04);
1599        input.push(0x00);
1600        input.extend_from_slice(&[0, 0, 0, 0]);
1601        for _ in 0..n_entries {
1602            input.extend_from_slice(&0u16.to_be_bytes());
1603            input.extend_from_slice(&0u32.to_be_bytes());
1604        }
1605
1606        let (remaining, header) =
1607            frame_header(&input, 16_777_215).expect("frame header parses cleanly");
1608        let result = frame_body(remaining, &header);
1609        assert!(result.is_ok(), "exactly MAX_SETTINGS_ENTRIES must parse");
1610    }
1611
1612    // ---- RST_STREAM with wrong payload size ----
1613
1614    /// RFC 9113 §6.4: RST_STREAM payload MUST be exactly 4 octets.
1615    #[test]
1616    fn test_rst_stream_wrong_payload_size_rejected() {
1617        // 8 bytes instead of 4
1618        let input = [
1619            0x00, 0x00, 0x08, // payload_len = 8
1620            0x03, // type = RST_STREAM
1621            0x00, // flags = 0
1622            0x00, 0x00, 0x00, 0x01, // stream_id = 1
1623            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 8 bytes
1624        ];
1625
1626        let (remaining, header) =
1627            frame_header(&input, DEFAULT_MAX_FRAME_SIZE).expect("frame header parses cleanly");
1628        assert_eq!(header.frame_type, FrameType::RstStream);
1629
1630        let result = frame_body(remaining, &header);
1631        assert!(
1632            result.is_err(),
1633            "wrong RST_STREAM payload size must be rejected"
1634        );
1635        match result {
1636            Err(nom::Err::Failure(e)) => {
1637                assert_eq!(e.kind, ParserErrorKind::H2(H2Error::FrameSizeError));
1638            }
1639            other => panic!("expected Failure(FrameSizeError), got {other:?}"),
1640        }
1641    }
1642
1643    // ---- PING with wrong payload size ----
1644
1645    /// RFC 9113 §6.7: PING payload MUST be exactly 8 octets.
1646    #[test]
1647    fn test_ping_wrong_payload_size_rejected() {
1648        let input = [
1649            0x00, 0x00, 0x04, // payload_len = 4 (should be 8)
1650            0x06, // type = PING
1651            0x00, // flags = 0
1652            0x00, 0x00, 0x00, 0x00, // stream_id = 0
1653            0x01, 0x02, 0x03, 0x04, // only 4 bytes
1654        ];
1655
1656        let (remaining, header) =
1657            frame_header(&input, DEFAULT_MAX_FRAME_SIZE).expect("frame header parses cleanly");
1658        assert_eq!(header.frame_type, FrameType::Ping);
1659
1660        let result = frame_body(remaining, &header);
1661        assert!(result.is_err(), "wrong PING payload size must be rejected");
1662        match result {
1663            Err(nom::Err::Failure(e)) => {
1664                assert_eq!(e.kind, ParserErrorKind::H2(H2Error::FrameSizeError));
1665            }
1666            other => panic!("expected Failure(FrameSizeError), got {other:?}"),
1667        }
1668    }
1669
1670    // ---- Frame exceeding max_frame_size ----
1671
1672    /// RFC 9113 §4.2: a frame with payload_len > max_frame_size is a
1673    /// FRAME_SIZE_ERROR at the frame header parsing stage.
1674    #[test]
1675    fn test_frame_exceeding_max_frame_size_rejected() {
1676        // payload_len = 16385 (0x004001), which exceeds the default 16384
1677        let input = [
1678            0x00, 0x40, 0x01, // payload_len = 16385
1679            0x00, // type = DATA
1680            0x00, // flags = 0
1681            0x00, 0x00, 0x00, 0x01, // stream_id = 1
1682        ];
1683
1684        let result = frame_header(&input, DEFAULT_MAX_FRAME_SIZE);
1685        assert!(result.is_err(), "oversized frame must be rejected");
1686        match result {
1687            Err(nom::Err::Failure(e)) => {
1688                assert_eq!(e.kind, ParserErrorKind::H2(H2Error::FrameSizeError));
1689            }
1690            other => panic!("expected Failure(FrameSizeError), got {other:?}"),
1691        }
1692    }
1693
1694    // ---- SETTINGS on non-zero stream (PROTOCOL_ERROR) ----
1695
1696    /// RFC 9113 §6.5: SETTINGS frames MUST be associated with stream 0.
1697    #[test]
1698    fn test_settings_on_nonzero_stream_rejected() {
1699        let input = [
1700            0x00, 0x00, 0x00, // payload_len = 0
1701            0x04, // type = SETTINGS
1702            0x00, // flags = 0
1703            0x00, 0x00, 0x00, 0x01, // stream_id = 1 (invalid!)
1704        ];
1705
1706        let result = frame_header(&input, DEFAULT_MAX_FRAME_SIZE);
1707        assert!(
1708            result.is_err(),
1709            "SETTINGS on non-zero stream must be rejected"
1710        );
1711        match result {
1712            Err(nom::Err::Failure(e)) => {
1713                assert_eq!(e.kind, ParserErrorKind::H2(H2Error::ProtocolError));
1714            }
1715            other => panic!("expected Failure(ProtocolError), got {other:?}"),
1716        }
1717    }
1718
1719    // ---- DATA on stream 0 (PROTOCOL_ERROR) ----
1720
1721    /// RFC 9113 §6.1: DATA frames MUST be associated with a stream.
1722    #[test]
1723    fn test_data_on_stream_zero_rejected() {
1724        let input = [
1725            0x00, 0x00, 0x02, // payload_len = 2
1726            0x00, // type = DATA
1727            0x00, // flags = 0
1728            0x00, 0x00, 0x00, 0x00, // stream_id = 0 (invalid!)
1729            0xCA, 0xFE,
1730        ];
1731
1732        let result = frame_header(&input, DEFAULT_MAX_FRAME_SIZE);
1733        assert!(result.is_err(), "DATA on stream 0 must be rejected");
1734        match result {
1735            Err(nom::Err::Failure(e)) => {
1736                assert_eq!(e.kind, ParserErrorKind::H2(H2Error::ProtocolError));
1737            }
1738            other => panic!("expected Failure(ProtocolError), got {other:?}"),
1739        }
1740    }
1741
1742    // ---- WINDOW_UPDATE with reserved bit set (must be masked) ----
1743
1744    /// RFC 9113 §6.9: the reserved bit (MSB) of the window size increment
1745    /// MUST be ignored. An increment of 0x80000001 should be read as 1.
1746    #[test]
1747    fn test_window_update_reserved_bit_masked() {
1748        let input = [
1749            0x00, 0x00, 0x04, // payload_len = 4
1750            0x08, // type = WINDOW_UPDATE
1751            0x00, // flags = 0
1752            0x00, 0x00, 0x00, 0x01, // stream_id = 1
1753            0x80, 0x00, 0x00, 0x01, // increment with reserved bit set = 1
1754        ];
1755
1756        let (remaining, header) =
1757            frame_header(&input, DEFAULT_MAX_FRAME_SIZE).expect("frame header parses cleanly");
1758        let (_, frame) = frame_body(remaining, &header).expect("frame body parses cleanly");
1759        match frame {
1760            Frame::WindowUpdate(wu) => {
1761                assert_eq!(
1762                    wu.increment, 1,
1763                    "reserved bit must be masked to yield increment=1"
1764                );
1765            }
1766            other => panic!("expected Frame::WindowUpdate, got {other:?}"),
1767        }
1768    }
1769
1770    // ---- Helper: build a raw H2 frame (header + payload) ----
1771
1772    /// Build a raw H2 frame header (9 bytes) from explicit fields.
1773    fn build_frame_header(payload_len: u32, frame_type: u8, flags: u8, stream_id: u32) -> Vec<u8> {
1774        vec![
1775            ((payload_len >> 16) & 0xFF) as u8,
1776            ((payload_len >> 8) & 0xFF) as u8,
1777            (payload_len & 0xFF) as u8,
1778            frame_type,
1779            flags,
1780            ((stream_id >> 24) & 0xFF) as u8,
1781            ((stream_id >> 16) & 0xFF) as u8,
1782            ((stream_id >> 8) & 0xFF) as u8,
1783            (stream_id & 0xFF) as u8,
1784        ]
1785    }
1786
1787    /// Build a complete raw frame (header + payload).
1788    fn build_raw_frame(
1789        payload_len: u32,
1790        frame_type: u8,
1791        flags: u8,
1792        stream_id: u32,
1793        payload: &[u8],
1794    ) -> Vec<u8> {
1795        let mut raw = build_frame_header(payload_len, frame_type, flags, stream_id);
1796        raw.extend_from_slice(payload);
1797        raw
1798    }
1799
1800    // ---- Connection preface ----
1801
1802    #[test]
1803    fn test_preface_valid() {
1804        let input = b"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n";
1805        let (remaining, matched) = preface(input).expect("should parse preface");
1806        assert!(remaining.is_empty());
1807        assert_eq!(matched, input.as_slice());
1808    }
1809
1810    #[test]
1811    fn test_preface_invalid() {
1812        let input = b"GET / HTTP/1.1\r\n";
1813        let result = preface(input);
1814        assert!(result.is_err(), "invalid preface should fail");
1815    }
1816
1817    #[test]
1818    fn test_preface_with_trailing_data() {
1819        let mut input = b"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n".to_vec();
1820        input.extend_from_slice(b"extra stuff");
1821        let (remaining, _) = preface(&input).expect("should parse preface");
1822        assert_eq!(remaining, b"extra stuff");
1823    }
1824
1825    // ---- Frame header basic parsing ----
1826
1827    #[test]
1828    fn test_frame_header_settings_basic() {
1829        let raw = build_frame_header(0, 4, 0, 0);
1830        let (remaining, header) =
1831            frame_header(&raw, DEFAULT_MAX_FRAME_SIZE).expect("frame header parses cleanly");
1832        assert!(remaining.is_empty());
1833        assert_eq!(header.payload_len, 0);
1834        assert_eq!(header.frame_type, FrameType::Settings);
1835        assert_eq!(header.flags, 0);
1836        assert_eq!(header.stream_id, 0);
1837    }
1838
1839    #[test]
1840    fn test_frame_header_data_basic() {
1841        let raw = build_frame_header(100, 0, 1, 1);
1842        let (_, header) =
1843            frame_header(&raw, DEFAULT_MAX_FRAME_SIZE).expect("frame header parses cleanly");
1844        assert_eq!(header.payload_len, 100);
1845        assert_eq!(header.frame_type, FrameType::Data);
1846        assert_eq!(header.flags, 1);
1847        assert_eq!(header.stream_id, 1);
1848    }
1849
1850    #[test]
1851    fn test_frame_header_stream_id_reserved_bit_masked() {
1852        // stream_id with reserved bit set (0x80000001) should be masked to 1.
1853        // Use a DATA frame (stream-specific) to avoid the stream_id=0 rejection.
1854        let raw = build_frame_header(5, 0, 0, 0x80000001);
1855        let (_, header) =
1856            frame_header(&raw, DEFAULT_MAX_FRAME_SIZE).expect("frame header parses cleanly");
1857        assert_eq!(header.stream_id, 1, "reserved MSB must be masked off");
1858    }
1859
1860    // ---- DATA frame parsing ----
1861
1862    #[test]
1863    fn test_parse_data_frame_end_stream() {
1864        let payload = b"hello";
1865        let raw = build_raw_frame(payload.len() as u32, 0, 0x01, 1, payload);
1866        let (remaining, header) =
1867            frame_header(&raw, DEFAULT_MAX_FRAME_SIZE).expect("frame header parses cleanly");
1868        let (_, f) = frame_body(remaining, &header).expect("frame body parses cleanly");
1869        match f {
1870            Frame::Data(d) => {
1871                assert_eq!(d.stream_id, 1);
1872                assert!(d.end_stream);
1873            }
1874            other => panic!("expected Data, got {other:?}"),
1875        }
1876    }
1877
1878    #[test]
1879    fn test_parse_data_frame_no_end_stream() {
1880        let payload = b"data";
1881        let raw = build_raw_frame(payload.len() as u32, 0, 0x00, 3, payload);
1882        let (remaining, header) =
1883            frame_header(&raw, DEFAULT_MAX_FRAME_SIZE).expect("frame header parses cleanly");
1884        let (_, f) = frame_body(remaining, &header).expect("frame body parses cleanly");
1885        match f {
1886            Frame::Data(d) => {
1887                assert_eq!(d.stream_id, 3);
1888                assert!(!d.end_stream);
1889            }
1890            other => panic!("expected Data, got {other:?}"),
1891        }
1892    }
1893
1894    #[test]
1895    fn test_parse_data_frame_with_padding() {
1896        // PADDED flag (0x08), 2 bytes of padding
1897        let pad_length: u8 = 2;
1898        let actual_data = b"hello";
1899        let total_payload = 1 + actual_data.len() + pad_length as usize;
1900        let mut payload = Vec::new();
1901        payload.push(pad_length);
1902        payload.extend_from_slice(actual_data);
1903        payload.extend_from_slice(&[0x00; 2]);
1904
1905        let raw = build_raw_frame(total_payload as u32, 0, 0x08, 1, &payload);
1906        let (remaining, header) =
1907            frame_header(&raw, DEFAULT_MAX_FRAME_SIZE).expect("frame header parses cleanly");
1908        let (_, f) = frame_body(remaining, &header).expect("frame body parses cleanly");
1909        match f {
1910            Frame::Data(d) => {
1911                assert_eq!(d.stream_id, 1);
1912                assert_eq!(d.payload.len, actual_data.len() as u32);
1913            }
1914            other => panic!("expected Data, got {other:?}"),
1915        }
1916    }
1917
1918    #[test]
1919    fn test_parse_data_frame_padding_exceeds_payload() {
1920        // pad_length claims more padding than remaining bytes
1921        let mut payload = Vec::new();
1922        payload.push(10); // pad_length = 10, but only 5 bytes remain
1923        payload.extend_from_slice(b"hello");
1924
1925        let raw = build_raw_frame(payload.len() as u32, 0, 0x08, 1, &payload);
1926        let (remaining, header) =
1927            frame_header(&raw, DEFAULT_MAX_FRAME_SIZE).expect("frame header parses cleanly");
1928        let result = frame_body(remaining, &header);
1929        assert!(
1930            result.is_err(),
1931            "padding exceeding payload should be a protocol error"
1932        );
1933    }
1934
1935    // ---- HEADERS frame parsing ----
1936
1937    #[test]
1938    fn test_parse_headers_frame_basic() {
1939        let hblock = b"\x82\x86";
1940        let raw = build_raw_frame(hblock.len() as u32, 1, 0x04, 1, hblock);
1941        let (remaining, header) =
1942            frame_header(&raw, DEFAULT_MAX_FRAME_SIZE).expect("frame header parses cleanly");
1943        let (_, f) = frame_body(remaining, &header).expect("frame body parses cleanly");
1944        match f {
1945            Frame::Headers(h) => {
1946                assert_eq!(h.stream_id, 1);
1947                assert!(!h.end_stream);
1948                assert!(h.end_headers);
1949                assert!(h.priority.is_none());
1950            }
1951            other => panic!("expected Headers, got {other:?}"),
1952        }
1953    }
1954
1955    #[test]
1956    fn test_parse_headers_frame_end_stream_and_headers() {
1957        let hblock = b"\x82";
1958        let raw = build_raw_frame(hblock.len() as u32, 1, 0x05, 1, hblock);
1959        let (remaining, header) =
1960            frame_header(&raw, DEFAULT_MAX_FRAME_SIZE).expect("frame header parses cleanly");
1961        let (_, f) = frame_body(remaining, &header).expect("frame body parses cleanly");
1962        match f {
1963            Frame::Headers(h) => {
1964                assert!(h.end_stream);
1965                assert!(h.end_headers);
1966            }
1967            other => panic!("expected Headers, got {other:?}"),
1968        }
1969    }
1970
1971    #[test]
1972    fn test_parse_headers_frame_with_priority() {
1973        let hblock = b"\x82";
1974        let payload_len = 5 + hblock.len(); // 4 (stream dep) + 1 (weight) + hblock
1975        let mut payload = Vec::new();
1976        // Stream dependency: non-exclusive, stream_id=1
1977        payload.extend_from_slice(&[0x00, 0x00, 0x00, 0x01]);
1978        payload.push(15); // weight
1979        payload.extend_from_slice(hblock);
1980
1981        let raw = build_raw_frame(payload_len as u32, 1, 0x24, 3, &payload);
1982        let (remaining, header) =
1983            frame_header(&raw, DEFAULT_MAX_FRAME_SIZE).expect("frame header parses cleanly");
1984        let (_, f) = frame_body(remaining, &header).expect("frame body parses cleanly");
1985        match f {
1986            Frame::Headers(h) => {
1987                assert_eq!(h.stream_id, 3);
1988                let priority = h.priority.expect("should have priority");
1989                match priority {
1990                    PriorityPart::Rfc7540 {
1991                        stream_dependency,
1992                        weight,
1993                    } => {
1994                        assert!(!stream_dependency.exclusive);
1995                        assert_eq!(stream_dependency.stream_id, 1);
1996                        assert_eq!(weight, 15);
1997                    }
1998                    other => panic!("expected Rfc7540, got {other:?}"),
1999                }
2000            }
2001            other => panic!("expected Headers, got {other:?}"),
2002        }
2003    }
2004
2005    #[test]
2006    fn test_parse_headers_frame_with_exclusive_priority() {
2007        let hblock = b"\x82\x86";
2008        let mut payload = Vec::new();
2009        // Stream dependency: exclusive (bit 31 set), stream_id=5
2010        let dep = 0x80000005u32;
2011        payload.extend_from_slice(&dep.to_be_bytes());
2012        payload.push(255); // weight
2013        payload.extend_from_slice(hblock);
2014
2015        let payload_len = payload.len();
2016        let raw = build_raw_frame(payload_len as u32, 1, 0x24, 3, &payload);
2017        let (remaining, header) =
2018            frame_header(&raw, DEFAULT_MAX_FRAME_SIZE).expect("frame header parses cleanly");
2019        let (_, f) = frame_body(remaining, &header).expect("frame body parses cleanly");
2020        match f {
2021            Frame::Headers(h) => {
2022                let priority = h.priority.expect("should have priority");
2023                match priority {
2024                    PriorityPart::Rfc7540 {
2025                        stream_dependency,
2026                        weight,
2027                    } => {
2028                        assert!(stream_dependency.exclusive, "exclusive bit should be set");
2029                        assert_eq!(stream_dependency.stream_id, 5);
2030                        assert_eq!(weight, 255);
2031                    }
2032                    other => panic!("expected Rfc7540, got {other:?}"),
2033                }
2034            }
2035            other => panic!("expected Headers, got {other:?}"),
2036        }
2037    }
2038
2039    #[test]
2040    fn test_parse_headers_stream_id_zero_rejected() {
2041        let raw = build_raw_frame(2, 1, 0x04, 0, b"\x82\x86");
2042        let result = frame_header(&raw, DEFAULT_MAX_FRAME_SIZE);
2043        assert!(
2044            result.is_err(),
2045            "HEADERS with stream_id=0 should be rejected"
2046        );
2047    }
2048
2049    // ---- RST_STREAM frame parsing ----
2050
2051    #[test]
2052    fn test_parse_rst_stream() {
2053        let error_code = 0x00000008u32; // CANCEL
2054        let raw = build_raw_frame(4, 3, 0, 1, &error_code.to_be_bytes());
2055        let (remaining, header) =
2056            frame_header(&raw, DEFAULT_MAX_FRAME_SIZE).expect("frame header parses cleanly");
2057        let (_, f) = frame_body(remaining, &header).expect("frame body parses cleanly");
2058        match f {
2059            Frame::RstStream(rst) => {
2060                assert_eq!(rst.stream_id, 1);
2061                assert_eq!(rst.error_code, 0x08);
2062            }
2063            other => panic!("expected RstStream, got {other:?}"),
2064        }
2065    }
2066
2067    #[test]
2068    fn test_parse_rst_stream_stream_id_zero_rejected() {
2069        let raw = build_raw_frame(4, 3, 0, 0, &[0u8; 4]);
2070        let result = frame_header(&raw, DEFAULT_MAX_FRAME_SIZE);
2071        assert!(
2072            result.is_err(),
2073            "RST_STREAM with stream_id=0 should be rejected"
2074        );
2075    }
2076
2077    // ---- SETTINGS frame parsing ----
2078
2079    #[test]
2080    fn test_parse_settings_frame_with_values() {
2081        let mut payload = Vec::new();
2082        // SETTINGS_HEADER_TABLE_SIZE (0x1) = 4096
2083        payload.extend_from_slice(&0x0001u16.to_be_bytes());
2084        payload.extend_from_slice(&4096u32.to_be_bytes());
2085        // SETTINGS_MAX_CONCURRENT_STREAMS (0x3) = 100
2086        payload.extend_from_slice(&0x0003u16.to_be_bytes());
2087        payload.extend_from_slice(&100u32.to_be_bytes());
2088        // SETTINGS_INITIAL_WINDOW_SIZE (0x4) = 65535
2089        payload.extend_from_slice(&0x0004u16.to_be_bytes());
2090        payload.extend_from_slice(&65535u32.to_be_bytes());
2091
2092        let raw = build_raw_frame(payload.len() as u32, 4, 0x0, 0, &payload);
2093        let (remaining, header) =
2094            frame_header(&raw, DEFAULT_MAX_FRAME_SIZE).expect("frame header parses cleanly");
2095        let (_, f) = frame_body(remaining, &header).expect("frame body parses cleanly");
2096        match f {
2097            Frame::Settings(s) => {
2098                assert!(!s.ack);
2099                assert_eq!(s.settings.len(), 3);
2100                assert_eq!(s.settings[0].identifier, 0x0001);
2101                assert_eq!(s.settings[0].value, 4096);
2102                assert_eq!(s.settings[1].identifier, 0x0003);
2103                assert_eq!(s.settings[1].value, 100);
2104                assert_eq!(s.settings[2].identifier, 0x0004);
2105                assert_eq!(s.settings[2].value, 65535);
2106            }
2107            other => panic!("expected Settings, got {other:?}"),
2108        }
2109    }
2110
2111    // ---- PING frame parsing ----
2112
2113    #[test]
2114    fn test_parse_ping_frame() {
2115        let ping_payload = [1u8, 2, 3, 4, 5, 6, 7, 8];
2116        let raw = build_raw_frame(8, 6, 0, 0, &ping_payload);
2117        let (remaining, header) =
2118            frame_header(&raw, DEFAULT_MAX_FRAME_SIZE).expect("frame header parses cleanly");
2119        let (_, f) = frame_body(remaining, &header).expect("frame body parses cleanly");
2120        match f {
2121            Frame::Ping(p) => {
2122                assert_eq!(p.payload, ping_payload);
2123                assert!(!p.ack);
2124            }
2125            other => panic!("expected Ping, got {other:?}"),
2126        }
2127    }
2128
2129    #[test]
2130    fn test_parse_ping_ack_preserves_payload() {
2131        let ping_payload = [0xDE, 0xAD, 0xBE, 0xEF, 0xCA, 0xFE, 0xBA, 0xBE];
2132        let raw = build_raw_frame(8, 6, 0x01, 0, &ping_payload);
2133        let (remaining, header) =
2134            frame_header(&raw, DEFAULT_MAX_FRAME_SIZE).expect("frame header parses cleanly");
2135        let (_, f) = frame_body(remaining, &header).expect("frame body parses cleanly");
2136        match f {
2137            Frame::Ping(p) => {
2138                assert_eq!(
2139                    p.payload, ping_payload,
2140                    "PING ACK must echo the exact payload"
2141                );
2142                assert!(p.ack);
2143            }
2144            other => panic!("expected Ping, got {other:?}"),
2145        }
2146    }
2147
2148    #[test]
2149    fn test_parse_ping_stream_id_nonzero_rejected() {
2150        let raw = build_raw_frame(8, 6, 0, 1, &[0u8; 8]);
2151        let result = frame_header(&raw, DEFAULT_MAX_FRAME_SIZE);
2152        assert!(result.is_err(), "PING with stream_id!=0 should be rejected");
2153    }
2154
2155    // ---- WINDOW_UPDATE frame parsing ----
2156
2157    #[test]
2158    fn test_parse_window_update_connection_level() {
2159        let increment = 1000u32;
2160        let raw = build_raw_frame(4, 8, 0, 0, &increment.to_be_bytes());
2161        let (remaining, header) =
2162            frame_header(&raw, DEFAULT_MAX_FRAME_SIZE).expect("frame header parses cleanly");
2163        let (_, f) = frame_body(remaining, &header).expect("frame body parses cleanly");
2164        match f {
2165            Frame::WindowUpdate(w) => {
2166                assert_eq!(w.stream_id, 0);
2167                assert_eq!(w.increment, 1000);
2168            }
2169            other => panic!("expected WindowUpdate, got {other:?}"),
2170        }
2171    }
2172
2173    #[test]
2174    fn test_parse_window_update_stream_level() {
2175        let increment = 65535u32;
2176        let raw = build_raw_frame(4, 8, 0, 5, &increment.to_be_bytes());
2177        let (remaining, header) =
2178            frame_header(&raw, DEFAULT_MAX_FRAME_SIZE).expect("frame header parses cleanly");
2179        let (_, f) = frame_body(remaining, &header).expect("frame body parses cleanly");
2180        match f {
2181            Frame::WindowUpdate(w) => {
2182                assert_eq!(w.stream_id, 5);
2183                // mux parser uses STREAM_ID_MASK (0x7FFFFFFF), so 65535 & 0x7FFFFFFF = 65535
2184                assert_eq!(w.increment, 65535);
2185            }
2186            other => panic!("expected WindowUpdate, got {other:?}"),
2187        }
2188    }
2189
2190    #[test]
2191    fn test_parse_window_update_wrong_size() {
2192        let raw = build_raw_frame(3, 8, 0, 0, &[0u8; 3]);
2193        let (remaining, header) =
2194            frame_header(&raw, DEFAULT_MAX_FRAME_SIZE).expect("frame header parses cleanly");
2195        let result = frame_body(remaining, &header);
2196        assert!(
2197            result.is_err(),
2198            "WINDOW_UPDATE with payload != 4 should fail"
2199        );
2200        match result {
2201            Err(Err::Failure(e)) => {
2202                assert_eq!(e.kind, ParserErrorKind::H2(H2Error::FrameSizeError));
2203            }
2204            other => panic!("expected FrameSizeError, got {other:?}"),
2205        }
2206    }
2207
2208    // ---- Frame at max_frame_size boundary ----
2209
2210    #[test]
2211    fn test_parse_frame_at_max_frame_size() {
2212        let payload = vec![0u8; DEFAULT_MAX_FRAME_SIZE as usize];
2213        let raw = build_raw_frame(DEFAULT_MAX_FRAME_SIZE, 0, 0x0, 1, &payload);
2214        let (remaining, header) =
2215            frame_header(&raw, DEFAULT_MAX_FRAME_SIZE).expect("frame header parses cleanly");
2216        let (_, f) = frame_body(remaining, &header).expect("frame body parses cleanly");
2217        match f {
2218            Frame::Data(d) => {
2219                assert_eq!(d.payload.len, DEFAULT_MAX_FRAME_SIZE);
2220            }
2221            other => panic!("expected Data, got {other:?}"),
2222        }
2223    }
2224
2225    #[test]
2226    fn test_parse_frame_with_custom_max_frame_size() {
2227        let custom_max = 32768u32;
2228        let payload = vec![0u8; 20000];
2229        let raw = build_raw_frame(20000, 0, 0x0, 1, &payload);
2230        let (remaining, header) =
2231            frame_header(&raw, custom_max).expect("frame header parses with custom max");
2232        let (_, f) = frame_body(remaining, &header).expect("frame body parses cleanly");
2233        match f {
2234            Frame::Data(d) => {
2235                assert_eq!(d.payload.len, 20000);
2236            }
2237            other => panic!("expected Data, got {other:?}"),
2238        }
2239    }
2240
2241    // ---- H2Error conversions ----
2242
2243    #[test]
2244    fn test_h2_error_try_from_valid() {
2245        assert_eq!(H2Error::try_from(0x0), Ok(H2Error::NoError));
2246        assert_eq!(H2Error::try_from(0x1), Ok(H2Error::ProtocolError));
2247        assert_eq!(H2Error::try_from(0x6), Ok(H2Error::FrameSizeError));
2248        assert_eq!(H2Error::try_from(0xd), Ok(H2Error::HTTP11Required));
2249    }
2250
2251    #[test]
2252    fn test_h2_error_try_from_invalid() {
2253        assert_eq!(H2Error::try_from(0x0e), Err(0x0e));
2254        assert_eq!(H2Error::try_from(0xFF), Err(0xFF));
2255    }
2256
2257    #[test]
2258    fn test_h2_error_from_str() {
2259        assert_eq!("NO_ERROR".parse::<H2Error>(), Ok(H2Error::NoError));
2260        assert_eq!(
2261            "PROTOCOL_ERROR".parse::<H2Error>(),
2262            Ok(H2Error::ProtocolError)
2263        );
2264        assert_eq!(
2265            "ENHANCE_YOUR_CALM".parse::<H2Error>(),
2266            Ok(H2Error::EnhanceYourCalm)
2267        );
2268        assert!("INVALID_ERROR".parse::<H2Error>().is_err());
2269    }
2270
2271    #[test]
2272    fn test_h2_error_as_str_roundtrip() {
2273        let errors = [
2274            H2Error::NoError,
2275            H2Error::ProtocolError,
2276            H2Error::InternalError,
2277            H2Error::FlowControlError,
2278            H2Error::SettingsTimeout,
2279            H2Error::StreamClosed,
2280            H2Error::FrameSizeError,
2281            H2Error::RefusedStream,
2282            H2Error::Cancel,
2283            H2Error::CompressionError,
2284            H2Error::ConnectError,
2285            H2Error::EnhanceYourCalm,
2286            H2Error::InadequateSecurity,
2287            H2Error::HTTP11Required,
2288        ];
2289
2290        for error in &errors {
2291            let s = error.as_str();
2292            let parsed: H2Error = s.parse().unwrap_or_else(|_| panic!("failed to parse {s}"));
2293            assert_eq!(*error, parsed, "roundtrip failed for {s}");
2294        }
2295    }
2296
2297    // ---- PRIORITY frame parsing ----
2298
2299    #[test]
2300    fn test_parse_priority_frame() {
2301        let mut payload = Vec::new();
2302        // Stream dependency: non-exclusive, stream_id=1
2303        payload.extend_from_slice(&0x00000001u32.to_be_bytes());
2304        payload.push(15); // weight
2305        assert_eq!(payload.len(), 5);
2306
2307        let raw = build_raw_frame(5, 2, 0, 3, &payload);
2308        let (remaining, header) =
2309            frame_header(&raw, DEFAULT_MAX_FRAME_SIZE).expect("frame header parses cleanly");
2310        let (_, f) = frame_body(remaining, &header).expect("frame body parses cleanly");
2311        match f {
2312            Frame::Priority(p) => {
2313                assert_eq!(p.stream_id, 3);
2314                match p.inner {
2315                    PriorityPart::Rfc7540 {
2316                        stream_dependency,
2317                        weight,
2318                    } => {
2319                        assert!(!stream_dependency.exclusive);
2320                        assert_eq!(stream_dependency.stream_id, 1);
2321                        assert_eq!(weight, 15);
2322                    }
2323                    other => panic!("expected Rfc7540, got {other:?}"),
2324                }
2325            }
2326            other => panic!("expected Priority, got {other:?}"),
2327        }
2328    }
2329
2330    #[test]
2331    fn test_parse_priority_wrong_size_rejected() {
2332        let raw = build_raw_frame(4, 2, 0, 1, &[0u8; 4]);
2333        let (remaining, header) =
2334            frame_header(&raw, DEFAULT_MAX_FRAME_SIZE).expect("frame header parses cleanly");
2335        let result = frame_body(remaining, &header);
2336        assert!(
2337            result.is_err(),
2338            "PRIORITY with wrong payload size should fail"
2339        );
2340    }
2341
2342    // A HEADERS frame with PADDED+PRIORITY where the declared pad_length
2343    // exceeds the bytes left after consuming the 5-byte priority payload
2344    // must be rejected as PROTOCOL_ERROR instead of panicking on underflow.
2345    // Regression for fuzz crash d7a34a0d: payload_len=6, pad_length=3 →
2346    // 6 − 1 (pad_length byte) − 5 (priority) = 0 content bytes, yet the
2347    // parser tried to strip 3 padding bytes from 0.
2348    #[test]
2349    fn test_headers_padded_priority_underflow_rejected() {
2350        let raw: [u8; 16] = [
2351            0x00, 0x00, 0x06, 0x01, 0xff, 0xff, 0xff, 0x00, 0x00, 0x03, 0x00, 0x64, 0x6d, 0x6d,
2352            0x6d, 0x6d,
2353        ];
2354        let (remaining, header) =
2355            frame_header(&raw, DEFAULT_MAX_FRAME_SIZE).expect("frame header parses cleanly");
2356        let result = frame_body(remaining, &header);
2357        assert!(
2358            result.is_err(),
2359            "PADDED+PRIORITY HEADERS with oversized pad_length must error, not panic"
2360        );
2361    }
2362
2363    // ---- strip_padding boundary: pad_length == payload_len - 1 (all padding) ----
2364
2365    /// RFC 9113 §6.1: "The Pad Length field MUST be less than the length of
2366    /// the frame payload". With payload_len = N, pad_length up to N-1 is
2367    /// valid (content may be empty, the final byte is padding). The previous
2368    /// `i.len() <= pad_length` check rejected the N-1 case; the fix changes
2369    /// the comparison to `(pad_length as usize) > i.len()`.
2370    #[test]
2371    fn test_strip_padding_all_padding_accepted() {
2372        // DATA frame: payload_len = 2, FLAG_PADDED, pad_length = 1.
2373        // After consuming the pad-length byte, `i.len() = 1` and
2374        // `pad_length = 1`, which is exactly the maximum valid value.
2375        let input = [
2376            0x00, 0x00, 0x02, // payload_len = 2
2377            0x00, // type = DATA
2378            0x08, // flags = PADDED
2379            0x00, 0x00, 0x00, 0x01, // stream_id = 1
2380            0x01, // pad_length
2381            0x00, // padding byte
2382        ];
2383
2384        let (remaining, header) =
2385            frame_header(&input, DEFAULT_MAX_FRAME_SIZE).expect("frame header parses cleanly");
2386        let (_, frame) =
2387            frame_body(remaining, &header).expect("DATA with all-padding body must parse");
2388        match frame {
2389            Frame::Data(data) => {
2390                assert_eq!(data.stream_id, 1);
2391                // Content length is 0 (all of the post-pad-length-byte payload is padding).
2392                assert_eq!(data.payload.len, 0);
2393            }
2394            other => panic!("expected Frame::Data, got {other:?}"),
2395        }
2396    }
2397
2398    #[test]
2399    fn test_strip_padding_pad_length_equals_payload_len_rejected() {
2400        // payload_len = 1, FLAG_PADDED, pad_length = 1 → invalid
2401        // (the pad-length byte consumes one byte, leaving i.len()=0 but
2402        // pad_length wants to strip 1 extra byte of trailing padding).
2403        let input = [
2404            0x00, 0x00, 0x01, // payload_len = 1
2405            0x00, // type = DATA
2406            0x08, // flags = PADDED
2407            0x00, 0x00, 0x00, 0x01, // stream_id = 1
2408            0x01, // pad_length (invalid: exceeds remaining body)
2409        ];
2410
2411        let (remaining, header) =
2412            frame_header(&input, DEFAULT_MAX_FRAME_SIZE).expect("frame header parses cleanly");
2413        let result = frame_body(remaining, &header);
2414        assert!(
2415            matches!(
2416                result,
2417                Err(nom::Err::Failure(ParserError {
2418                    kind: ParserErrorKind::H2(H2Error::ProtocolError),
2419                    ..
2420                }))
2421            ),
2422            "pad_length > remaining body must yield PROTOCOL_ERROR, got {result:?}"
2423        );
2424    }
2425
2426    // ---- GOAWAY payload-length validation ----
2427
2428    /// RFC 9113 §6.8: a GOAWAY frame payload is at least 8 bytes (4-byte
2429    /// last-stream-id + 4-byte error-code). Shorter payloads MUST be treated
2430    /// as FRAME_SIZE_ERROR.
2431    #[test]
2432    fn test_goaway_short_payload_rejected() {
2433        let input = [
2434            0x00, 0x00, 0x04, // payload_len = 4 (too short)
2435            0x07, // type = GOAWAY
2436            0x00, // flags = 0
2437            0x00, 0x00, 0x00, 0x00, // stream_id = 0
2438            0x00, 0x00, 0x00, 0x00, // partial payload (only last_stream_id)
2439        ];
2440
2441        let (remaining, header) =
2442            frame_header(&input, DEFAULT_MAX_FRAME_SIZE).expect("frame header parses cleanly");
2443        assert_eq!(header.frame_type, FrameType::GoAway);
2444        let result = frame_body(remaining, &header);
2445        assert!(
2446            matches!(
2447                result,
2448                Err(nom::Err::Failure(ParserError {
2449                    kind: ParserErrorKind::H2(H2Error::FrameSizeError),
2450                    ..
2451                }))
2452            ),
2453            "GOAWAY with payload_len < 8 must yield FRAME_SIZE_ERROR, got {result:?}"
2454        );
2455    }
2456
2457    #[test]
2458    fn test_goaway_minimum_payload_accepted() {
2459        let input = [
2460            0x00, 0x00, 0x08, // payload_len = 8
2461            0x07, // type = GOAWAY
2462            0x00, // flags = 0
2463            0x00, 0x00, 0x00, 0x00, // stream_id = 0
2464            0x00, 0x00, 0x00, 0x0a, // last_stream_id = 10
2465            0x00, 0x00, 0x00, 0x00, // error_code = NO_ERROR
2466        ];
2467
2468        let (remaining, header) =
2469            frame_header(&input, DEFAULT_MAX_FRAME_SIZE).expect("frame header parses cleanly");
2470        let (_, frame) = frame_body(remaining, &header).expect("minimal GOAWAY must parse cleanly");
2471        match frame {
2472            Frame::GoAway(goaway) => {
2473                assert_eq!(goaway.last_stream_id, 10);
2474                assert_eq!(goaway.error_code, 0);
2475            }
2476            other => panic!("expected Frame::GoAway, got {other:?}"),
2477        }
2478    }
2479
2480    // ---- PUSH_PROMISE wire-level rejection ----
2481
2482    /// RFC 9113 §8.4: a peer that has not enabled server push MUST treat a
2483    /// received PUSH_PROMISE as a connection error of type PROTOCOL_ERROR.
2484    /// Sozu never advertises `SETTINGS_ENABLE_PUSH=1`, so we reject
2485    /// PUSH_PROMISE at the wire layer for defence-in-depth — even if a
2486    /// future refactor of `mux/h2.rs` forgets the explicit check.
2487    #[test]
2488    fn test_push_promise_rejected_at_wire_layer() {
2489        let input = [
2490            0x00, 0x00, 0x08, // payload_len = 8
2491            0x05, // type = PUSH_PROMISE
2492            0x04, // flags = END_HEADERS
2493            0x00, 0x00, 0x00, 0x01, // stream_id = 1 (associated stream)
2494            0x00, 0x00, 0x00, 0x02, // promised stream_id = 2
2495            0x00, 0x00, 0x00, 0x00, // arbitrary header-block placeholder
2496        ];
2497
2498        let (remaining, header) =
2499            frame_header(&input, DEFAULT_MAX_FRAME_SIZE).expect("frame header parses cleanly");
2500        assert_eq!(header.frame_type, FrameType::PushPromise);
2501        let result = frame_body(remaining, &header);
2502        assert!(
2503            matches!(
2504                result,
2505                Err(nom::Err::Failure(ParserError {
2506                    kind: ParserErrorKind::H2(H2Error::ProtocolError),
2507                    ..
2508                }))
2509            ),
2510            "PUSH_PROMISE must be rejected at wire layer with PROTOCOL_ERROR, got {result:?}"
2511        );
2512    }
2513}