noq_proto/connection/streams/
mod.rs

1use std::{
2    collections::{BinaryHeap, hash_map},
3    io,
4};
5
6use bytes::Bytes;
7use thiserror::Error;
8use tracing::trace;
9
10use super::spaces::Retransmits;
11use crate::{
12    Dir, StreamId, VarInt,
13    connection::streams::state::{StreamRecv, get_or_insert_recv, get_or_insert_send},
14    frame,
15};
16
17mod recv;
18use recv::Recv;
19pub use recv::{Chunks, ReadError, ReadableError};
20
21mod send;
22pub(crate) use send::{ByteSlice, BytesArray, Written};
23use send::{BytesSource, Send, SendState};
24pub use send::{FinishError, WriteError};
25
26mod state;
27#[allow(unreachable_pub)] // fuzzing only
28pub use state::StreamsState;
29
30/// Access to streams
31pub struct Streams<'a> {
32    pub(super) state: &'a mut StreamsState,
33    pub(super) conn_state: &'a super::State,
34}
35
36#[allow(clippy::needless_lifetimes)] // Needed for cfg(fuzzing)
37impl<'a> Streams<'a> {
38    #[cfg(fuzzing)]
39    pub fn new(state: &'a mut StreamsState, conn_state: &'a super::State) -> Self {
40        Self { state, conn_state }
41    }
42
43    /// Open a single stream if possible
44    ///
45    /// Returns `None` if the streams in the given direction are currently exhausted.
46    pub fn open(&mut self, dir: Dir) -> Option<StreamId> {
47        if self.conn_state.is_closed() {
48            return None;
49        }
50
51        if self.state.next[dir as usize] >= self.state.max[dir as usize] {
52            self.state.streams_blocked[dir as usize] = true;
53            return None;
54        }
55
56        self.state.next[dir as usize] += 1;
57        let id = StreamId::new(self.state.side, dir, self.state.next[dir as usize] - 1);
58        self.state.insert_local(id);
59        self.state.send_streams += 1;
60        Some(id)
61    }
62
63    /// Accept a remotely initiated stream of a certain directionality, if possible
64    ///
65    /// Returns `None` if there are no new incoming streams for this connection.
66    /// Has no impact on the data flow-control or stream concurrency limits.
67    pub fn accept(&mut self, dir: Dir) -> Option<StreamId> {
68        if self.state.next_remote[dir as usize] == self.state.next_reported_remote[dir as usize] {
69            return None;
70        }
71
72        let x = self.state.next_reported_remote[dir as usize];
73        self.state.next_reported_remote[dir as usize] = x + 1;
74        if dir == Dir::Bi {
75            self.state.send_streams += 1;
76        }
77
78        Some(StreamId::new(!self.state.side, dir, x))
79    }
80
81    #[cfg(fuzzing)]
82    pub fn state(&mut self) -> &mut StreamsState {
83        self.state
84    }
85
86    /// The number of streams that may have unacknowledged data.
87    pub fn send_streams(&self) -> usize {
88        self.state.send_streams
89    }
90
91    /// The number of remotely initiated open streams of a certain directionality.
92    ///
93    /// Includes remotely initiated streams, which have not been accepted via
94    /// [`accept`](Self::accept). These streams count against the respective concurrency limit
95    /// reported by
96    /// [`Connection::max_concurrent_streams`](super::Connection::max_concurrent_streams).
97    pub fn remote_open_streams(&self, dir: Dir) -> u64 {
98        // total opened - total closed = total opened - (total permitted - total permitted unclosed)
99        self.state.next_remote[dir as usize]
100            - (self.state.max_remote[dir as usize]
101                - self.state.allocated_remote_count[dir as usize])
102    }
103}
104
105/// Access to streams
106pub struct RecvStream<'a> {
107    pub(super) id: StreamId,
108    pub(super) state: &'a mut StreamsState,
109    pub(super) pending: &'a mut Retransmits,
110}
111
112impl RecvStream<'_> {
113    /// Whether this stream is still in ordered read mode.
114    ///
115    /// A stream switches permanently to unordered mode when [`Self::read`] is called with
116    /// `ordered` set to `false`.
117    pub fn is_ordered(&self) -> Result<bool, ClosedStream> {
118        let Some(stream) = self.state.recv.get(&self.id) else {
119            return Err(ClosedStream { _private: () });
120        };
121        let Some(stream) = stream.as_ref().and_then(StreamRecv::as_open_recv) else {
122            return Ok(true);
123        };
124        if stream.stopped {
125            return Err(ClosedStream { _private: () });
126        }
127        Ok(stream.assembler.is_ordered())
128    }
129
130    /// Read from the given recv stream
131    ///
132    /// `max_length` limits the maximum size of the returned `Bytes` value; passing `usize::MAX`
133    /// will yield the best performance. `ordered` will make sure the returned chunk's offset will
134    /// have an offset exactly equal to the previously returned offset plus the previously returned
135    /// bytes' length.
136    ///
137    /// Yields `Ok(None)` if the stream was finished. Otherwise, yields a segment of data and its
138    /// offset in the stream. If `ordered` is `false`, segments may be received in any order, and
139    /// the `Chunk`'s `offset` field can be used to determine ordering in the caller.
140    ///
141    /// While most applications will prefer to consume stream data in order, unordered reads can
142    /// improve performance when packet loss occurs and data cannot be retransmitted before the flow
143    /// control window is filled. On any given stream, you can switch from ordered to unordered
144    /// reads, but ordered reads on streams that have seen previous unordered reads will return
145    /// `ReadError::IllegalOrderedRead`.
146    pub fn read(&mut self, ordered: bool) -> Result<Chunks<'_>, ReadableError> {
147        Chunks::new(self.id, ordered, self.state, self.pending)
148    }
149
150    /// Stop accepting data on the given receive stream
151    ///
152    /// Discards unread data and notifies the peer to stop transmitting. Once stopped, further
153    /// attempts to operate on a stream will yield `ClosedStream` errors.
154    pub fn stop(&mut self, error_code: VarInt) -> Result<(), ClosedStream> {
155        let mut entry = match self.state.recv.entry(self.id) {
156            hash_map::Entry::Occupied(s) => s,
157            hash_map::Entry::Vacant(_) => return Err(ClosedStream { _private: () }),
158        };
159        let stream = get_or_insert_recv(self.state.stream_receive_window)(entry.get_mut());
160
161        let (read_credits, stop_sending) = stream.stop()?;
162        if stop_sending.should_transmit() {
163            self.pending.stop_sending.push(frame::StopSending {
164                id: self.id,
165                error_code,
166            });
167        }
168
169        // We need to keep stopped streams around until they're finished or reset so we can update
170        // connection-level flow control to account for discarded data. Otherwise, we can discard
171        // state immediately.
172        if !stream.final_offset_unknown() {
173            let recv = entry.remove().expect("must have recv when stopping");
174            self.state.stream_recv_freed(self.id, recv);
175        }
176
177        if self.state.add_read_credits(read_credits).should_transmit() {
178            self.pending.max_data = true;
179        }
180
181        Ok(())
182    }
183
184    /// Returns the number of bytes read from this stream.
185    ///
186    /// This is the offset of the next byte to be read, i.e. the length of the contiguous
187    /// prefix of the stream consumed by the application.
188    pub fn bytes_read(&self) -> Result<u64, ClosedStream> {
189        let recv = self
190            .state
191            .recv
192            .get(&self.id)
193            .and_then(|s| s.as_ref())
194            .and_then(|s| s.as_open_recv())
195            .ok_or(ClosedStream { _private: () })?;
196        Ok(recv.assembler.bytes_read())
197    }
198
199    /// Check whether this stream has been reset by the peer, returning the reset error code if so
200    ///
201    /// After returning `Ok(Some(_))` once, stream state will be discarded and all future calls will
202    /// return `Err(ClosedStream)`.
203    pub fn received_reset(&mut self) -> Result<Option<VarInt>, ClosedStream> {
204        let hash_map::Entry::Occupied(entry) = self.state.recv.entry(self.id) else {
205            return Err(ClosedStream { _private: () });
206        };
207        let Some(s) = entry.get().as_ref().and_then(|s| s.as_open_recv()) else {
208            return Ok(None);
209        };
210        if s.stopped {
211            return Err(ClosedStream { _private: () });
212        }
213        let Some(code) = s.reset_code() else {
214            return Ok(None);
215        };
216
217        // Clean up state after application observes the reset, since there's no reason for the
218        // application to attempt to read or stop the stream once it knows it's reset
219        let (_, recv) = entry.remove_entry();
220        self.state
221            .stream_recv_freed(self.id, recv.expect("must have recv on reset"));
222        self.state.queue_max_stream_id(self.pending);
223
224        Ok(Some(code))
225    }
226}
227
228/// Access to streams
229pub struct SendStream<'a> {
230    pub(super) id: StreamId,
231    pub(super) state: &'a mut StreamsState,
232    pub(super) pending: &'a mut Retransmits,
233    pub(super) conn_state: &'a super::State,
234}
235
236#[allow(clippy::needless_lifetimes)] // Needed for cfg(fuzzing)
237impl<'a> SendStream<'a> {
238    #[cfg(fuzzing)]
239    pub fn new(
240        id: StreamId,
241        state: &'a mut StreamsState,
242        pending: &'a mut Retransmits,
243        conn_state: &'a super::State,
244    ) -> Self {
245        Self {
246            id,
247            state,
248            pending,
249            conn_state,
250        }
251    }
252
253    /// Send data on the given stream
254    ///
255    /// Returns the number of bytes successfully written.
256    pub fn write(&mut self, data: &[u8]) -> Result<usize, WriteError> {
257        Ok(self.write_source(&mut ByteSlice::from_slice(data))?.bytes)
258    }
259
260    /// Send data on the given stream
261    ///
262    /// Returns the number of bytes written and advances the provided `Bytes`
263    /// slice, removing all completely written chunks.
264    ///
265    /// Note that this method might also write a partial chunk. In this case
266    /// the chunk will be advanced and contain only non-written data after the call.
267    pub fn write_chunks(&mut self, data: &mut &mut [Bytes]) -> Result<usize, WriteError> {
268        let written = self.write_source(&mut BytesArray::from_chunks(data))?;
269        *data = &mut std::mem::take(data)[written.chunks..];
270        Ok(written.bytes)
271    }
272
273    fn write_source<'b, B: BytesSource<'b>>(
274        &mut self,
275        source: &'b mut B,
276    ) -> Result<Written, WriteError> {
277        if self.conn_state.is_closed() {
278            trace!(%self.id, "write blocked; connection draining");
279            return Err(WriteError::Blocked);
280        }
281
282        let limit = self.state.write_limit();
283
284        let max_send_data = self.state.max_send_data(self.id);
285
286        let stream = self
287            .state
288            .send
289            .get_mut(&self.id)
290            .map(get_or_insert_send(max_send_data))
291            .ok_or(WriteError::ClosedStream)?;
292
293        if limit == 0 {
294            trace!(
295                stream = %self.id, max_data = self.state.max_data, data_sent = self.state.data_sent,
296                "write blocked by connection-level flow control or send window"
297            );
298            if !stream.connection_blocked {
299                stream.connection_blocked = true;
300                self.state.connection_blocked.push(self.id);
301            }
302            return Err(WriteError::Blocked);
303        }
304
305        let was_pending = stream.is_pending();
306        let written = stream.write(source, limit)?;
307        self.state.data_sent += written.bytes as u64;
308        self.state.unacked_data += written.bytes as u64;
309        trace!(stream = %self.id, "wrote {} bytes", written.bytes);
310        if !was_pending {
311            self.state.pending.push_pending(self.id, stream.priority);
312        }
313        Ok(written)
314    }
315
316    /// Check if this stream was stopped, get the reason if it was
317    pub fn stopped(&self) -> Result<Option<VarInt>, ClosedStream> {
318        match self.state.send.get(&self.id).as_ref() {
319            Some(Some(s)) => Ok(s.stop_reason),
320            Some(None) => Ok(None),
321            None => Err(ClosedStream { _private: () }),
322        }
323    }
324
325    /// Finish a send stream, signalling that no more data will be sent.
326    ///
327    /// If this fails, no [`StreamEvent::Finished`] will be generated.
328    ///
329    /// [`StreamEvent::Finished`]: crate::StreamEvent::Finished
330    pub fn finish(&mut self) -> Result<(), FinishError> {
331        let max_send_data = self.state.max_send_data(self.id);
332        let stream = self
333            .state
334            .send
335            .get_mut(&self.id)
336            .map(get_or_insert_send(max_send_data))
337            .ok_or(FinishError::ClosedStream)?;
338
339        let was_pending = stream.is_pending();
340        stream.finish()?;
341        if !was_pending {
342            self.state.pending.push_pending(self.id, stream.priority);
343        }
344
345        Ok(())
346    }
347
348    /// Abandon transmitting data on a stream
349    ///
350    /// # Panics
351    /// - when applied to a receive stream
352    pub fn reset(&mut self, error_code: VarInt) -> Result<(), ClosedStream> {
353        let max_send_data = self.state.max_send_data(self.id);
354        let stream = self
355            .state
356            .send
357            .get_mut(&self.id)
358            .map(get_or_insert_send(max_send_data))
359            .ok_or(ClosedStream { _private: () })?;
360
361        if matches!(stream.state, SendState::ResetSent) {
362            // Redundant reset call
363            return Err(ClosedStream { _private: () });
364        }
365
366        // Restore the portion of the send window consumed by the data that we aren't about to
367        // send. We leave flow control alone because the peer's responsible for issuing additional
368        // credit based on the final offset communicated in the RESET_STREAM frame we send.
369        self.state.unacked_data -= stream.pending.unacked();
370        stream.reset();
371        self.pending.reset_stream.push((self.id, error_code));
372
373        // Don't reopen an already-closed stream we haven't forgotten yet
374        Ok(())
375    }
376
377    /// Set the priority of a stream
378    ///
379    /// # Panics
380    /// - when applied to a receive stream
381    pub fn set_priority(&mut self, priority: i32) -> Result<(), ClosedStream> {
382        let max_send_data = self.state.max_send_data(self.id);
383        let stream = self
384            .state
385            .send
386            .get_mut(&self.id)
387            .map(get_or_insert_send(max_send_data))
388            .ok_or(ClosedStream { _private: () })?;
389
390        stream.priority = priority;
391        Ok(())
392    }
393
394    /// Get the priority of a stream
395    ///
396    /// # Panics
397    /// - when applied to a receive stream
398    pub fn priority(&self) -> Result<i32, ClosedStream> {
399        let stream = self
400            .state
401            .send
402            .get(&self.id)
403            .ok_or(ClosedStream { _private: () })?;
404
405        Ok(stream.as_ref().map(|s| s.priority).unwrap_or_default())
406    }
407}
408
409/// A queue of streams with pending outgoing data, sorted by priority
410struct PendingStreamsQueue {
411    streams: BinaryHeap<PendingStream>,
412    /// The next stream to write out. This is `Some` when `TransportConfig::send_fairness(false)`
413    /// and writing a stream is interrupted while the stream still has some pending data. See
414    /// `reinsert_pending()`.
415    next: Option<PendingStream>,
416    /// A monotonically decreasing counter, used to implement round-robin scheduling for streams of
417    /// the same priority. Underflowing is not a practical concern, as it is initialized to
418    /// u64::MAX and only decremented by 1 in `push_pending`
419    recency: u64,
420}
421
422impl PendingStreamsQueue {
423    fn new() -> Self {
424        Self {
425            streams: BinaryHeap::new(),
426            next: None,
427            recency: u64::MAX,
428        }
429    }
430
431    /// Reinsert a stream that was pending and still contains unsent data.
432    fn reinsert_pending(&mut self, id: StreamId, priority: i32) {
433        assert!(self.next.is_none());
434
435        self.next = Some(PendingStream {
436            priority,
437            recency: self.recency, // the value here doesn't really matter
438            id,
439        });
440    }
441
442    /// Push a pending stream ID with the given priority, queued after any already-queued streams
443    /// for the priority
444    fn push_pending(&mut self, id: StreamId, priority: i32) {
445        // Note that in the case where fairness is disabled, if we have a reinserted stream we don't
446        // bump it even if priority > next.priority. In order to minimize fragmentation we
447        // always try to complete a stream once part of it has been written.
448
449        // As the recency counter is monotonically decreasing, we know that using its value to sort
450        // this stream will queue it after all other queued streams of the same priority.
451        // This is enough to implement round-robin scheduling for streams that are still pending
452        // even after being handled, as in that case they are removed from the `BinaryHeap`,
453        // handled, and then immediately reinserted.
454        self.recency -= 1;
455        self.streams.push(PendingStream {
456            priority,
457            recency: self.recency,
458            id,
459        });
460    }
461
462    fn pop(&mut self) -> Option<PendingStream> {
463        self.next.take().or_else(|| self.streams.pop())
464    }
465
466    fn clear(&mut self) {
467        self.next = None;
468        self.streams.clear();
469    }
470
471    fn iter(&self) -> impl Iterator<Item = &PendingStream> {
472        self.next.iter().chain(self.streams.iter())
473    }
474
475    #[cfg(test)]
476    fn len(&self) -> usize {
477        self.streams.len() + self.next.is_some() as usize
478    }
479}
480
481/// The [`StreamId`] of a stream with pending data queued, ordered by its priority and recency
482#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
483struct PendingStream {
484    /// The priority of the stream
485    // Note that this field should be kept above the `recency` field, in order for the `Ord` derive
486    // to be correct (See https://doc.rust-lang.org/stable/std/cmp/trait.Ord.html#derivable)
487    priority: i32,
488    /// A tie-breaker for streams of the same priority, used to improve fairness by implementing
489    /// round-robin scheduling: Larger values are prioritized, so it is initialised to
490    /// `u64::MAX`, and when a stream writes data, we know that it currently has the highest
491    /// recency value, so it is deprioritized by setting its recency to 1 less than the
492    /// previous lowest recency value, such that all other streams of this priority will get
493    /// processed once before we get back round to this one
494    recency: u64,
495    /// The ID of the stream
496    // The way this type is used ensures that every instance has a unique `recency` value, so this
497    // field should be kept below the `priority` and `recency` fields, so that it does not
498    // interfere with the behaviour of the `Ord` derive
499    id: StreamId,
500}
501
502/// Application events about streams
503#[derive(Debug, PartialEq, Eq)]
504pub enum StreamEvent {
505    /// One or more new streams has been opened and might be readable
506    Opened {
507        /// Directionality for which streams have been opened
508        dir: Dir,
509    },
510    /// A currently open stream likely has data or errors waiting to be read
511    Readable {
512        /// Which stream is now readable
513        id: StreamId,
514    },
515    /// A formerly write-blocked stream might be ready for a write or have been stopped
516    ///
517    /// Only generated for streams that are currently open.
518    Writable {
519        /// Which stream is now writable
520        id: StreamId,
521    },
522    /// A finished stream has been fully acknowledged or stopped
523    Finished {
524        /// Which stream has been finished
525        id: StreamId,
526    },
527    /// The peer asked us to stop sending on an outgoing stream
528    Stopped {
529        /// Which stream has been stopped
530        id: StreamId,
531        /// Error code supplied by the peer
532        error_code: VarInt,
533    },
534    /// At least one new stream of a certain directionality may be opened
535    Available {
536        /// Directionality for which streams are newly available
537        dir: Dir,
538    },
539}
540
541/// Indicates whether a frame needs to be transmitted
542///
543/// This type wraps around bool and uses the `#[must_use]` attribute in order
544/// to prevent accidental loss of the frame transmission requirement.
545#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
546#[must_use = "A frame might need to be enqueued"]
547pub struct ShouldTransmit(bool);
548
549impl ShouldTransmit {
550    /// Returns whether a frame should be transmitted
551    pub fn should_transmit(self) -> bool {
552        self.0
553    }
554}
555
556/// Error indicating that a stream has not been opened or has already been finished or reset
557#[derive(Debug, Default, Error, Clone, PartialEq, Eq)]
558#[error("closed stream")]
559pub struct ClosedStream {
560    _private: (),
561}
562
563impl From<ClosedStream> for io::Error {
564    fn from(x: ClosedStream) -> Self {
565        Self::new(io::ErrorKind::NotConnected, x)
566    }
567}
568
569#[derive(Debug, Copy, Clone, Eq, PartialEq)]
570enum StreamHalf {
571    Send,
572    Recv,
573}
574
575/// A helper trait to unify Bytes, `Vec<u8>` and `&[u8]` as sources of bytes
576pub(super) trait BytesOrSlice<'a>: AsRef<[u8]> + 'a {
577    fn len(&self) -> usize {
578        self.as_ref().len()
579    }
580    fn is_empty(&self) -> bool {
581        self.as_ref().is_empty()
582    }
583    fn into_bytes(self) -> Bytes;
584}
585
586impl BytesOrSlice<'_> for Bytes {
587    fn into_bytes(self) -> Bytes {
588        self
589    }
590}
591
592impl<'a> BytesOrSlice<'a> for &'a [u8] {
593    fn into_bytes(self) -> Bytes {
594        Bytes::copy_from_slice(self)
595    }
596}