Skip to main content

moq_uring/quic/noq/
stream.rs

1//! Stream handles: thin, direct calls into the shared noq-proto connection.
2//!
3//! Single-threaded sans-IO means a write goes straight into noq's send
4//! queue (no staging copy) and a read comes straight out of its reassembly
5//! buffer; the handles just kick the driver so egress reaches the wire and
6//! park on the per-stream waiter lists the driver wakes.
7
8use std::task::{Context, Poll};
9
10use bytes::{Buf, Bytes, BytesMut};
11use moq_noq_proto::{StreamId, VarInt};
12
13use super::super::Error;
14use super::{End, Shared};
15
16/// An outgoing stream. Dropping it unfinished resets it with code 0.
17pub struct SendStream {
18	shared: Shared,
19	id: StreamId,
20	park: kio::Park,
21	/// The FIN went out; further writes are refused and `poll_closed` waits
22	/// for the acknowledgement.
23	fin: bool,
24	/// We reset the stream; it is as closed as it will ever be.
25	reset: bool,
26}
27
28impl SendStream {
29	pub(crate) fn new(shared: Shared, id: StreamId) -> Self {
30		// The driver records how this stream ends only while a handle holds
31		// it, so the handle is what announces itself.
32		shared.track(id);
33		Self {
34			shared,
35			id,
36			park: kio::Park::default(),
37			fin: false,
38			reset: false,
39		}
40	}
41
42	/// The QUIC stream id, which the WebTransport layer uses as the session id.
43	pub(crate) fn id(&self) -> u64 {
44		self.id.into()
45	}
46
47	/// Whether the send side is already terminated, so [`Drop`] would do
48	/// nothing. The WebTransport wrapper asks before mapping a reset of its
49	/// own, since a finished stream must not be reset instead.
50	pub(crate) fn ended(&self) -> bool {
51		self.fin || self.reset
52	}
53
54	/// Queue as much of `buf` as noq will take right now, without parking.
55	/// Best-effort, for the close path where nobody is left to poll.
56	pub(crate) fn try_write(&mut self, buf: &[u8]) -> usize {
57		if self.fin || self.reset {
58			return 0;
59		}
60		let n = self
61			.shared
62			.conn
63			.borrow_mut()
64			.send_stream(self.id)
65			.write(buf)
66			.unwrap_or(0);
67		if n > 0 {
68			self.shared.kick();
69		}
70		n
71	}
72
73	/// [`reset`](web_transport_trait::poll::SendStream::reset) with a
74	/// full-width code, for the WebTransport HTTP/3 error mapping.
75	pub(crate) fn reset_code(&mut self, code: u64) {
76		if self.reset {
77			return;
78		}
79		// Err means the stream is already gone, which is what we wanted.
80		let _ = self
81			.shared
82			.conn
83			.borrow_mut()
84			.send_stream(self.id)
85			.reset(VarInt::from_u64(code).unwrap_or(VarInt::MAX));
86		self.reset = true;
87		self.shared.kick();
88	}
89}
90
91impl web_transport_trait::poll::SendStream for SendStream {
92	type Error = Error;
93
94	fn poll_write(&mut self, cx: &mut Context<'_>, buf: &[u8]) -> Poll<Result<usize, Self::Error>> {
95		let waiter = self.park.hold(cx);
96		if self.fin || self.reset {
97			return Poll::Ready(Err(Error::Quic("stream already finished".to_string())));
98		}
99		if let Some(err) = self.shared.closed() {
100			return Poll::Ready(Err(err));
101		}
102		let result = self.shared.conn.borrow_mut().send_stream(self.id).write(buf);
103		match result {
104			Ok(n) => {
105				self.shared.kick();
106				Poll::Ready(Ok(n))
107			}
108			// No capacity right now; the driver wakes us when noq reports
109			// the stream writable.
110			Err(moq_noq_proto::WriteError::Blocked) => {
111				self.shared.park_writable(self.id, waiter);
112				Poll::Pending
113			}
114			Err(moq_noq_proto::WriteError::Stopped(code)) => Poll::Ready(Err(Error::Stop(code.into_inner()))),
115			Err(moq_noq_proto::WriteError::ClosedStream) => {
116				Poll::Ready(Err(Error::Quic("stream already finished".to_string())))
117			}
118		}
119	}
120
121	fn set_priority(&mut self, order: u8) {
122		// The trait (like W3C sendOrder) sends HIGHER values first, and so
123		// does noq.
124		let _ = self
125			.shared
126			.conn
127			.borrow_mut()
128			.send_stream(self.id)
129			.set_priority(i32::from(order));
130	}
131
132	fn finish(&mut self) -> Result<(), Self::Error> {
133		if self.fin || self.reset {
134			return Ok(());
135		}
136		match self.shared.conn.borrow_mut().send_stream(self.id).finish() {
137			Ok(()) => {}
138			// A STOP_SENDING beat us here. Carry the code like `poll_write`
139			// does, or `moq_net::Error::from_transport` cannot decode a
140			// routine cancellation.
141			Err(moq_noq_proto::FinishError::Stopped(code)) => {
142				self.reset = true;
143				return Err(Error::Stop(code.into_inner()));
144			}
145			// Already finished or reset, so the FIN it wanted is out.
146			Err(moq_noq_proto::FinishError::ClosedStream) => {}
147		}
148		self.fin = true;
149		self.shared.kick();
150		Ok(())
151	}
152
153	fn reset(&mut self, code: u32) {
154		self.reset_code(u64::from(code));
155	}
156
157	fn poll_closed(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
158		let waiter = self.park.hold(cx);
159		if self.reset {
160			return Poll::Ready(Ok(()));
161		}
162		// noq reports a send stream's end as an event, which the driver
163		// records: an acknowledged FIN, or the peer's STOP_SENDING.
164		match self.shared.ended(self.id) {
165			Some(End::Stopped(code)) => Poll::Ready(Err(Error::Stop(code))),
166			Some(End::Delivered) => Poll::Ready(Ok(())),
167			// The end never came. If the connection died first the caller
168			// cannot read success as "every byte arrived".
169			None => match self.shared.closed() {
170				Some(err) => Poll::Ready(Err(err)),
171				None => {
172					self.shared.park_finishing(self.id, waiter);
173					Poll::Pending
174				}
175			},
176		}
177	}
178}
179
180impl Drop for SendStream {
181	fn drop(&mut self) {
182		self.shared.forget_send(self.id);
183		if !self.fin && !self.reset {
184			let _ = self
185				.shared
186				.conn
187				.borrow_mut()
188				.send_stream(self.id)
189				.reset(VarInt::from_u32(0));
190			self.shared.kick();
191		}
192	}
193}
194
195impl std::fmt::Debug for SendStream {
196	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
197		f.debug_struct("SendStream").field("id", &self.id).finish()
198	}
199}
200
201/// How far [`RecvStream::poll_closed`] reads ahead of the application before
202/// it waits for the backlog to drain.
203const READ_AHEAD: usize = 64 * 1024;
204/// How much to ask for per read-ahead chunk.
205const READ_CHUNK: usize = 8 * 1024;
206
207/// One read out of noq's reassembly buffer.
208enum Read {
209	/// Bytes, at most as many as were asked for.
210	Chunk(Bytes),
211	/// Every byte up to the FIN has been handed over.
212	Finished,
213	/// Nothing buffered; the driver wakes us when that changes.
214	Blocked,
215	/// The peer reset the stream with this code.
216	Reset(u64),
217}
218
219/// An incoming stream. Dropping it unfinished sends STOP_SENDING with code 0.
220pub struct RecvStream {
221	shared: Shared,
222	id: StreamId,
223	park: kio::Park,
224	/// Every byte up to the FIN was read out of noq; reads report the end
225	/// once `backlog` is drained too.
226	finished: bool,
227	/// We stopped the stream; no more reads matter.
228	stopped: bool,
229	/// Bytes `poll_closed` read ahead, handed to `poll_read` before noq's.
230	backlog: BytesMut,
231}
232
233impl RecvStream {
234	pub(crate) fn new(shared: Shared, id: StreamId) -> Self {
235		Self {
236			shared,
237			id,
238			park: kio::Park::default(),
239			finished: false,
240			stopped: false,
241			backlog: BytesMut::new(),
242		}
243	}
244
245	/// Whether the read side is already terminated, so [`Drop`] would do
246	/// nothing.
247	pub(crate) fn ended(&self) -> bool {
248		self.finished || self.stopped
249	}
250
251	/// [`stop`](web_transport_trait::poll::RecvStream::stop) with a
252	/// full-width code, for the WebTransport HTTP/3 error mapping.
253	pub(crate) fn stop_code(&mut self, code: u64) {
254		self.backlog.clear();
255		if self.stopped || self.finished {
256			return;
257		}
258		// Err means the stream is already gone, which is what we wanted.
259		let _ = self
260			.shared
261			.conn
262			.borrow_mut()
263			.recv_stream(self.id)
264			.stop(VarInt::from_u64(code).unwrap_or(VarInt::MAX));
265		self.stopped = true;
266		self.shared.kick();
267	}
268
269	/// Take up to `max` bytes out of noq's reassembly buffer.
270	///
271	/// Reading is what returns the peer's flow control credit, so a read that
272	/// owes it a frame kicks the driver.
273	///
274	/// Takes the shared state rather than `&mut self` so a caller can hold a
275	/// waiter from `self.park` across the read.
276	fn read(shared: &Shared, id: StreamId, max: usize) -> Read {
277		let mut conn = shared.conn.borrow_mut();
278		let mut recv = conn.recv_stream(id);
279		let mut chunks = match recv.read(true) {
280			Ok(chunks) => chunks,
281			// The stream is gone, so everything it held is already ours.
282			Err(_) => return Read::Finished,
283		};
284		let read = match chunks.next(max) {
285			Ok(Some(chunk)) => Read::Chunk(chunk.bytes),
286			Ok(None) => Read::Finished,
287			Err(moq_noq_proto::ReadError::Blocked) => Read::Blocked,
288			Err(moq_noq_proto::ReadError::Reset(code)) => Read::Reset(code.into_inner()),
289		};
290		let transmit = chunks.finalize().should_transmit();
291		drop(conn);
292		if transmit {
293			shared.kick();
294		}
295		read
296	}
297
298	/// Move up to `dst.len()` read-ahead bytes out of the backlog.
299	///
300	/// Wakes a `poll_closed` parked at the read-ahead cap: the room it was
301	/// waiting for is what this just made.
302	fn drain(&mut self, dst: &mut [u8]) -> usize {
303		let n = dst.len().min(self.backlog.len());
304		dst[..n].copy_from_slice(&self.backlog[..n]);
305		self.backlog.advance(n);
306		if n > 0 {
307			self.shared.wake_readable(self.id);
308		}
309		n
310	}
311}
312
313impl web_transport_trait::poll::RecvStream for RecvStream {
314	type Error = Error;
315
316	fn poll_read(&mut self, cx: &mut Context<'_>, dst: &mut [u8]) -> Poll<Result<Option<usize>, Self::Error>> {
317		let waiter = self.park.hold(cx);
318		if dst.is_empty() {
319			return Poll::Ready(Ok(Some(0)));
320		}
321		if !self.backlog.is_empty() {
322			return Poll::Ready(Ok(Some(self.drain(dst))));
323		}
324		if self.finished {
325			return Poll::Ready(Ok(None));
326		}
327		match Self::read(&self.shared, self.id, dst.len()) {
328			Read::Chunk(bytes) => {
329				let n = bytes.len().min(dst.len());
330				dst[..n].copy_from_slice(&bytes[..n]);
331				Poll::Ready(Ok(Some(n)))
332			}
333			Read::Finished => {
334				self.finished = true;
335				Poll::Ready(Ok(None))
336			}
337			Read::Blocked => {
338				if let Some(err) = self.shared.closed() {
339					return Poll::Ready(Err(err));
340				}
341				self.shared.park_readable(self.id, waiter);
342				Poll::Pending
343			}
344			Read::Reset(code) => Poll::Ready(Err(Error::Reset(code))),
345		}
346	}
347
348	fn stop(&mut self, code: u32) {
349		// Giving up on the read side abandons whatever was read ahead, even
350		// when the FIN is already in and only the backlog is left.
351		self.stop_code(u64::from(code));
352	}
353
354	fn poll_closed(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
355		let waiter = self.park.hold(cx);
356		if self.finished || self.stopped {
357			return Poll::Ready(Ok(()));
358		}
359		// The FIN sits behind whatever the peer sent before it, and noq only
360		// reports the stream finished once that is read out. Waiting on
361		// readability alone would park behind bytes nobody is reading, so read
362		// ahead into the backlog `poll_read` serves first: this watch resolves
363		// without the application draining the stream, and without losing what
364		// it might still want.
365		loop {
366			if self.backlog.len() >= READ_AHEAD {
367				// Enough held: reading further would let the peer send more
368				// still, so the memory bound wins over watch liveness here.
369				// `drain` wakes this once the application takes some, and a
370				// stream nobody reads past the cap keeps its watch pending
371				// until the connection's idle timeout.
372				self.shared.park_readable(self.id, waiter);
373				return Poll::Pending;
374			}
375			match Self::read(&self.shared, self.id, READ_CHUNK) {
376				Read::Chunk(bytes) => self.backlog.extend_from_slice(&bytes),
377				Read::Finished => {
378					self.finished = true;
379					return Poll::Ready(Ok(()));
380				}
381				Read::Blocked => {
382					if let Some(err) = self.shared.closed() {
383						return Poll::Ready(Err(err));
384					}
385					self.shared.park_readable(self.id, waiter);
386					return Poll::Pending;
387				}
388				Read::Reset(code) => return Poll::Ready(Err(Error::Reset(code))),
389			}
390		}
391	}
392}
393
394impl Drop for RecvStream {
395	fn drop(&mut self) {
396		self.shared.forget_recv(self.id);
397		if !self.finished && !self.stopped {
398			let _ = self
399				.shared
400				.conn
401				.borrow_mut()
402				.recv_stream(self.id)
403				.stop(VarInt::from_u32(0));
404			self.shared.kick();
405		}
406	}
407}
408
409impl std::fmt::Debug for RecvStream {
410	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
411		f.debug_struct("RecvStream").field("id", &self.id).finish()
412	}
413}