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;