Skip to main content

cellos_supervisor/sni_proxy/h2/
error.rs

1//! Error types produced by the h2 frame parser + HPACK decoder.
2
3use std::fmt;
4
5/// Errors produced by `extract_h2_authority` and `HpackDecoder::decode_block`.
6#[derive(Debug, PartialEq, Eq, Clone)]
7pub enum H2ParseError {
8    /// A frame's declared length exceeds [`super::DEFAULT_MAX_FRAME_SIZE`].
9    OversizedFrame { declared: u32, max: u32 },
10    /// First non-SETTINGS frame is not a HEADERS frame.
11    UnexpectedFirstFrame { frame_type: u8 },
12    /// HEADERS frame flagged something other than END_HEADERS — Phase 3g
13    /// reassembles via CONTINUATION but this variant is preserved for
14    /// callers of the *stateless* one-shot API who set the END_HEADERS
15    /// requirement explicitly.
16    ContinuationFragmentation,
17    /// HPACK encoding requires features we don't decode (legacy P3c
18    /// variant — Phase 3g supports dynamic-table state and Huffman, so
19    /// this is now only emitted for representation classes outside the
20    /// four RFC 7541 §6 cases).
21    HpackUnsupported,
22    /// HEADERS payload is malformed — padding length exceeds payload
23    /// length, integer overflow in HPACK varint, etc.
24    MalformedHeaders,
25    /// A header literal contains a non-ASCII byte. RFC 3986 hosts are
26    /// ASCII; rejecting protects downstream `make_ascii_lowercase` from
27    /// altering UTF-8 byte sequences.
28    NonAsciiAuthority,
29    /// Padded HEADERS payload declared a pad length larger than the
30    /// remaining payload.
31    PaddingOverflow,
32    /// HPACK indexed reference points outside the static + current
33    /// dynamic tables.
34    HpackInvalidIndex { index: u64 },
35    /// `SETTINGS_HEADER_TABLE_SIZE` (HPACK §6.3 dynamic-table size update)
36    /// requested a table size larger than our defensive ceiling.
37    HpackTableSizeOversized { requested: usize, max: usize },
38    /// CONTINUATION reassembly observed a non-CONTINUATION frame
39    /// (DATA, SETTINGS, etc.) between the initial HEADERS and the
40    /// END_HEADERS-bearing CONTINUATION (RFC 7540 §4.3).
41    InterleavedFrame { frame_type: u8 },
42    /// Aggregate header block (HEADERS + all CONTINUATION payloads)
43    /// exceeded the defensive cap.
44    HpackOversizedHeaderBlock { total: usize, max: usize },
45    /// Huffman bitstream contained the EOS symbol or had >7 bits of
46    /// padding (RFC 7541 §5.2).
47    HuffmanInvalid,
48    /// Huffman-decoded output exceeded our decoded-length bound.
49    HuffmanOversized { max: usize },
50    /// scope: per-stream HEADERS+CONTINUATION reassembler exceeded a
51    /// defensive memory bound (per-stream block, total in-flight bytes
52    /// across all streams, or maximum concurrent in-flight streams).
53    /// Caller emits `l7_h2_unparseable_headers` for the offending stream
54    /// and RST_STREAMs only that stream — other streams continue.
55    ReassemblerOverflow {
56        /// Which bound tripped. Stored as an enum below so error matchers
57        /// can distinguish operator-actionable cases (raise the bound,
58        /// see runbook) from defence-in-depth trips.
59        kind: ReassemblerOverflowKind,
60    },
61}
62
63/// Which [`H2ParseError::ReassemblerOverflow`] limit was exceeded.
64#[derive(Debug, PartialEq, Eq, Clone, Copy)]
65pub enum ReassemblerOverflowKind {
66    /// One stream's accumulated HEADERS + CONTINUATION bytes exceeded
67    /// 64 KiB (matches `MAX_HEADER_BLOCK_SIZE`).
68    PerStreamBlock,
69    /// Sum of in-flight bytes across all open reassemblies exceeded
70    /// 256 KiB.
71    TotalInFlight,
72    /// Concurrent in-flight reassemblies exceeded 64.
73    ConcurrentStreams,
74}
75
76impl fmt::Display for H2ParseError {
77    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
78        match self {
79            H2ParseError::OversizedFrame { declared, max } => {
80                write!(
81                    f,
82                    "HTTP/2 frame length {declared} exceeds max-frame-size {max}"
83                )
84            }
85            H2ParseError::UnexpectedFirstFrame { frame_type } => write!(
86                f,
87                "HTTP/2 first frame type 0x{frame_type:02x} is not HEADERS or SETTINGS"
88            ),
89            H2ParseError::ContinuationFragmentation => write!(
90                f,
91                "HTTP/2 HEADERS fragmented across CONTINUATION frames; \
92                 stateless one-shot API does not reassemble"
93            ),
94            H2ParseError::HpackUnsupported => write!(
95                f,
96                "HPACK encoding requires representation classes outside RFC 7541 §6"
97            ),
98            H2ParseError::MalformedHeaders => write!(f, "HEADERS payload is malformed"),
99            H2ParseError::NonAsciiAuthority => {
100                write!(f, ":authority literal contains non-ASCII bytes")
101            }
102            H2ParseError::PaddingOverflow => {
103                write!(f, "HEADERS padding length exceeds payload length")
104            }
105            H2ParseError::HpackInvalidIndex { index } => {
106                write!(f, "HPACK indexed reference {index} out of range")
107            }
108            H2ParseError::HpackTableSizeOversized { requested, max } => write!(
109                f,
110                "HPACK SETTINGS_HEADER_TABLE_SIZE update {requested} exceeds defensive cap {max}"
111            ),
112            H2ParseError::InterleavedFrame { frame_type } => write!(
113                f,
114                "HTTP/2 frame type 0x{frame_type:02x} interleaved between HEADERS and END_HEADERS"
115            ),
116            H2ParseError::HpackOversizedHeaderBlock { total, max } => write!(
117                f,
118                "aggregate HPACK header block {total} octets exceeds defensive cap {max}"
119            ),
120            H2ParseError::HuffmanInvalid => write!(
121                f,
122                "Huffman bitstream contained EOS symbol or invalid padding (RFC 7541 §5.2)"
123            ),
124            H2ParseError::HuffmanOversized { max } => {
125                write!(
126                    f,
127                    "Huffman-decoded output exceeded defensive cap of {max} octets"
128                )
129            }
130            H2ParseError::ReassemblerOverflow { kind } => match kind {
131                ReassemblerOverflowKind::PerStreamBlock => {
132                    write!(f, "per-stream HEADERS+CONTINUATION block exceeded 64 KiB")
133                }
134                ReassemblerOverflowKind::TotalInFlight => write!(
135                    f,
136                    "aggregate HEADERS+CONTINUATION reassembler in-flight bytes exceeded 256 KiB"
137                ),
138                ReassemblerOverflowKind::ConcurrentStreams => write!(
139                    f,
140                    "concurrent in-flight HEADERS+CONTINUATION reassemblies exceeded 64"
141                ),
142            },
143        }
144    }
145}
146
147impl std::error::Error for H2ParseError {}