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