1use std::borrow::Cow;
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
8#[non_exhaustive]
9pub enum H3ErrorCode {
10 #[error("No error. Used when closing without an error to signal.")]
12 NoError = 0x0100,
13
14 #[error("Peer violated protocol requirements.")]
16 GeneralProtocolError = 0x0101,
17
18 #[error("An internal error in the HTTP stack.")]
20 InternalError = 0x0102,
21
22 #[error("Peer created a stream that will not be accepted.")]
24 StreamCreationError = 0x0103,
25
26 #[error("A required stream was closed or reset.")]
28 ClosedCriticalStream = 0x0104,
29
30 #[error("A frame was not permitted in the current state or stream.")]
32 FrameUnexpected = 0x0105,
33
34 #[error("A frame fails layout requirements or has an invalid size.")]
36 FrameError = 0x0106,
37
38 #[error("Peer is generating excessive load.")]
40 ExcessiveLoad = 0x0107,
41
42 #[error("A stream ID or push ID was used incorrectly.")]
44 IdError = 0x0108,
45
46 #[error("Error in the payload of a SETTINGS frame.")]
48 SettingsError = 0x0109,
49
50 #[error("No SETTINGS frame at the beginning of the control stream.")]
52 MissingSettings = 0x010a,
53
54 #[error("Server rejected a request without application processing.")]
56 RequestRejected = 0x010b,
57
58 #[error("Request or response (including pushed) is cancelled.")]
60 RequestCancelled = 0x010c,
61
62 #[error("Client stream terminated without a fully formed request.")]
64 RequestIncomplete = 0x010d,
65
66 #[error("HTTP message was malformed.")]
68 MessageError = 0x010e,
69
70 #[error("TCP connection for CONNECT was reset or abnormally closed.")]
72 ConnectError = 0x010f,
73
74 #[error("Requested operation cannot be served over HTTP/3.")]
76 VersionFallback = 0x0110,
77
78 #[error("WebTransport data stream rejected due to lack of associated session.")]
81 WebTransportBufferedStreamRejected = 0x3994_bd84,
82
83 #[error("WebTransport session gone.")]
85 WebTransportSessionGone = 0x170d_7b68,
86
87 #[error("WebTransport flow control error.")]
89 WebTransportFlowControlError = 0x045d_4487,
90
91 #[error("WebTransport ALPN error.")]
93 WebTransportAlpnError = 0x0817_b3dd,
94
95 #[error("WebTransport requirements not met.")]
97 WebTransportRequirementsNotMet = 0x212c_0d48,
98
99 #[error("QPACK decompression failed.")]
102 QpackDecompressionFailed = 0x200,
103
104 #[error("QPACK encoder stream error.")]
106 QpackEncoderStreamError = 0x201,
107
108 #[error("QPACK decoder stream error.")]
110 QpackDecoderStreamError = 0x202,
111}
112
113impl H3ErrorCode {
114 pub fn reason(&self) -> Cow<'static, str> {
116 Cow::Owned(format!("{self}"))
118 }
119
120 pub fn is_connection_error(&self) -> bool {
127 matches!(
128 self,
129 Self::GeneralProtocolError
130 | Self::InternalError
131 | Self::ClosedCriticalStream
132 | Self::FrameUnexpected
133 | Self::FrameError
134 | Self::ExcessiveLoad
135 | Self::IdError
136 | Self::SettingsError
137 | Self::MissingSettings
138 | Self::QpackDecompressionFailed
139 | Self::QpackEncoderStreamError
140 | Self::QpackDecoderStreamError
141 )
142 }
143}
144
145impl From<u64> for H3ErrorCode {
146 fn from(value: u64) -> Self {
149 match value {
150 0x0101 => Self::GeneralProtocolError,
151 0x0102 => Self::InternalError,
152 0x0103 => Self::StreamCreationError,
153 0x0104 => Self::ClosedCriticalStream,
154 0x0105 => Self::FrameUnexpected,
155 0x0106 => Self::FrameError,
156 0x0107 => Self::ExcessiveLoad,
157 0x0108 => Self::IdError,
158 0x0109 => Self::SettingsError,
159 0x010a => Self::MissingSettings,
160 0x010b => Self::RequestRejected,
161 0x010c => Self::RequestCancelled,
162 0x010d => Self::RequestIncomplete,
163 0x010e => Self::MessageError,
164 0x010f => Self::ConnectError,
165 0x0110 => Self::VersionFallback,
166 0x3994_bd84 => Self::WebTransportBufferedStreamRejected,
167 0x170d_7b68 => Self::WebTransportSessionGone,
168 0x045d_4487 => Self::WebTransportFlowControlError,
169 0x0817_b3dd => Self::WebTransportAlpnError,
170 0x212c_0d48 => Self::WebTransportRequirementsNotMet,
171 0x200 => Self::QpackDecompressionFailed,
172 0x201 => Self::QpackEncoderStreamError,
173 0x202 => Self::QpackDecoderStreamError,
174 _ => Self::NoError,
175 }
176 }
177}
178
179impl From<H3ErrorCode> for u64 {
180 fn from(code: H3ErrorCode) -> u64 {
184 match code {
185 H3ErrorCode::NoError => {
186 let n = u64::from(fastrand::u16(..));
187 0x1f * n + 0x21
188 }
189 other => other as u64,
190 }
191 }
192}
193
194#[cfg(test)]
195mod tests {
196 use super::*;
197
198 #[test]
199 fn known_codes_roundtrip() {
200 for code in [
201 H3ErrorCode::GeneralProtocolError,
202 H3ErrorCode::InternalError,
203 H3ErrorCode::StreamCreationError,
204 H3ErrorCode::ClosedCriticalStream,
205 H3ErrorCode::FrameUnexpected,
206 H3ErrorCode::FrameError,
207 H3ErrorCode::ExcessiveLoad,
208 H3ErrorCode::IdError,
209 H3ErrorCode::SettingsError,
210 H3ErrorCode::MissingSettings,
211 H3ErrorCode::RequestRejected,
212 H3ErrorCode::RequestCancelled,
213 H3ErrorCode::RequestIncomplete,
214 H3ErrorCode::MessageError,
215 H3ErrorCode::ConnectError,
216 H3ErrorCode::VersionFallback,
217 H3ErrorCode::WebTransportBufferedStreamRejected,
218 H3ErrorCode::WebTransportSessionGone,
219 H3ErrorCode::WebTransportFlowControlError,
220 H3ErrorCode::WebTransportAlpnError,
221 H3ErrorCode::WebTransportRequirementsNotMet,
222 H3ErrorCode::QpackDecompressionFailed,
223 H3ErrorCode::QpackEncoderStreamError,
224 H3ErrorCode::QpackDecoderStreamError,
225 ] {
226 let wire: u64 = code.into();
227 let decoded = H3ErrorCode::from(wire);
228 assert_eq!(decoded, code, "roundtrip failed for {code:?}");
229 }
230 }
231
232 #[test]
233 fn no_error_encodes_as_grease() {
234 for _ in 0..100 {
235 let wire: u64 = H3ErrorCode::NoError.into();
236 assert_ne!(wire, 0x0100, "should emit GREASE, not literal NoError");
237 assert_eq!(
238 (wire - 0x21) % 0x1f,
239 0,
240 "{wire:#x} is not a valid GREASE value"
241 );
242 }
243 }
244
245 #[test]
246 fn grease_decodes_as_no_error() {
247 for n in [0u64, 1, 100, 0xFFFF] {
248 let grease = 0x1f * n + 0x21;
249 assert_eq!(H3ErrorCode::from(grease), H3ErrorCode::NoError);
250 }
251 }
252
253 #[test]
254 fn unknown_non_grease_decodes_as_no_error() {
255 assert_eq!(H3ErrorCode::from(0xDEAD), H3ErrorCode::NoError);
256 assert_eq!(H3ErrorCode::from(0), H3ErrorCode::NoError);
257 assert_eq!(H3ErrorCode::from(u64::MAX), H3ErrorCode::NoError);
258 }
259}