Skip to main content

webtrans_trait/
lib.rs

1//! Transport-agnostic traits for WebTransport sessions and streams.
2//!
3//! This crate defines the core trait contracts shared by native and WASM
4//! backends, including error mapping, stream operations, and datagram support.
5
6mod bounds;
7
8use std::future::Future;
9
10pub use crate::bounds::{MaybeSend, MaybeSync};
11use bytes::{Buf, BufMut, Bytes, BytesMut};
12
13/// Error trait for WebTransport operations.
14///
15/// Implementations must be Send + Sync + 'static to cross async boundaries.
16pub trait Error: std::error::Error + MaybeSend + MaybeSync + 'static {
17    /// Return the error code and reason if this was an application error.
18    ///
19    /// NOTE: Reasons are bytes on the wire, but are converted to `String` for convenience.
20    fn session_error(&self) -> Option<(u32, String)>;
21
22    /// Return the error code if this was a stream error.
23    fn stream_error(&self) -> Option<u32> {
24        None
25    }
26}
27
28/// A WebTransport session that can accept/create streams and send/receive datagrams.
29///
30/// The session can be cloned to create multiple handles.
31/// The session will be closed on drop.
32pub trait Session: Clone + MaybeSend + MaybeSync + 'static {
33    /// Outgoing stream type returned by `open_*` and `accept_bi`.
34    type SendStream: SendStream;
35    /// Incoming stream type returned by `accept_*` and `open_bi`.
36    type RecvStream: RecvStream;
37    /// Error type returned by session operations.
38    type Error: Error;
39
40    /// Block until the peer creates a new unidirectional stream.
41    fn accept_uni(&self)
42    -> impl Future<Output = Result<Self::RecvStream, Self::Error>> + MaybeSend;
43
44    /// Block until the peer creates a new bidirectional stream.
45    fn accept_bi(
46        &self,
47    ) -> impl Future<Output = Result<(Self::SendStream, Self::RecvStream), Self::Error>> + MaybeSend;
48
49    /// Open a new bidirectional stream, which may block if too many streams are open.
50    fn open_bi(
51        &self,
52    ) -> impl Future<Output = Result<(Self::SendStream, Self::RecvStream), Self::Error>> + MaybeSend;
53
54    /// Open a new unidirectional stream, which may block if too many streams are open.
55    fn open_uni(&self) -> impl Future<Output = Result<Self::SendStream, Self::Error>> + MaybeSend;
56
57    /// Send a datagram over the network.
58    ///
59    /// QUIC datagrams may be dropped for any reason:
60    /// - Network congestion.
61    /// - Random packet loss.
62    /// - Payload is larger than `max_datagram_size()`.
63    /// - Peer is not receiving datagrams.
64    /// - Peer has too many outstanding datagrams.
65    /// - Implementation-specific limits.
66    fn send_datagram(&self, payload: Bytes) -> Result<(), Self::Error>;
67
68    /// Receive a datagram over the network.
69    fn recv_datagram(&self) -> impl Future<Output = Result<Bytes, Self::Error>> + MaybeSend;
70
71    /// Return the maximum size of a datagram that can be sent.
72    fn max_datagram_size(&self) -> usize;
73
74    /// Close the connection immediately with a code and reason.
75    fn close(&self, code: u32, reason: &str);
76
77    /// Block until the connection is closed by either side.
78    fn closed(&self) -> impl Future<Output = Self::Error> + MaybeSend;
79}
80
81/// An outgoing stream of bytes to the peer.
82///
83/// QUIC streams have flow control, which means the send rate is limited by the peer's receive window.
84/// The stream is closed with a graceful FIN when dropped.
85pub trait SendStream: MaybeSend {
86    /// Error type returned by send-side stream operations.
87    type Error: Error;
88
89    /// Write some of the buffer to the stream.
90    fn write(&mut self, buf: &[u8])
91    -> impl Future<Output = Result<usize, Self::Error>> + MaybeSend;
92
93    /// Write the given buffer to the stream, advancing the internal position.
94    fn write_buf<B: Buf + MaybeSend>(
95        &mut self,
96        buf: &mut B,
97    ) -> impl Future<Output = Result<usize, Self::Error>> + MaybeSend {
98        async move {
99            let chunk = buf.chunk();
100            let size = self.write(chunk).await?;
101            buf.advance(size);
102            Ok(size)
103        }
104    }
105
106    /// Write the entire [Bytes] chunk to the stream, potentially avoiding a copy.
107    fn write_chunk(
108        &mut self,
109        chunk: Bytes,
110    ) -> impl Future<Output = Result<(), Self::Error>> + MaybeSend {
111        async move {
112            // Avoid a mutable binding for the argument.
113            let mut c = chunk;
114            self.write_buf(&mut c).await?;
115            Ok(())
116        }
117    }
118
119    /// Helper to write all data in the buffer.
120    fn write_all(
121        &mut self,
122        buf: &[u8],
123    ) -> impl Future<Output = Result<(), Self::Error>> + MaybeSend {
124        async move {
125            let mut pos = 0;
126            while pos < buf.len() {
127                pos += self.write(&buf[pos..]).await?;
128            }
129            Ok(())
130        }
131    }
132
133    /// Helper to write all data in the buffer.
134    fn write_all_buf<B: Buf + MaybeSend>(
135        &mut self,
136        buf: &mut B,
137    ) -> impl Future<Output = Result<(), Self::Error>> + MaybeSend {
138        async move {
139            while buf.has_remaining() {
140                self.write_buf(buf).await?;
141            }
142            Ok(())
143        }
144    }
145
146    /// Set the stream's priority.
147    ///
148    /// Streams with lower values are sent first, but arrival order is not guaranteed.
149    fn set_priority(&mut self, order: u8);
150
151    /// Mark the stream as finished, erroring on any future writes.
152    ///
153    /// [SendStream::reset] can still be called to abandon queued data.
154    /// [SendStream::closed] should return when the FIN is acknowledged by the peer.
155    ///
156    /// NOTE: Quinn implicitly calls this on drop, but it is a common footgun.
157    /// Implementations should call [SendStream::reset] on drop instead.
158    fn finish(&mut self) -> Result<(), Self::Error>;
159
160    /// Immediately close the stream and discard any remaining data.
161    ///
162    /// This translates into a RESET_STREAM QUIC code.
163    /// The peer may not receive the reset code if the stream is already closed.
164    fn reset(&mut self, code: u32);
165
166    /// Block until the stream is closed by either side.
167    ///
168    /// This includes:
169    /// - We sent a RESET_STREAM via [SendStream::reset]
170    /// - We received a STOP_SENDING via [RecvStream::stop]
171    /// - A FIN is acknowledged by the peer via [SendStream::finish]
172    ///
173    /// Some implementations do not support FIN acknowledgement, in which case this blocks until the FIN is sent.
174    ///
175    /// NOTE: This takes `&mut` to match Quinn and simplify the implementation.
176    fn closed(&mut self) -> impl Future<Output = Result<(), Self::Error>> + MaybeSend;
177}
178
179/// An incoming stream of bytes from the peer.
180///
181/// All bytes are flushed in order and the stream is flow controlled.
182/// The stream is closed with STOP_SENDING code=0 when dropped.
183pub trait RecvStream: MaybeSend {
184    /// Error type returned by receive-side stream operations.
185    type Error: Error;
186
187    /// Read the next chunk of data, up to the max size.
188    ///
189    /// This returns a chunk of data instead of copying, which can be more efficient.
190    fn read(
191        &mut self,
192        dst: &mut [u8],
193    ) -> impl Future<Output = Result<Option<usize>, Self::Error>> + MaybeSend;
194
195    /// Read some data into the provided buffer.
196    ///
197    /// The number of bytes read is returned, or `None` if the stream is closed.
198    /// The buffer is advanced by the number of bytes read.
199    fn read_buf<B: BufMut + MaybeSend>(
200        &mut self,
201        buf: &mut B,
202    ) -> impl Future<Output = Result<Option<usize>, Self::Error>> + MaybeSend {
203        async move {
204            let dst = unsafe {
205                std::mem::transmute::<&mut bytes::buf::UninitSlice, &mut [u8]>(buf.chunk_mut())
206            };
207            let size = match self.read(dst).await? {
208                Some(size) => size,
209                None => return Ok(None),
210            };
211
212            unsafe { buf.advance_mut(size) };
213
214            Ok(Some(size))
215        }
216    }
217
218    /// Read the next chunk of data, up to the max size.
219    ///
220    /// This returns a chunk of data instead of copying, which can be more efficient.
221    fn read_chunk(
222        &mut self,
223        max: usize,
224    ) -> impl Future<Output = Result<Option<Bytes>, Self::Error>> + MaybeSend {
225        async move {
226            // Avoid excessive allocation; provide your own buffer to increase this limit.
227            let mut buf = BytesMut::with_capacity(max.min(8 * 1024));
228
229            Ok(self.read_buf(&mut buf).await?.map(|_| buf.freeze()))
230        }
231    }
232
233    /// Send a `STOP_SENDING` QUIC code, informing the peer that no more data will be read.
234    ///
235    /// Implementations must do this on drop to avoid leaking flow control.
236    /// Call this method manually to specify a custom code.
237    fn stop(&mut self, code: u32);
238
239    /// Block until the stream has been closed by either side.
240    ///
241    /// This includes:
242    /// - We received a RESET_STREAM via [SendStream::reset]
243    /// - We sent a STOP_SENDING via [RecvStream::stop]
244    /// - We received a FIN via [SendStream::finish] and read all data.
245    fn closed(&mut self) -> impl Future<Output = Result<(), Self::Error>> + MaybeSend;
246
247    /// Helper to keep reading until the stream is closed.
248    fn read_all(&mut self) -> impl Future<Output = Result<Bytes, Self::Error>> + MaybeSend {
249        async move {
250            let mut buf = BytesMut::new();
251            self.read_all_buf(&mut buf).await?;
252            Ok(buf.freeze())
253        }
254    }
255
256    /// Helper to keep reading until the buffer is full.
257    fn read_all_buf<B: BufMut + MaybeSend>(
258        &mut self,
259        buf: &mut B,
260    ) -> impl Future<Output = Result<usize, Self::Error>> + MaybeSend {
261        async move {
262            let mut size = 0;
263            while buf.has_remaining_mut() {
264                match self.read_buf(buf).await? {
265                    Some(n) => size += n,
266                    None => break,
267                }
268            }
269            Ok(size)
270        }
271    }
272}
273
274#[cfg(test)]
275mod tests {
276    use super::{Error, MaybeSend, RecvStream};
277    use bytes::{Bytes, BytesMut};
278    use futures::executor::block_on;
279    use std::fmt;
280
281    #[derive(Debug)]
282    struct TestError;
283
284    impl fmt::Display for TestError {
285        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
286            write!(f, "test error")
287        }
288    }
289
290    impl std::error::Error for TestError {}
291
292    impl Error for TestError {
293        fn session_error(&self) -> Option<(u32, String)> {
294            None
295        }
296    }
297
298    struct TestRecvStream {
299        data: Vec<u8>,
300        pos: usize,
301    }
302
303    impl TestRecvStream {
304        fn new(data: &[u8]) -> Self {
305            Self {
306                data: data.to_vec(),
307                pos: 0,
308            }
309        }
310    }
311
312    impl RecvStream for TestRecvStream {
313        type Error = TestError;
314
315        fn read(
316            &mut self,
317            dst: &mut [u8],
318        ) -> impl std::future::Future<Output = Result<Option<usize>, Self::Error>> + MaybeSend
319        {
320            async move {
321                let available = self.data.len().saturating_sub(self.pos);
322                if available == 0 {
323                    return Ok(None);
324                }
325
326                let size = available.min(dst.len());
327                let end = self.pos + size;
328                dst[..size].copy_from_slice(&self.data[self.pos..end]);
329                self.pos = end;
330
331                Ok(Some(size))
332            }
333        }
334
335        fn stop(&mut self, _code: u32) {}
336
337        fn closed(
338            &mut self,
339        ) -> impl std::future::Future<Output = Result<(), Self::Error>> + MaybeSend {
340            async { Ok(()) }
341        }
342    }
343
344    #[test]
345    fn read_chunk_respects_max_and_eof() {
346        let mut stream = TestRecvStream::new(b"hello world");
347
348        let first = block_on(stream.read_chunk(5)).unwrap().unwrap();
349        assert_eq!(first, Bytes::from_static(b"hello"));
350
351        let second = block_on(stream.read_chunk(1024)).unwrap().unwrap();
352        assert_eq!(second, Bytes::from_static(b" world"));
353
354        let end = block_on(stream.read_chunk(1)).unwrap();
355        assert!(end.is_none());
356    }
357
358    #[test]
359    fn read_buf_advances_buffer() {
360        let mut stream = TestRecvStream::new(b"test");
361        let mut buf = BytesMut::with_capacity(4);
362
363        let size = block_on(stream.read_buf(&mut buf)).unwrap().unwrap();
364        assert_eq!(size, 4);
365        assert_eq!(&buf[..], b"test");
366    }
367}