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 {}