Skip to main content

net_wire/qpack/state/
decoder.rs

1//! Caller-owned decoder feedback accounting for RFC 9204 section 4.4.
2//!
3//! The decoder chooses when to emit Insert Count Increments and may coalesce them;
4//! a Section Acknowledgment can also imply progress. Successful output advances this
5//! accounting, so callers must retain and publish its exact bytes in invocation order.
6
7use core::fmt;
8
9use crate::qpack::field::section::decode::QpackDecodedFieldSection;
10use crate::qpack::integer::QPACK_INTEGER_MAX;
11use crate::qpack::stream::decoder::{
12    QpackDecoderInstructionBuildError, QpackInsertCountIncrement, QpackInsertCountIncrementBuilder,
13    QpackSectionAcknowledgment, QpackSectionAcknowledgmentBuilder, QpackStreamCancellation,
14    QpackStreamCancellationBuilder,
15};
16
17/// Caller-owned QPACK decoder feedback accounting.
18///
19/// This tracks the decoder-side Known Received Count already reported or implied to
20/// the peer encoder. It owns no output queue, stream lifecycle state, blocked payloads,
21/// transport buffering, or field decoding.
22#[derive(Clone, Copy, Debug, Eq, PartialEq)]
23pub struct QpackDecoderState {
24    known_received_count: u64,
25}
26
27impl QpackDecoderState {
28    /// Creates decoder feedback accounting with no reported received inserts.
29    pub const fn new() -> Self {
30        Self {
31            known_received_count: 0,
32        }
33    }
34
35    /// Returns the number of received inserts already reported or implied to the encoder.
36    pub const fn known_received_count(&self) -> u64 {
37        self.known_received_count
38    }
39
40    /// Builds a Section Acknowledgment for a successfully decoded dynamic field section.
41    ///
42    /// Acknowledgments are coupled to decoded dynamic-table references rather than a
43    /// caller-supplied count. A section with no dynamic references needs no acknowledgment,
44    /// even when its resolved Required Insert Count is nonzero. On success, callers must retain
45    /// and publish the returned bytes in invocation order because the acknowledgment advances
46    /// local accounting.
47    pub fn acknowledge_field_section<'a>(
48        &mut self,
49        destination: &'a mut [u8],
50        stream_id: u64,
51        section: QpackDecodedFieldSection<'_>,
52    ) -> Result<Option<QpackSectionAcknowledgment<'a>>, QpackDecoderFeedbackError> {
53        if !section.contains_dynamic_references() {
54            return Ok(None);
55        }
56
57        let required_insert_count = section.required_insert_count();
58        let acknowledgment = QpackSectionAcknowledgmentBuilder::new(destination, stream_id)
59            .build()
60            .map_err(QpackDecoderFeedbackError::InstructionBuild)?;
61        self.known_received_count = self.known_received_count.max(required_insert_count);
62        Ok(Some(acknowledgment))
63    }
64
65    /// Builds a Stream Cancellation for the supplied stream lifecycle event.
66    ///
67    /// Stream tracking is caller and HTTP/3 lifecycle responsibility; this emits the
68    /// supplied event without changing the Known Received Count.
69    pub fn cancel_stream<'a>(
70        &mut self,
71        destination: &'a mut [u8],
72        stream_id: u64,
73    ) -> Result<QpackStreamCancellation<'a>, QpackDecoderFeedbackError> {
74        QpackStreamCancellationBuilder::new(destination, stream_id)
75            .build()
76            .map_err(QpackDecoderFeedbackError::InstructionBuild)
77    }
78
79    /// Coalesces and builds one Insert Count Increment for received encoder inserts.
80    ///
81    /// RFC 9204 lets the decoder choose increment timing. At most `QPACK_INTEGER_MAX`
82    /// pending inserts are emitted at once; callers can invoke this again for a larger
83    /// pending delta. Successful output advances local accounting, so its returned bytes
84    /// must be retained and published in invocation order.
85    pub fn emit_insert_count_increment<'a>(
86        &mut self,
87        destination: &'a mut [u8],
88        decoder_insert_count: u64,
89    ) -> Result<Option<QpackInsertCountIncrement<'a>>, QpackDecoderFeedbackError> {
90        if decoder_insert_count == self.known_received_count {
91            return Ok(None);
92        }
93        if decoder_insert_count < self.known_received_count {
94            return Err(QpackDecoderFeedbackError::InsertCountRegression {
95                known_received_count: self.known_received_count,
96                decoder_insert_count,
97            });
98        }
99
100        let increment = core::cmp::min(
101            decoder_insert_count - self.known_received_count,
102            QPACK_INTEGER_MAX,
103        );
104        let advanced_count = self.known_received_count.checked_add(increment).ok_or(
105            QpackDecoderFeedbackError::InsertCountAdvanceOverflow {
106                known_received_count: self.known_received_count,
107                increment,
108            },
109        )?;
110        let instruction = QpackInsertCountIncrementBuilder::new(destination, increment)
111            .build()
112            .map_err(QpackDecoderFeedbackError::InstructionBuild)?;
113        self.known_received_count = advanced_count;
114        Ok(Some(instruction))
115    }
116}
117
118impl Default for QpackDecoderState {
119    fn default() -> Self {
120        Self::new()
121    }
122}
123
124/// Failure while emitting caller-owned QPACK decoder feedback.
125#[derive(Clone, Copy, Debug, Eq, PartialEq)]
126pub enum QpackDecoderFeedbackError {
127    /// The supplied decoder insert count regressed below local accounting.
128    InsertCountRegression {
129        /// The decoder-side Known Received Count.
130        known_received_count: u64,
131        /// The supplied current decoder insert count.
132        decoder_insert_count: u64,
133    },
134    /// Advancing local accounting would overflow `u64`.
135    InsertCountAdvanceOverflow {
136        /// The decoder-side Known Received Count.
137        known_received_count: u64,
138        /// The emitted Insert Count Increment.
139        increment: u64,
140    },
141    /// A canonical decoder instruction could not be built.
142    InstructionBuild(
143        #[doc = "The nested local instruction build failure."] QpackDecoderInstructionBuildError,
144    ),
145}
146
147impl QpackDecoderFeedbackError {
148    /// Returns whether this failure is insufficient caller-owned output storage.
149    pub const fn is_provisioning_error(self) -> bool {
150        matches!(
151            self,
152            Self::InstructionBuild(QpackDecoderInstructionBuildError::BufferTooShort { .. })
153        )
154    }
155}
156
157impl fmt::Display for QpackDecoderFeedbackError {
158    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
159        match self {
160            Self::InsertCountRegression {
161                known_received_count,
162                decoder_insert_count,
163            } => write!(
164                formatter,
165                "decoder insert count {decoder_insert_count} regresses below Known Received Count {known_received_count}"
166            ),
167            Self::InsertCountAdvanceOverflow {
168                known_received_count,
169                increment,
170            } => write!(
171                formatter,
172                "Known Received Count {known_received_count} cannot advance by {increment}"
173            ),
174            Self::InstructionBuild(error) => write!(
175                formatter,
176                "QPACK decoder feedback instruction cannot be built: {error}"
177            ),
178        }
179    }
180}
181
182impl core::error::Error for QpackDecoderFeedbackError {}