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}