1use std::borrow::Cow;
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
8pub enum H3ErrorCode {
9 #[error("No error. Used when closing without an error to signal.")]
11 NoError = 0x0100,
12
13 #[error("Peer violated protocol requirements.")]
15 GeneralProtocolError = 0x0101,
16
17 #[error("An internal error in the HTTP stack.")]
19 InternalError = 0x0102,
20
21 #[error("Peer created a stream that will not be accepted.")]
23 StreamCreationError = 0x0103,
24
25 #[error("A required stream was closed or reset.")]
27 ClosedCriticalStream = 0x0104,
28
29 #[error("A frame was not permitted in the current state or stream.")]
31 FrameUnexpected = 0x0105,
32
33 #[error("A frame fails layout requirements or has an invalid size.")]
35 FrameError = 0x0106,
36
37 #[error("Peer is generating excessive load.")]
39 ExcessiveLoad = 0x0107,
40
41 #[error("A stream ID or push ID was used incorrectly.")]
43 IdError = 0x0108,
44
45 #[error("Error in the payload of a SETTINGS frame.")]
47 SettingsError = 0x0109,
48
49 #[error("No SETTINGS frame at the beginning of the control stream.")]
51 MissingSettings = 0x010a,
52
53 #[error("Server rejected a request without application processing.")]
55 RequestRejected = 0x010b,
56
57 #[error("Request or response (including pushed) is cancelled.")]
59 RequestCancelled = 0x010c,
60
61 #[error("Client stream terminated without a fully formed request.")]
63 RequestIncomplete = 0x010d,
64
65 #[error("HTTP message was malformed.")]
67 MessageError = 0x010e,
68
69 #[error("TCP connection for CONNECT was reset or abnormally closed.")]
71 ConnectError = 0x010f,
72
73 #[error("Requested operation cannot be served over HTTP/3.")]
75 VersionFallback = 0x0110,
76
77 #[error("WebTransport data stream rejected due to lack of associated session.")]
80 WebTransportBufferedStreamRejected = 0x3994_bd84,
81
82 #[error("WebTransport session gone.")]
84 WebTransportSessionGone = 0x170d_7b68,
85
86 #[error("WebTransport flow control error.")]
88 WebTransportFlowControlError = 0x045d_4487,
89
90 #[error("WebTransport ALPN error.")]
92 WebTransportAlpnError = 0x0817_b3dd,
93
94 #[error("WebTransport requirements not met.")]
96 WebTransportRequirementsNotMet = 0x212c_0d48,
97}
98
99impl H3ErrorCode {
100 pub fn reason(&self) -> Cow<'static, str> {
102 Cow::Owned(format!("{self}"))
104 }
105}
106
107impl From<u64> for H3ErrorCode {
108 fn from(value: u64) -> Self {
111 match value {
112 0x0101 => Self::GeneralProtocolError,
113 0x0102 => Self::InternalError,
114 0x0103 => Self::StreamCreationError,
115 0x0104 => Self::ClosedCriticalStream,
116 0x0105 => Self::FrameUnexpected,
117 0x0106 => Self::FrameError,
118 0x0107 => Self::ExcessiveLoad,
119 0x0108 => Self::IdError,
120 0x0109 => Self::SettingsError,
121 0x010a => Self::MissingSettings,
122 0x010b => Self::RequestRejected,
123 0x010c => Self::RequestCancelled,
124 0x010d => Self::RequestIncomplete,
125 0x010e => Self::MessageError,
126 0x010f => Self::ConnectError,
127 0x0110 => Self::VersionFallback,
128 0x3994_bd84 => Self::WebTransportBufferedStreamRejected,
129 0x170d_7b68 => Self::WebTransportSessionGone,
130 0x045d_4487 => Self::WebTransportFlowControlError,
131 0x0817_b3dd => Self::WebTransportAlpnError,
132 0x212c_0d48 => Self::WebTransportRequirementsNotMet,
133 _ => Self::NoError,
134 }
135 }
136}
137
138impl From<H3ErrorCode> for u64 {
139 fn from(code: H3ErrorCode) -> u64 {
143 match code {
144 H3ErrorCode::NoError => {
145 let n = u64::from(fastrand::u16(..));
146 0x1f * n + 0x21
147 }
148 other => other as u64,
149 }
150 }
151}
152
153#[cfg(test)]
154mod tests {
155 use super::*;
156
157 #[test]
158 fn known_codes_roundtrip() {
159 for code in [
160 H3ErrorCode::GeneralProtocolError,
161 H3ErrorCode::InternalError,
162 H3ErrorCode::StreamCreationError,
163 H3ErrorCode::ClosedCriticalStream,
164 H3ErrorCode::FrameUnexpected,
165 H3ErrorCode::FrameError,
166 H3ErrorCode::ExcessiveLoad,
167 H3ErrorCode::IdError,
168 H3ErrorCode::SettingsError,
169 H3ErrorCode::MissingSettings,
170 H3ErrorCode::RequestRejected,
171 H3ErrorCode::RequestCancelled,
172 H3ErrorCode::RequestIncomplete,
173 H3ErrorCode::MessageError,
174 H3ErrorCode::ConnectError,
175 H3ErrorCode::VersionFallback,
176 H3ErrorCode::WebTransportBufferedStreamRejected,
177 H3ErrorCode::WebTransportSessionGone,
178 H3ErrorCode::WebTransportFlowControlError,
179 H3ErrorCode::WebTransportAlpnError,
180 H3ErrorCode::WebTransportRequirementsNotMet,
181 ] {
182 let wire: u64 = code.into();
183 let decoded = H3ErrorCode::from(wire);
184 assert_eq!(decoded, code, "roundtrip failed for {code:?}");
185 }
186 }
187
188 #[test]
189 fn no_error_encodes_as_grease() {
190 for _ in 0..100 {
191 let wire: u64 = H3ErrorCode::NoError.into();
192 assert_ne!(wire, 0x0100, "should emit GREASE, not literal NoError");
193 assert_eq!(
194 (wire - 0x21) % 0x1f,
195 0,
196 "{wire:#x} is not a valid GREASE value"
197 );
198 }
199 }
200
201 #[test]
202 fn grease_decodes_as_no_error() {
203 for n in [0u64, 1, 100, 0xFFFF] {
204 let grease = 0x1f * n + 0x21;
205 assert_eq!(H3ErrorCode::from(grease), H3ErrorCode::NoError);
206 }
207 }
208
209 #[test]
210 fn unknown_non_grease_decodes_as_no_error() {
211 assert_eq!(H3ErrorCode::from(0xDEAD), H3ErrorCode::NoError);
212 assert_eq!(H3ErrorCode::from(0), H3ErrorCode::NoError);
213 assert_eq!(H3ErrorCode::from(u64::MAX), H3ErrorCode::NoError);
214 }
215}