httproxide_h3/
error.rs

1//! HTTP/3 Error types
2
3use std::{fmt, sync::Arc};
4
5use crate::{frame, proto, qpack, quic};
6
7/// Cause of an error thrown by our own h3 layer
8type Cause = Box<dyn std::error::Error + Send + Sync>;
9/// Error thrown by the underlying QUIC impl
10pub(crate) type TransportError = Box<dyn quic::Error>;
11
12/// A general error that can occur when handling the HTTP/3 protocol.
13#[derive(Clone)]
14pub struct Error {
15    inner: Box<ErrorImpl>,
16}
17
18/// An HTTP/3 "application error code".
19#[derive(PartialEq, Eq, Hash, Clone, Copy)]
20pub struct Code(u64);
21
22impl Code {
23    pub fn value(&self) -> u64 {
24        self.0
25    }
26}
27
28impl PartialEq<u64> for Code {
29    fn eq(&self, other: &u64) -> bool {
30        *other == self.0
31    }
32}
33
34#[derive(Clone)]
35struct ErrorImpl {
36    kind: Kind,
37    cause: Option<Arc<Cause>>,
38}
39
40// Warning: this enum is public only for testing purposes. Do not use it in
41// downstream code or be prepared to refactor as changes happen.
42#[doc(hidden)]
43#[non_exhaustive]
44#[derive(Clone, Debug)]
45pub enum Kind {
46    #[non_exhaustive]
47    Application {
48        code: Code,
49        reason: Option<Box<str>>,
50    },
51    #[non_exhaustive]
52    HeaderTooBig {
53        actual_size: u64,
54        max_size: u64,
55    },
56    // Error from QUIC layer
57    #[non_exhaustive]
58    Transport(Arc<TransportError>),
59    // Connection has been closed with `Code::NO_ERROR`
60    Closed,
61    // Currently in a graceful shutdown procedure
62    Closing,
63    Timeout,
64}
65
66// ===== impl Code =====
67
68macro_rules! codes {
69    (
70        $(
71            $(#[$docs:meta])*
72            ($num:expr, $name:ident);
73        )+
74    ) => {
75        impl Code {
76        $(
77            $(#[$docs])*
78            pub const $name: Code = Code($num);
79        )+
80        }
81
82        impl fmt::Debug for Code {
83            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
84                match self.0 {
85                $(
86                    $num => f.write_str(stringify!($name)),
87                )+
88                    other => write!(f, "{:#x}", other),
89                }
90            }
91        }
92    }
93}
94
95codes! {
96    /// No error. This is used when the connection or stream needs to be
97    /// closed, but there is no error to signal.
98    (0x100, H3_NO_ERROR);
99
100    /// Peer violated protocol requirements in a way that does not match a more
101    /// specific error code, or endpoint declines to use the more specific
102    /// error code.
103    (0x101, H3_GENERAL_PROTOCOL_ERROR);
104
105    /// An internal error has occurred in the HTTP stack.
106    (0x102, H3_INTERNAL_ERROR);
107
108    /// The endpoint detected that its peer created a stream that it will not
109    /// accept.
110    (0x103, H3_STREAM_CREATION_ERROR);
111
112    /// A stream required by the HTTP/3 connection was closed or reset.
113    (0x104, H3_CLOSED_CRITICAL_STREAM);
114
115    /// A frame was received that was not permitted in the current state or on
116    /// the current stream.
117    (0x105, H3_FRAME_UNEXPECTED);
118
119    /// A frame that fails to satisfy layout requirements or with an invalid
120    /// size was received.
121    (0x106, H3_FRAME_ERROR);
122
123    /// The endpoint detected that its peer is exhibiting a behavior that might
124    /// be generating excessive load.
125    (0x107, H3_EXCESSIVE_LOAD);
126
127    /// A Stream ID or Push ID was used incorrectly, such as exceeding a limit,
128    /// reducing a limit, or being reused.
129    (0x108, H3_ID_ERROR);
130
131    /// An endpoint detected an error in the payload of a SETTINGS frame.
132    (0x109, H3_SETTINGS_ERROR);
133
134    /// No SETTINGS frame was received at the beginning of the control stream.
135    (0x10a, H3_MISSING_SETTINGS);
136
137    /// A server rejected a request without performing any application
138    /// processing.
139    (0x10b, H3_REQUEST_REJECTED);
140
141    /// The request or its response (including pushed response) is cancelled.
142    (0x10c, H3_REQUEST_CANCELLED);
143
144    /// The client's stream terminated without containing a fully-formed
145    /// request.
146    (0x10d, H3_REQUEST_INCOMPLETE);
147
148    /// An HTTP message was malformed and cannot be processed.
149    (0x10e, H3_MESSAGE_ERROR);
150
151    /// The TCP connection established in response to a CONNECT request was
152    /// reset or abnormally closed.
153    (0x10f, H3_CONNECT_ERROR);
154
155    /// The requested operation cannot be served over HTTP/3. The peer should
156    /// retry over HTTP/1.1.
157    (0x110, H3_VERSION_FALLBACK);
158
159    /// The decoder failed to interpret an encoded field section and is not
160    /// able to continue decoding that field section.
161    (0x200, QPACK_DECOMPRESSION_FAILED);
162
163    /// The decoder failed to interpret an encoder instruction received on the
164    /// encoder stream.
165    (0x201, QPACK_ENCODER_STREAM_ERROR);
166
167    /// The encoder failed to interpret a decoder instruction received on the
168    /// decoder stream.
169    (0x202, QPACK_DECODER_STREAM_ERROR);
170}
171
172impl Code {
173    pub(crate) fn with_reason<S: Into<Box<str>>>(self, reason: S) -> Error {
174        Error::new(Kind::Application {
175            code: self,
176            reason: Some(reason.into()),
177        })
178    }
179
180    pub(crate) fn with_cause<E: Into<Cause>>(self, cause: E) -> Error {
181        Error::from(self).with_cause(cause)
182    }
183
184    pub(crate) fn with_transport<E: Into<Box<dyn quic::Error>>>(self, err: E) -> Error {
185        Error::new(Kind::Transport(Arc::new(err.into())))
186    }
187}
188
189impl From<Code> for u64 {
190    fn from(code: Code) -> u64 {
191        code.0
192    }
193}
194
195// ===== impl Error =====
196
197impl Error {
198    fn new(kind: Kind) -> Self {
199        Error {
200            inner: Box::new(ErrorImpl { kind, cause: None }),
201        }
202    }
203
204    pub(crate) fn header_too_big(actual_size: u64, max_size: u64) -> Self {
205        Error::new(Kind::HeaderTooBig {
206            actual_size,
207            max_size,
208        })
209    }
210
211    pub(crate) fn with_cause<E: Into<Cause>>(mut self, cause: E) -> Self {
212        self.inner.cause = Some(Arc::new(cause.into()));
213        self
214    }
215
216    pub(crate) fn closing() -> Self {
217        Self::new(Kind::Closing)
218    }
219
220    pub(crate) fn closed() -> Self {
221        Self::new(Kind::Closed)
222    }
223
224    pub(crate) fn is_closed(&self) -> bool {
225        if let Kind::Closed = self.inner.kind {
226            return true;
227        }
228        false
229    }
230
231    pub(crate) fn is_header_too_big(&self) -> bool {
232        matches!(&self.inner.kind, Kind::HeaderTooBig { .. })
233    }
234
235    #[cfg(feature = "test_helpers")]
236    pub fn kind(&self) -> Kind {
237        self.inner.kind.clone()
238    }
239}
240
241impl fmt::Debug for Error {
242    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
243        let mut builder = f.debug_struct("h3::Error");
244
245        match self.inner.kind {
246            Kind::Closed => {
247                builder.field("connection closed", &true);
248            }
249            Kind::Closing => {
250                builder.field("closing", &true);
251            }
252            Kind::Timeout => {
253                builder.field("timeout", &true);
254            }
255            Kind::Application { code, ref reason } => {
256                builder.field("code", &code);
257                if let Some(reason) = reason {
258                    builder.field("reason", reason);
259                }
260            }
261            Kind::Transport(ref e) => {
262                builder.field("kind", &e);
263                builder.field("code: ", &e.err_code());
264            }
265            Kind::HeaderTooBig {
266                actual_size,
267                max_size,
268            } => {
269                builder.field("header_size", &actual_size);
270                builder.field("max_size", &max_size);
271            }
272        }
273
274        if let Some(ref cause) = self.inner.cause {
275            builder.field("cause", cause);
276        }
277
278        builder.finish()
279    }
280}
281
282impl fmt::Display for Error {
283    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
284        match self.inner.kind {
285            Kind::Closed => write!(f, "connection is closed")?,
286            Kind::Closing => write!(f, "connection is gracefully closing")?,
287            Kind::Transport(ref e) => write!(f, "quic transport error: {}", e)?,
288            Kind::Timeout => write!(f, "timeout",)?,
289            Kind::Application { code, ref reason } => {
290                if let Some(reason) = reason {
291                    write!(f, "application error: {}", reason)?
292                } else {
293                    write!(f, "application error {:?}", code)?
294                }
295            }
296            Kind::HeaderTooBig {
297                actual_size,
298                max_size,
299            } => write!(
300                f,
301                "issued header size {} o is beyond peer's limit {} o",
302                actual_size, max_size
303            )?,
304        };
305        if let Some(ref cause) = self.inner.cause {
306            write!(f, "cause: {}", cause)?
307        }
308        Ok(())
309    }
310}
311
312impl std::error::Error for Error {
313    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
314        self.inner.cause.as_ref().map(|e| &***e as _)
315    }
316}
317
318impl From<Code> for Error {
319    fn from(code: Code) -> Error {
320        Error::new(Kind::Application { code, reason: None })
321    }
322}
323
324impl From<qpack::EncoderError> for Error {
325    fn from(e: qpack::EncoderError) -> Self {
326        Self::from(Code::QPACK_ENCODER_STREAM_ERROR).with_cause(e)
327    }
328}
329
330impl From<qpack::DecoderError> for Error {
331    fn from(e: qpack::DecoderError) -> Self {
332        Self::from(Code::QPACK_DECODER_STREAM_ERROR).with_cause(e)
333    }
334}
335
336impl From<proto::headers::Error> for Error {
337    fn from(e: proto::headers::Error) -> Self {
338        Self::from(Code::H3_MESSAGE_ERROR).with_cause(e)
339    }
340}
341
342impl From<frame::Error> for Error {
343    fn from(e: frame::Error) -> Self {
344        match e {
345            frame::Error::Quic(e) => e.into(),
346            frame::Error::UnexpectedEnd => {
347                Code::H3_FRAME_ERROR.with_reason("received incomplete frame")
348            }
349            frame::Error::Proto(e) => match e {
350                proto::frame::Error::InvalidStreamId(_) => Code::H3_ID_ERROR,
351                proto::frame::Error::Settings(_) => Code::H3_SETTINGS_ERROR,
352                proto::frame::Error::UnsupportedFrame(_) | proto::frame::Error::UnknownFrame(_) => {
353                    Code::H3_FRAME_UNEXPECTED
354                }
355                proto::frame::Error::Incomplete(_)
356                | proto::frame::Error::InvalidFrameValue
357                | proto::frame::Error::Malformed => Code::H3_FRAME_ERROR,
358            }
359            .with_cause(e),
360        }
361    }
362}
363
364impl From<Error> for Box<dyn std::error::Error + std::marker::Send> {
365    fn from(e: Error) -> Self {
366        Box::new(e)
367    }
368}
369
370impl<T> From<T> for Error
371where
372    T: Into<TransportError>,
373{
374    fn from(e: T) -> Self {
375        let quic_error: TransportError = e.into();
376        if quic_error.is_timeout() {
377            return Error::new(Kind::Timeout);
378        }
379
380        match quic_error.err_code() {
381            Some(c) if Code::H3_NO_ERROR == c => Error::new(Kind::Closed),
382            Some(c) => Error::new(Kind::Application {
383                code: Code(c),
384                reason: None,
385            }),
386            None => Error::new(Kind::Transport(Arc::new(quic_error))),
387        }
388    }
389}
390
391impl From<proto::stream::InvalidStreamId> for Error {
392    fn from(e: proto::stream::InvalidStreamId) -> Self {
393        Self::from(Code::H3_ID_ERROR).with_cause(format!("{}", e))
394    }
395}
396
397#[cfg(test)]
398mod tests {
399    use super::Error;
400    use std::mem;
401
402    #[test]
403    fn test_size_of() {
404        assert_eq!(mem::size_of::<Error>(), mem::size_of::<usize>());
405    }
406}