1use std::{
2 borrow::Cow,
3 error::Error,
4 fmt::{self, Display, Formatter},
5};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12#[non_exhaustive]
13pub enum H3ErrorCode {
14 NoError = 0x0100,
16
17 GeneralProtocolError = 0x0101,
19
20 InternalError = 0x0102,
22
23 StreamCreationError = 0x0103,
25
26 ClosedCriticalStream = 0x0104,
28
29 FrameUnexpected = 0x0105,
31
32 FrameError = 0x0106,
34
35 ExcessiveLoad = 0x0107,
37
38 IdError = 0x0108,
40
41 SettingsError = 0x0109,
43
44 MissingSettings = 0x010a,
46
47 RequestRejected = 0x010b,
49
50 RequestCancelled = 0x010c,
52
53 RequestIncomplete = 0x010d,
55
56 MessageError = 0x010e,
58
59 ConnectError = 0x010f,
61
62 VersionFallback = 0x0110,
64
65 WebTransportBufferedStreamRejected = 0x3994_bd84,
67
68 WebTransportSessionGone = 0x170d_7b68,
70
71 WebTransportFlowControlError = 0x045d_4487,
73
74 WebTransportAlpnError = 0x0817_b3dd,
76
77 WebTransportRequirementsNotMet = 0x212c_0d48,
79
80 QpackDecompressionFailed = 0x200,
82
83 QpackEncoderStreamError = 0x201,
85
86 QpackDecoderStreamError = 0x202,
88}
89
90impl Display for H3ErrorCode {
91 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
92 write!(f, "H3 Error: {}", self.reason_str())
93 }
94}
95
96impl Error for H3ErrorCode {}
97
98impl H3ErrorCode {
99 pub fn reason(&self) -> Cow<'static, str> {
101 Cow::Borrowed(self.reason_str())
102 }
103
104 fn reason_str(self) -> &'static str {
105 match self {
106 H3ErrorCode::NoError => "No error. Used when closing without an error to signal.",
107 H3ErrorCode::GeneralProtocolError => "Peer violated protocol requirements.",
108 H3ErrorCode::InternalError => "An internal error in the HTTP stack.",
109 H3ErrorCode::StreamCreationError => "Peer created a stream that will not be accepted.",
110 H3ErrorCode::ClosedCriticalStream => "A required stream was closed or reset.",
111 H3ErrorCode::FrameUnexpected => {
112 "A frame was not permitted in the current state or stream."
113 }
114 H3ErrorCode::FrameError => "A frame fails layout requirements or has an invalid size.",
115
116 H3ErrorCode::ExcessiveLoad => "Peer is generating excessive load.",
117
118 H3ErrorCode::IdError => "A stream ID or push ID was used incorrectly.",
119
120 H3ErrorCode::SettingsError => "Error in the payload of a SETTINGS frame.",
121
122 H3ErrorCode::MissingSettings => {
123 "No SETTINGS frame at the beginning of the control stream."
124 }
125
126 H3ErrorCode::RequestRejected => {
127 "Server rejected a request without application processing."
128 }
129
130 H3ErrorCode::RequestCancelled => "Request or response (including pushed) is cancelled.",
131
132 H3ErrorCode::RequestIncomplete => {
133 "Client stream terminated without a fully formed request."
134 }
135
136 H3ErrorCode::MessageError => "HTTP message was malformed.",
137
138 H3ErrorCode::ConnectError => "Connection for CONNECT was reset or abnormally closed.",
139
140 H3ErrorCode::VersionFallback => "Requested operation cannot be served over HTTP/3.",
141
142 H3ErrorCode::WebTransportBufferedStreamRejected => {
143 "WebTransport data stream rejected due to lack of associated session."
144 }
145
146 H3ErrorCode::WebTransportSessionGone => "WebTransport session gone.",
147
148 H3ErrorCode::WebTransportFlowControlError => "WebTransport flow control error.",
149
150 H3ErrorCode::WebTransportAlpnError => "WebTransport ALPN error.",
151
152 H3ErrorCode::WebTransportRequirementsNotMet => "WebTransport requirements not met.",
153
154 H3ErrorCode::QpackDecompressionFailed => "QPACK decompression failed.",
155
156 H3ErrorCode::QpackEncoderStreamError => "QPACK encoder stream error.",
157
158 H3ErrorCode::QpackDecoderStreamError => "QPACK decoder stream error.",
159 }
160 }
161
162 pub fn is_connection_error(&self) -> bool {
169 matches!(
170 self,
171 Self::GeneralProtocolError
172 | Self::InternalError
173 | Self::ClosedCriticalStream
174 | Self::FrameUnexpected
175 | Self::FrameError
176 | Self::ExcessiveLoad
177 | Self::IdError
178 | Self::SettingsError
179 | Self::MissingSettings
180 | Self::QpackDecompressionFailed
181 | Self::QpackEncoderStreamError
182 | Self::QpackDecoderStreamError
183 )
184 }
185}
186
187impl From<u64> for H3ErrorCode {
188 fn from(value: u64) -> Self {
190 match value {
191 0x0101 => Self::GeneralProtocolError,
192 0x0102 => Self::InternalError,
193 0x0103 => Self::StreamCreationError,
194 0x0104 => Self::ClosedCriticalStream,
195 0x0105 => Self::FrameUnexpected,
196 0x0106 => Self::FrameError,
197 0x0107 => Self::ExcessiveLoad,
198 0x0108 => Self::IdError,
199 0x0109 => Self::SettingsError,
200 0x010a => Self::MissingSettings,
201 0x010b => Self::RequestRejected,
202 0x010c => Self::RequestCancelled,
203 0x010d => Self::RequestIncomplete,
204 0x010e => Self::MessageError,
205 0x010f => Self::ConnectError,
206 0x0110 => Self::VersionFallback,
207 0x3994_bd84 => Self::WebTransportBufferedStreamRejected,
208 0x170d_7b68 => Self::WebTransportSessionGone,
209 0x045d_4487 => Self::WebTransportFlowControlError,
210 0x0817_b3dd => Self::WebTransportAlpnError,
211 0x212c_0d48 => Self::WebTransportRequirementsNotMet,
212 0x200 => Self::QpackDecompressionFailed,
213 0x201 => Self::QpackEncoderStreamError,
214 0x202 => Self::QpackDecoderStreamError,
215 _ => Self::NoError,
216 }
217 }
218}
219
220impl From<H3ErrorCode> for u64 {
221 fn from(code: H3ErrorCode) -> u64 {
224 match code {
225 H3ErrorCode::NoError => {
226 let n = u64::from(fastrand::u16(..));
227 0x1f * n + 0x21
228 }
229 other => other as u64,
230 }
231 }
232}
233
234#[cfg(test)]
235mod tests {
236 use super::*;
237
238 #[test]
239 fn known_codes_roundtrip() {
240 for code in [
241 H3ErrorCode::GeneralProtocolError,
242 H3ErrorCode::InternalError,
243 H3ErrorCode::StreamCreationError,
244 H3ErrorCode::ClosedCriticalStream,
245 H3ErrorCode::FrameUnexpected,
246 H3ErrorCode::FrameError,
247 H3ErrorCode::ExcessiveLoad,
248 H3ErrorCode::IdError,
249 H3ErrorCode::SettingsError,
250 H3ErrorCode::MissingSettings,
251 H3ErrorCode::RequestRejected,
252 H3ErrorCode::RequestCancelled,
253 H3ErrorCode::RequestIncomplete,
254 H3ErrorCode::MessageError,
255 H3ErrorCode::ConnectError,
256 H3ErrorCode::VersionFallback,
257 H3ErrorCode::WebTransportBufferedStreamRejected,
258 H3ErrorCode::WebTransportSessionGone,
259 H3ErrorCode::WebTransportFlowControlError,
260 H3ErrorCode::WebTransportAlpnError,
261 H3ErrorCode::WebTransportRequirementsNotMet,
262 H3ErrorCode::QpackDecompressionFailed,
263 H3ErrorCode::QpackEncoderStreamError,
264 H3ErrorCode::QpackDecoderStreamError,
265 ] {
266 let wire: u64 = code.into();
267 let decoded = H3ErrorCode::from(wire);
268 assert_eq!(decoded, code, "roundtrip failed for {code:?}");
269 }
270 }
271
272 #[test]
273 fn no_error_encodes_as_grease() {
274 for _ in 0..100 {
275 let wire: u64 = H3ErrorCode::NoError.into();
276 assert_ne!(wire, 0x0100, "should emit GREASE, not literal NoError");
277 assert_eq!(
278 (wire - 0x21) % 0x1f,
279 0,
280 "{wire:#x} is not a valid GREASE value"
281 );
282 }
283 }
284
285 #[test]
286 fn grease_decodes_as_no_error() {
287 for n in [0u64, 1, 100, 0xFFFF] {
288 let grease = 0x1f * n + 0x21;
289 assert_eq!(H3ErrorCode::from(grease), H3ErrorCode::NoError);
290 }
291 }
292
293 #[test]
294 fn unknown_non_grease_decodes_as_no_error() {
295 assert_eq!(H3ErrorCode::from(0xDEAD), H3ErrorCode::NoError);
296 assert_eq!(H3ErrorCode::from(0), H3ErrorCode::NoError);
297 assert_eq!(H3ErrorCode::from(u64::MAX), H3ErrorCode::NoError);
298 }
299}