Skip to main content

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