Skip to main content

net_wire/qpack/state/
blocked.rs

1//! Caller-owned accounting for QPACK field sections awaiting insertions.
2
3use core::fmt;
4use core::iter::FusedIterator;
5
6use crate::qpack::field::section::decode::QpackFieldSectionBlocked;
7
8/// A caller-owned record of one stream with unresolved QPACK field sections.
9#[derive(Clone, Copy, Debug, Eq, PartialEq)]
10pub struct QpackBlockedStream {
11    occupied: bool,
12    stream_id: u64,
13    required_insert_count: u64,
14}
15
16impl QpackBlockedStream {
17    /// An unoccupied record suitable for caller-supplied blocked-stream storage.
18    pub const EMPTY: Self = Self {
19        occupied: false,
20        stream_id: 0,
21        required_insert_count: 0,
22    };
23
24    /// Returns the stream identifier for this record.
25    pub const fn stream_id(self) -> u64 {
26        self.stream_id
27    }
28
29    /// Returns the greatest unresolved Required Insert Count for this stream.
30    pub const fn required_insert_count(self) -> u64 {
31        self.required_insert_count
32    }
33}
34
35/// A QPACK blocked-stream accounting failure.
36#[derive(Clone, Copy, Debug, Eq, PartialEq)]
37pub enum QpackBlockedStreamsError {
38    /// The advertised maximum cannot be represented by or does not fit supplied storage.
39    MaximumBlockedStreamsExceedsStorage {
40        /// The requested SETTINGS_QPACK_BLOCKED_STREAMS value.
41        maximum: u64,
42        /// The number of supplied record slots.
43        storage_capacity: usize,
44    },
45    /// Registering another distinct stream exceeds the advertised maximum.
46    BlockedStreamsLimitExceeded {
47        /// The advertised SETTINGS_QPACK_BLOCKED_STREAMS value.
48        maximum: u64,
49    },
50}
51
52impl QpackBlockedStreamsError {
53    /// Returns whether this error represents QPACK decompression failure.
54    pub const fn is_decompression_failed(self) -> bool {
55        matches!(self, Self::BlockedStreamsLimitExceeded { .. })
56    }
57
58    /// Returns whether this error is a caller storage provisioning mismatch.
59    pub const fn is_provisioning_error(self) -> bool {
60        matches!(self, Self::MaximumBlockedStreamsExceedsStorage { .. })
61    }
62}
63
64impl fmt::Display for QpackBlockedStreamsError {
65    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
66        match self {
67            Self::MaximumBlockedStreamsExceedsStorage {
68                maximum,
69                storage_capacity,
70            } => write!(
71                formatter,
72                "maximum blocked streams {maximum} exceeds storage capacity {storage_capacity}"
73            ),
74            Self::BlockedStreamsLimitExceeded { maximum } => {
75                write!(formatter, "blocked streams exceed maximum {maximum}")
76            }
77        }
78    }
79}
80
81impl core::error::Error for QpackBlockedStreamsError {}
82
83/// Caller-owned accounting for streams with unresolved QPACK field sections.
84///
85/// `maximum_blocked_streams` is the local decoder's advertised
86/// SETTINGS_QPACK_BLOCKED_STREAMS value. Registering the same stream aggregates the
87/// greatest Required Insert Count across its started unresolved sections. Ready records
88/// remain registered until explicitly removed. The caller and HTTP/3 own encoded bytes
89/// and stream lifecycle; remove a record after a successful represented retry or when
90/// its stream is abandoned or reset.
91pub struct QpackBlockedStreams<'storage> {
92    records: &'storage mut [QpackBlockedStream],
93    maximum_blocked_streams: u64,
94    len: usize,
95}
96
97impl<'storage> QpackBlockedStreams<'storage> {
98    /// Creates empty accounting backed by caller-supplied records.
99    ///
100    /// On failure, the supplied records are unchanged. On success, every supplied
101    /// record is initialized to [`QpackBlockedStream::EMPTY`].
102    pub fn new(
103        records: &'storage mut [QpackBlockedStream],
104        maximum_blocked_streams: u64,
105    ) -> Result<Self, QpackBlockedStreamsError> {
106        let maximum = match usize::try_from(maximum_blocked_streams) {
107            Ok(maximum) => maximum,
108            Err(_) => {
109                return Err(
110                    QpackBlockedStreamsError::MaximumBlockedStreamsExceedsStorage {
111                        maximum: maximum_blocked_streams,
112                        storage_capacity: records.len(),
113                    },
114                );
115            }
116        };
117        if maximum > records.len() {
118            return Err(
119                QpackBlockedStreamsError::MaximumBlockedStreamsExceedsStorage {
120                    maximum: maximum_blocked_streams,
121                    storage_capacity: records.len(),
122                },
123            );
124        }
125        for record in records.iter_mut() {
126            *record = QpackBlockedStream::EMPTY;
127        }
128        Ok(Self {
129            records,
130            maximum_blocked_streams,
131            len: 0,
132        })
133    }
134
135    /// Returns the local decoder's advertised SETTINGS_QPACK_BLOCKED_STREAMS value.
136    pub const fn maximum_blocked_streams(&self) -> u64 {
137        self.maximum_blocked_streams
138    }
139
140    /// Returns the number of caller-supplied physical record slots.
141    pub const fn storage_capacity(&self) -> usize {
142        self.records.len()
143    }
144
145    /// Returns the number of distinct registered streams.
146    pub const fn len(&self) -> usize {
147        self.len
148    }
149
150    /// Returns whether no streams are registered.
151    pub const fn is_empty(&self) -> bool {
152        self.len == 0
153    }
154
155    /// Returns whether a stream has a registered unresolved field section.
156    pub fn contains(&self, stream_id: u64) -> bool {
157        self.records
158            .iter()
159            .any(|record| record.occupied && record.stream_id == stream_id)
160    }
161
162    /// Returns whether a registered stream still awaits insertions at `insert_count`.
163    pub fn is_blocked(&self, stream_id: u64, insert_count: u64) -> bool {
164        self.records.iter().any(|record| {
165            record.occupied
166                && record.stream_id == stream_id
167                && record.required_insert_count > insert_count
168        })
169    }
170
171    /// Registers a blocked field section for a stream.
172    ///
173    /// Re-registering a stream preserves the greatest Required Insert Count across its
174    /// started unresolved sections.
175    pub fn register(
176        &mut self,
177        stream_id: u64,
178        blocked: QpackFieldSectionBlocked,
179    ) -> Result<(), QpackBlockedStreamsError> {
180        for record in self.records.iter_mut() {
181            if record.occupied && record.stream_id == stream_id {
182                record.required_insert_count = record
183                    .required_insert_count
184                    .max(blocked.required_insert_count());
185                return Ok(());
186            }
187        }
188        if self.len >= self.maximum_blocked_streams as usize {
189            return Err(QpackBlockedStreamsError::BlockedStreamsLimitExceeded {
190                maximum: self.maximum_blocked_streams,
191            });
192        }
193        for record in self.records.iter_mut() {
194            if !record.occupied {
195                *record = QpackBlockedStream {
196                    occupied: true,
197                    stream_id,
198                    required_insert_count: blocked.required_insert_count(),
199                };
200                self.len += 1;
201                return Ok(());
202            }
203        }
204        Err(QpackBlockedStreamsError::BlockedStreamsLimitExceeded {
205            maximum: self.maximum_blocked_streams,
206        })
207    }
208
209    /// Removes a stream's accounting record, returning whether it was registered.
210    pub fn remove(&mut self, stream_id: u64) -> bool {
211        for record in self.records.iter_mut() {
212            if record.occupied && record.stream_id == stream_id {
213                *record = QpackBlockedStream::EMPTY;
214                self.len -= 1;
215                return true;
216            }
217        }
218        false
219    }
220
221    /// Iterates registered records ready to retry at `insert_count`.
222    pub fn ready(&self, insert_count: u64) -> QpackReadyBlockedStreamIter<'_> {
223        QpackReadyBlockedStreamIter {
224            remaining: self.records,
225            insert_count,
226        }
227    }
228}
229
230/// An iterator over blocked streams whose Required Insert Count has been reached.
231#[derive(Clone, Debug)]
232pub struct QpackReadyBlockedStreamIter<'a> {
233    remaining: &'a [QpackBlockedStream],
234    insert_count: u64,
235}
236
237impl Iterator for QpackReadyBlockedStreamIter<'_> {
238    type Item = QpackBlockedStream;
239
240    fn next(&mut self) -> Option<Self::Item> {
241        while let Some((record, remaining)) = self.remaining.split_first() {
242            self.remaining = remaining;
243            if record.occupied && record.required_insert_count <= self.insert_count {
244                return Some(*record);
245            }
246        }
247        None
248    }
249}
250
251impl FusedIterator for QpackReadyBlockedStreamIter<'_> {}