Skip to main content

webtrans_quinn/
error.rs

1use std::sync::Arc;
2
3use thiserror::Error;
4
5use crate::{ConnectError, SettingsError};
6
7/// Error returned when connecting to a WebTransport endpoint.
8#[derive(Error, Debug, Clone)]
9pub enum ClientError {
10    /// Incoming bytes ended before the handshake exchange completed.
11    #[error("unexpected end of stream")]
12    UnexpectedEnd,
13
14    /// QUIC connection-level failure.
15    #[error("connection error: {0}")]
16    Connection(#[from] quinn::ConnectionError),
17
18    /// Failed to write handshake data.
19    #[error("failed to write: {0}")]
20    WriteError(#[from] quinn::WriteError),
21
22    /// Failed to read handshake data.
23    #[error("failed to read: {0}")]
24    ReadError(#[from] quinn::ReadError),
25
26    /// HTTP/3 SETTINGS negotiation failed.
27    #[error("failed to exchange h3 settings: {0}")]
28    SettingsError(#[from] SettingsError),
29
30    /// HTTP/3 CONNECT negotiation failed.
31    #[error("failed to exchange h3 connect: {0}")]
32    HttpError(#[from] ConnectError),
33
34    /// Quinn connect attempt failed before a connection was established.
35    #[error("quic error: {0}")]
36    QuinnError(#[from] quinn::ConnectError),
37
38    /// URL host component could not be converted to a DNS name.
39    #[error("invalid DNS name: {0}")]
40    InvalidDnsName(String),
41
42    /// URL was invalid for WebTransport usage.
43    #[error("invalid url: {0}")]
44    InvalidUrl(String),
45
46    /// DNS resolution exceeded the configured timeout.
47    #[error("DNS resolution timed out")]
48    DnsTimeout,
49
50    /// QUIC and HTTP/3 session establishment exceeded the configured timeout.
51    #[error("connection handshake timed out")]
52    HandshakeTimeout,
53
54    /// Local UDP endpoint creation failed.
55    #[error("io error: {0}")]
56    Io(Arc<std::io::Error>),
57
58    /// TLS configuration did not provide a QUIC-compatible initial cipher suite.
59    #[error("TLS configuration has no QUIC-compatible initial cipher suite")]
60    InvalidCryptoConfiguration,
61
62    #[cfg(any(feature = "ring", feature = "aws-lc-rs"))]
63    /// Rustls-level TLS configuration or handshake error.
64    #[error("rustls error: {0}")]
65    Rustls(#[from] rustls::Error),
66}
67
68/// Errors returned by [`crate::Session`], grouped by QUIC or WebTransport origin.
69#[derive(Clone, Error, Debug)]
70pub enum SessionError {
71    /// Generic QUIC connection failure.
72    #[error("connection error: {0}")]
73    ConnectionError(quinn::ConnectionError),
74
75    /// WebTransport semantic error mapped from connection context.
76    #[error("webtransport error: {0}")]
77    WebTransportError(#[from] WebTransportError),
78
79    /// Failed to send a datagram over the active connection.
80    #[error("send datagram error: {0}")]
81    SendDatagramError(#[from] quinn::SendDatagramError),
82}
83
84impl From<quinn::ConnectionError> for SessionError {
85    fn from(e: quinn::ConnectionError) -> Self {
86        match &e {
87            quinn::ConnectionError::ApplicationClosed(close) => {
88                match webtrans_proto::error_from_http3(close.error_code.into_inner()) {
89                    Some(code) => WebTransportError::Closed(
90                        code,
91                        String::from_utf8_lossy(&close.reason).into_owned(),
92                    )
93                    .into(),
94                    None => SessionError::ConnectionError(e),
95                }
96            }
97            _ => SessionError::ConnectionError(e),
98        }
99    }
100}
101
102/// Error that can occur when reading or writing the WebTransport stream header.
103#[derive(Clone, Error, Debug)]
104pub enum WebTransportError {
105    /// Session was closed with an application code and reason.
106    #[error("closed: code={0} reason={1}")]
107    Closed(u32, String),
108
109    /// Stream/session header did not match any known session.
110    #[error("unknown session")]
111    UnknownSession,
112
113    /// Failed to read stream/session preface data.
114    #[error("read error: {0}")]
115    ReadError(#[from] quinn::ReadExactError),
116
117    /// Failed to write stream/session preface data.
118    #[error("write error: {0}")]
119    WriteError(#[from] quinn::WriteError),
120}
121
122/// Error when writing to [`crate::SendStream`], similar to [`quinn::WriteError`].
123#[derive(Clone, Error, Debug)]
124pub enum WriteError {
125    /// Peer sent STOP_SENDING with the provided WebTransport code.
126    #[error("STOP_SENDING: {0}")]
127    Stopped(u32),
128
129    /// STOP_SENDING carried a non-WebTransport error code.
130    #[error("invalid STOP_SENDING: {0}")]
131    InvalidStopped(quinn::VarInt),
132
133    /// Stream write failed because the parent session failed.
134    #[error("session error: {0}")]
135    SessionError(#[from] SessionError),
136
137    /// Stream was already closed.
138    #[error("stream closed")]
139    ClosedStream,
140}
141
142impl From<quinn::WriteError> for WriteError {
143    fn from(e: quinn::WriteError) -> Self {
144        match e {
145            quinn::WriteError::Stopped(code) => {
146                match webtrans_proto::error_from_http3(code.into_inner()) {
147                    Some(code) => WriteError::Stopped(code),
148                    None => WriteError::InvalidStopped(code),
149                }
150            }
151            quinn::WriteError::ClosedStream => WriteError::ClosedStream,
152            quinn::WriteError::ConnectionLost(e) => WriteError::SessionError(e.into()),
153            quinn::WriteError::ZeroRttRejected => unreachable!("0-RTT not supported"),
154        }
155    }
156}
157
158/// Error when reading from [`crate::RecvStream`], similar to [`quinn::ReadError`].
159#[derive(Clone, Error, Debug)]
160pub enum ReadError {
161    /// Stream read failed because the parent session failed.
162    #[error("session error: {0}")]
163    SessionError(#[from] SessionError),
164
165    /// Peer reset the stream with the provided WebTransport code.
166    #[error("RESET_STREAM: {0}")]
167    Reset(u32),
168
169    /// RESET_STREAM carried a non-WebTransport error code.
170    #[error("invalid RESET_STREAM: {0}")]
171    InvalidReset(quinn::VarInt),
172
173    /// Stream was already closed.
174    #[error("stream already closed")]
175    ClosedStream,
176
177    /// Ordered read API was used on an unordered stream.
178    #[error("ordered read on unordered stream")]
179    IllegalOrderedRead,
180}
181
182impl From<quinn::ReadError> for ReadError {
183    fn from(value: quinn::ReadError) -> Self {
184        match value {
185            quinn::ReadError::Reset(code) => {
186                match webtrans_proto::error_from_http3(code.into_inner()) {
187                    Some(code) => ReadError::Reset(code),
188                    None => ReadError::InvalidReset(code),
189                }
190            }
191            quinn::ReadError::ConnectionLost(e) => ReadError::SessionError(e.into()),
192            quinn::ReadError::IllegalOrderedRead => ReadError::IllegalOrderedRead,
193            quinn::ReadError::ClosedStream => ReadError::ClosedStream,
194            quinn::ReadError::ZeroRttRejected => unreachable!("0-RTT not supported"),
195        }
196    }
197}
198
199/// Error returned by [`crate::RecvStream::read_exact`], similar to [`quinn::ReadExactError`].
200#[derive(Clone, Error, Debug)]
201pub enum ReadExactError {
202    /// Stream ended before the requested number of bytes was read.
203    #[error("finished early")]
204    FinishedEarly(usize),
205
206    /// Underlying read operation failed.
207    #[error("read error: {0}")]
208    ReadError(#[from] ReadError),
209}
210
211impl From<quinn::ReadExactError> for ReadExactError {
212    fn from(e: quinn::ReadExactError) -> Self {
213        match e {
214            quinn::ReadExactError::FinishedEarly(size) => ReadExactError::FinishedEarly(size),
215            quinn::ReadExactError::ReadError(e) => ReadExactError::ReadError(e.into()),
216        }
217    }
218}
219
220/// Error returned by [`crate::RecvStream::read_to_end`], similar to [`quinn::ReadToEndError`].
221#[derive(Clone, Error, Debug)]
222pub enum ReadToEndError {
223    /// Read exceeded the caller-provided limit.
224    #[error("too long")]
225    TooLong,
226
227    /// Underlying read operation failed.
228    #[error("read error: {0}")]
229    ReadError(#[from] ReadError),
230}
231
232impl From<quinn::ReadToEndError> for ReadToEndError {
233    fn from(e: quinn::ReadToEndError) -> Self {
234        match e {
235            quinn::ReadToEndError::TooLong => ReadToEndError::TooLong,
236            quinn::ReadToEndError::Read(e) => ReadToEndError::ReadError(e.into()),
237        }
238    }
239}
240
241/// Error indicating the stream was already closed.
242#[derive(Clone, Error, Debug)]
243#[error("stream closed")]
244pub struct ClosedStream;
245
246impl From<quinn::ClosedStream> for ClosedStream {
247    fn from(_: quinn::ClosedStream) -> Self {
248        ClosedStream
249    }
250}
251
252/// Error returned when receiving a new WebTransport session.
253#[derive(Error, Debug, Clone)]
254pub enum ServerError {
255    /// A request no longer owns the handshake state needed to complete it.
256    #[error("WebTransport request was already completed")]
257    RequestAlreadyCompleted,
258
259    /// Incoming bytes ended before the handshake exchange completed.
260    #[error("unexpected end of stream")]
261    UnexpectedEnd,
262
263    /// QUIC connection-level failure.
264    #[error("connection error")]
265    Connection(#[from] quinn::ConnectionError),
266
267    /// QUIC and HTTP/3 session establishment exceeded the configured timeout.
268    #[error("connection handshake timed out")]
269    HandshakeTimeout,
270
271    /// Failed to write handshake data.
272    #[error("failed to write")]
273    WriteError(#[from] quinn::WriteError),
274
275    /// Failed to read handshake data.
276    #[error("failed to read")]
277    ReadError(#[from] quinn::ReadError),
278
279    /// HTTP/3 SETTINGS negotiation failed.
280    #[error("failed to exchange h3 settings")]
281    SettingsError(#[from] SettingsError),
282
283    /// HTTP/3 CONNECT negotiation failed.
284    #[error("failed to exchange h3 connect")]
285    ConnectError(#[from] ConnectError),
286
287    /// Generic I/O failure during server setup or handshake.
288    #[error("io error: {0}")]
289    IoError(Arc<std::io::Error>),
290
291    /// TLS configuration did not provide a QUIC-compatible initial cipher suite.
292    #[error("TLS configuration has no QUIC-compatible initial cipher suite")]
293    InvalidCryptoConfiguration,
294
295    #[cfg(any(feature = "ring", feature = "aws-lc-rs"))]
296    /// Rustls-level TLS configuration or handshake error.
297    #[error("rustls error: {0}")]
298    Rustls(#[from] rustls::Error),
299}
300
301// #[derive(Clone, Error, Debug)]
302// pub enum SendDatagramError {
303//     #[error("peer does not support datagrams")]
304//     UnsupportedPeer,
305//
306//     #[error("peer has disabled datagram support")]
307//     DatagramSupportDisabled,
308//
309//     #[error("datagram too large")]
310//     TooLarge,
311//
312//     #[error("session error: {0}")]
313//     SessionError(#[from] SessionError),
314// }
315//
316// impl From<quinn::SendDatagramError> for SendDatagramError {
317//     fn from(value: quinn::SendDatagramError) -> Self {
318//         match value {
319//             quinn::SendDatagramError::UnsupportedByPeer => SendDatagramError::UnsupportedPeer,
320//             quinn::SendDatagramError::Disabled => SendDatagramError::DatagramSupportDisabled,
321//             quinn::SendDatagramError::TooLarge => SendDatagramError::TooLarge,
322//             quinn::SendDatagramError::ConnectionLost(e) => SendDatagramError::SessionError(e.into()),
323//         }
324//     }
325// }
326
327impl webtrans_trait::Error for SessionError {
328    fn session_error(&self) -> Option<(u32, String)> {
329        if let SessionError::WebTransportError(WebTransportError::Closed(code, reason)) = self {
330            return Some((*code, reason.to_string()));
331        }
332
333        None
334    }
335}
336
337impl webtrans_trait::Error for WriteError {
338    fn session_error(&self) -> Option<(u32, String)> {
339        if let WriteError::SessionError(e) = self {
340            return e.session_error();
341        }
342
343        None
344    }
345
346    fn stream_error(&self) -> Option<u32> {
347        match self {
348            WriteError::Stopped(code) => Some(*code),
349            _ => None,
350        }
351    }
352}
353
354impl webtrans_trait::Error for ReadError {
355    fn session_error(&self) -> Option<(u32, String)> {
356        if let ReadError::SessionError(e) = self {
357            return e.session_error();
358        }
359
360        None
361    }
362
363    fn stream_error(&self) -> Option<u32> {
364        match self {
365            ReadError::Reset(code) => Some(*code),
366            _ => None,
367        }
368    }
369}