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;