Skip to main content

net_wire/qpack/state/
encoder.rs

1//! Caller-owned encoder accounting for outstanding dynamic QPACK references.
2
3use core::fmt;
4
5use crate::qpack::stream::decoder::{
6    QpackDecoderInstruction, QpackDecoderInstructions, QpackDecoderInstructionsParseError,
7};
8
9/// Metadata for one outstanding field section that contains dynamic references.
10///
11/// The reference range identifies internal packed storage used for eviction protection and
12/// decoder-instruction application.
13#[derive(Clone, Copy, Debug, Eq, PartialEq)]
14pub struct QpackEncoderOutstandingSection {
15    stream_id: u64,
16    required_insert_count: u64,
17    reference_start: usize,
18    reference_len: usize,
19}
20
21impl QpackEncoderOutstandingSection {
22    /// Empty metadata suitable for caller-supplied section storage.
23    pub const EMPTY: Self = Self {
24        stream_id: 0,
25        required_insert_count: 0,
26        reference_start: 0,
27        reference_len: 0,
28    };
29
30    /// Returns the field section's stream identifier.
31    pub const fn stream_id(self) -> u64 {
32        self.stream_id
33    }
34
35    /// Returns the field section's Required Insert Count.
36    pub const fn required_insert_count(self) -> u64 {
37        self.required_insert_count
38    }
39
40    /// Returns the number of dynamic-reference occurrences in this field section.
41    pub const fn reference_len(self) -> usize {
42        self.reference_len
43    }
44}
45
46/// A failure while accounting for an encoder field section's dynamic references.
47#[derive(Clone, Copy, Debug, Eq, PartialEq)]
48pub enum QpackEncoderStateError {
49    /// The Required Insert Count does not match the supplied dynamic references.
50    RequiredInsertCountMismatch {
51        /// The supplied Required Insert Count.
52        required_insert_count: u64,
53        /// The Required Insert Count implied by the references.
54        expected_required_insert_count: u64,
55    },
56    /// Adding one to the largest absolute reference overflowed `u64`.
57    RequiredInsertCountOverflow {
58        /// The largest supplied zero-based absolute index.
59        absolute_index: u64,
60    },
61    /// The Required Insert Count is greater than the encoder's insert count.
62    RequiredInsertCountExceedsInsertCount {
63        /// The supplied Required Insert Count.
64        required_insert_count: u64,
65        /// The encoder's current insert count.
66        encoder_insert_count: u64,
67    },
68    /// The caller supplied no remaining section metadata slots.
69    SectionStorageExhausted {
70        /// The number of supplied section metadata slots.
71        capacity: usize,
72    },
73    /// The caller supplied too few reference slots for all occurrences.
74    ReferenceStorageTooShort {
75        /// The number of additional reference slots required.
76        required: usize,
77        /// The number of remaining reference slots available.
78        available: usize,
79    },
80    /// Starting a field section would exceed the peer's blocked-stream limit.
81    BlockedStreamsLimitExceeded {
82        /// The peer's SETTINGS_QPACK_BLOCKED_STREAMS value.
83        maximum: u64,
84    },
85}
86
87impl QpackEncoderStateError {
88    /// Returns whether this failure is caused by insufficient caller-owned storage.
89    pub const fn is_provisioning_error(self) -> bool {
90        matches!(
91            self,
92            Self::SectionStorageExhausted { .. } | Self::ReferenceStorageTooShort { .. }
93        )
94    }
95
96    /// Returns whether this failure requires choosing a nonblocking representation.
97    pub const fn is_blocked_streams_limit_error(self) -> bool {
98        matches!(self, Self::BlockedStreamsLimitExceeded { .. })
99    }
100}
101
102impl fmt::Display for QpackEncoderStateError {
103    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
104        match self {
105            Self::RequiredInsertCountMismatch {
106                required_insert_count,
107                expected_required_insert_count,
108            } => write!(
109                formatter,
110                "Required Insert Count {required_insert_count} does not match expected count {expected_required_insert_count}"
111            ),
112            Self::RequiredInsertCountOverflow { absolute_index } => write!(
113                formatter,
114                "Required Insert Count overflows u64 for absolute index {absolute_index}"
115            ),
116            Self::RequiredInsertCountExceedsInsertCount {
117                required_insert_count,
118                encoder_insert_count,
119            } => write!(
120                formatter,
121                "Required Insert Count {required_insert_count} exceeds encoder insert count {encoder_insert_count}"
122            ),
123            Self::SectionStorageExhausted { capacity } => {
124                write!(
125                    formatter,
126                    "section storage capacity {capacity} is exhausted"
127                )
128            }
129            Self::ReferenceStorageTooShort {
130                required,
131                available,
132            } => write!(
133                formatter,
134                "reference storage requires {required} slots but only {available} remain"
135            ),
136            Self::BlockedStreamsLimitExceeded { maximum } => {
137                write!(formatter, "blocked streams exceed maximum {maximum}")
138            }
139        }
140    }
141}
142
143impl core::error::Error for QpackEncoderStateError {}
144
145/// A failure while applying a QPACK decoder-stream instruction to encoder accounting.
146#[derive(Clone, Copy, Debug, Eq, PartialEq)]
147pub enum QpackDecoderInstructionApplyError {
148    /// A Section Acknowledgment named no outstanding dynamic-reference field section.
149    SectionAcknowledgmentWithoutOutstandingReferences {
150        /// The acknowledged stream identifier.
151        stream_id: u64,
152    },
153    /// An Insert Count Increment carried an increment of zero.
154    InsertCountIncrementZero,
155    /// An Insert Count Increment would exceed the encoder's insert count.
156    InsertCountIncrementExceedsInsertCount {
157        /// The previous Known Received Count.
158        known_received_count: u64,
159        /// The supplied Insert Count Increment.
160        increment: u64,
161        /// The encoder's current insert count.
162        encoder_insert_count: u64,
163    },
164}
165
166impl QpackDecoderInstructionApplyError {
167    /// Returns whether this failure is a QPACK decoder-stream error.
168    pub const fn is_decoder_stream_error(self) -> bool {
169        true
170    }
171}
172
173impl fmt::Display for QpackDecoderInstructionApplyError {
174    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
175        match self {
176            Self::SectionAcknowledgmentWithoutOutstandingReferences { stream_id } => write!(
177                formatter,
178                "Section Acknowledgment stream {stream_id} has no outstanding dynamic references"
179            ),
180            Self::InsertCountIncrementZero => {
181                formatter.write_str("Insert Count Increment must be greater than zero")
182            }
183            Self::InsertCountIncrementExceedsInsertCount {
184                known_received_count,
185                increment,
186                encoder_insert_count,
187            } => write!(
188                formatter,
189                "Insert Count Increment {increment} from Known Received Count {known_received_count} exceeds encoder insert count {encoder_insert_count}"
190            ),
191        }
192    }
193}
194
195impl core::error::Error for QpackDecoderInstructionApplyError {}
196
197/// Failure to validate or apply a QPACK decoder-stream instruction sequence.
198#[derive(Clone, Copy, Debug, Eq, PartialEq)]
199pub enum QpackDecoderInstructionsApplyError {
200    /// The complete sequence is malformed before application begins.
201    Parse(QpackDecoderInstructionsParseError),
202    /// Applying one validated instruction failed at this absolute byte offset.
203    Apply {
204        /// Absolute byte offset of the instruction that failed to apply.
205        offset: usize,
206        /// The instruction application failure.
207        error: QpackDecoderInstructionApplyError,
208    },
209}
210
211impl QpackDecoderInstructionsApplyError {
212    /// Returns whether this failure is a QPACK decoder-stream error.
213    pub const fn is_decoder_stream_error(self) -> bool {
214        match self {
215            Self::Parse(_) => true,
216            Self::Apply { error, .. } => error.is_decoder_stream_error(),
217        }
218    }
219}
220
221impl fmt::Display for QpackDecoderInstructionsApplyError {
222    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
223        match self {
224            Self::Parse(error) => write!(
225                formatter,
226                "QPACK decoder instruction sequence is invalid: {error}"
227            ),
228            Self::Apply { offset, error } => write!(
229                formatter,
230                "QPACK decoder instruction application failed at byte {offset}: {error}"
231            ),
232        }
233    }
234}
235
236impl core::error::Error for QpackDecoderInstructionsApplyError {}
237
238/// Caller-owned encoder accounting for outstanding field sections with dynamic references.
239///
240/// `maximum_blocked_streams` is the peer's SETTINGS_QPACK_BLOCKED_STREAMS value. Field-section
241/// encoding reserves this state before writing output and commits the reservation only after
242/// emission succeeds, keeping output and accounting atomic. Packed sections and reference
243/// occurrences are retained in registration order for later decoder-instruction application.
244pub struct QpackEncoderState<'sections, 'references> {
245    sections: &'sections mut [QpackEncoderOutstandingSection],
246    references: &'references mut [u64],
247    section_len: usize,
248    reference_len: usize,
249    known_received_count: u64,
250    maximum_blocked_streams: u64,
251}
252
253/// A validated registration that keeps encoder accounting unmodified until commit.
254///
255/// A combined field-section encoder can retain this reservation while emitting prevalidated
256/// output, then atomically commit the corresponding accounting.
257pub(in crate::qpack) struct QpackEncoderStateRegistration<'state, 'sections, 'references, 'input> {
258    state: &'state mut QpackEncoderState<'sections, 'references>,
259    stream_id: u64,
260    required_insert_count: u64,
261    absolute_references: &'input [u64],
262    reference_start: usize,
263}
264
265impl<'state, 'sections, 'references, 'input>
266    QpackEncoderStateRegistration<'state, 'sections, 'references, 'input>
267{
268    /// Commits this validated registration to encoder accounting.
269    pub(in crate::qpack) fn commit(self) {
270        if self.absolute_references.is_empty() {
271            return;
272        }
273
274        let reference_end = self.reference_start + self.absolute_references.len();
275        self.state.references[self.reference_start..reference_end]
276            .copy_from_slice(self.absolute_references);
277        self.state.sections[self.state.section_len] = QpackEncoderOutstandingSection {
278            stream_id: self.stream_id,
279            required_insert_count: self.required_insert_count,
280            reference_start: self.reference_start,
281            reference_len: self.absolute_references.len(),
282        };
283        self.state.reference_len += self.absolute_references.len();
284        self.state.section_len += 1;
285    }
286}
287
288impl<'sections, 'references> QpackEncoderState<'sections, 'references> {
289    /// Creates empty accounting backed by caller-owned section and reference storage.
290    pub fn new(
291        sections: &'sections mut [QpackEncoderOutstandingSection],
292        references: &'references mut [u64],
293        maximum_blocked_streams: u64,
294    ) -> Self {
295        for section in sections.iter_mut() {
296            *section = QpackEncoderOutstandingSection::EMPTY;
297        }
298        for reference in references.iter_mut() {
299            *reference = 0;
300        }
301        Self {
302            sections,
303            references,
304            section_len: 0,
305            reference_len: 0,
306            known_received_count: 0,
307            maximum_blocked_streams,
308        }
309    }
310
311    /// Returns the number of inserts known to have been received by the peer decoder.
312    pub const fn known_received_count(&self) -> u64 {
313        self.known_received_count
314    }
315
316    /// Returns the peer's SETTINGS_QPACK_BLOCKED_STREAMS value.
317    pub const fn maximum_blocked_streams(&self) -> u64 {
318        self.maximum_blocked_streams
319    }
320
321    /// Returns the number of outstanding dynamic-reference field sections.
322    pub const fn len(&self) -> usize {
323        self.section_len
324    }
325
326    /// Returns whether there are no outstanding dynamic-reference field sections.
327    pub const fn is_empty(&self) -> bool {
328        self.section_len == 0
329    }
330
331    /// Returns the number of retained dynamic-reference occurrences.
332    pub const fn reference_len(&self) -> usize {
333        self.reference_len
334    }
335
336    /// Returns the number of caller-supplied section metadata slots.
337    pub const fn section_storage_capacity(&self) -> usize {
338        self.sections.len()
339    }
340
341    /// Returns the number of caller-supplied reference slots.
342    pub const fn reference_storage_capacity(&self) -> usize {
343        self.references.len()
344    }
345
346    /// Returns the number of distinct streams currently at risk of blocking.
347    pub fn blocked_stream_count(&self) -> usize {
348        let mut count = 0;
349        for (index, section) in self.sections[..self.section_len].iter().enumerate() {
350            if section.required_insert_count > self.known_received_count
351                && !self.sections[..index].iter().any(|previous| {
352                    previous.required_insert_count > self.known_received_count
353                        && previous.stream_id == section.stream_id
354                })
355            {
356                count += 1;
357            }
358        }
359        count
360    }
361
362    /// Registers an outstanding field section containing dynamic-reference occurrences.
363    ///
364    /// References are copied unchanged and may contain duplicates. This method validates every
365    /// condition before mutating state. It does not check whether referenced entries are active;
366    /// that is the future field-section encoder's responsibility.
367    pub fn register(
368        &mut self,
369        stream_id: u64,
370        required_insert_count: u64,
371        absolute_references: &[u64],
372        encoder_insert_count: u64,
373    ) -> Result<(), QpackEncoderStateError> {
374        self.reserve_registration(
375            stream_id,
376            required_insert_count,
377            absolute_references,
378            encoder_insert_count,
379        )?
380        .commit();
381        Ok(())
382    }
383
384    /// Validates a registration while leaving encoder accounting unmodified until commit.
385    pub(in crate::qpack) fn reserve_registration<'input>(
386        &mut self,
387        stream_id: u64,
388        required_insert_count: u64,
389        absolute_references: &'input [u64],
390        encoder_insert_count: u64,
391    ) -> Result<
392        QpackEncoderStateRegistration<'_, 'sections, 'references, 'input>,
393        QpackEncoderStateError,
394    > {
395        if absolute_references.is_empty() {
396            if required_insert_count == 0 {
397                let reference_start = self.reference_len;
398                return Ok(QpackEncoderStateRegistration {
399                    state: self,
400                    stream_id,
401                    required_insert_count,
402                    absolute_references,
403                    reference_start,
404                });
405            }
406            return Err(QpackEncoderStateError::RequiredInsertCountMismatch {
407                required_insert_count,
408                expected_required_insert_count: 0,
409            });
410        }
411
412        let mut largest = absolute_references[0];
413        for &absolute_index in &absolute_references[1..] {
414            if absolute_index > largest {
415                largest = absolute_index;
416            }
417        }
418        let expected_required_insert_count =
419            largest
420                .checked_add(1)
421                .ok_or(QpackEncoderStateError::RequiredInsertCountOverflow {
422                    absolute_index: largest,
423                })?;
424        if required_insert_count != expected_required_insert_count {
425            return Err(QpackEncoderStateError::RequiredInsertCountMismatch {
426                required_insert_count,
427                expected_required_insert_count,
428            });
429        }
430        if required_insert_count > encoder_insert_count {
431            return Err(
432                QpackEncoderStateError::RequiredInsertCountExceedsInsertCount {
433                    required_insert_count,
434                    encoder_insert_count,
435                },
436            );
437        }
438        if self.section_len == self.sections.len() {
439            return Err(QpackEncoderStateError::SectionStorageExhausted {
440                capacity: self.sections.len(),
441            });
442        }
443
444        let available_references = self.references.len() - self.reference_len;
445        if absolute_references.len() > available_references {
446            return Err(QpackEncoderStateError::ReferenceStorageTooShort {
447                required: absolute_references.len(),
448                available: available_references,
449            });
450        }
451
452        let could_block = required_insert_count > self.known_received_count;
453        let stream_already_risks_blocking = could_block
454            && self.sections[..self.section_len].iter().any(|section| {
455                section.stream_id == stream_id
456                    && section.required_insert_count > self.known_received_count
457            });
458        if could_block && !stream_already_risks_blocking {
459            let blocked_stream_count = self.blocked_stream_count();
460            if u64::try_from(blocked_stream_count)
461                .map_or(true, |count| count >= self.maximum_blocked_streams)
462            {
463                return Err(QpackEncoderStateError::BlockedStreamsLimitExceeded {
464                    maximum: self.maximum_blocked_streams,
465                });
466            }
467        }
468
469        let reference_start = self.reference_len;
470        Ok(QpackEncoderStateRegistration {
471            state: self,
472            stream_id,
473            required_insert_count,
474            absolute_references,
475            reference_start,
476        })
477    }
478
479    /// Applies one parsed decoder-stream instruction to outstanding-reference accounting.
480    ///
481    /// A Stream Cancellation removes all matching sections but does not advance the Known
482    /// Received Count.
483    pub fn apply(
484        &mut self,
485        instruction: QpackDecoderInstruction<'_>,
486        encoder_insert_count: u64,
487    ) -> Result<(), QpackDecoderInstructionApplyError> {
488        match instruction {
489            QpackDecoderInstruction::SectionAcknowledgment(acknowledgment) => {
490                let stream_id = acknowledgment.stream_id().value();
491                let section_index = self.sections[..self.section_len]
492                    .iter()
493                    .position(|section| section.stream_id == stream_id)
494                    .ok_or(
495                        QpackDecoderInstructionApplyError::SectionAcknowledgmentWithoutOutstandingReferences {
496                            stream_id,
497                        },
498                    )?;
499                self.known_received_count = self
500                    .known_received_count
501                    .max(self.sections[section_index].required_insert_count);
502                self.remove_section(section_index);
503                Ok(())
504            }
505            QpackDecoderInstruction::StreamCancellation(cancellation) => {
506                let stream_id = cancellation.stream_id().value();
507                let mut section_index = 0;
508                while section_index < self.section_len {
509                    if self.sections[section_index].stream_id == stream_id {
510                        self.remove_section(section_index);
511                    } else {
512                        section_index += 1;
513                    }
514                }
515                Ok(())
516            }
517            QpackDecoderInstruction::InsertCountIncrement(increment) => {
518                let increment = increment.increment().value();
519                if increment == 0 {
520                    return Err(QpackDecoderInstructionApplyError::InsertCountIncrementZero);
521                }
522
523                let known_received_count = self.known_received_count;
524                let Some(next_known_received_count) = known_received_count.checked_add(increment)
525                else {
526                    return Err(
527                        QpackDecoderInstructionApplyError::InsertCountIncrementExceedsInsertCount {
528                            known_received_count,
529                            increment,
530                            encoder_insert_count,
531                        },
532                    );
533                };
534                if next_known_received_count > encoder_insert_count {
535                    return Err(
536                        QpackDecoderInstructionApplyError::InsertCountIncrementExceedsInsertCount {
537                            known_received_count,
538                            increment,
539                            encoder_insert_count,
540                        },
541                    );
542                }
543                self.known_received_count = next_known_received_count;
544                Ok(())
545            }
546        }
547    }
548
549    /// Validates and applies a complete unframed decoder-stream instruction sequence.
550    ///
551    /// The complete sequence is validated before any accounting state or caller-owned backing
552    /// storage is changed. On an application failure, earlier successful instructions remain
553    /// committed, the failing instruction remains atomic under [`Self::apply`], and later
554    /// instructions are not attempted.
555    pub fn apply_sequence(
556        &mut self,
557        bytes: &[u8],
558        encoder_insert_count: u64,
559    ) -> Result<(), QpackDecoderInstructionsApplyError> {
560        let instructions = QpackDecoderInstructions::parse(bytes)
561            .map_err(QpackDecoderInstructionsApplyError::Parse)?;
562        let mut offset = 0usize;
563
564        for instruction in instructions.iter() {
565            let instruction = instruction.map_err(QpackDecoderInstructionsApplyError::Parse)?;
566            self.apply(instruction, encoder_insert_count)
567                .map_err(|error| QpackDecoderInstructionsApplyError::Apply { offset, error })?;
568            offset += instruction.as_bytes().len();
569        }
570
571        Ok(())
572    }
573
574    fn remove_section(&mut self, section_index: usize) {
575        let removed = self.sections[section_index];
576        let reference_end = removed.reference_start + removed.reference_len;
577        self.references
578            .copy_within(reference_end..self.reference_len, removed.reference_start);
579        for section in &mut self.sections[section_index + 1..self.section_len] {
580            section.reference_start -= removed.reference_len;
581        }
582        self.sections
583            .copy_within(section_index + 1..self.section_len, section_index);
584
585        self.section_len -= 1;
586        self.reference_len -= removed.reference_len;
587        self.sections[self.section_len] = QpackEncoderOutstandingSection::EMPTY;
588        self.references[self.reference_len..self.reference_len + removed.reference_len].fill(0);
589    }
590
591    /// Returns whether an entry may be evicted without invalidating outstanding references.
592    pub fn is_evictable(&self, absolute_index: u64) -> bool {
593        self.known_received_count > absolute_index
594            && !self.references[..self.reference_len].contains(&absolute_index)
595    }
596}