Skip to main content

sozu_lib/protocol/mux/
parser.rs

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