web_transport_trait/lib.rs
1pub mod poll;
2mod util;
3
4use std::future::Future;
5use std::time::Duration;
6
7pub use crate::util::{MaybeSend, MaybeSync};
8use bytes::{Buf, BufMut, Bytes, BytesMut};
9
10/// Connection-level statistics.
11///
12/// Methods return `Option` — `None` means the implementation doesn't track
13/// this metric, while `Some(0)` means actually zero.
14pub trait Stats {
15 /// Total bytes sent over the connection, including retransmissions and overhead.
16 fn bytes_sent(&self) -> Option<u64> {
17 None
18 }
19
20 /// Total bytes received over the connection, including duplicate and overhead.
21 fn bytes_received(&self) -> Option<u64> {
22 None
23 }
24
25 /// Total bytes lost (detected via retransmission or acknowledgement).
26 fn bytes_lost(&self) -> Option<u64> {
27 None
28 }
29
30 /// Total number of datagrams sent.
31 fn packets_sent(&self) -> Option<u64> {
32 None
33 }
34
35 /// Total number of datagrams received.
36 fn packets_received(&self) -> Option<u64> {
37 None
38 }
39
40 /// Total number of datagrams detected as lost.
41 fn packets_lost(&self) -> Option<u64> {
42 None
43 }
44
45 /// Smoothed round-trip time estimate.
46 fn rtt(&self) -> Option<Duration> {
47 None
48 }
49
50 /// Estimated available send bandwidth, in bits per second.
51 fn estimated_send_rate(&self) -> Option<u64> {
52 None
53 }
54}
55
56/// Default stats implementation that returns `None` for all metrics.
57pub struct StatsUnavailable;
58impl Stats for StatsUnavailable {}
59
60/// Error trait for WebTransport operations.
61///
62/// Implementations must be Send + Sync + 'static for use across async boundaries.
63pub trait Error: std::error::Error + MaybeSend + MaybeSync + 'static {
64 /// Returns the error code and reason if this was an application error.
65 ///
66 /// NOTE: Reason reasons are technically bytes on the wire, but we convert to a String for convenience.
67 fn session_error(&self) -> Option<(u32, String)>;
68
69 /// Returns the error code if this was a stream error.
70 fn stream_error(&self) -> Option<u32> {
71 None
72 }
73}
74
75/// A WebTransport Session, able to accept/create streams and send/recv datagrams.
76///
77/// The session can be cloned to create multiple handles.
78/// The session will be closed on drop.
79pub trait Session: Clone + MaybeSend + MaybeSync + 'static {
80 type SendStream: SendStream;
81 type RecvStream: RecvStream;
82 type Error: Error;
83
84 /// Block until the peer creates a new unidirectional stream.
85 fn accept_uni(&self)
86 -> impl Future<Output = Result<Self::RecvStream, Self::Error>> + MaybeSend;
87
88 /// Block until the peer creates a new bidirectional stream.
89 fn accept_bi(
90 &self,
91 ) -> impl Future<Output = Result<(Self::SendStream, Self::RecvStream), Self::Error>> + MaybeSend;
92
93 /// Open a new bidirectional stream, which may block when there are too many concurrent streams.
94 fn open_bi(
95 &self,
96 ) -> impl Future<Output = Result<(Self::SendStream, Self::RecvStream), Self::Error>> + MaybeSend;
97
98 /// Open a new unidirectional stream, which may block when there are too many concurrent streams.
99 fn open_uni(&self) -> impl Future<Output = Result<Self::SendStream, Self::Error>> + MaybeSend;
100
101 /// Send a datagram over the network.
102 ///
103 /// QUIC datagrams may be dropped for any reason:
104 /// - Network congestion.
105 /// - Random packet loss.
106 /// - Payload is larger than `max_datagram_size()`
107 /// - Peer is not receiving datagrams.
108 /// - Peer has too many outstanding datagrams.
109 /// - ???
110 fn send_datagram(&self, payload: Bytes) -> Result<(), Self::Error>;
111
112 /// Receive a datagram over the network.
113 fn recv_datagram(&self) -> impl Future<Output = Result<Bytes, Self::Error>> + MaybeSend;
114
115 /// The maximum size of a datagram that can be sent.
116 fn max_datagram_size(&self) -> usize;
117
118 /// Return the application protocol negotiated for this session, if any.
119 ///
120 /// For WebTransport over HTTP/3 this is the selected WebTransport subprotocol;
121 /// for raw QUIC it is the negotiated ALPN. Returns `None` if neither was
122 /// negotiated or the ALPN is not valid UTF-8.
123 fn protocol(&self) -> Option<&str> {
124 None
125 }
126
127 /// Close the connection immediately with a code and reason.
128 fn close(&self, code: u32, reason: &str);
129
130 /// Block until the connection is closed by either side.
131 fn closed(&self) -> impl Future<Output = Self::Error> + MaybeSend;
132
133 /// Return connection-level statistics, if supported.
134 fn stats(&self) -> impl Stats {
135 StatsUnavailable
136 }
137}
138
139/// An outgoing stream of bytes to the peer.
140///
141/// QUIC streams have flow control, which means the send rate is limited by the peer's receive window.
142/// The stream will be closed with a graceful FIN when dropped.
143pub trait SendStream: MaybeSend {
144 type Error: Error;
145
146 /// Write some of the buffer to the stream, returning how many bytes were
147 /// written. See [`write_buf`](Self::write_buf) for the cancel-safety contract,
148 /// which this shares.
149 fn write(&mut self, buf: &[u8])
150 -> impl Future<Output = Result<usize, Self::Error>> + MaybeSend;
151
152 /// Write some of the given buffer to the stream, advancing it by the number of
153 /// bytes written. This may be less than the whole buffer, so callers loop (or
154 /// use [`write_all`](Self::write_all)).
155 ///
156 /// # Cancel safety
157 ///
158 /// Implementations must be cancel safe: if the returned future is dropped
159 /// before it resolves, `buf` must not have been advanced past the bytes the
160 /// implementation accepted for sending. (Whether they reach the peer is a
161 /// separate matter — a reset or a dead connection can still discard accepted
162 /// bytes.) Callers race writes against other work, so a byte taken from `buf`
163 /// but never accepted becomes a silent hole in the stream, which the peer
164 /// decodes as a truncated or garbage frame. Wait for send capacity *before*
165 /// consuming from `buf`, never after.
166 fn write_buf<B: Buf + MaybeSend>(
167 &mut self,
168 buf: &mut B,
169 ) -> impl Future<Output = Result<usize, Self::Error>> + MaybeSend {
170 async move {
171 let chunk = buf.chunk();
172 let size = self.write(chunk).await?;
173 buf.advance(size);
174 Ok(size)
175 }
176 }
177
178 /// Write the entire [Bytes] chunk to the stream, potentially avoiding a copy.
179 fn write_chunk(
180 &mut self,
181 chunk: Bytes,
182 ) -> impl Future<Output = Result<(), Self::Error>> + MaybeSend {
183 async move {
184 // Just so the arg isn't mut
185 let mut c = chunk;
186 // `write_all_buf`, not `write_buf`: this method promises the whole chunk,
187 // and a single `write_buf` may accept only part of it. Stopping there
188 // would drop the rest silently, which the peer decodes as truncation.
189 self.write_all_buf(&mut c).await?;
190 Ok(())
191 }
192 }
193
194 /// A helper to write all the data in the buffer.
195 fn write_all(
196 &mut self,
197 buf: &[u8],
198 ) -> impl Future<Output = Result<(), Self::Error>> + MaybeSend {
199 async move {
200 let mut pos = 0;
201 while pos < buf.len() {
202 pos += self.write(&buf[pos..]).await?;
203 }
204 Ok(())
205 }
206 }
207
208 /// A helper to write all of the data in the buffer.
209 fn write_all_buf<B: Buf + MaybeSend>(
210 &mut self,
211 buf: &mut B,
212 ) -> impl Future<Output = Result<(), Self::Error>> + MaybeSend {
213 async move {
214 while buf.has_remaining() {
215 self.write_buf(buf).await?;
216 }
217 Ok(())
218 }
219 }
220
221 /// Set the stream's priority.
222 ///
223 /// Streams with higher values will be sent first, but are not guaranteed to arrive first.
224 /// This matches the W3C WebTransport `sendOrder` convention (and quinn's scheduler).
225 fn set_priority(&mut self, order: u8);
226
227 /// Mark the stream as finished, erroring on any future writes.
228 ///
229 /// [SendStream::reset] can still be called to abandon any queued data.
230 /// [SendStream::closed] should return when the FIN is acknowledged by the peer.
231 ///
232 /// NOTE: Quinn implicitly calls this on Drop, but it's a common footgun.
233 /// Implementations SHOULD [SendStream::reset] on Drop instead.
234 fn finish(&mut self) -> Result<(), Self::Error>;
235
236 /// Immediately closes the stream and discards any remaining data.
237 ///
238 /// This translates into a RESET_STREAM QUIC code.
239 /// The peer may not receive the reset code if the stream is already closed.
240 fn reset(&mut self, code: u32);
241
242 /// Block until the stream is closed by either side.
243 ///
244 /// This includes:
245 /// - We sent a RESET_STREAM via [SendStream::reset]
246 /// - We received a STOP_SENDING via [RecvStream::stop]
247 /// - A FIN is acknowledged by the peer via [SendStream::finish]
248 ///
249 /// Some implementations do not support FIN acknowledgement, in which case this will block until the FIN is sent.
250 ///
251 /// NOTE: This takes a &mut to match Quinn and to simplify the implementation.
252 fn closed(&mut self) -> impl Future<Output = Result<(), Self::Error>> + MaybeSend;
253}
254
255/// An incoming stream of bytes from the peer.
256///
257/// All bytes are flushed in order and the stream is flow controlled.
258/// The stream will be closed with STOP_SENDING code=0 when dropped.
259pub trait RecvStream: MaybeSend {
260 type Error: Error;
261
262 /// Read the next chunk of data, up to the max size.
263 ///
264 /// This returns a chunk of data instead of copying, which may be more efficient.
265 fn read(
266 &mut self,
267 dst: &mut [u8],
268 ) -> impl Future<Output = Result<Option<usize>, Self::Error>> + MaybeSend;
269
270 /// Read some data into the provided buffer.
271 ///
272 /// The number of bytes read is returned, or None if the stream is closed.
273 /// The buffer will be advanced by the number of bytes read.
274 fn read_buf<B: BufMut + MaybeSend>(
275 &mut self,
276 buf: &mut B,
277 ) -> impl Future<Output = Result<Option<usize>, Self::Error>> + MaybeSend {
278 async move {
279 let dst = unsafe {
280 std::mem::transmute::<&mut bytes::buf::UninitSlice, &mut [u8]>(buf.chunk_mut())
281 };
282 let size = match self.read(dst).await? {
283 Some(size) if size > 0 => size,
284 _ => return Ok(None),
285 };
286
287 unsafe { buf.advance_mut(size) };
288
289 Ok(Some(size))
290 }
291 }
292
293 /// Read the next chunk of data, up to the max size.
294 ///
295 /// This returns a chunk of data instead of copying, which may be more efficient.
296 fn read_chunk(
297 &mut self,
298 max: usize,
299 ) -> impl Future<Output = Result<Option<Bytes>, Self::Error>> + MaybeSend {
300 async move {
301 // Don't allocate too much. Write your own if you want to increase this buffer.
302 let mut buf = BytesMut::with_capacity(max.min(8 * 1024));
303
304 // TODO Test this, I think it will work?
305 Ok(self.read_buf(&mut buf).await?.map(|_| buf.freeze()))
306 }
307 }
308
309 /// Send a `STOP_SENDING` QUIC code, informing the peer that no more data will be read.
310 ///
311 /// An implementation MUST do this on Drop otherwise flow control will be leaked.
312 /// Call this method manually if you want to specify a code yourself.
313 fn stop(&mut self, code: u32);
314
315 /// Block until the stream has been closed by either side.
316 ///
317 /// This includes:
318 /// - We received a RESET_STREAM via [SendStream::reset]
319 /// - We sent a STOP_SENDING via [RecvStream::stop]
320 /// - We received a FIN via [SendStream::finish] and read all data.
321 fn closed(&mut self) -> impl Future<Output = Result<(), Self::Error>> + MaybeSend;
322
323 /// A helper to keep reading until the stream is closed.
324 fn read_all(&mut self) -> impl Future<Output = Result<Bytes, Self::Error>> + MaybeSend {
325 async move {
326 let mut buf = BytesMut::new();
327 self.read_all_buf(&mut buf).await?;
328 Ok(buf.freeze())
329 }
330 }
331
332 /// A helper to keep reading until the buffer is full.
333 fn read_all_buf<B: BufMut + MaybeSend>(
334 &mut self,
335 buf: &mut B,
336 ) -> impl Future<Output = Result<usize, Self::Error>> + MaybeSend {
337 async move {
338 let mut size = 0;
339 while buf.has_remaining_mut() {
340 match self.read_buf(buf).await? {
341 Some(n) if n > 0 => size += n,
342 _ => break,
343 }
344 }
345 Ok(size)
346 }
347 }
348}