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(
67        &self,
68        payload: Bytes,
69    ) -> impl Future<Output = Result<(), Self::Error>> + MaybeSend;
70
71    /// Receive a datagram over the network.
72    fn recv_datagram(&self) -> impl Future<Output = Result<Bytes, Self::Error>> + MaybeSend;
73
74    /// Return the maximum size of a datagram that can be sent.
75    fn max_datagram_size(&self) -> usize;
76
77    /// Close the connection immediately with a code and reason.
78    fn close(&self, code: u32, reason: &str);
79
80    /// Block until the connection is closed by either side.
81    fn closed(&self) -> impl Future<Output = Self::Error> + MaybeSend;
82}
83
84/// An outgoing stream of bytes to the peer.
85///
86/// QUIC streams have flow control, which means the send rate is limited by the peer's receive window.
87/// The stream is closed with a graceful FIN when dropped.
88pub trait SendStream: MaybeSend {
89    /// Error type returned by send-side stream operations.
90    type Error: Error;
91
92    /// Write some of the buffer to the stream.
93    ///
94    /// Implementations must not return `Ok(0)` when `buf` is non-empty.
95    fn write(&mut self, buf: &[u8])
96    -> impl Future<Output = Result<usize, Self::Error>> + MaybeSend;
97
98    /// Write the given buffer to the stream, advancing the internal position.
99    fn write_buf<B: Buf + MaybeSend>(
100        &mut self,
101        buf: &mut B,
102    ) -> impl Future<Output = Result<usize, Self::Error>> + MaybeSend {
103        async move {
104            let chunk = buf.chunk();
105            let size = self.write(chunk).await?;
106            assert!(
107                size > 0 || chunk.is_empty(),
108                "SendStream::write returned zero for a non-empty buffer"
109            );
110            assert!(
111                size <= chunk.len(),
112                "SendStream::write returned more bytes than provided"
113            );
114            buf.advance(size);
115            Ok(size)
116        }
117    }
118
119    /// Write the entire [Bytes] chunk to the stream, potentially avoiding a copy.
120    fn write_chunk(
121        &mut self,
122        chunk: Bytes,
123    ) -> impl Future<Output = Result<(), Self::Error>> + MaybeSend {
124        async move {
125            let mut c = chunk;
126            self.write_all_buf(&mut c).await
127        }
128    }
129
130    /// Helper to write all data in the buffer.
131    fn write_all(
132        &mut self,
133        buf: &[u8],
134    ) -> impl Future<Output = Result<(), Self::Error>> + MaybeSend {
135        async move {
136            let mut pos = 0;
137            while pos < buf.len() {
138                let written = self.write(&buf[pos..]).await?;
139                assert!(
140                    written > 0,
141                    "SendStream::write returned zero for a non-empty buffer"
142                );
143                assert!(
144                    written <= buf.len() - pos,
145                    "SendStream::write returned more bytes than provided"
146                );
147                pos += written;
148            }
149            Ok(())
150        }
151    }
152
153    /// Helper to write all data in the buffer.
154    fn write_all_buf<B: Buf + MaybeSend>(
155        &mut self,
156        buf: &mut B,
157    ) -> impl Future<Output = Result<(), Self::Error>> + MaybeSend {
158        async move {
159            while buf.has_remaining() {
160                let written = self.write_buf(buf).await?;
161                assert!(
162                    written > 0,
163                    "SendStream::write returned zero for a non-empty buffer"
164                );
165            }
166            Ok(())
167        }
168    }
169
170    /// Set the stream's priority.
171    ///
172    /// Streams with lower values are sent first, but arrival order is not guaranteed.
173    fn set_priority(&mut self, order: u8);
174
175    /// Mark the stream as finished, erroring on any future writes.
176    ///
177    /// [SendStream::reset] can still be called to abandon queued data.
178    /// [SendStream::closed] should return when the FIN is acknowledged by the peer.
179    ///
180    /// NOTE: Quinn implicitly calls this on drop, but it is a common footgun.
181    /// Implementations should call [SendStream::reset] on drop instead.
182    fn finish(&mut self) -> Result<(), Self::Error>;
183
184    /// Immediately close the stream and discard any remaining data.
185    ///
186    /// This translates into a RESET_STREAM QUIC code.
187    /// The peer may not receive the reset code if the stream is already closed.
188    fn reset(&mut self, code: u32);
189
190    /// Block until the stream is closed by either side.
191    ///
192    /// This includes:
193    /// - We sent a RESET_STREAM via [SendStream::reset]
194    /// - We received a STOP_SENDING via [RecvStream::stop]
195    /// - A FIN is acknowledged by the peer via [SendStream::finish]
196    ///
197    /// Some implementations do not support FIN acknowledgement, in which case this blocks until the FIN is sent.
198    ///
199    /// NOTE: This takes `&mut` to match Quinn and simplify the implementation.
200    fn closed(&mut self) -> impl Future<Output = Result<(), Self::Error>> + MaybeSend;
201}
202
203/// An incoming stream of bytes from the peer.
204///
205/// All bytes are flushed in order and the stream is flow controlled.
206/// The stream is closed with STOP_SENDING code=0 when dropped.
207pub trait RecvStream: MaybeSend {
208    /// Error type returned by receive-side stream operations.
209    type Error: Error;
210
211    /// Read the next chunk of data, up to the max size.
212    ///
213    /// This returns a chunk of data instead of copying, which can be more efficient.
214    fn read(
215        &mut self,
216        dst: &mut [u8],
217    ) -> impl Future<Output = Result<Option<usize>, Self::Error>> + MaybeSend;
218
219    /// Read some data into the provided buffer.
220    ///
221    /// The number of bytes read is returned, or `None` if the stream is closed.
222    /// The buffer is advanced by the number of bytes read.
223    fn read_buf<B: BufMut + MaybeSend>(
224        &mut self,
225        buf: &mut B,
226    ) -> impl Future<Output = Result<Option<usize>, Self::Error>> + MaybeSend {
227        async move {
228            // Use initialized temporary storage so a faulty third-party RecvStream
229            // implementation cannot turn uninitialized memory into a safe byte slice.
230            let capacity = buf.chunk_mut().len().min(8 * 1024);
231            if capacity == 0 {
232                return Ok(Some(0));
233            }
234            let mut dst = vec![0; capacity];
235            let size = match self.read(&mut dst).await? {
236                Some(size) => size,
237                None => return Ok(None),
238            };
239            assert!(
240                size <= dst.len(),
241                "RecvStream::read returned more bytes than the provided buffer"
242            );
243            buf.put_slice(&dst[..size]);
244
245            Ok(Some(size))
246        }
247    }
248
249    /// Read the next chunk of data, up to the max size.
250    ///
251    /// This returns a chunk of data instead of copying, which can be more efficient.
252    fn read_chunk(
253        &mut self,
254        max: usize,
255    ) -> impl Future<Output = Result<Option<Bytes>, Self::Error>> + MaybeSend {
256        async move {
257            // Avoid excessive allocation; provide your own buffer to increase this limit.
258            let mut buf = BytesMut::with_capacity(max.min(8 * 1024));
259
260            Ok(self.read_buf(&mut buf).await?.map(|_| buf.freeze()))
261        }
262    }
263
264    /// Send a `STOP_SENDING` QUIC code, informing the peer that no more data will be read.
265    ///
266    /// Implementations must do this on drop to avoid leaking flow control.
267    /// Call this method manually to specify a custom code.
268    fn stop(&mut self, code: u32);
269
270    /// Block until the stream has been closed by either side.
271    ///
272    /// This includes:
273    /// - We received a RESET_STREAM via [SendStream::reset]
274    /// - We sent a STOP_SENDING via [RecvStream::stop]
275    /// - We received a FIN via [SendStream::finish] and read all data.
276    fn closed(&mut self) -> impl Future<Output = Result<(), Self::Error>> + MaybeSend;
277
278    /// Helper to keep reading until the stream is closed.
279    fn read_all(&mut self) -> impl Future<Output = Result<Bytes, Self::Error>> + MaybeSend {
280        async move {
281            let mut buf = BytesMut::new();
282            self.read_all_buf(&mut buf).await?;
283            Ok(buf.freeze())
284        }
285    }
286
287    /// Helper to keep reading until the buffer is full.
288    fn read_all_buf<B: BufMut + MaybeSend>(
289        &mut self,
290        buf: &mut B,
291    ) -> impl Future<Output = Result<usize, Self::Error>> + MaybeSend {
292        async move {
293            let mut size = 0;
294            while buf.has_remaining_mut() {
295                match self.read_buf(buf).await? {
296                    Some(n) => size += n,
297                    None => break,
298                }
299            }
300            Ok(size)
301        }
302    }
303}
304
305#[cfg(test)]
306mod tests {
307    use super::{Error, RecvStream, SendStream};
308    use bytes::{Bytes, BytesMut};
309    use futures::executor::block_on;
310    use std::fmt;
311
312    #[derive(Debug)]
313    struct TestError;
314
315    impl fmt::Display for TestError {
316        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
317            write!(f, "test error")
318        }
319    }
320
321    impl std::error::Error for TestError {}
322
323    impl Error for TestError {
324        fn session_error(&self) -> Option<(u32, String)> {
325            None
326        }
327    }
328
329    struct TestRecvStream {
330        data: Vec<u8>,
331        pos: usize,
332    }
333
334    impl TestRecvStream {
335        fn new(data: &[u8]) -> Self {
336            Self {
337                data: data.to_vec(),
338                pos: 0,
339            }
340        }
341    }
342
343    impl RecvStream for TestRecvStream {
344        type Error = TestError;
345
346        async fn read(&mut self, dst: &mut [u8]) -> Result<Option<usize>, Self::Error> {
347            let available = self.data.len().saturating_sub(self.pos);
348            if available == 0 {
349                return Ok(None);
350            }
351
352            let size = available.min(dst.len());
353            let end = self.pos + size;
354            dst[..size].copy_from_slice(&self.data[self.pos..end]);
355            self.pos = end;
356
357            Ok(Some(size))
358        }
359
360        fn stop(&mut self, _code: u32) {}
361
362        async fn closed(&mut self) -> Result<(), Self::Error> {
363            Ok(())
364        }
365    }
366
367    struct PartialSendStream {
368        data: Vec<u8>,
369        max_write: usize,
370    }
371
372    impl SendStream for PartialSendStream {
373        type Error = TestError;
374
375        async fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
376            let size = buf.len().min(self.max_write);
377            self.data.extend_from_slice(&buf[..size]);
378            Ok(size)
379        }
380
381        fn set_priority(&mut self, _order: u8) {}
382
383        fn finish(&mut self) -> Result<(), Self::Error> {
384            Ok(())
385        }
386
387        fn reset(&mut self, _code: u32) {}
388
389        async fn closed(&mut self) -> Result<(), Self::Error> {
390            Ok(())
391        }
392    }
393
394    #[test]
395    fn read_chunk_respects_max_and_eof() {
396        let mut stream = TestRecvStream::new(b"hello world");
397
398        let first = block_on(stream.read_chunk(5)).unwrap().unwrap();
399        assert_eq!(first, Bytes::from_static(b"hello"));
400
401        let second = block_on(stream.read_chunk(1024)).unwrap().unwrap();
402        assert_eq!(second, Bytes::from_static(b" world"));
403
404        let end = block_on(stream.read_chunk(1)).unwrap();
405        assert!(end.is_none());
406    }
407
408    #[test]
409    fn read_buf_advances_buffer() {
410        let mut stream = TestRecvStream::new(b"test");
411        let mut buf = BytesMut::with_capacity(4);
412
413        let size = block_on(stream.read_buf(&mut buf)).unwrap().unwrap();
414        assert_eq!(size, 4);
415        assert_eq!(&buf[..], b"test");
416    }
417
418    #[test]
419    fn write_chunk_retries_partial_writes() {
420        let mut stream = PartialSendStream {
421            data: Vec::new(),
422            max_write: 2,
423        };
424
425        block_on(stream.write_chunk(Bytes::from_static(b"hello"))).unwrap();
426        assert_eq!(stream.data, b"hello");
427    }
428}