Skip to main content

ant_quic/connection/streams/
recv.rs

1// Copyright 2024 Saorsa Labs Ltd.
2//
3// This Saorsa Network Software is licensed under the General Public License (GPL), version 3.
4// Please see the file LICENSE-GPL, or visit <http://www.gnu.org/licenses/> for the full text.
5//
6// Full details available at https://saorsalabs.com/licenses
7
8use std::collections::hash_map::Entry;
9use std::mem;
10
11use thiserror::Error;
12use tracing::debug;
13
14use super::state::get_or_insert_recv;
15use super::{ClosedStream, Retransmits, ShouldTransmit, StreamId, StreamsState};
16use crate::connection::assembler::{Assembler, Chunk, IllegalOrderedRead};
17use crate::connection::streams::state::StreamRecv;
18use crate::{TransportError, VarInt, frame};
19
20#[derive(Debug, Default)]
21pub(super) struct Recv {
22    // NB: when adding or removing fields, remember to update `reinit`.
23    state: RecvState,
24    pub(super) assembler: Assembler,
25    sent_max_stream_data: u64,
26    pub(super) end: u64,
27    pub(super) stopped: bool,
28}
29
30impl Recv {
31    pub(super) fn new(initial_max_data: u64) -> Box<Self> {
32        Box::new(Self {
33            state: RecvState::default(),
34            assembler: Assembler::new(),
35            sent_max_stream_data: initial_max_data,
36            end: 0,
37            stopped: false,
38        })
39    }
40
41    /// Reset to the initial state
42    pub(super) fn reinit(&mut self, initial_max_data: u64) {
43        self.state = RecvState::default();
44        self.assembler.reinit();
45        self.sent_max_stream_data = initial_max_data;
46        self.end = 0;
47        self.stopped = false;
48    }
49
50    /// Process a STREAM frame
51    ///
52    /// Return value is `(number_of_new_bytes_ingested, stream_is_closed)`
53    pub(super) fn ingest(
54        &mut self,
55        frame: frame::Stream,
56        payload_len: usize,
57        received: u64,
58        max_data: u64,
59    ) -> Result<(u64, bool), TransportError> {
60        let end = frame.offset + frame.data.len() as u64;
61        if end >= 2u64.pow(62) {
62            return Err(TransportError::FLOW_CONTROL_ERROR(
63                "maximum stream offset too large",
64            ));
65        }
66
67        if let Some(final_offset) = self.final_offset() {
68            if end > final_offset || (frame.fin && end != final_offset) {
69                debug!(end, final_offset, "final size error");
70                return Err(TransportError::FINAL_SIZE_ERROR(""));
71            }
72        }
73
74        let new_bytes = self.credit_consumed_by(end, received, max_data)?;
75
76        // Stopped streams don't need to wait for the actual data, they just need to know
77        // how much there was.
78        if frame.fin && !self.stopped {
79            if let RecvState::Recv { ref mut size } = self.state {
80                *size = Some(end);
81            }
82        }
83
84        self.end = self.end.max(end);
85        // Don't bother storing data or releasing stream-level flow control credit if the stream's
86        // already stopped
87        if !self.stopped {
88            self.assembler
89                .insert(frame.offset, frame.data, payload_len)
90                .map_err(|_| TransportError::INTERNAL_ERROR("too many gaps in stream buffer"))?;
91        }
92
93        Ok((new_bytes, frame.fin && self.stopped))
94    }
95
96    pub(super) fn stop(&mut self) -> Result<(u64, ShouldTransmit), ClosedStream> {
97        if self.stopped {
98            return Err(ClosedStream { _private: () });
99        }
100
101        self.stopped = true;
102        self.assembler.clear();
103        // Issue flow control credit for unread data
104        let read_credits = self.end - self.assembler.bytes_read();
105        // This may send a spurious STOP_SENDING if we've already received all data, but it's a bit
106        // fiddly to distinguish that from the case where we've received a FIN but are missing some
107        // data that the peer might still be trying to retransmit, in which case a STOP_SENDING is
108        // still useful.
109        Ok((read_credits, ShouldTransmit(self.is_receiving())))
110    }
111
112    /// Returns the window that should be advertised in a `MAX_STREAM_DATA` frame
113    ///
114    /// The method returns a tuple which consists of the window that should be
115    /// announced, as well as a boolean parameter which indicates if a new
116    /// transmission of the value is recommended. If the boolean value is
117    /// `false` the new window should only be transmitted if a previous transmission
118    /// had failed.
119    pub(super) fn max_stream_data(&mut self, stream_receive_window: u64) -> (u64, ShouldTransmit) {
120        let max_stream_data = self.assembler.bytes_read() + stream_receive_window;
121
122        // Only announce a window update if it's significant enough
123        // to make it worthwhile sending a MAX_STREAM_DATA frame.
124        // We use here a fraction of the configured stream receive window to make
125        // the decision, and accommodate for streams using bigger windows requiring
126        // less updates. A fixed size would also work - but it would need to be
127        // smaller than `stream_receive_window` in order to make sure the stream
128        // does not get stuck.
129        let diff = max_stream_data - self.sent_max_stream_data;
130        let transmit = self.can_send_flow_control() && diff >= (stream_receive_window / 8);
131        (max_stream_data, ShouldTransmit(transmit))
132    }
133
134    /// Records that a `MAX_STREAM_DATA` announcing a certain window was sent
135    ///
136    /// This will suppress enqueuing further `MAX_STREAM_DATA` frames unless
137    /// either the previous transmission was not acknowledged or the window
138    /// further increased.
139    pub(super) fn record_sent_max_stream_data(&mut self, sent_value: u64) {
140        if sent_value > self.sent_max_stream_data {
141            self.sent_max_stream_data = sent_value;
142        }
143    }
144
145    /// Whether the total amount of data that the peer will send on this stream is unknown
146    ///
147    /// True until we've received either a reset or the final frame.
148    ///
149    /// Implies that the sender might benefit from stream-level flow control updates, and we might
150    /// need to issue connection-level flow control updates due to flow control budget use by this
151    /// stream in the future, even if it's been stopped.
152    pub(super) fn final_offset_unknown(&self) -> bool {
153        matches!(self.state, RecvState::Recv { size: None })
154    }
155
156    /// Whether stream-level flow control updates should be sent for this stream
157    pub(super) fn can_send_flow_control(&self) -> bool {
158        // Stream-level flow control is redundant if the sender has already sent the whole stream,
159        // and moot if we no longer want data on this stream.
160        self.final_offset_unknown() && !self.stopped
161    }
162
163    /// Whether data is still being accepted from the peer
164    pub(super) fn is_receiving(&self) -> bool {
165        matches!(self.state, RecvState::Recv { .. })
166    }
167
168    fn final_offset(&self) -> Option<u64> {
169        match self.state {
170            RecvState::Recv { size } => size,
171            RecvState::ResetRecvd { size, .. } => Some(size),
172        }
173    }
174
175    /// Returns `false` iff the reset was redundant
176    pub(super) fn reset(
177        &mut self,
178        error_code: VarInt,
179        final_offset: VarInt,
180        received: u64,
181        max_data: u64,
182    ) -> Result<bool, TransportError> {
183        // Validate final_offset
184        if let Some(offset) = self.final_offset() {
185            if offset != final_offset.into_inner() {
186                return Err(TransportError::FINAL_SIZE_ERROR("inconsistent value"));
187            }
188        } else if self.end > u64::from(final_offset) {
189            return Err(TransportError::FINAL_SIZE_ERROR(
190                "lower than high water mark",
191            ));
192        }
193        self.credit_consumed_by(final_offset.into(), received, max_data)?;
194
195        if matches!(self.state, RecvState::ResetRecvd { .. }) {
196            return Ok(false);
197        }
198        self.state = RecvState::ResetRecvd {
199            size: final_offset.into(),
200            error_code,
201        };
202        // Nuke buffers so that future reads fail immediately, which ensures future reads don't
203        // issue flow control credit redundant to that already issued. We could instead special-case
204        // reset streams during read, but it's unclear if there's any benefit to retaining data for
205        // reset streams.
206        self.assembler.clear();
207        Ok(true)
208    }
209
210    pub(super) fn reset_code(&self) -> Option<VarInt> {
211        match self.state {
212            RecvState::ResetRecvd { error_code, .. } => Some(error_code),
213            _ => None,
214        }
215    }
216
217    /// Compute the amount of flow control credit consumed, or return an error if more was consumed
218    /// than issued
219    fn credit_consumed_by(
220        &self,
221        offset: u64,
222        received: u64,
223        max_data: u64,
224    ) -> Result<u64, TransportError> {
225        let prev_end = self.end;
226        let new_bytes = offset.saturating_sub(prev_end);
227        if offset > self.sent_max_stream_data || received + new_bytes > max_data {
228            debug!(
229                received,
230                new_bytes,
231                max_data,
232                offset,
233                stream_max_data = self.sent_max_stream_data,
234                "flow control error"
235            );
236            return Err(TransportError::FLOW_CONTROL_ERROR(""));
237        }
238
239        Ok(new_bytes)
240    }
241}
242
243/// Chunks returned from [`RecvStream::read()`][crate::RecvStream::read].
244///
245/// ### Note: Finalization Needed
246/// Bytes read from the stream are not released from the congestion window until
247/// either [`Self::finalize()`] is called, or this type is dropped.
248///
249/// It is recommended that you call [`Self::finalize()`] because it returns a flag
250/// telling you whether reading from the stream has resulted in the need to transmit a packet.
251///
252/// If this type is leaked, the stream will remain blocked on the remote peer until
253/// another read from the stream is done.
254pub struct Chunks<'a> {
255    id: StreamId,
256    ordered: bool,
257    streams: &'a mut StreamsState,
258    pending: &'a mut Retransmits,
259    state: ChunksState,
260    read: u64,
261}
262
263impl<'a> Chunks<'a> {
264    pub(super) fn new(
265        id: StreamId,
266        ordered: bool,
267        streams: &'a mut StreamsState,
268        pending: &'a mut Retransmits,
269    ) -> Result<Self, ReadableError> {
270        let mut entry = match streams.recv.entry(id) {
271            Entry::Occupied(entry) => entry,
272            Entry::Vacant(_) => return Err(ReadableError::ClosedStream),
273        };
274
275        let mut recv =
276            match get_or_insert_recv(streams.stream_receive_window)(entry.get_mut()).stopped {
277                true => return Err(ReadableError::ClosedStream),
278                false => entry.remove().unwrap().into_inner(), // this can't fail due to the previous get_or_insert_with
279            };
280
281        recv.assembler.ensure_ordering(ordered)?;
282        Ok(Self {
283            id,
284            ordered,
285            streams,
286            pending,
287            state: ChunksState::Readable(recv),
288            read: 0,
289        })
290    }
291
292    /// Next
293    ///
294    /// Should call finalize() when done calling this.
295    pub fn next(&mut self, max_length: usize) -> Result<Option<Chunk>, ReadError> {
296        let rs = match self.state {
297            ChunksState::Readable(ref mut rs) => rs,
298            ChunksState::Reset(error_code) => {
299                return Err(ReadError::Reset(error_code));
300            }
301            ChunksState::Finished => {
302                return Ok(None);
303            }
304            ChunksState::Finalized => panic!("must not call next() after finalize()"),
305        };
306
307        if let Some(chunk) = rs.assembler.read(max_length, self.ordered) {
308            self.read += chunk.bytes.len() as u64;
309            return Ok(Some(chunk));
310        }
311
312        match rs.state {
313            RecvState::ResetRecvd { error_code, .. } => {
314                debug_assert_eq!(self.read, 0, "reset streams have empty buffers");
315                let state = mem::replace(&mut self.state, ChunksState::Reset(error_code));
316                // At this point if we have `rs` self.state must be `ChunksState::Readable`
317                let recv = match state {
318                    ChunksState::Readable(recv) => StreamRecv::Open(recv),
319                    _ => unreachable!("state must be ChunkState::Readable"),
320                };
321                self.streams.stream_recv_freed(self.id, recv);
322                Err(ReadError::Reset(error_code))
323            }
324            RecvState::Recv { size } => {
325                if size == Some(rs.end) && rs.assembler.bytes_read() == rs.end {
326                    let state = mem::replace(&mut self.state, ChunksState::Finished);
327                    // At this point if we have `rs` self.state must be `ChunksState::Readable`
328                    let recv = match state {
329                        ChunksState::Readable(recv) => StreamRecv::Open(recv),
330                        _ => unreachable!("state must be ChunkState::Readable"),
331                    };
332                    self.streams.stream_recv_freed(self.id, recv);
333                    Ok(None)
334                } else {
335                    // We don't need a distinct `ChunksState` variant for a blocked stream because
336                    // retrying a read harmlessly re-traces our steps back to returning
337                    // `Err(Blocked)` again. The buffers can't refill and the stream's own state
338                    // can't change so long as this `Chunks` exists.
339                    Err(ReadError::Blocked)
340                }
341            }
342        }
343    }
344
345    /// Mark the read data as consumed from the stream.
346    ///
347    /// The number of read bytes will be released from the congestion window,
348    /// allowing the remote peer to send more data if it was previously blocked.
349    ///
350    /// If [`ShouldTransmit::should_transmit()`] returns `true`,
351    /// a packet needs to be sent to the peer informing them that the stream is unblocked.
352    /// This means that you should call [`Connection::poll_transmit()`][crate::Connection::poll_transmit]
353    /// and send the returned packet as soon as is reasonable, to unblock the remote peer.
354    pub fn finalize(mut self) -> ShouldTransmit {
355        self.finalize_inner()
356    }
357
358    fn finalize_inner(&mut self) -> ShouldTransmit {
359        let state = mem::replace(&mut self.state, ChunksState::Finalized);
360        if let ChunksState::Finalized = state {
361            // Noop on repeated calls
362            return ShouldTransmit(false);
363        }
364
365        // We issue additional stream ID credit after the application is notified that a previously
366        // open stream has finished or been reset and we've therefore disposed of its state, as
367        // recorded by `stream_freed` calls in `next`.
368        let mut should_transmit = self.streams.queue_max_stream_id(self.pending);
369
370        // If the stream hasn't finished, we may need to issue stream-level flow control credit
371        if let ChunksState::Readable(mut rs) = state {
372            let (_, max_stream_data) = rs.max_stream_data(self.streams.stream_receive_window);
373            should_transmit |= max_stream_data.0;
374            if max_stream_data.0 {
375                self.pending.max_stream_data.insert(self.id);
376            }
377            // Return the stream to storage for future use
378            self.streams
379                .recv
380                .insert(self.id, Some(StreamRecv::Open(rs)));
381        }
382
383        // Issue connection-level flow control credit for any data we read regardless of state
384        let max_data = self.streams.add_read_credits(self.read);
385        self.pending.max_data |= max_data.0;
386        should_transmit |= max_data.0;
387        ShouldTransmit(should_transmit)
388    }
389}
390
391impl Drop for Chunks<'_> {
392    fn drop(&mut self) {
393        let _ = self.finalize_inner();
394    }
395}
396
397enum ChunksState {
398    Readable(Box<Recv>),
399    Reset(VarInt),
400    Finished,
401    Finalized,
402}
403
404/// Errors triggered when reading from a recv stream
405#[derive(Debug, Error, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
406pub enum ReadError {
407    /// No more data is currently available on this stream.
408    ///
409    /// If more data on this stream is received from the peer, an `Event::StreamReadable` will be
410    /// generated for this stream, indicating that retrying the read might succeed.
411    #[error("blocked")]
412    Blocked,
413    /// The peer abandoned transmitting data on this stream.
414    ///
415    /// Carries an application-defined error code.
416    #[error("reset by peer: code {0}")]
417    Reset(VarInt),
418    /// The stream has been closed due to connection error
419    #[error("stream closed due to connection error")]
420    ConnectionClosed,
421}
422
423/// Errors triggered when opening a recv stream for reading
424#[derive(Debug, Error, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
425pub enum ReadableError {
426    /// The stream has not been opened or was already stopped, finished, or reset
427    #[error("closed stream")]
428    ClosedStream,
429    /// Attempted an ordered read following an unordered read
430    ///
431    /// Performing an unordered read allows discontinuities to arise in the receive buffer of a
432    /// stream which cannot be recovered, making further ordered reads impossible.
433    #[error("ordered read after unordered read")]
434    IllegalOrderedRead,
435    /// The stream has been closed due to connection error
436    #[error("stream closed due to connection error")]
437    ConnectionClosed,
438}
439
440impl From<IllegalOrderedRead> for ReadableError {
441    fn from(_: IllegalOrderedRead) -> Self {
442        Self::IllegalOrderedRead
443    }
444}
445
446#[derive(Debug, Copy, Clone, Eq, PartialEq)]
447enum RecvState {
448    Recv { size: Option<u64> },
449    ResetRecvd { size: u64, error_code: VarInt },
450}
451
452impl Default for RecvState {
453    fn default() -> Self {
454        Self::Recv { size: None }
455    }
456}
457
458#[cfg(test)]
459mod tests {
460    use bytes::Bytes;
461
462    use crate::{Dir, Side};
463
464    use super::*;
465
466    #[test]
467    fn reordered_frames_while_stopped() {
468        const INITIAL_BYTES: u64 = 3;
469        const INITIAL_OFFSET: u64 = 3;
470        const RECV_WINDOW: u64 = 8;
471        let mut s = Recv::new(RECV_WINDOW);
472        let mut data_recvd = 0;
473        // Receive bytes 3..6
474        let (new_bytes, is_closed) = s
475            .ingest(
476                frame::Stream {
477                    id: StreamId::new(Side::Client, Dir::Uni, 0),
478                    offset: INITIAL_OFFSET,
479                    fin: false,
480                    data: Bytes::from_static(&[0; INITIAL_BYTES as usize]),
481                },
482                123,
483                data_recvd,
484                data_recvd + 1024,
485            )
486            .unwrap();
487        data_recvd += new_bytes;
488        assert_eq!(new_bytes, INITIAL_OFFSET + INITIAL_BYTES);
489        assert!(!is_closed);
490
491        let (credits, transmit) = s.stop().unwrap();
492        assert!(transmit.should_transmit());
493        assert_eq!(
494            credits,
495            INITIAL_OFFSET + INITIAL_BYTES,
496            "full connection flow control credit is issued by stop"
497        );
498
499        let (max_stream_data, transmit) = s.max_stream_data(RECV_WINDOW);
500        assert!(!transmit.should_transmit());
501        assert_eq!(
502            max_stream_data, RECV_WINDOW,
503            "stream flow control credit isn't issued by stop"
504        );
505
506        // Receive byte 7
507        let (new_bytes, is_closed) = s
508            .ingest(
509                frame::Stream {
510                    id: StreamId::new(Side::Client, Dir::Uni, 0),
511                    offset: RECV_WINDOW - 1,
512                    fin: false,
513                    data: Bytes::from_static(&[0; 1]),
514                },
515                123,
516                data_recvd,
517                data_recvd + 1024,
518            )
519            .unwrap();
520        data_recvd += new_bytes;
521        assert_eq!(new_bytes, RECV_WINDOW - (INITIAL_OFFSET + INITIAL_BYTES));
522        assert!(!is_closed);
523
524        let (max_stream_data, transmit) = s.max_stream_data(RECV_WINDOW);
525        assert!(!transmit.should_transmit());
526        assert_eq!(
527            max_stream_data, RECV_WINDOW,
528            "stream flow control credit isn't issued after stop"
529        );
530
531        // Receive bytes 0..3
532        let (new_bytes, is_closed) = s
533            .ingest(
534                frame::Stream {
535                    id: StreamId::new(Side::Client, Dir::Uni, 0),
536                    offset: 0,
537                    fin: false,
538                    data: Bytes::from_static(&[0; INITIAL_OFFSET as usize]),
539                },
540                123,
541                data_recvd,
542                data_recvd + 1024,
543            )
544            .unwrap();
545        assert_eq!(
546            new_bytes, 0,
547            "reordered frames don't issue connection-level flow control for stopped streams"
548        );
549        assert!(!is_closed);
550
551        let (max_stream_data, transmit) = s.max_stream_data(RECV_WINDOW);
552        assert!(!transmit.should_transmit());
553        assert_eq!(
554            max_stream_data, RECV_WINDOW,
555            "stream flow control credit isn't issued after stop"
556        );
557    }
558}