Skip to main content

web_transport/
quinn.rs

1use bytes::{Buf, BufMut, Bytes};
2use url::Url;
3
4// Export the Quinn implementation to simplify Cargo.toml
5pub use web_transport_quinn as quinn;
6
7pub use web_transport_quinn::CongestionControl;
8
9/// Create a [Client] that can be used to dial multiple [Session]s.
10#[derive(Default, Clone)]
11pub struct ClientBuilder {
12    inner: quinn::ClientBuilder,
13    protocols: Vec<String>,
14}
15
16impl ClientBuilder {
17    pub fn new() -> Self {
18        Self::default()
19    }
20
21    /// Allow a lower latency congestion controller.
22    pub fn with_congestion_control(self, cc: CongestionControl) -> Self {
23        Self {
24            inner: self.inner.with_congestion_control(cc),
25            ..self
26        }
27    }
28
29    /// Advertise the application protocols (subprotocols) offered for negotiation.
30    ///
31    /// The server selects one of these, available afterwards via [`Session::protocol`].
32    pub fn with_protocols<I, S>(mut self, protocols: I) -> Self
33    where
34        I: IntoIterator<Item = S>,
35        S: AsRef<str>,
36    {
37        self.protocols = protocols
38            .into_iter()
39            .map(|p| p.as_ref().to_string())
40            .collect();
41        self
42    }
43
44    /// Accept the server's certificate hashes (sha256) instead of using a root CA.
45    pub fn with_server_certificate_hashes(self, hashes: Vec<Vec<u8>>) -> Result<Client, Error> {
46        Ok(Client {
47            inner: self.inner.with_server_certificate_hashes(hashes)?,
48            protocols: self.protocols,
49        })
50    }
51
52    /// Accept certificates using root CAs.
53    pub fn with_system_roots(self) -> Result<Client, Error> {
54        Ok(Client {
55            inner: self.inner.with_system_roots()?,
56            protocols: self.protocols,
57        })
58    }
59}
60
61/// Used to dial multiple [Session]s.
62#[derive(Clone, Debug)]
63pub struct Client {
64    inner: quinn::Client,
65    protocols: Vec<String>,
66}
67
68impl Client {
69    /// Connect to the server.
70    pub async fn connect(&self, url: Url) -> Result<Session, Error> {
71        let request =
72            quinn::proto::ConnectRequest::new(url).with_protocols(self.protocols.iter().cloned());
73        Ok(self.inner.connect(request).await?.into())
74    }
75}
76
77/// Used to accept incoming connections and create [Session]s. (native only)
78///
79/// NOTE: This is not supported in the WASM runtime, as browsers are clients.
80///
81/// Use a [web_transport_quinn::ServerBuilder] to create a [web_transport_quinn::Server] and then [Into<Server>].
82/// Alternatively, establish a [web_transport_quinn::Session] directly and then [Into<Session>].
83pub struct Server {
84    inner: quinn::Server,
85}
86
87impl From<quinn::Server> for Server {
88    fn from(server: quinn::Server) -> Self {
89        Self { inner: server }
90    }
91}
92
93impl Server {
94    /// Accept an incoming connection.
95    pub async fn accept(&mut self) -> Result<Option<Session>, Error> {
96        match self.inner.accept().await {
97            // TODO add sub-protocol support
98            Some(session) => Ok(Some(session.ok().await?.into())),
99            None => Ok(None),
100        }
101    }
102}
103
104/// A WebTransport Session, able to accept/create streams and send/recv datagrams.
105///
106/// The session can be cloned to create multiple handles, which is which no method is &mut.
107/// The session will be closed with on drop.
108#[derive(Clone, PartialEq, Eq)]
109pub struct Session {
110    inner: quinn::Session,
111}
112
113impl Session {
114    /// Block until the peer creates a new unidirectional stream.
115    ///
116    /// Won't return None unless the connection is closed.
117    pub async fn accept_uni(&self) -> Result<RecvStream, Error> {
118        let stream = self.inner.accept_uni().await?;
119        Ok(RecvStream::new(stream))
120    }
121
122    /// Block until the peer creates a new bidirectional stream.
123    pub async fn accept_bi(&self) -> Result<(SendStream, RecvStream), Error> {
124        let (s, r) = self.inner.accept_bi().await?;
125        Ok((SendStream::new(s), RecvStream::new(r)))
126    }
127
128    /// Open a new bidirectional stream, which may block when there are too many concurrent streams.
129    pub async fn open_bi(&self) -> Result<(SendStream, RecvStream), Error> {
130        Ok(self
131            .inner
132            .open_bi()
133            .await
134            .map(|(s, r)| (SendStream::new(s), RecvStream::new(r)))?)
135    }
136
137    /// Open a new unidirectional stream, which may block when there are too many concurrent streams.
138    pub async fn open_uni(&self) -> Result<SendStream, Error> {
139        Ok(self.inner.open_uni().await.map(SendStream::new)?)
140    }
141
142    /// Send a datagram over the network.
143    ///
144    /// QUIC datagrams may be dropped for any reason:
145    /// - Network congestion.
146    /// - Random packet loss.
147    /// - Payload is larger than `max_datagram_size()`
148    /// - Peer is not receiving datagrams.
149    /// - Peer has too many outstanding datagrams.
150    /// - ???
151    pub async fn send_datagram(&self, payload: Bytes) -> Result<(), Error> {
152        // NOTE: This is not async, but we need to make it async to match the wasm implementation.
153        Ok(self.inner.send_datagram(payload)?)
154    }
155
156    /// The maximum size of a datagram that can be sent.
157    pub async fn max_datagram_size(&self) -> usize {
158        self.inner.max_datagram_size()
159    }
160
161    /// Receive a datagram over the network.
162    pub async fn recv_datagram(&self) -> Result<Bytes, Error> {
163        Ok(self.inner.read_datagram().await?)
164    }
165
166    /// Close the connection immediately with a code and reason.
167    pub fn close(&self, code: u32, reason: &str) {
168        self.inner.close(code, reason.as_bytes())
169    }
170
171    /// Block until the connection is closed.
172    pub async fn closed(&self) -> Error {
173        self.inner.closed().await.into()
174    }
175
176    /// Return the URL used to create the session, or `None` for a raw QUIC
177    /// session established without an HTTP/3 CONNECT request.
178    pub fn url(&self) -> Option<&Url> {
179        self.inner.request().map(|request| &request.url)
180    }
181
182    /// Return the application protocol used to create the session.
183    ///
184    /// For a WebTransport session this is the negotiated subprotocol; for a raw QUIC
185    /// session it is the negotiated ALPN.
186    pub fn protocol(&self) -> Option<&str> {
187        self.inner.protocol()
188    }
189}
190
191/// Convert a `web_transport_quinn::Session` into a `web_transport::Session`.
192impl From<quinn::Session> for Session {
193    fn from(session: quinn::Session) -> Self {
194        Session { inner: session }
195    }
196}
197
198/// An outgoing stream of bytes to the peer.
199///
200/// QUIC streams have flow control, which means the send rate is limited by the peer's receive window.
201/// The stream will be closed with a graceful FIN when dropped.
202pub struct SendStream {
203    inner: quinn::SendStream,
204}
205
206impl SendStream {
207    fn new(inner: quinn::SendStream) -> Self {
208        Self { inner }
209    }
210
211    /// Write some of the buffer to the stream.
212    #[must_use = "returns the number of bytes written"]
213    pub async fn write(&mut self, buf: &[u8]) -> Result<usize, Error> {
214        self.inner.write(buf).await.map_err(Into::into)
215    }
216
217    /// Write some of the buffer to the stream, advancing the internal position.
218    pub async fn write_buf<B: Buf>(&mut self, buf: &mut B) -> Result<usize, Error> {
219        // We use copy_to_bytes+write_chunk so if Bytes is provided, we can avoid allocating.
220        let size = buf.chunk().len();
221        let chunk = buf.copy_to_bytes(size);
222        self.inner.write_chunk(chunk).await?;
223        Ok(size)
224    }
225
226    /// Set the stream's priority.
227    ///
228    /// Streams with lower values will be sent first, but are not guaranteed to arrive first.
229    pub fn set_priority(&mut self, order: i32) {
230        self.inner.set_priority(order).ok();
231    }
232
233    /// Send an immediate reset code, closing the stream.
234    pub fn reset(&mut self, code: u32) {
235        self.inner.reset(code).ok();
236    }
237
238    /// Mark the stream as finished.
239    ///
240    /// This is automatically called on Drop, but can be called manually.
241    pub fn finish(&mut self) -> Result<(), Error> {
242        self.inner
243            .finish()
244            .map_err(|_| Error::Write(quinn::WriteError::ClosedStream))?;
245        Ok(())
246    }
247
248    /// Block until the stream is closed by either side.
249    ///
250    /// This returns a (potentially truncated) u8 because that's what the WASM implementation returns.
251    // TODO this should be &self but requires modifying quinn.
252    pub async fn closed(&mut self) -> Result<Option<u8>, Error> {
253        match self.inner.stopped().await {
254            Ok(None) => Ok(None),
255            Ok(Some(code)) => Ok(Some(code as u8)),
256            Err(e) => Err(Error::Session(e)),
257        }
258    }
259}
260
261/// An incoming stream of bytes from the peer.
262///
263/// All bytes are flushed in order and the stream is flow controlled.
264/// The stream will be closed with STOP_SENDING code=0 when dropped.
265pub struct RecvStream {
266    inner: quinn::RecvStream,
267}
268
269impl RecvStream {
270    fn new(inner: quinn::RecvStream) -> Self {
271        Self { inner }
272    }
273
274    /// Read the next chunk of data with the provided maximum size.
275    ///
276    /// This returns a chunk of data instead of copying, which may be more efficient.
277    pub async fn read(&mut self, max: usize) -> Result<Option<Bytes>, Error> {
278        Ok(self
279            .inner
280            .read_chunk(max, true)
281            .await?
282            .map(|chunk| chunk.bytes))
283    }
284
285    /// Read some data into the provided buffer.
286    ///
287    /// The number of bytes read is returned, or None if the stream is closed.
288    /// The buffer will be advanced by the number of bytes read.
289    pub async fn read_buf<B: BufMut>(&mut self, buf: &mut B) -> Result<Option<usize>, Error> {
290        let dst = buf.chunk_mut();
291        let dst = unsafe { &mut *(dst as *mut _ as *mut [u8]) };
292
293        let size = match self.inner.read(dst).await? {
294            Some(size) if size > 0 => size,
295            _ => return Ok(None),
296        };
297
298        unsafe { buf.advance_mut(size) };
299
300        Ok(Some(size))
301    }
302
303    /// Send a `STOP_SENDING` QUIC code.
304    pub fn stop(&mut self, code: u32) {
305        self.inner.stop(code).ok();
306    }
307
308    /// Block until the stream has been closed and return the error code, if any.
309    ///
310    /// This returns a (potentially truncated) u8 because that's what the WASM implementation returns.
311    /// web-transport-quinn returns a u32 because that's what the specification says.
312    // TODO Validate the correct behavior.
313    pub async fn closed(&mut self) -> Result<Option<u8>, Error> {
314        match self.inner.received_reset().await {
315            Ok(None) => Ok(None),
316            Ok(Some(code)) => Ok(Some(code as u8)),
317            Err(e) => Err(Error::Session(e)),
318        }
319    }
320}
321
322/// A WebTransport error.
323///
324/// The source can either be a session error or a stream error.
325/// TODO This interface is currently not generic.
326#[derive(Debug, thiserror::Error, Clone)]
327pub enum Error {
328    #[error("session error: {0}")]
329    Session(#[from] quinn::SessionError),
330
331    #[error("server error: {0}")]
332    Server(#[from] quinn::ServerError),
333
334    #[error("client error: {0}")]
335    Client(#[from] quinn::ClientError),
336
337    #[error("write error: {0}")]
338    Write(quinn::WriteError),
339
340    #[error("read error: {0}")]
341    Read(quinn::ReadError),
342}
343
344impl From<quinn::WriteError> for Error {
345    fn from(e: quinn::WriteError) -> Self {
346        match e {
347            quinn::WriteError::SessionError(e) => Error::Session(e),
348            e => Error::Write(e),
349        }
350    }
351}
352impl From<quinn::ReadError> for Error {
353    fn from(e: quinn::ReadError) -> Self {
354        match e {
355            quinn::ReadError::SessionError(e) => Error::Session(e),
356            e => Error::Read(e),
357        }
358    }
359}