wtransport/
error.rs

1use crate::driver::utils::varint_q2w;
2use crate::driver::DriverError;
3use crate::VarInt;
4use std::borrow::Cow;
5use std::fmt::Display;
6use std::net::SocketAddr;
7use wtransport_proto::error::ErrorCode;
8
9/// An enumeration representing various errors that can occur during a WebTransport connection.
10#[derive(thiserror::Error, Debug, Clone, Eq, PartialEq)]
11pub enum ConnectionError {
12    /// The connection was aborted by the peer (protocol level).
13    #[error("connection aborted by peer: {0}")]
14    ConnectionClosed(ConnectionClose),
15
16    /// The connection was closed by the peer (application level).
17    #[error("connection closed by peer: {0}")]
18    ApplicationClosed(ApplicationClose),
19
20    /// The connection was locally closed.
21    #[error("connection locally closed")]
22    LocallyClosed,
23
24    /// The connection was locally closed because an HTTP3 protocol violation.
25    #[error("connection locally aborted: {0}")]
26    LocalH3Error(H3Error),
27
28    /// The connection timed out.
29    #[error("connection timed out")]
30    TimedOut,
31
32    /// The connection was closed because a QUIC protocol error.
33    #[error("QUIC protocol error: {0}")]
34    QuicProto(QuicProtoError),
35
36    /// The connection could not be created because not enough of the CID space is available
37    ///
38    /// Try using longer connection IDs.
39    #[error("CIDs exhausted")]
40    CidsExhausted,
41}
42
43impl ConnectionError {
44    pub(crate) fn with_driver_error(
45        driver_error: DriverError,
46        quic_connection: &quinn::Connection,
47    ) -> Self {
48        match driver_error {
49            DriverError::Proto(error_code) => Self::local_h3_error(error_code),
50            DriverError::ApplicationClosed(close) => Self::ApplicationClosed(close),
51            DriverError::NotConnected => Self::no_connect(quic_connection),
52        }
53    }
54
55    pub(crate) fn no_connect(quic_connection: &quinn::Connection) -> Self {
56        quic_connection
57            .close_reason()
58            .expect("QUIC connection is still alive on close-cast")
59            .into()
60    }
61
62    pub(crate) fn local_h3_error(error_code: ErrorCode) -> Self {
63        ConnectionError::LocalH3Error(H3Error { code: error_code })
64    }
65}
66
67/// An enumeration representing various errors that can occur during a WebTransport client connecting.
68#[derive(thiserror::Error, Debug)]
69pub enum ConnectingError {
70    /// URL provided for connection is not valid.
71    #[error("invalid URL: {0}")]
72    InvalidUrl(String),
73
74    /// Failure during DNS resolution.
75    #[error("cannot resolve domain: {0}")]
76    DnsLookup(std::io::Error),
77
78    /// Cannot find any DNS.
79    #[error("cannot resolve domain")]
80    DnsNotFound,
81
82    /// Connection error during handshaking.
83    #[error(transparent)]
84    ConnectionError(ConnectionError),
85
86    /// Request rejected.
87    #[error("server rejected WebTransport session request")]
88    SessionRejected,
89
90    /// Cannot use reserved key for additional headers.
91    #[error("additional header '{0}' is reserved")]
92    ReservedHeader(String),
93
94    /// The endpoint can no longer create new connections
95    ///
96    /// Indicates that a necessary component of the endpoint has been dropped or otherwise disabled.
97    #[error("endpoint stopping")]
98    EndpointStopping,
99
100    /// The connection could not be created because not enough of the CID space is available
101    ///
102    /// Try using longer connection IDs
103    #[error("CIDs exhausted")]
104    CidsExhausted,
105
106    /// The server name supplied was malformed
107    #[error("invalid server name: {0}")]
108    InvalidServerName(String),
109
110    /// The remote [`SocketAddr`] supplied was malformed.
111    ///
112    /// Examples include attempting to connect to port 0, or using an inappropriate address family.
113    #[error("invalid remote address: {0}")]
114    InvalidRemoteAddress(SocketAddr),
115}
116
117impl ConnectingError {
118    pub(crate) fn with_no_connection(quic_connection: &quinn::Connection) -> Self {
119        ConnectingError::ConnectionError(
120            quic_connection
121                .close_reason()
122                .expect("QUIC connection is still alive on close-cast")
123                .into(),
124        )
125    }
126
127    pub(crate) fn with_connect_error(error: quinn::ConnectError) -> Self {
128        match error {
129            quinn::ConnectError::EndpointStopping => ConnectingError::EndpointStopping,
130            quinn::ConnectError::CidsExhausted => ConnectingError::CidsExhausted,
131            quinn::ConnectError::InvalidServerName(name) => {
132                ConnectingError::InvalidServerName(name)
133            }
134            quinn::ConnectError::InvalidRemoteAddress(socket_addr) => {
135                ConnectingError::InvalidRemoteAddress(socket_addr)
136            }
137            quinn::ConnectError::NoDefaultClientConfig => {
138                unreachable!("quic client config is internally provided")
139            }
140            quinn::ConnectError::UnsupportedVersion => {
141                unreachable!("quic version is internally configured")
142            }
143        }
144    }
145}
146
147/// Error indicating that a stream has been already finished or reset.
148#[derive(thiserror::Error, Debug)]
149#[error("closed stream")]
150pub struct ClosedStream;
151
152/// An error that arise from writing to a stream.
153#[derive(thiserror::Error, Debug, Clone, Eq, PartialEq)]
154pub enum StreamWriteError {
155    /// Connection has been dropped.
156    #[error("not connected")]
157    NotConnected,
158
159    /// The stream was already finished or reset locally.
160    #[error("stream closed")]
161    Closed,
162
163    /// The peer is no longer accepting data on this stream.
164    #[error("stream stopped (code: {0})")]
165    Stopped(VarInt),
166
167    /// QUIC protocol error.
168    #[error("QUIC protocol error")]
169    QuicProto,
170}
171
172/// An error that arise from reading from a stream.
173#[derive(thiserror::Error, Debug, Clone, Eq, PartialEq)]
174pub enum StreamReadError {
175    /// Connection has been dropped.
176    #[error("not connected")]
177    NotConnected,
178
179    /// The peer abandoned transmitting data on this stream
180    #[error("stream reset (code: {0})")]
181    Reset(VarInt),
182
183    /// QUIC protocol error.
184    #[error("QUIC protocol error")]
185    QuicProto,
186}
187
188/// An error that arise from reading from a stream.
189#[derive(thiserror::Error, Debug, Clone)]
190pub enum StreamReadExactError {
191    /// The stream finished before all bytes were read.
192    #[error("stream finished too early ({0} bytes read)")]
193    FinishedEarly(usize),
194
195    /// A read error occurred.
196    #[error(transparent)]
197    Read(StreamReadError),
198}
199
200/// An error that arise from sending a datagram.
201#[derive(thiserror::Error, Debug, Clone, Eq, PartialEq)]
202pub enum SendDatagramError {
203    /// Connection has been dropped.
204    #[error("not connected")]
205    NotConnected,
206
207    /// The peer does not support receiving datagram frames.
208    #[error("peer does not support datagrams")]
209    UnsupportedByPeer,
210
211    /// The datagram is larger than the connection can currently accommodate.
212    #[error("datagram payload too large")]
213    TooLarge,
214}
215
216/// An error that arise when opening a new stream.
217#[derive(thiserror::Error, Debug, Clone, Eq, PartialEq)]
218pub enum StreamOpeningError {
219    /// Connection has been dropped.
220    #[error("not connected")]
221    NotConnected,
222
223    /// The peer refused the stream, stopping it during initialization.
224    #[error("opening stream refused")]
225    Refused,
226}
227
228/// Reason given by an application for closing the connection
229#[derive(Debug, Clone, Eq, PartialEq)]
230pub struct ApplicationClose {
231    code: VarInt,
232    reason: Box<[u8]>,
233}
234
235impl Display for ApplicationClose {
236    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
237        if self.reason.is_empty() {
238            self.code.fmt(f)?;
239        } else {
240            f.write_str(&String::from_utf8_lossy(&self.reason))?;
241            f.write_str(" (code ")?;
242            self.code.fmt(f)?;
243            f.write_str(")")?;
244        }
245        Ok(())
246    }
247}
248
249impl ApplicationClose {
250    /// Creates a new application close reason.
251    pub(crate) fn new(code: VarInt, reason: Box<[u8]>) -> Self {
252        Self { code, reason }
253    }
254
255    /// Application-specific code for close operation.
256    pub fn code(&self) -> VarInt {
257        self.code
258    }
259
260    /// Data containing the reason for closing operation.
261    pub fn reason(&self) -> &[u8] {
262        &self.reason
263    }
264}
265
266/// Reason given by the transport for closing the connection.
267#[derive(Debug, Clone, PartialEq, Eq)]
268pub struct ConnectionClose(quinn::ConnectionClose);
269
270impl Display for ConnectionClose {
271    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
272        self.0.fmt(f)
273    }
274}
275
276/// A struct representing an error in the HTTP3 layer.
277#[derive(Debug, Clone, PartialEq, Eq)]
278pub struct H3Error {
279    code: ErrorCode,
280}
281
282impl Display for H3Error {
283    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
284        self.code.fmt(f)
285    }
286}
287
288impl From<quinn::ConnectionError> for ConnectionError {
289    fn from(error: quinn::ConnectionError) -> Self {
290        match error {
291            quinn::ConnectionError::VersionMismatch => ConnectionError::QuicProto(QuicProtoError {
292                code: None,
293                reason: Cow::Borrowed("QUIC protocol version mismatched"),
294            }),
295            quinn::ConnectionError::TransportError(e) => {
296                ConnectionError::QuicProto(QuicProtoError {
297                    code: VarInt::try_from_u64(e.code.into()).ok(),
298                    reason: Cow::Owned(e.reason),
299                })
300            }
301            quinn::ConnectionError::ConnectionClosed(close) => {
302                ConnectionError::ConnectionClosed(ConnectionClose(close))
303            }
304            quinn::ConnectionError::ApplicationClosed(close) => {
305                ConnectionError::ApplicationClosed(ApplicationClose {
306                    code: varint_q2w(close.error_code),
307                    reason: close.reason.to_vec().into_boxed_slice(),
308                })
309            }
310            quinn::ConnectionError::Reset => ConnectionError::QuicProto(QuicProtoError {
311                code: None,
312                reason: Cow::Borrowed("connection has been reset"),
313            }),
314            quinn::ConnectionError::TimedOut => ConnectionError::TimedOut,
315            quinn::ConnectionError::LocallyClosed => ConnectionError::LocallyClosed,
316            quinn::ConnectionError::CidsExhausted => ConnectionError::CidsExhausted,
317        }
318    }
319}
320
321/// A complete specification of an error over QUIC protocol.
322#[derive(Debug, Clone, Eq, PartialEq)]
323pub struct QuicProtoError {
324    code: Option<VarInt>,
325    reason: Cow<'static, str>,
326}
327
328impl Display for QuicProtoError {
329    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
330        let code = self
331            .code
332            .map(|code| format!(" (code: {})", code))
333            .unwrap_or_default();
334
335        f.write_fmt(format_args!("{}{}", self.reason, code))
336    }
337}
338
339/// Error returned by [`Connection::export_keying_material`](crate::Connection::export_keying_material).
340///
341/// This error occurs if the requested output length is too large.
342#[derive(Debug, thiserror::Error, Clone, Eq, PartialEq)]
343#[error("cannot derive keying material as requested output length is too large")]
344pub struct ExportKeyingMaterialError;