Skip to main content

sozu_lib/protocol/mux/
parser.rs

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