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