Skip to main content

zincio_http/h3/qpack/decoder/
mod.rs

1//! QPACK decoder (RFC 9204 Sections 2.2, 4.3 and 4.5).
2//!
3//! Consumption: the HTTP/3 layer drives the decoder per connection (it feeds
4//! encoder stream data and decoded field sections, and drains the decoder
5//! stream); until that lands, the whole module is dead in non-test builds,
6//! which is why `dead_code` is expected here. It errors again once the
7//! decoder is used, reminding us to remove the expectation.
8//!
9//! The decoder materializes the shared dynamic table (Section 4.2) from the
10//! encoder stream (Section 4.3): every instruction is parsed and mirrored as
11//! a table insertion or capacity change. Field sections (Section 4.5) are
12//! decoded against the table; a section whose Required Insert Count exceeds
13//! the decoder's insert count is buffered as blocked (Section 2.2.1) until a
14//! later encoder stream update makes it decodable.
15//!
16//! The decoder emits decoder stream instructions (Section 4.4): a Section
17//! Acknowledgment for every decoded field section with a positive Required
18//! Insert Count (Section 2.2.2.1), a Stream Cancellation for abandoned or
19//! timed-out blocked streams (Section 2.2.2.2), and coalesced Insert Count
20//! Increment instructions (Section 2.2.2.3).
21//!
22//! Validation is strict: malformed instructions are `QPACK_ENCODER_STREAM_
23//! ERROR`, malformed field sections are `QPACK_DECOMPRESSION_FAILED`, a
24//! Required Insert Count that does not equal the largest referenced absolute
25//! index plus one is rejected (Sections 2.1.2 and 2.2.1), evictions that
26//! touch entries with an absolute index at or above the Known Received Count
27//! are rejected (Sections 2.1.1 and 3.2.2), and field sections that push a
28//! stream's cumulative decoded size over the advertised
29//! `SETTINGS_MAX_FIELD_SECTION_SIZE` are rejected (RFC 9114 Section
30//! 7.2.4.1).
31#![expect(dead_code)]
32
33use std::collections::VecDeque;
34
35use bytes::Bytes;
36use rustc_hash::FxHashMap;
37
38use crate::h3::qpack::error::QpackError;
39use crate::h3::qpack::static_table;
40use crate::h3::qpack::table::DynamicTable;
41use crate::hpack::{huffman, integer, HpackError};
42
43/// `1` + 7-bit stream ID: Section Acknowledgment (RFC 9204 4.4.1).
44const SECTION_ACK: u8 = 0x80;
45/// `01` + 6-bit stream ID: Stream Cancellation (RFC 9204 4.4.2).
46const STREAM_CANCELLATION: u8 = 0x40;
47/// `00` + 6-bit increment: Insert Count Increment (RFC 9204 4.4.3).
48const INSERT_COUNT_INCREMENT: u8 = 0x00;
49
50// Encoder instruction patterns (RFC 9204 4.3), mirrored from the encoder.
51/// `001` + 5-bit capacity: Set Dynamic Table Capacity (4.3.1).
52const SET_CAPACITY: u8 = 0b0010_0000;
53/// `1 T` + 6-bit name index: Insert with Name Reference (4.3.2).
54const INSERT_WITH_NAME_REF: u8 = 0b1000_0000;
55/// `01` + H + 5-bit name length: Insert with Literal Name (4.3.3).
56const INSERT_WITH_LITERAL_NAME: u8 = 0b0100_0000;
57/// `000` + 5-bit relative index: Duplicate (4.3.4).
58const DUPLICATE: u8 = 0b0000_0000;
59
60// Field line patterns (RFC 9204 4.5), mirrored from the encoder.
61/// `1 T` + 6-bit index: Indexed Field Line (4.5.2).
62const INDEXED: u8 = 0b1000_0000;
63/// `0001` + 4-bit post-Base index: Indexed Field Line with Post-Base Index
64/// (4.5.3).
65const INDEXED_POST_BASE: u8 = 0b0001_0000;
66/// `01 N T` + 4-bit name index: Literal Field Line with Name Reference
67/// (4.5.4).
68const LITERAL_NAME_REF: u8 = 0b0100_0000;
69/// `0000 N` + 3-bit post-Base name index: Literal Field Line with Post-Base
70/// Name Reference (4.5.5).
71const LITERAL_POST_BASE_NAME_REF: u8 = 0b0000_0000;
72/// `001 N` + H + 3-bit name length: Literal Field Line with Literal Name
73/// (4.5.6).
74const LITERAL_LITERAL_NAME: u8 = 0b0010_0000;
75
76/// A field section that was buffered as blocked and has since been decoded
77/// after an encoder stream update.
78#[derive(Debug, Clone, PartialEq, Eq)]
79pub struct UnblockedSection {
80    /// The stream the encoded field section was received on.
81    pub stream_id: u64,
82    /// The decoded header list.
83    pub headers: Vec<(Bytes, Bytes)>,
84}
85
86/// A field section buffered because its Required Insert Count had not been
87/// reached yet (RFC 9204 Section 2.2.1).
88#[derive(Debug)]
89struct BlockedSection {
90    stream_id: u64,
91    buf: Bytes,
92    ric: u64,
93    since: u64,
94}
95
96/// QPACK decoder: dynamic table mirror and field section decoder.
97#[derive(Debug)]
98pub struct Decoder {
99    dynamic: DynamicTable,
100    /// The maximum dynamic table capacity advertised by this decoder in
101    /// SETTINGS_QPACK_MAX_TABLE_CAPACITY (RFC 9204 Section 3.2.3).
102    max_capacity: u64,
103    /// Total insertions and duplications received on the encoder stream.
104    /// Part of the field section prefix decoding context.
105    ///
106    /// The Known Received Count, which rules evictability and the Insert
107    /// Count Increment instruction, is tracked separately in
108    /// [`Decoder::known_received`] (RFC 9204 Section 2.1.4).
109    ///
110    /// Invariant: `known_received <= inserted`.
111    known_received: u64,
112    /// Blocked field sections, in arrival order.
113    blocked: VecDeque<BlockedSection>,
114    /// Number of blocked sections per stream. Kept separately so admission
115    /// does not rescan every buffered section (or allocate a temporary list)
116    /// for every field section received while the table is catching up.
117    blocked_by_stream: FxHashMap<u64, usize>,
118    /// Decoded field-section size per stream, summed so the
119    /// `SETTINGS_MAX_FIELD_SECTION_SIZE` budget applies across a stream's
120    /// field sections (request headers, trailers) like
121    /// `SETTINGS_MAX_HEADER_LIST_SIZE` does for HTTP/2 — not per section.
122    ///
123    /// A stream's budget is only charged when a section is actually
124    /// decoded, so a section buffered as blocked is charged when it is
125    /// unblocked. Entries are dropped when the stream finishes, is reset,
126    /// or is abandoned ([`Decoder::stream_finished`],
127    /// [`Decoder::stream_cancelled`], [`Decoder::expire_blocked`]); QUIC
128    /// stream IDs are never reused, so a stale entry could not affect
129    /// another stream anyway.
130    section_size_by_stream: FxHashMap<u64, usize>,
131    /// The maximum number of streams that may be blocked at once,
132    /// SETTINGS_QPACK_BLOCKED_STREAMS (RFC 9204 Section 5).
133    max_blocked_streams: usize,
134    /// Cap on the total size of a decoded field section (the sum of the
135    /// lengths of the names and values of its field lines), the locally
136    /// advertised `SETTINGS_MAX_FIELD_SECTION_SIZE` (RFC 9114 Section
137    /// 7.2.4.1). Exceeding it is `QPACK_DECOMPRESSION_FAILED`.
138    max_field_section_size: usize,
139    /// Decoder stream instructions awaiting transmission.
140    decoder_stream: Vec<u8>,
141    /// Bytes of the peer's encoder stream received so far but not yet forming
142    /// a complete instruction. QPACK encoder-stream instructions carry
143    /// variable-length strings and can span the arbitrary chunk boundaries of
144    /// the underlying QUIC stream, so partial instructions are buffered here
145    /// until the rest arrives (RFC 9204 Section 4.3) instead of being treated
146    /// as a stream error.
147    encoder_stream_pending: Vec<u8>,
148    /// Bytes of the peer's decoder stream received so far but not yet forming
149    /// a complete instruction. The same chunk-boundary buffering as
150    /// `encoder_stream_pending` (RFC 9204 Section 4.4).
151    decoder_stream_pending: Vec<u8>,
152}
153
154/// Upper bound on the buffered prefix of either peer stream. A complete
155/// decoder-stream instruction is a single prefixed integer of at most 10
156/// bytes, so a buffered prefix far larger than this can never become one.
157const MAX_DECODER_STREAM_PENDING: usize = 64;
158
159/// Byte length of a string literal whose length integer starts at the
160/// beginning of `buf` with `prefix_bits` (the Huffman/control bit occupies the
161/// high bit of that prefix, so the integer itself uses `prefix_bits - 1`
162/// bits). Returns `None` when `buf` is too short to hold the length integer
163/// or its advertised content.
164fn string_len(buf: &[u8], prefix_bits: u8) -> Option<usize> {
165    let int_prefix = prefix_bits - 1;
166    let int_len = integer::encoded_len(buf, int_prefix)?;
167    // `decode` consumes `buf[0]` as the header, then continuation octets from
168    // `off` onward, so `off` must point past the header byte.
169    let mut off = 1;
170    let len = integer::decode(buf, &mut off, int_prefix, buf[0]).ok()?;
171    let content = usize::try_from(len).ok()?;
172    let total = int_len.checked_add(content)?;
173    if buf.len() < total {
174        return None;
175    }
176    Some(total)
177}
178
179/// Byte length of one encoder-stream instruction (RFC 9204 Section 4.3)
180/// starting at the beginning of `buf`, or `None` when `buf` is too short to
181/// contain the whole instruction.
182fn encoder_instruction_len(buf: &[u8]) -> Option<usize> {
183    let header = *buf.first()?;
184    match header & 0xC0 {
185        // Insert with Name Reference (4.3.2): relative/static index (6-bit
186        // prefix) followed by the value string.
187        0x80 | 0xC0 => {
188            let index_len = integer::encoded_len(buf, 6)?;
189            let value_len = string_len(buf.get(index_len..)?, 8)?;
190            Some(index_len + value_len)
191        }
192        // Insert with Literal Name (4.3.3): name string (the instruction's
193        // first byte doubles as the name-length prefix, 6-bit prefix) followed
194        // by the value string (8-bit prefix).
195        0x40 => {
196            let name_len = string_len(buf, 6)?;
197            let value_len = string_len(buf.get(name_len..)?, 8)?;
198            Some(name_len + value_len)
199        }
200        // `00`: Set Dynamic Table Capacity (4.3.1) or Duplicate (4.3.4), each
201        // a 5-bit prefixed integer.
202        _ => integer::encoded_len(buf, 5),
203    }
204}
205
206/// Byte length of one decoder-stream instruction (RFC 9204 Section 4.4)
207/// starting at the beginning of `buf`, or `None` when `buf` is too short.
208fn decoder_instruction_len(buf: &[u8]) -> Option<usize> {
209    let header = *buf.first()?;
210    let prefix_bits: u8 = if header & 0x80 != 0 { 7 } else { 6 };
211    integer::encoded_len(buf, prefix_bits)
212}
213
214impl Decoder {
215    /// Creates a decoder that advertised `max_capacity` in
216    /// SETTINGS_QPACK_MAX_TABLE_CAPACITY and `max_blocked_streams` in
217    /// SETTINGS_QPACK_BLOCKED_STREAMS.
218    #[inline]
219    pub fn new(max_capacity: u64, max_blocked_streams: usize) -> Self {
220        Self {
221            dynamic: DynamicTable::without_maps(0),
222            max_capacity,
223            known_received: 0,
224            blocked: VecDeque::new(),
225            blocked_by_stream: FxHashMap::default(),
226            section_size_by_stream: FxHashMap::default(),
227            max_blocked_streams,
228            max_field_section_size: usize::MAX,
229            decoder_stream: Vec::new(),
230            encoder_stream_pending: Vec::new(),
231            decoder_stream_pending: Vec::new(),
232        }
233    }
234
235    /// Sets the maximum size of a decoded field section: the locally
236    /// advertised `SETTINGS_MAX_FIELD_SECTION_SIZE` (RFC 9114 Section
237    /// 7.2.4.1). The budget is per stream and accumulates across its field
238    /// sections (request headers and trailers); a section that pushes a
239    /// stream's cumulative name and value octets over this is rejected
240    /// with `QPACK_DECOMPRESSION_FAILED` (RFC 9204 Section 4.5).
241    #[inline]
242    pub fn set_max_field_section_size(&mut self, size: usize) {
243        self.max_field_section_size = size;
244    }
245
246    /// The total number of insertions and duplications materialized from the
247    /// encoder stream so far; the decoder's Insert Count (RFC 9204
248    /// Section 2.1.1).
249    #[inline]
250    pub fn inserted(&self) -> u64 {
251        self.dynamic.inserted()
252    }
253
254    /// The Known Received Count: insertions and duplications the decoder has
255    /// acknowledged or incremented (RFC 9204 Section 2.1.4).
256    #[inline]
257    pub fn known_received(&self) -> u64 {
258        self.known_received
259    }
260
261    /// The number of field sections currently buffered as blocked.
262    #[inline]
263    pub fn pending_blocked(&self) -> usize {
264        self.blocked.len()
265    }
266
267    /// Takes the accumulated decoder stream instructions.
268    #[inline]
269    pub fn take_decoder_stream(&mut self) -> Bytes {
270        Bytes::from(std::mem::take(&mut self.decoder_stream))
271    }
272
273    /// Parses the peer's QPACK decoder stream instructions (RFC 9204
274    /// Section 4.4): Section Acknowledgments, Stream Cancellations, and
275    /// Insert Count Increments.
276    ///
277    /// An Insert Count Increment with a zero value is a decoder stream
278    /// error (Section 4.4.3); any malformed instruction is too. The
279    /// instructions are not otherwise acted upon: this decoder emits its own
280    /// decoder-stream instructions and never tracks the peer's
281    /// acknowledgements, so it only needs to validate what the peer sends.
282    #[inline]
283    pub fn feed_decoder_stream(&mut self, buf: &[u8]) -> Result<(), QpackError> {
284        self.decoder_stream_pending.extend_from_slice(buf);
285        // A decoder-stream instruction is a single prefixed integer whose
286        // length is known once enough bytes arrive; parse only complete
287        // instructions and buffer any trailing partial one (RFC 9204 4.4). The
288        // QUIC stream beneath can deliver an instruction split across chunks.
289        if self.decoder_stream_pending.len() > MAX_DECODER_STREAM_PENDING {
290            return Err(QpackError::DecoderStream);
291        }
292        let mut consumed = 0;
293        while consumed < self.decoder_stream_pending.len() {
294            let Some(len) = decoder_instruction_len(&self.decoder_stream_pending[consumed..])
295            else {
296                break;
297            };
298            if consumed + len > self.decoder_stream_pending.len() {
299                break;
300            }
301            let instr = &self.decoder_stream_pending[consumed..consumed + len];
302            let mut off = 0;
303            let header = instr[0];
304            if header & 0x80 != 0 {
305                // `1` + 7-bit stream ID: Section Acknowledgment (4.4.1).
306                integer::decode(instr, &mut off, 7, header).map_err(dec_stream_err)?;
307            } else if header & 0x40 != 0 {
308                // `01` + 6-bit stream ID: Stream Cancellation (4.4.2).
309                integer::decode(instr, &mut off, 6, header).map_err(dec_stream_err)?;
310            } else {
311                // `00` + 6-bit increment: Insert Count Increment (4.4.3). A
312                // zero increment is forbidden.
313                let increment =
314                    integer::decode(instr, &mut off, 6, header).map_err(dec_stream_err)?;
315                if increment == 0 {
316                    return Err(QpackError::DecoderStream);
317                }
318            }
319            consumed += len;
320        }
321        self.decoder_stream_pending.drain(..consumed);
322        Ok(())
323    }
324
325    /// Processes the encoder stream instructions in `buf`, materializing
326    /// dynamic table updates.
327    ///
328    /// Returns the field sections that were blocked and can now be decoded,
329    /// in arrival order. The Section Acknowledgment for each is queued in
330    /// the decoder stream, together with a coalesced Insert Count Increment
331    /// when the table grew beyond the acknowledged count.
332    #[inline]
333    pub fn feed_encoder_stream(&mut self, buf: &[u8]) -> Result<Vec<UnblockedSection>, QpackError> {
334        self.encoder_stream_pending.extend_from_slice(buf);
335        // Buffer partial instructions: an encoder-stream instruction can carry
336        // variable-length strings and may arrive split across the arbitrary
337        // chunk boundaries of the underlying QUIC stream, so only complete
338        // instructions are processed (RFC 9204 Section 4.3). A complete
339        // instruction is bounded by the dynamic table capacity, so a much
340        // larger buffered prefix can never become one and is rejected to bound
341        // memory against a peer that streams continuation bytes.
342        let cap = (self.max_capacity as usize).saturating_add(1024);
343        if self.encoder_stream_pending.len() > cap {
344            return Err(QpackError::EncoderStream);
345        }
346        let mut consumed = 0;
347        while consumed < self.encoder_stream_pending.len() {
348            let Some(len) = encoder_instruction_len(&self.encoder_stream_pending[consumed..])
349            else {
350                break;
351            };
352            if consumed + len > self.encoder_stream_pending.len() {
353                break;
354            }
355            // Copy the complete instruction so it can be parsed while `self`
356            // is mutably borrowed by the insert below (no borrow aliasing).
357            let instr = self.encoder_stream_pending[consumed..consumed + len].to_vec();
358            self.parse_encoder_instruction(&instr)?;
359            consumed += len;
360        }
361        self.encoder_stream_pending.drain(..consumed);
362
363        // Unblock every field section whose Required Insert Count has been
364        // reached, in arrival order.
365        let mut sections = Vec::with_capacity(self.blocked.len());
366        while let Some(front) = self.blocked.front() {
367            if front.ric > self.dynamic.inserted() {
368                break;
369            }
370            let front = self.blocked.pop_front().expect("front just inspected");
371            self.remove_blocked_section(front.stream_id);
372            let headers = self.decode_ready(&front.buf)?;
373            let size: usize = headers.iter().map(|(n, v)| n.len() + v.len()).sum();
374            if self.account_section(front.stream_id, size) > self.max_field_section_size {
375                return Err(QpackError::DecompressionFailed);
376            }
377            if front.ric > 0 {
378                self.acknowledge(front.ric);
379                self.emit_section_ack(front.stream_id);
380            }
381            sections.push(UnblockedSection {
382                stream_id: front.stream_id,
383                headers,
384            });
385        }
386
387        // Coalesced Insert Count Increment (2.2.2.3): the encoder may free
388        // references as soon as the received entries are acknowledged.
389        if self.dynamic.inserted() > self.known_received {
390            integer::encode(
391                &mut self.decoder_stream,
392                self.dynamic.inserted() - self.known_received,
393                6,
394                INSERT_COUNT_INCREMENT,
395            );
396            self.known_received = self.dynamic.inserted();
397        }
398        Ok(sections)
399    }
400
401    /// Parses a single *complete* encoder-stream instruction (RFC 9204
402    /// Section 4.3) from `instr` and materializes its dynamic-table update.
403    /// `feed_encoder_stream` guarantees `instr` holds a full instruction, so
404    /// the parses below cannot run out of bytes.
405    #[inline]
406    fn parse_encoder_instruction(&mut self, instr: &[u8]) -> Result<(), QpackError> {
407        let mut off = 0;
408        let header = instr[0];
409        off += 1;
410        match header & 0xC0 {
411            // `1 T` + 6-bit name index: Insert with Name Reference (4.3.2).
412            // The T bit being set masks to 0xC0, hence the two-arm pattern.
413            0x80 | 0xC0 => {
414                let index = integer::decode(instr, &mut off, 6, header).map_err(enc_stream_err)?;
415                let value = self
416                    .read_value_string(instr, &mut off)
417                    .map_err(enc_stream_err)?;
418                let name = if header & 0x40 != 0 {
419                    // T=1: static table.
420                    let idx = usize::try_from(index).map_err(|_| QpackError::EncoderStream)?;
421                    let (name, _) = static_table::get(idx).ok_or(QpackError::EncoderStream)?;
422                    Bytes::from_static(name)
423                } else {
424                    // T=0: dynamic table, relative index (index 0 is the most
425                    // recently inserted entry).
426                    let (name, _) = self
427                        .dynamic
428                        .get_relative_bytes(index)
429                        .ok_or(QpackError::EncoderStream)?;
430                    name
431                };
432                self.insert_entry(name, value)?;
433            }
434            0x40 => {
435                // Insert with Literal Name (4.3.3): the name length uses a
436                // 5-bit prefix, so `read_string` receives 6 (it reserves one
437                // bit for the Huffman flag).
438                let name = self
439                    .read_string(instr, &mut off, 6, header)
440                    .map_err(enc_stream_err)?;
441                let value = self
442                    .read_value_string(instr, &mut off)
443                    .map_err(enc_stream_err)?;
444                self.insert_entry(name, value)?;
445            }
446            _ => {
447                if header & 0x20 != 0 {
448                    // Set Dynamic Table Capacity (4.3.1).
449                    let capacity =
450                        integer::decode(instr, &mut off, 5, header).map_err(enc_stream_err)?;
451                    if capacity > self.max_capacity {
452                        return Err(QpackError::EncoderStream);
453                    }
454                    let evicted = self.dynamic.evict_for_capacity(capacity);
455                    if evicted > self.known_received {
456                        return Err(QpackError::EncoderStream);
457                    }
458                    self.dynamic.set_capacity(capacity);
459                } else {
460                    // Duplicate (4.3.4): relative index, 0 being the most
461                    // recently inserted entry.
462                    let index =
463                        integer::decode(instr, &mut off, 5, header).map_err(enc_stream_err)?;
464                    let (name, value) = self
465                        .dynamic
466                        .get_relative_bytes(index)
467                        .ok_or(QpackError::EncoderStream)?;
468                    self.insert_entry(name, value)?;
469                }
470            }
471        }
472        Ok(())
473    }
474
475    /// Decodes an encoded field section received on `stream_id`.
476    ///
477    /// Returns the decoded header list, or `None` when the section was
478    /// buffered as blocked (it is returned by a later
479    /// [`Decoder::feed_encoder_stream`] call).
480    ///
481    /// `now` is the caller's monotonic clock (any unit); it is recorded for
482    /// [`Decoder::expire_blocked`]. Sections that cannot be processed in
483    /// order are never decoded early: a section on a stream with buffered
484    /// blocked sections joins the queue even when it could be decoded
485    /// already (RFC 9204 Section 2.2.1 requires in-order processing).
486    #[inline]
487    pub fn decode_block(
488        &mut self,
489        buf: &[u8],
490        stream_id: u64,
491        now: u64,
492    ) -> Result<Option<Vec<(Bytes, Bytes)>>, QpackError> {
493        let (ric, _, _) = self.read_prefix(buf)?;
494        let stream_blocked = self.stream_has_blocked(stream_id);
495        if ric > self.dynamic.inserted() || stream_blocked {
496            if !stream_blocked && self.blocked_by_stream.len() >= self.max_blocked_streams {
497                return Err(QpackError::DecompressionFailed);
498            }
499            self.blocked.push_back(BlockedSection {
500                stream_id,
501                buf: Bytes::copy_from_slice(buf),
502                ric,
503                since: now,
504            });
505            self.add_blocked_section(stream_id);
506            return Ok(None);
507        }
508        let headers = self.decode_ready(buf)?;
509        let size: usize = headers.iter().map(|(n, v)| n.len() + v.len()).sum();
510        if self.account_section(stream_id, size) > self.max_field_section_size {
511            return Err(QpackError::DecompressionFailed);
512        }
513        if ric > 0 {
514            self.acknowledge(ric);
515            self.emit_section_ack(stream_id);
516        }
517        Ok(Some(headers))
518    }
519
520    /// Notifies that `stream_id` finished receiving (the peer closed its
521    /// send side): no further field sections can arrive on it, so its
522    /// field-section size budget is released. Safe to call when the stream
523    /// never carried a field section.
524    ///
525    /// Must only be called when no field section of the stream remains
526    /// buffered as blocked: a buffered section is decoded later by
527    /// [`Decoder::feed_encoder_stream`], which would then restart the
528    /// stream's budget from scratch. The HTTP/3 layer calls this only upon
529    /// observing the peer's stream end, which implies every section of the
530    /// stream (headers, trailers) has decoded.
531    #[inline]
532    pub fn stream_finished(&mut self, stream_id: u64) {
533        self.section_size_by_stream.remove(&stream_id);
534    }
535
536    /// Notifies that `stream_id` was reset or abandoned: buffered blocked
537    /// sections for it are dropped and a Stream Cancellation instruction is
538    /// queued (RFC 9204 Section 2.2.2.2). Returns the instruction.
539    #[inline]
540    pub fn stream_cancelled(&mut self, stream_id: u64) -> Bytes {
541        self.blocked.retain(|b| b.stream_id != stream_id);
542        self.blocked_by_stream.remove(&stream_id);
543        self.section_size_by_stream.remove(&stream_id);
544        let mut out = Vec::new();
545        integer::encode(&mut out, stream_id, 6, STREAM_CANCELLATION);
546        self.decoder_stream.extend_from_slice(&out);
547        Bytes::from_owner(out)
548    }
549
550    /// Drops blocked sections older than `max_age` in the caller's clock
551    /// units, queueing one Stream Cancellation per affected stream. Returns
552    /// the instructions.
553    #[inline]
554    pub fn expire_blocked(&mut self, now: u64, max_age: u64) -> Bytes {
555        let mut out = Vec::new();
556        let mut cancelled = Vec::with_capacity(self.blocked.len() / 2);
557        for blocked in &self.blocked {
558            if now.saturating_sub(blocked.since) > max_age
559                && !cancelled.contains(&blocked.stream_id)
560            {
561                cancelled.push(blocked.stream_id);
562                integer::encode(&mut out, blocked.stream_id, 6, STREAM_CANCELLATION);
563            }
564        }
565        if !cancelled.is_empty() {
566            // Cancelling a stream abandons every queued section on it, not
567            // merely the one whose timer fired. Keep the queue and its
568            // per-stream accounting in lockstep.
569            self.blocked
570                .retain(|blocked| !cancelled.contains(&blocked.stream_id));
571            for id in &cancelled {
572                self.blocked_by_stream.remove(id);
573                self.section_size_by_stream.remove(id);
574            }
575        }
576        self.decoder_stream.extend_from_slice(&out);
577        Bytes::from(out)
578    }
579
580    /// Decodes a field section whose Required Insert Count has been reached
581    /// (immediate or retried from the blocked queue), validating that the
582    /// announced Required Insert Count equals the largest referenced
583    /// absolute index plus one (RFC 9204 Section 2.1.2).
584    #[inline]
585    fn decode_ready(&self, buf: &[u8]) -> Result<Vec<(Bytes, Bytes)>, QpackError> {
586        let (ric, base, mut off) = self.read_prefix(buf)?;
587        let mut headers = Vec::new();
588        let mut needed = 0u64;
589        while off < buf.len() {
590            let header = buf[off];
591            off += 1;
592            if header & 0x80 != 0 {
593                // Indexed Field Line (4.5.2).
594                let index = integer::decode(buf, &mut off, 6, header).map_err(dec_failed)?;
595                if header & 0x40 != 0 {
596                    // Static table (T=1).
597                    let idx =
598                        usize::try_from(index).map_err(|_| QpackError::DecompressionFailed)?;
599                    let (name, value) =
600                        static_table::get(idx).ok_or(QpackError::DecompressionFailed)?;
601                    headers.push((Bytes::from_static(name), Bytes::from_static(value)));
602                } else {
603                    // Dynamic table (T=0): relative index from the Base.
604                    let (name, value) = self
605                        .dynamic
606                        .get_base_relative_bytes(base, index)
607                        .ok_or(QpackError::DecompressionFailed)?;
608                    needed = needed.max(base - index);
609                    headers.push((name, value));
610                }
611            } else if header & 0x40 != 0 {
612                // Literal Field Line with Name Reference (4.5.4).
613                let index = integer::decode(buf, &mut off, 4, header).map_err(dec_failed)?;
614                let value = self.read_value_string(buf, &mut off).map_err(dec_failed)?;
615                if header & 0x10 != 0 {
616                    // Static table (T=1).
617                    let idx =
618                        usize::try_from(index).map_err(|_| QpackError::DecompressionFailed)?;
619                    let (name, _) =
620                        static_table::get(idx).ok_or(QpackError::DecompressionFailed)?;
621                    headers.push((Bytes::from_static(name), value));
622                } else {
623                    // Dynamic table (T=0): relative index from the Base.
624                    let (name, _) = self
625                        .dynamic
626                        .get_base_relative_bytes(base, index)
627                        .ok_or(QpackError::DecompressionFailed)?;
628                    needed = needed.max(base - index);
629                    headers.push((name, value));
630                }
631            } else if header & 0x20 != 0 {
632                // Literal Field Line with Literal Name (4.5.6): the N bit is
633                // an instruction to peers not to index the line; it does not
634                // affect decoding.
635                let name = self
636                    .read_string(buf, &mut off, 4, header)
637                    .map_err(dec_failed)?;
638                let value = self.read_value_string(buf, &mut off).map_err(dec_failed)?;
639                headers.push((name, value));
640            } else if header & 0x10 != 0 {
641                // Indexed Field Line with Post-Base Index (4.5.3).
642                let index = integer::decode(buf, &mut off, 4, header).map_err(dec_failed)?;
643                let (name, value) = self
644                    .dynamic
645                    .get_post_base_bytes(base, index)
646                    .ok_or(QpackError::DecompressionFailed)?;
647                needed = needed.max(base + index + 1);
648                headers.push((name, value));
649            } else {
650                // Literal Field Line with Post-Base Name Reference (4.5.5).
651                let index = integer::decode(buf, &mut off, 3, header).map_err(dec_failed)?;
652                let (name, _) = self
653                    .dynamic
654                    .get_post_base_bytes(base, index)
655                    .ok_or(QpackError::DecompressionFailed)?;
656                needed = needed.max(base + index + 1);
657                let value = self.read_value_string(buf, &mut off).map_err(dec_failed)?;
658                headers.push((name, value));
659            }
660        }
661        if ric != needed {
662            return Err(QpackError::DecompressionFailed);
663        }
664        Ok(headers)
665    }
666
667    /// Parses the Encoded Field Section Prefix (RFC 9204 Section 4.5.1):
668    /// the Required Insert Count and the Base. Returns both and the number
669    /// of octets consumed.
670    #[inline]
671    fn read_prefix(&self, buf: &[u8]) -> Result<(u64, u64, usize), QpackError> {
672        let header = *buf.first().ok_or(QpackError::DecompressionFailed)?;
673        let mut off = 1;
674        let enc_ric = integer::decode(buf, &mut off, 8, header).map_err(dec_failed)?;
675        let max_entries = self.max_capacity / 32;
676        let ric = if enc_ric == 0 || max_entries == 0 {
677            0
678        } else {
679            let full_range = 2 * max_entries;
680            if enc_ric > full_range {
681                return Err(QpackError::DecompressionFailed);
682            }
683            let max_value = self.dynamic.inserted() + max_entries;
684            let max_wrapped = (max_value / full_range) * full_range;
685            let mut ric = max_wrapped + enc_ric - 1;
686            if ric > max_value {
687                if ric <= full_range {
688                    return Err(QpackError::DecompressionFailed);
689                }
690                ric -= full_range;
691            }
692            if ric == 0 {
693                return Err(QpackError::DecompressionFailed);
694            }
695            ric
696        };
697
698        // Base (4.5.1.2): Sign = 0 means Base = Ric + Delta; Sign = 1 means
699        // Base = Ric - Delta - 1.
700        let header = *buf.get(off).ok_or(QpackError::DecompressionFailed)?;
701        off += 1;
702        let delta = integer::decode(buf, &mut off, 7, header).map_err(dec_failed)?;
703        let base = if header & 0x80 != 0 {
704            ric.checked_sub(delta + 1)
705                .ok_or(QpackError::DecompressionFailed)?
706        } else {
707            ric.checked_add(delta)
708                .ok_or(QpackError::DecompressionFailed)?
709        };
710        Ok((ric, base, off))
711    }
712
713    /// Reads an N-bit-prefix string literal (RFC 9204 Section 4.1.2) at
714    /// `off`, advancing `off` past it. `header` is an already-consumed octet
715    /// carrying the Huffman bit (its `prefix_bits - 1` bit) and the length
716    /// prefix; for name strings it is the field line's or instruction's
717    /// first octet, which pairs with the name length prefix.
718    #[inline]
719    fn read_string(
720        &self,
721        buf: &[u8],
722        off: &mut usize,
723        prefix_bits: u8,
724        header: u8,
725    ) -> Result<Bytes, HpackError> {
726        let huffman = header & (1 << (prefix_bits - 1)) != 0;
727        let len = integer::decode(buf, off, prefix_bits - 1, header)?;
728        let len = usize::try_from(len).map_err(|_| HpackError::InvalidString)?;
729        let end = (*off).checked_add(len).ok_or(HpackError::InvalidString)?;
730        let src = buf.get(*off..end).ok_or(HpackError::InvalidString)?;
731        *off = end;
732        if huffman {
733            let mut dst = Vec::with_capacity(len);
734            huffman::decode(src, &mut dst)?;
735            Ok(Bytes::from(dst))
736        } else {
737            Ok(Bytes::copy_from_slice(src))
738        }
739    }
740
741    /// Reads an 8-bit-prefix string literal whose length octet is next in
742    /// the buffer, advancing `off` past it. This is the form of every value
743    /// string (RFC 9204 Section 4.5).
744    #[inline]
745    fn read_value_string(&self, buf: &[u8], off: &mut usize) -> Result<Bytes, HpackError> {
746        let header = *buf.get(*off).ok_or(HpackError::InvalidString)?;
747        *off += 1;
748        self.read_string(buf, off, 8, header)
749    }
750
751    /// Inserts an entry named `name` with `value`, enforcing the eviction
752    /// rules of RFC 9204 Sections 2.1.1 and 3.2.2: entries with an absolute
753    /// index at or above the Known Received Count are not evictable, so an
754    /// insert that would evict them is an encoder error.
755    #[inline]
756    fn insert_entry(&mut self, name: Bytes, value: Bytes) -> Result<(), QpackError> {
757        let size = DynamicTable::entry_size(&name, &value);
758        let evicted = self.dynamic.would_evict(size);
759        if evicted > self.known_received {
760            return Err(QpackError::EncoderStream);
761        }
762        self.dynamic
763            .insert(name, value)
764            .map_err(|_| QpackError::EncoderStream)
765    }
766
767    /// Raises the Known Received Count to `ric` (RFC 9204 Section 2.1.4).
768    #[inline]
769    fn acknowledge(&mut self, ric: u64) {
770        self.known_received = self.known_received.max(ric);
771    }
772
773    /// Queues a Section Acknowledgment for `stream_id`, unless the maximum
774    /// dynamic table capacity is zero (the encoder has no dynamic table
775    /// references to free).
776    #[inline]
777    fn emit_section_ack(&mut self, stream_id: u64) {
778        integer::encode(&mut self.decoder_stream, stream_id, 7, SECTION_ACK);
779    }
780
781    /// Whether `stream_id` has buffered blocked sections.
782    #[inline]
783    fn stream_has_blocked(&self, stream_id: u64) -> bool {
784        self.blocked_by_stream.contains_key(&stream_id)
785    }
786
787    /// Records a newly buffered field section without rescanning the queue.
788    #[inline]
789    fn add_blocked_section(&mut self, stream_id: u64) {
790        *self.blocked_by_stream.entry(stream_id).or_insert(0) += 1;
791    }
792
793    /// Charges `size` against `stream_id`'s field-section budget and
794    /// returns the stream's new cumulative total. Creates the entry on
795    /// first use.
796    ///
797    /// `size` is the decoded name and value octets, not the size of the
798    /// encoded block: Huffman encoding and index references make the two
799    /// diverge arbitrarily (RFC 9114 Section 7.2.4.1).
800    #[inline]
801    fn account_section(&mut self, stream_id: u64, size: usize) -> usize {
802        let total = self.section_size_by_stream.entry(stream_id).or_insert(0);
803        *total = total.saturating_add(size);
804        *total
805    }
806
807    /// Removes one decoded blocked section from the per-stream count.
808    #[inline]
809    fn remove_blocked_section(&mut self, stream_id: u64) {
810        let Some(count) = self.blocked_by_stream.get_mut(&stream_id) else {
811            debug_assert!(false, "blocked section missing stream accounting");
812            return;
813        };
814        if *count == 1 {
815            self.blocked_by_stream.remove(&stream_id);
816        } else {
817            *count -= 1;
818        }
819    }
820}
821
822/// Maps an `HpackError` from an encoder stream instruction to
823/// `QPACK_ENCODER_STREAM_ERROR`.
824#[inline]
825fn enc_stream_err(_: HpackError) -> QpackError {
826    QpackError::EncoderStream
827}
828
829/// Maps an `HpackError` from a field section to `QPACK_DECOMPRESSION_FAILED`.
830#[inline]
831fn dec_failed(_: HpackError) -> QpackError {
832    QpackError::DecompressionFailed
833}
834
835/// Maps an `HpackError` from a decoder stream instruction to
836/// `QPACK_DECODER_STREAM_ERROR`.
837#[inline]
838fn dec_stream_err(_: HpackError) -> QpackError {
839    QpackError::DecoderStream
840}
841#[cfg(test)]
842mod tests;