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}