Skip to main content

sozu_lib/protocol/mux/
parser.rs

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