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