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