Skip to main content

web_transport_trait/
poll.rs

1//! The `poll`-based surface of a WebTransport session and its streams.
2//!
3//! These traits are the sans-I/O half of this crate. They describe a transport as a
4//! state machine that a caller steps from its own loop, rather than a set of futures
5//! that a runtime drives.
6//!
7//! Two properties are deliberate, and both are constraints on what an implementation
8//! is *allowed* to be rather than features it gets:
9//!
10//! - **No `Send` or `Sync` bound on sessions or streams.** A transport pinned to one
11//!   thread — a thread-per-core `io_uring` runtime, say — can implement these. The
12//!   async traits in the crate root add `MaybeSend` on top, because their futures
13//!   need it; the poll traits do not, so a `!Send` stack stays expressible all the
14//!   way down. [`Session`]'s associated stream types only require the poll halves,
15//!   so the bound cannot leak back in through them. Error types still implement the
16//!   shared crate-level [`crate::Error`] trait and retain its `MaybeSend + MaybeSync`
17//!   bounds.
18//!
19//! - **`&mut self` throughout, and no `Clone`.** A sans-I/O state machine owns its
20//!   state and mutates it in place. A `&self` surface would force every
21//!   implementation into interior mutability — an `Arc<Mutex<..>>` around the
22//!   connection, or a slot shared between callers — whether or not its own design
23//!   needs one. Taking `&mut self` gives each handle exactly one owner by
24//!   construction, which is also what makes retained in-progress state safe: a
25//!   `poll_open_uni` that claimed stream credit before returning [`Poll::Pending`]
26//!   has an unambiguous owner to resume it.
27//!
28//! Run operations concurrently by cloning the concrete session, where each clone
29//! gets its own state. `Clone` is deliberately *not* a supertrait, so a session that
30//! cannot be duplicated is still expressible.
31//!
32//! # Retaining state between calls
33//!
34//! A `poll_*` method may keep its own progress across calls, and often should —
35//! [`SendStream::poll_write_buf`] will typically have reserved send capacity by
36//! the time it returns [`Poll::Pending`], and starting over would give that back.
37//! What it must not keep is anything belonging to the *caller*:
38//!
39//! - Nothing about a buffer argument may be assumed to survive. Every call gets a
40//!   fresh one, and a caller is free to retry a pending write with a shorter buffer.
41//!   Retained progress must be reconciled against the buffer actually presented, not
42//!   the one that started the operation.
43//! - Whatever ends the stream — [`SendStream::reset`],
44//!   [`SendStream::finish`], a peer STOP_SENDING seen by
45//!   [`SendStream::poll_closed`] — must release retained progress. Nothing else
46//!   will: the guards at the top of a write return early once the stream is closed,
47//!   so no later call reaches the cleanup, and a reservation held past that point is
48//!   leaked for the life of the stream.
49//! - A resource that other handles contend for — a queue slot, a shared lock —
50//!   should not be held across a wait. A caller may abandon an operation simply by
51//!   never polling it again, and anything the retained state owns is abandoned with
52//!   it. Retain the *waiting*, not the resource.
53
54use std::task::{ready, Context, Poll};
55
56use bytes::{Buf, BufMut, Bytes, BytesMut};
57
58use crate::{Error, Stats};
59
60/// The stream pair produced by opening or accepting a bidirectional stream.
61pub type BiStreams<S> = (<S as Session>::SendStream, <S as Session>::RecvStream);
62
63/// The `poll`-based surface of a WebTransport session.
64///
65/// See the [module docs](self) for why this takes `&mut self` and carries no `Send`
66/// bound.
67pub trait Session {
68    /// The outgoing stream type. Only the poll half is required, so a `!Send`
69    /// session can hang `!Send` streams off it.
70    type SendStream: SendStream;
71
72    /// The incoming stream type. Only the poll half is required, so a `!Send`
73    /// session can hang `!Send` streams off it.
74    type RecvStream: RecvStream;
75
76    /// The error type for every operation on this session.
77    type Error: Error;
78
79    /// Poll for a unidirectional stream created by the peer.
80    fn poll_accept_uni(
81        &mut self,
82        cx: &mut Context<'_>,
83    ) -> Poll<Result<Self::RecvStream, Self::Error>>;
84
85    /// Poll for a bidirectional stream created by the peer.
86    fn poll_accept_bi(
87        &mut self,
88        cx: &mut Context<'_>,
89    ) -> Poll<Result<BiStreams<Self>, Self::Error>>;
90
91    /// Poll to open a unidirectional stream, which blocks while there are too many
92    /// concurrent streams.
93    fn poll_open_uni(
94        &mut self,
95        cx: &mut Context<'_>,
96    ) -> Poll<Result<Self::SendStream, Self::Error>>;
97
98    /// Poll to open a bidirectional stream, which blocks while there are too many
99    /// concurrent streams.
100    fn poll_open_bi(&mut self, cx: &mut Context<'_>) -> Poll<Result<BiStreams<Self>, Self::Error>>;
101
102    /// Poll to send a datagram over the network.
103    ///
104    /// Returns [`Poll::Pending`] while the transport has no room for it, so a caller
105    /// can wait for capacity rather than having the payload dropped underneath it.
106    ///
107    /// `payload` is taken by reference, not by value or as a [`Buf`]: a
108    /// [`Poll::Pending`] return means the caller retries with the same datagram, and
109    /// both of those would have consumed it. (A datagram also needs *contiguous*
110    /// bytes, and the only way to get those from a generic [`Buf`] is
111    /// [`Buf::copy_to_bytes`], which consumes.)
112    ///
113    /// Accepting a datagram is not delivery. QUIC datagrams may still be dropped:
114    /// - Network congestion.
115    /// - Random packet loss.
116    /// - Payload is larger than `max_datagram_size()`
117    /// - Peer is not receiving datagrams.
118    /// - ???
119    fn poll_send_datagram(
120        &mut self,
121        cx: &mut Context<'_>,
122        payload: &[u8],
123    ) -> Poll<Result<(), Self::Error>>;
124
125    /// Poll for a datagram from the network.
126    fn poll_recv_datagram(&mut self, cx: &mut Context<'_>) -> Poll<Result<Bytes, Self::Error>>;
127
128    /// The maximum size of a datagram that can be sent.
129    fn max_datagram_size(&self) -> usize;
130
131    /// Return the application protocol negotiated for this session, if any.
132    ///
133    /// For WebTransport over HTTP/3 this is the selected WebTransport subprotocol;
134    /// for raw QUIC it is the negotiated ALPN. Return `None` if the transport does
135    /// not negotiate either or the ALPN is not valid UTF-8. This is required rather
136    /// than defaulted: a transport that negotiates an application protocol and
137    /// forgets to report it is a silent bug, and the default hid that.
138    fn protocol(&self) -> Option<&str>;
139
140    /// Close the connection immediately with a code and reason.
141    ///
142    /// Idempotent, and deliberately infallible: closing an already-closed connection
143    /// achieved what the caller asked for, and there is nothing they could do with an
144    /// error.
145    fn close(&mut self, code: u32, reason: &str);
146
147    /// Poll until the connection is closed by either side.
148    fn poll_closed(&mut self, cx: &mut Context<'_>) -> Poll<Self::Error>;
149
150    /// Return connection-level statistics.
151    ///
152    /// Return [`crate::StatsUnavailable`] if the transport does not track them. Required
153    /// rather than defaulted for the same reason as [`protocol`](Self::protocol).
154    fn stats(&self) -> impl Stats;
155}
156
157/// The `poll`-based surface of an outgoing stream.
158///
159/// See the [module docs](self) for what a `poll_*` method may retain between calls.
160pub trait SendStream {
161    /// The error type for every operation on this stream.
162    type Error: Error;
163
164    /// Poll to write some of the buffer to the stream, returning how many bytes were
165    /// written. See [`poll_write_buf`](Self::poll_write_buf) for the partial-write
166    /// contract, which this shares.
167    fn poll_write(&mut self, cx: &mut Context<'_>, buf: &[u8]) -> Poll<Result<usize, Self::Error>>;
168
169    /// Poll to write some of the given buffer to the stream, advancing it by the
170    /// number of bytes written. This may be less than the whole buffer, so callers
171    /// loop.
172    ///
173    /// # Partial writes
174    ///
175    /// Implementations must not advance `buf` past the bytes they accepted for
176    /// sending. (Whether those bytes reach the peer is a separate matter — a reset
177    /// or a dead connection can still discard accepted bytes.) A returned
178    /// [`Poll::Pending`] must leave `buf` exactly where the accepted bytes end.
179    /// Callers race writes against other work, so a byte taken from `buf` but never
180    /// accepted becomes a silent hole in the stream, which the peer decodes as a
181    /// truncated or garbage frame. Wait for send capacity *before* consuming from
182    /// `buf`, never after.
183    ///
184    /// Override this to avoid a copy when the underlying transport can take
185    /// ownership of `buf`'s bytes — see [`Buf::copy_to_bytes`], which is free for a
186    /// [`Bytes`] source.
187    fn poll_write_buf<B: Buf>(
188        &mut self,
189        cx: &mut Context<'_>,
190        buf: &mut B,
191    ) -> Poll<Result<usize, Self::Error>> {
192        let size = ready!(self.poll_write(cx, buf.chunk()))?;
193        buf.advance(size);
194        Poll::Ready(Ok(size))
195    }
196
197    /// Set the stream's priority.
198    ///
199    /// Streams with higher values will be sent first, but are not guaranteed to
200    /// arrive first. This matches the W3C WebTransport `sendOrder` convention (and
201    /// quinn's scheduler).
202    fn set_priority(&mut self, order: u8);
203
204    /// Mark the stream as finished, erroring on any future writes.
205    ///
206    /// [`reset`](Self::reset) can still be called to abandon any queued data.
207    /// [`poll_closed`](Self::poll_closed) should resolve when the FIN is acknowledged
208    /// by the peer.
209    ///
210    /// NOTE: Quinn implicitly calls this on Drop, but it's a common footgun.
211    /// Implementations SHOULD [`reset`](Self::reset) on Drop instead.
212    fn finish(&mut self) -> Result<(), Self::Error>;
213
214    /// Immediately closes the stream and discards any remaining data.
215    ///
216    /// This translates into a RESET_STREAM QUIC code.
217    /// The peer may not receive the reset code if the stream is already closed.
218    ///
219    /// Takes `&mut self` rather than `self` even though it is terminal, so a caller
220    /// can still [`poll_closed`](Self::poll_closed) afterwards to await the peer —
221    /// and so it matches [`finish`](Self::finish), which must not consume the stream
222    /// for exactly that reason.
223    fn reset(&mut self, code: u32);
224
225    /// Poll until the stream is closed by either side.
226    ///
227    /// This includes:
228    /// - We sent a RESET_STREAM via [`reset`](Self::reset)
229    /// - We received a STOP_SENDING via [`RecvStream::stop`]
230    /// - A FIN is acknowledged by the peer via [`finish`](Self::finish)
231    ///
232    /// Some implementations do not support FIN acknowledgement, in which case this
233    /// resolves once the FIN is sent.
234    fn poll_closed(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>>;
235}
236
237/// The `poll`-based surface of an incoming stream.
238///
239/// See the [module docs](self) for what a `poll_*` method may retain between calls.
240pub trait RecvStream {
241    /// The error type for every operation on this stream.
242    type Error: Error;
243
244    /// Poll to read some data into the provided slice.
245    ///
246    /// Returns the number of bytes read, or `None` once the peer has finished the
247    /// stream. An empty `dst` reads nothing and returns `Some(0)` — asking for no
248    /// bytes is not end of stream.
249    fn poll_read(
250        &mut self,
251        cx: &mut Context<'_>,
252        dst: &mut [u8],
253    ) -> Poll<Result<Option<usize>, Self::Error>>;
254
255    /// Poll to read some data into the provided buffer, advancing it by the number
256    /// of bytes read.
257    ///
258    /// Override this to avoid a copy when the underlying transport already owns the
259    /// bytes as a [`Bytes`], which can be handed to [`BufMut::put`] directly.
260    fn poll_read_buf<B: BufMut>(
261        &mut self,
262        cx: &mut Context<'_>,
263        buf: &mut B,
264    ) -> Poll<Result<Option<usize>, Self::Error>> {
265        let len = buf.chunk_mut().len();
266
267        // A destination with no room is not a closed stream. Collapsing the two
268        // would turn "buffer full" into "stream ended", which reads as truncation.
269        if len == 0 {
270            return Poll::Ready(Ok(Some(0)));
271        }
272
273        let dst = unsafe {
274            std::mem::transmute::<&mut bytes::buf::UninitSlice, &mut [u8]>(buf.chunk_mut())
275        };
276
277        let size = match ready!(self.poll_read(cx, dst))? {
278            Some(size) if size > 0 => size,
279            Some(_) => return Poll::Ready(Ok(Some(0))),
280            None => return Poll::Ready(Ok(None)),
281        };
282
283        unsafe { buf.advance_mut(size) };
284
285        Poll::Ready(Ok(Some(size)))
286    }
287
288    /// Poll for the next chunk of data, up to `max` bytes.
289    ///
290    /// Override this when the transport can hand over a [`Bytes`] it already owns;
291    /// the default allocates and copies.
292    fn poll_read_chunk(
293        &mut self,
294        cx: &mut Context<'_>,
295        max: usize,
296    ) -> Poll<Result<Option<Bytes>, Self::Error>> {
297        // As in `poll_read_buf`: asking for nothing is not end of stream.
298        if max == 0 {
299            return Poll::Ready(Ok(Some(Bytes::new())));
300        }
301
302        // Don't allocate too much. Override this to avoid the copy, or to use a
303        // larger per-poll buffer.
304        let capacity = max.min(8 * 1024);
305        let mut buf = BytesMut::with_capacity(capacity);
306
307        // Slice to `capacity` rather than trusting the allocation: `with_capacity`
308        // promises only a lower bound, so an over-allocation would let this return
309        // more than `max`, which the method documents it won't.
310        let dst = unsafe {
311            std::mem::transmute::<&mut bytes::buf::UninitSlice, &mut [u8]>(buf.chunk_mut())
312        };
313        let dst = &mut dst[..capacity];
314
315        let size = match ready!(self.poll_read(cx, dst))? {
316            Some(size) if size > 0 => size,
317            Some(_) => return Poll::Ready(Ok(Some(Bytes::new()))),
318            None => return Poll::Ready(Ok(None)),
319        };
320
321        // The read wrote into spare capacity, so the length is still zero.
322        unsafe { buf.advance_mut(size) };
323
324        Poll::Ready(Ok(Some(buf.freeze())))
325    }
326
327    /// Send a `STOP_SENDING` QUIC code, informing the peer that no more data will be
328    /// read.
329    ///
330    /// An implementation MUST do this on Drop otherwise flow control will be leaked.
331    /// Call this method manually if you want to specify a code yourself.
332    fn stop(&mut self, code: u32);
333
334    /// Poll until the stream has been closed by either side.
335    ///
336    /// This includes:
337    /// - We received a RESET_STREAM via [`SendStream::reset`]
338    /// - We sent a STOP_SENDING via [`stop`](Self::stop)
339    /// - We received a FIN via [`SendStream::finish`] and read all data.
340    fn poll_closed(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>>;
341}