Skip to main content

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