Skip to main content

zincio_http/h3/qpack/encoder/
mod.rs

1//! QPACK encoder (RFC 9204 Sections 4.3 and 4.5).
2//!
3//! Consumption: the HTTP/3 layer drives the encoder per connection (it feeds
4//! field sections and drains the encoder stream); until that lands, the whole
5//! module is dead in non-test builds, which is why `dead_code` is expected
6//! here. It errors again once the encoder is used, reminding us to remove the
7//! expectation.
8//!
9//! The encoder owns the shared dynamic table (Section 4.2): it alone adds
10//! entries, and it emits an unframed sequence of instructions on the encoder
11//! stream (Section 4.3) describing every mutation. Field sections are encoded
12//! as a Required Insert Count, a Base, and one or more field line
13//! representations (Section 4.5).
14//!
15//! The encoder fixes the Base at the dynamic table insertion count before
16//! encoding a section, so every reference to entries that existed before the
17//! section is relative, and entries inserted while encoding the section are
18//! referenced with post-Base indexes (Section 4.5.1.2 recommends exactly this:
19//! Base equal to the Required Insert Count, which makes the Sign bit and the
20//! Delta Base zero whenever nothing new is referenced).
21//!
22//! Inserts are speculative but bounded: an entry is only inserted when it
23//! fits and when the eviction it would cause cannot invalidate an index
24//! already referenced by the section being encoded, nor remove an entry the
25//! decoder still needs. The decoder's acknowledgments (Section 4.4
26//! instructions on its decoder stream) raise a Known Received Count and free
27//! the references of acknowledged field sections; an entry is evictable only
28//! below the lower of the Known Received Count and the smallest reference
29//! still outstanding (RFC 9204 Sections 2.1.1 and 2.1.4). The encoder never
30//! evicts above that floor, so the peer's decoder never has to reject an
31//! insert as a QPACK_ENCODER_STREAM_ERROR.
32//!
33//! Huffman encoding follows RFC 9204 Section 4.1.2: a string is Huffman
34//! encoded when that is shorter, matching HPACK practice.
35#![expect(dead_code)]
36
37use std::collections::VecDeque;
38
39use bytes::Bytes;
40
41use crate::h3::qpack::table::DynamicTable;
42use crate::h3::qpack::{static_table, QpackError};
43use crate::hpack::{huffman, integer};
44
45/// `001` + 5-bit capacity: Set Dynamic Table Capacity (RFC 9204 4.3.1).
46const SET_CAPACITY: u8 = 0b0010_0000;
47/// `1 T` + 6-bit name index: Insert with Name Reference (RFC 9204 4.3.2).
48const INSERT_WITH_NAME_REF: u8 = 0b1000_0000;
49/// `01` + H + 5-bit name length: Insert with Literal Name (RFC 9204 4.3.3).
50const INSERT_WITH_LITERAL_NAME: u8 = 0b0100_0000;
51/// `000` + 5-bit relative index: Duplicate (RFC 9204 4.3.4).
52const DUPLICATE: u8 = 0b0000_0000;
53
54/// `1` + 7-bit stream ID: Section Acknowledgment (RFC 9204 4.4.1).
55const SECTION_ACK: u8 = 0x80;
56/// `01` + 6-bit stream ID: Stream Cancellation (RFC 9204 4.4.2).
57const STREAM_CANCELLATION: u8 = 0x40;
58/// `00` + 6-bit increment: Insert Count Increment (RFC 9204 4.4.3).
59const INSERT_COUNT_INCREMENT: u8 = 0x00;
60
61/// Upper bound on `decoder_stream_pending`. A complete decoder-stream
62/// instruction is a single prefixed integer of at most 10 bytes (a 62-bit
63/// integer), so a buffered prefix far larger than this can never become a
64/// valid instruction.
65const MAX_DECODER_STREAM_PENDING: usize = 64;
66
67/// `1 T` + 6-bit index: Indexed Field Line (RFC 9204 4.5.2).
68const INDEXED: u8 = 0b1000_0000;
69/// `0001` + 4-bit post-Base index: Indexed Field Line with Post-Base Index
70/// (RFC 9204 4.5.3).
71const INDEXED_POST_BASE: u8 = 0b0001_0000;
72/// `01 N T` + 4-bit name index: Literal Field Line with Name Reference
73/// (RFC 9204 4.5.4).
74const LITERAL_NAME_REF: u8 = 0b0100_0000;
75/// `0000 N` + 3-bit post-Base name index: Literal Field Line with Post-Base
76/// Name Reference (RFC 9204 4.5.5).
77const LITERAL_POST_BASE_NAME_REF: u8 = 0b0000_0000;
78/// `001 N` + H + 3-bit name length: Literal Field Line with Literal Name
79/// (RFC 9204 4.5.6).
80const LITERAL_LITERAL_NAME: u8 = 0b0010_0000;
81
82/// Field names that must never be added to the dynamic table and must always
83/// be sent as literal field lines with the N bit set (RFC 9204 Section 7.1).
84const NEVER_INDEXED: [&[u8]; 3] = [b"authorization", b"proxy-authorization", b"cookie"];
85
86/// Result of encoding one field section: the encoded field section carried on
87/// the request/response stream, and any encoder stream instructions queued
88/// while encoding it.
89#[derive(Debug, Clone, PartialEq, Eq)]
90pub struct EncodedSection {
91    /// Encoded field section prefix plus field lines (RFC 9204 Section 4.5.1).
92    pub block: Bytes,
93    /// Encoder stream instructions (RFC 9204 Section 4.3).
94    pub encoder_stream: Bytes,
95}
96
97/// QPACK encoder: dynamic table owner and field section encoder.
98#[derive(Debug)]
99pub struct Encoder {
100    dynamic: DynamicTable,
101    /// Upper bound on the dynamic table capacity allowed by the decoder's
102    /// SETTINGS_QPACK_MAX_TABLE_CAPACITY (RFC 9204 Section 5).
103    max_capacity: u64,
104    /// Whether string literals are Huffman encoded when that is shorter.
105    huffman: bool,
106    /// Number of dynamic table insertions and duplications acknowledged by
107    /// the decoder (RFC 9204 Section 2.1.4). Absolute indexes below it are
108    /// acknowledged and can be evicted.
109    known_received: u64,
110    /// Smallest absolute index referenced by each unacknowledged field
111    /// section that has dynamic references, in send order (RFC 9204
112    /// Sections 2.1.1 and 4.4.1). A section is freed by a Section
113    /// Acknowledgment for its stream or by a Stream Cancellation.
114    pending_refs: VecDeque<(u64, u64)>,
115    /// Cached minimum of `pending_refs` second values (`u64::MAX` when empty).
116    /// Avoids `O(N_pending)` scan per header insertion in `evictable_floor`.
117    cached_min_pending: u64,
118    /// Bytes of the peer's decoder stream received so far but not yet forming
119    /// a complete instruction. QPACK decoder-stream instructions can span the
120    /// arbitrary chunk boundaries of the underlying QUIC stream, so partial
121    /// instructions are buffered here until the rest arrives (RFC 9204
122    /// Section 4.4) instead of being treated as a stream error.
123    decoder_stream_pending: Vec<u8>,
124}
125
126impl Encoder {
127    /// Creates an encoder bound to a decoder that advertised the given
128    /// `max_capacity` in SETTINGS_QPACK_MAX_TABLE_CAPACITY.
129    #[inline]
130    pub fn new(max_capacity: u64, huffman: bool) -> Self {
131        Self {
132            dynamic: DynamicTable::new(0),
133            max_capacity,
134            huffman,
135            known_received: 0,
136            pending_refs: VecDeque::new(),
137            cached_min_pending: u64::MAX,
138            decoder_stream_pending: Vec::new(),
139        }
140    }
141
142    /// The maximum dynamic table capacity permitted by the decoder.
143    #[inline]
144    pub fn max_capacity(&self) -> u64 {
145        self.max_capacity
146    }
147
148    /// The lowest absolute index at or above which entries are NOT
149    /// evictable: the lower of the Known Received Count and the smallest
150    /// reference of any unacknowledged field section (RFC 9204
151    /// Sections 2.1.1 and 2.1.4). An insert whose eviction would reach it
152    /// is skipped.
153    #[inline]
154    fn evictable_floor(&self) -> u64 {
155        self.known_received.min(self.cached_min_pending)
156    }
157
158    #[inline]
159    fn recompute_min_pending(&mut self) {
160        self.cached_min_pending = self
161            .pending_refs
162            .iter()
163            .map(|(_, min_ref)| *min_ref)
164            .min()
165            .unwrap_or(u64::MAX);
166    }
167
168    /// Encodes `headers` for the field section on `stream_id` into an
169    /// encoded field section plus the encoder stream instructions the
170    /// decoder needs to process it.
171    ///
172    /// The dynamic table is used only when the decoder allows a capacity of
173    /// at least one entry (RFC 9204 Section 3.2.3): a maximum capacity below
174    /// 32 bytes cannot hold any entry and disables the dynamic table.
175    #[inline]
176    pub fn encode_section(&mut self, stream_id: u64, headers: &[(Bytes, Bytes)]) -> EncodedSection {
177        self.encode_section_with_base(stream_id, headers, self.dynamic.inserted())
178    }
179
180    /// Encodes `headers` using the decoder's **acknowledged** insert count
181    /// (`known_received`) as the QPACK Base.
182    ///
183    /// RFC 9204 Section 2.1.2 forbids a relative reference to a dynamic entry
184    /// whose absolute index exceeds the Largest Reference the decoder has
185    /// acknowledged: such an entry must instead be referenced with a Post-Base
186    /// index. The shared encoder's own insert count (`dynamic.inserted()`) can
187    /// run far ahead of what the peer has acknowledged — for instance when the
188    /// client is busy consuming a large response body and lags on its
189    /// decoder-stream acknowledgments. Encoding against the insert count there
190    /// would emit relative references above the peer's Largest Reference, which
191    /// a strict decoder (e.g. Neqo/Firefox) treats as a QPACK decompression
192    /// failure, tearing down the whole connection and dropping every other in-
193    /// flight request. Encoding against `known_received` keeps every relative
194    /// reference within the acknowledged range and uses Post-Base for anything
195    /// newer, so the section decodes correctly even if the peer ACKs late.
196    #[inline]
197    pub(crate) fn encode_section_with_ack_base(
198        &mut self,
199        stream_id: u64,
200        headers: &[(Bytes, Bytes)],
201    ) -> EncodedSection {
202        self.encode_section_with_base(stream_id, headers, self.known_received)
203    }
204
205    /// The insert count a decoder must have reached to process the most
206    /// recently encoded section.
207    #[inline]
208    pub(crate) fn required_insert_count(&self) -> u64 {
209        self.dynamic.inserted()
210    }
211
212    #[inline]
213    fn encode_section_with_base(
214        &mut self,
215        stream_id: u64,
216        headers: &[(Bytes, Bytes)],
217        base: u64,
218    ) -> EncodedSection {
219        let usable = self.max_capacity >= 32;
220        let mut encoder_stream = Vec::new();
221
222        let mut block = Vec::new();
223        // Smallest absolute index referenced by the section so far; an insert
224        // is skipped when its eviction would reach it.
225        let mut min_rel_ref: Option<u64> = None;
226        // Required Insert Count: one larger than the largest absolute index
227        // of all dynamic table entries referenced by the section, and 0 when
228        // none are referenced (RFC 9204 Section 2.1.2). It only grows with
229        // dynamic references, so a section that happens to reference only
230        // static entries does not inflate it.
231        let mut ric = 0u64;
232
233        for (name, value) in headers {
234            let sensitive = NEVER_INDEXED.contains(&name.as_ref());
235
236            if sensitive {
237                self.encode_literal(name, value, true, &mut block);
238                continue;
239            }
240
241            // Full match, dynamic table first (newest first), then static.
242            // Preserve the corresponding name matches so the literal path
243            // below does not repeat either table scan.
244            let dynamic_match = usable.then(|| self.dynamic.find_full_or_name(name, value));
245            if usable {
246                if let Some(abs) = dynamic_match.and_then(|(full, _)| full) {
247                    self.encode_indexed(abs, base, &mut block, &mut ric, &mut min_rel_ref);
248                    continue;
249                }
250            }
251            let static_match = static_table::find_full_or_name(name, value);
252            if let Some(idx) = static_match.0 {
253                // Indexed Field Line, static table (T=1).
254                integer::encode(&mut block, idx as u64, 6, INDEXED | 0x40);
255                continue;
256            }
257
258            if usable {
259                if let Some(abs) = dynamic_match.and_then(|(_, name)| name) {
260                    if abs >= base {
261                        // Post-Base name reference (4.5.5). Only reachable on
262                        // the vector path, which fixes Base below the insert
263                        // count.
264                        self.encode_literal_post_base_name_ref(
265                            abs,
266                            base,
267                            value,
268                            &mut block,
269                            &mut ric,
270                            &mut min_rel_ref,
271                        );
272                    } else {
273                        // Literal with Name Reference, dynamic table (T=0).
274                        self.encode_literal_with_name_ref(
275                            abs,
276                            base,
277                            value,
278                            &mut block,
279                            &mut ric,
280                            &mut min_rel_ref,
281                        );
282                    }
283                    continue;
284                }
285            }
286            if let Some(idx) = static_match.1 {
287                // Literal with Name Reference, static table (T=1).
288                self.encode_literal_with_static_name_ref(idx, value, &mut block);
289                continue;
290            }
291
292            // No name match anywhere: emit a literal name, and insert the
293            // entry so later sections can reference it (Section 4.4).
294            let size = DynamicTable::entry_size(name, value);
295            // An insert only evicts the oldest entries, so it is allowed
296            // when the first surviving entry sits at or below every entry
297            // the section references and everything the decoder still
298            // needs (RFC 9204 Section 2.1.1): evicting below that floor is
299            // what the decoder's mirror of this table permits.
300            let boundary = min_rel_ref.unwrap_or(u64::MAX).min(self.evictable_floor());
301            let safe = self.dynamic.inserted() - self.dynamic.len() as u64
302                + self.dynamic.would_evict(size)
303                <= boundary;
304            if usable && size <= self.max_capacity && safe {
305                // Set Dynamic Table Capacity (4.3.1), emitted lazily right
306                // before the first insert of this section.
307                if self.dynamic.capacity() != self.max_capacity {
308                    integer::encode(&mut encoder_stream, self.max_capacity, 5, SET_CAPACITY);
309                    self.dynamic.set_capacity(self.max_capacity);
310                }
311                let abs = self.dynamic.next_absolute();
312                // Insert with Literal Name (4.3.3): the name length uses a
313                // 5-bit prefix, so `push_string` receives 6 (it reserves one
314                // bit for the Huffman flag).
315                self.push_string(&mut encoder_stream, name, 6, INSERT_WITH_LITERAL_NAME);
316                self.push_string(&mut encoder_stream, value, 8, 0);
317                let _ = self.dynamic.insert(name.clone(), value.clone());
318                // The fresh entry is referenced with a post-Base index.
319                self.encode_indexed(abs, base, &mut block, &mut ric, &mut min_rel_ref);
320            } else {
321                self.encode_literal(name, value, false, &mut block);
322            }
323        }
324
325        // A section with dynamic references pins the referenced entries
326        // until the decoder acknowledges it (Section 4.4.1).
327        if ric > 0 {
328            let min_ref = min_rel_ref.unwrap_or(0);
329            self.pending_refs.push_back((stream_id, min_ref));
330            if min_ref < self.cached_min_pending {
331                self.cached_min_pending = min_ref;
332            }
333        }
334
335        // Encoded Field Section Prefix (RFC 9204 4.5.1).
336        let mut prefix = Vec::new();
337        self.encode_prefix(&mut prefix, ric, base);
338        prefix.reserve(block.len());
339        prefix.extend_from_slice(&block);
340
341        EncodedSection {
342            block: Bytes::from(prefix),
343            encoder_stream: Bytes::from(encoder_stream),
344        }
345    }
346
347    /// Encodes the Required Insert Count and the Base (RFC 9204 4.5.1).
348    #[inline]
349    fn encode_prefix(&self, out: &mut Vec<u8>, ric: u64, base: u64) {
350        // Required Insert Count (4.5.1.1): 0 stays 0, otherwise it is wrapped
351        // modulo 2 * MaxEntries, where MaxEntries = floor(MaxCapacity / 32).
352        let max_entries = self.max_capacity / 32;
353        let enc_ric = if ric == 0 || max_entries == 0 {
354            0
355        } else {
356            (ric % (2 * max_entries)) + 1
357        };
358        integer::encode(out, enc_ric, 8, 0);
359        // Base (4.5.1.2): Sign=0 when Base >= Ric (Delta = Base - Ric);
360        // Sign=1 when Base < Ric (Delta = Ric - Base - 1).
361        if base >= ric {
362            integer::encode(out, base - ric, 7, 0);
363        } else {
364            integer::encode(out, ric - base - 1, 7, 0x80);
365        }
366    }
367
368    /// Encodes an indexed reference to a dynamic table entry.
369    #[inline]
370    fn encode_indexed(
371        &self,
372        abs: u64,
373        base: u64,
374        block: &mut Vec<u8>,
375        ric: &mut u64,
376        min_rel_ref: &mut Option<u64>,
377    ) {
378        *ric = (*ric).max(abs + 1);
379        if abs >= base {
380            // Post-Base index (4.5.3).
381            integer::encode(block, abs - base, 4, INDEXED_POST_BASE);
382        } else {
383            // Relative index (4.5.2): Base - Absolute - 1.
384            integer::encode(block, base - abs - 1, 6, INDEXED);
385        }
386        *min_rel_ref = Some(min_rel_ref.map_or(abs, |m| m.min(abs)));
387    }
388
389    /// Encodes a literal field line with a dynamic name reference (T=0).
390    #[inline]
391    fn encode_literal_with_name_ref(
392        &self,
393        abs: u64,
394        base: u64,
395        value: &[u8],
396        block: &mut Vec<u8>,
397        ric: &mut u64,
398        min_rel_ref: &mut Option<u64>,
399    ) {
400        *ric = (*ric).max(abs + 1);
401        let rel = base - abs - 1;
402        integer::encode(block, rel, 4, LITERAL_NAME_REF);
403        *min_rel_ref = Some(min_rel_ref.map_or(abs, |m| m.min(abs)));
404        self.push_string(block, value, 8, 0);
405    }
406
407    /// Encodes a literal field line with a post-Base name reference (4.5.5).
408    #[inline]
409    fn encode_literal_post_base_name_ref(
410        &self,
411        abs: u64,
412        base: u64,
413        value: &[u8],
414        block: &mut Vec<u8>,
415        ric: &mut u64,
416        min_rel_ref: &mut Option<u64>,
417    ) {
418        *ric = (*ric).max(abs + 1);
419        integer::encode(block, abs - base, 3, LITERAL_POST_BASE_NAME_REF);
420        *min_rel_ref = Some(min_rel_ref.map_or(abs, |m| m.min(abs)));
421        self.push_string(block, value, 8, 0);
422    }
423
424    /// Encodes a literal field line with a static name reference (T=1).
425    #[inline]
426    fn encode_literal_with_static_name_ref(&self, idx: usize, value: &[u8], block: &mut Vec<u8>) {
427        integer::encode(block, idx as u64, 4, LITERAL_NAME_REF | 0x10);
428        self.push_string(block, value, 8, 0);
429    }
430
431    /// Encodes a literal field line with a literal name (4.5.6), preferring a
432    /// static name reference when one exists.
433    #[inline]
434    fn encode_literal(&self, name: &[u8], value: &[u8], sensitive: bool, block: &mut Vec<u8>) {
435        if let Some(idx) = static_table::find_name(name) {
436            integer::encode(
437                block,
438                idx as u64,
439                4,
440                LITERAL_NAME_REF | 0x10 | (u8::from(sensitive) << 4),
441            );
442        } else {
443            // `001 N` + H + 3-bit name length.
444            self.push_string(
445                block,
446                name,
447                4,
448                LITERAL_LITERAL_NAME | (u8::from(sensitive) << 4),
449            );
450        }
451        self.push_string(block, value, 8, 0);
452    }
453
454    /// Encodes an N-bit prefix string literal (RFC 9204 Section 4.1.2):
455    /// `header` carries the bits preceding the string, the Huffman flag is
456    /// set when Huffman encoding is shorter, and the length is encoded with
457    /// an (N-1)-bit prefix.
458    #[inline]
459    fn push_string(&self, out: &mut Vec<u8>, value: &[u8], prefix: u8, header: u8) {
460        let (huffman, len) = if self.huffman {
461            let huffman_bits = huffman::encoded_len(value);
462            if huffman_bits < value.len() * 8 {
463                (true, huffman_bits.div_ceil(8) as u64)
464            } else {
465                (false, value.len() as u64)
466            }
467        } else {
468            (false, value.len() as u64)
469        };
470        integer::encode(
471            out,
472            len,
473            prefix - 1,
474            header | (u8::from(huffman) << (prefix - 1)),
475        );
476        if huffman {
477            huffman::encode_with_len(value, out, len as usize);
478        } else {
479            out.extend_from_slice(value);
480        }
481    }
482
483    /// Inserts an entry with a literal name on the encoder stream (4.3.3)
484    /// and mirrors it in the local table. Returns the instruction, or `None`
485    /// when the entry does not fit in the dynamic table.
486    #[inline]
487    pub(crate) fn insert_literal(&mut self, name: &[u8], value: &[u8]) -> Option<Bytes> {
488        let size = DynamicTable::entry_size(name, value);
489        if size > self.dynamic.capacity() || self.insert_would_evict_needed(size) {
490            return None;
491        }
492        let mut out = Vec::new();
493        // Name length uses a 5-bit prefix (RFC 9204 4.3.3), so `push_string`
494        // receives 6 (it reserves one bit for the Huffman flag).
495        self.push_string(&mut out, name, 6, INSERT_WITH_LITERAL_NAME);
496        self.push_string(&mut out, value, 8, 0);
497        let res = self
498            .dynamic
499            .insert(Bytes::copy_from_slice(name), Bytes::copy_from_slice(value));
500        debug_assert!(res.is_ok(), "insert_literal: entry passed the size check");
501        res.ok()?;
502        Some(Bytes::from(out))
503    }
504
505    /// Inserts an entry with a name reference on the encoder stream (4.3.2),
506    /// preferring the dynamic table (T=0) then the static table (T=1), and
507    /// mirrors it in the local table. Returns the instruction, or `None`
508    /// when the name is not indexed anywhere or the entry does not fit.
509    #[inline]
510    pub(crate) fn insert_with_name_ref(&mut self, name: &[u8], value: &[u8]) -> Option<Bytes> {
511        let size = DynamicTable::entry_size(name, value);
512        if size > self.dynamic.capacity() || self.insert_would_evict_needed(size) {
513            return None;
514        }
515        let mut out = Vec::new();
516        let (name_idx, pattern) = self
517            .dynamic
518            .find_name(name)
519            .map(|abs| (abs, INSERT_WITH_NAME_REF))
520            .or_else(|| {
521                static_table::find_name(name).map(|idx| (idx as u64, INSERT_WITH_NAME_REF | 0x40))
522            })?;
523        if pattern == INSERT_WITH_NAME_REF {
524            integer::encode(&mut out, self.dynamic.inserted() - name_idx - 1, 6, pattern);
525        } else {
526            integer::encode(&mut out, name_idx, 6, pattern);
527        }
528        self.push_string(&mut out, value, 8, 0);
529        let res = self
530            .dynamic
531            .insert(Bytes::copy_from_slice(name), Bytes::copy_from_slice(value));
532        debug_assert!(
533            res.is_ok(),
534            "insert_with_name_ref: entry passed the size check"
535        );
536        res.ok()?;
537        Some(Bytes::from(out))
538    }
539
540    /// Duplicates the entry at the given relative index (4.3.4) and mirrors
541    /// it in the local table. Returns the instruction, or `None` when the
542    /// index is out of range or the copy does not fit.
543    #[inline]
544    pub(crate) fn duplicate(&mut self, relative: u64) -> Option<Bytes> {
545        if relative >= self.dynamic.len() as u64 {
546            return None;
547        }
548        // The relative index is the deque position: 0 is the most recently
549        // inserted entry.
550        let (name, value) = self.dynamic.entry_at(relative)?;
551        let size = DynamicTable::entry_size(name, value);
552        if size > self.dynamic.capacity() || self.insert_would_evict_needed(size) {
553            return None;
554        }
555        let mut out = Vec::new();
556        integer::encode(&mut out, relative, 5, DUPLICATE);
557        let res = self
558            .dynamic
559            .insert(Bytes::copy_from_slice(name), Bytes::copy_from_slice(value));
560        debug_assert!(res.is_ok(), "duplicate: entry passed the size check");
561        res.ok()?;
562        Some(Bytes::from(out))
563    }
564
565    /// Sets the dynamic table capacity (4.3.1), evicting as needed. Returns
566    /// the instruction, or `None` when the capacity is unchanged, exceeds
567    /// the decoder's maximum, or the reduction would evict entries the
568    /// decoder still needs (RFC 9204 Section 2.1.1).
569    #[inline]
570    pub fn set_capacity(&mut self, capacity: u64) -> Option<Bytes> {
571        if capacity > self.max_capacity || capacity == self.dynamic.capacity() {
572            return None;
573        }
574        if capacity < self.dynamic.capacity() {
575            let evicted = self.dynamic.evict_for_capacity(capacity);
576            if self.dynamic.inserted() - self.dynamic.len() as u64 + evicted
577                > self.evictable_floor()
578            {
579                return None;
580            }
581        }
582        let mut out = Vec::new();
583        integer::encode(&mut out, capacity, 5, SET_CAPACITY);
584        self.dynamic.set_capacity(capacity);
585        Some(Bytes::from(out))
586    }
587
588    /// Whether inserting an entry of `size` bytes would evict an entry the
589    /// decoder still needs (RFC 9204 Section 2.1.1): one that is not yet
590    /// acknowledged or is referenced by an unacknowledged field section.
591    #[inline]
592    fn insert_would_evict_needed(&self, size: u64) -> bool {
593        self.dynamic.inserted() - self.dynamic.len() as u64 + self.dynamic.would_evict(size)
594            > self.evictable_floor()
595    }
596
597    /// Processes the decoder stream instructions in `buf`. Section
598    /// Acknowledgments (4.4.1) free a field section's references, Stream
599    /// Cancellations (4.4.2) free every reference of a cancelled stream,
600    /// and Insert Count Increments (4.4.3) raise the Known Received Count.
601    ///
602    /// Only an Insert Count Increment (4.4.3) is a connection error: a zero
603    /// increment, or one that advances past the number of inserts sent
604    /// (RFC 9204 Section 2.1.4). A Section Acknowledgment or Stream
605    /// Cancellation that matches no outstanding field section is benign —
606    /// RFC 9204 Section 4.4 defines no error for it and a peer may send one
607    /// for a section already acknowledged or cancelled — so it is ignored
608    /// rather than closing the connection.
609    #[inline]
610    pub fn feed_decoder_stream(&mut self, buf: &[u8]) -> Result<(), QpackError> {
611        self.decoder_stream_pending.extend_from_slice(buf);
612        // A decoder-stream instruction is a single prefixed integer, so its
613        // length is known once enough bytes have arrived. Parse only complete
614        // instructions, leaving any trailing partial instruction buffered for
615        // the next call (RFC 9204 Section 4.4). The QUIC stream beneath can
616        // deliver an instruction split across several `poll_recv` chunks.
617        // A complete instruction is at most 10 bytes (a 62-bit integer), so a
618        // much larger buffered prefix can never become one and is rejected to
619        // bound memory against a peer that streams continuation bytes.
620        if self.decoder_stream_pending.len() > MAX_DECODER_STREAM_PENDING {
621            return Err(QpackError::DecoderStream);
622        }
623        let data = &self.decoder_stream_pending;
624        let mut consumed = 0;
625        while consumed < data.len() {
626            let header = data[consumed];
627            // Every decoder-stream instruction carries one prefixed integer:
628            // a Section Acknowledgment (4.4.1) uses a 7-bit prefix, the other
629            // two types use 6 bits.
630            let prefix_bits: u8 = if header & 0x80 != 0 { 7 } else { 6 };
631            let Some(len) = integer::encoded_len(&data[consumed..], prefix_bits) else {
632                // Instruction is truncated; wait for more bytes.
633                break;
634            };
635            if consumed + len > data.len() {
636                break;
637            }
638            let instr = &data[consumed..consumed + len];
639            let mut off = 0;
640            if header & 0x80 != 0 {
641                // `1` + 7-bit stream ID: Section Acknowledgment (4.4.1).
642                // A matching field section is freed; a stray
643                // acknowledgment (already freed, or never outstanding) is
644                // ignored.
645                let stream_id = integer::decode(instr, &mut off, 7, header)
646                    .map_err(|_| QpackError::DecoderStream)?;
647                if let Some(pos) = self
648                    .pending_refs
649                    .iter()
650                    .position(|(id, _)| *id == stream_id)
651                {
652                    let removed_min = self.pending_refs[pos].1;
653                    self.pending_refs.remove(pos);
654                    if removed_min == self.cached_min_pending {
655                        self.cached_min_pending = self
656                            .pending_refs
657                            .iter()
658                            .map(|(_, min_ref)| *min_ref)
659                            .min()
660                            .unwrap_or(u64::MAX);
661                    }
662                }
663            } else if header & 0x40 != 0 {
664                // `01` + 6-bit stream ID: Stream Cancellation (4.4.2).
665                // Every outstanding reference of the stream is dropped; a
666                // cancellation for a stream with none is a no-op.
667                let stream_id = integer::decode(instr, &mut off, 6, header)
668                    .map_err(|_| QpackError::DecoderStream)?;
669                let prev_len = self.pending_refs.len();
670                self.pending_refs.retain(|(id, _)| *id != stream_id);
671                if self.pending_refs.len() != prev_len {
672                    self.cached_min_pending = self
673                        .pending_refs
674                        .iter()
675                        .map(|(_, min_ref)| *min_ref)
676                        .min()
677                        .unwrap_or(u64::MAX);
678                }
679            } else {
680                // `00` + 6-bit increment: Insert Count Increment (4.4.3).
681                // A zero increment is forbidden, and the total may not
682                // exceed the number of inserts sent.
683                let increment = integer::decode(instr, &mut off, 6, header)
684                    .map_err(|_| QpackError::DecoderStream)?;
685                if increment == 0
686                    || self.known_received.saturating_add(increment) > self.dynamic.inserted()
687                {
688                    return Err(QpackError::DecoderStream);
689                }
690                self.known_received += increment;
691            }
692            consumed += len;
693        }
694        self.decoder_stream_pending.drain(..consumed);
695        Ok(())
696    }
697}
698
699#[cfg(test)]
700mod tests;