quiche/h3/qpack/decoder.rs
1// Copyright (C) 2019, Cloudflare, Inc.
2// All rights reserved.
3//
4// Redistribution and use in source and binary forms, with or without
5// modification, are permitted provided that the following conditions are
6// met:
7//
8// * Redistributions of source code must retain the above copyright notice,
9// this list of conditions and the following disclaimer.
10//
11// * Redistributions in binary form must reproduce the above copyright
12// notice, this list of conditions and the following disclaimer in the
13// documentation and/or other materials provided with the distribution.
14//
15// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
16// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
17// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
19// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
22// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
23// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
24// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
27use super::Error;
28use super::Result;
29
30use crate::h3::Header;
31
32use super::INDEXED;
33use super::INDEXED_WITH_POST_BASE;
34use super::LITERAL;
35use super::LITERAL_WITH_NAME_REF;
36
37#[derive(Clone, Copy, Debug, PartialEq)]
38enum Representation {
39 Indexed,
40 IndexedWithPostBase,
41 Literal,
42 LiteralWithNameRef,
43 LiteralWithPostBase,
44}
45
46impl Representation {
47 pub fn from_byte(b: u8) -> Representation {
48 if b & INDEXED == INDEXED {
49 return Representation::Indexed;
50 }
51
52 if b & LITERAL_WITH_NAME_REF == LITERAL_WITH_NAME_REF {
53 return Representation::LiteralWithNameRef;
54 }
55
56 if b & LITERAL == LITERAL {
57 return Representation::Literal;
58 }
59
60 if b & INDEXED_WITH_POST_BASE == INDEXED_WITH_POST_BASE {
61 return Representation::IndexedWithPostBase;
62 }
63
64 Representation::LiteralWithPostBase
65 }
66}
67
68/// Helper for tracking decoded field list sizes.
69///
70/// The size of a field list is calculated based on the uncompressed size of
71/// fields, including the length of the name and value in bytes plus an overhead
72/// of 32 bytes for each field. See
73/// <https://datatracker.ietf.org/doc/html/rfc9114#section-4.2.2>
74struct FieldListSizeTracker {
75 remaining: u64,
76}
77
78impl FieldListSizeTracker {
79 /// Initialize tracker with the maximum field list size.
80 ///
81 /// The `max_size` parameter is the maximum size in bytes of the full
82 /// decoded field list. See
83 /// <https://datatracker.ietf.org/doc/html/rfc9114#section-4.2.2>
84 fn new(max_size: u64) -> Self {
85 Self {
86 remaining: max_size,
87 }
88 }
89
90 /// Mark the start of parsing a new field.
91 ///
92 /// Must be called when a new field is ready to be parsed.
93 fn on_field_start(&mut self) -> Result<()> {
94 // Each complete field has a 32-byte overhead, so subtract that first.
95 self.remaining = self
96 .remaining
97 .checked_sub(32)
98 .ok_or(Error::HeaderListTooLarge)?;
99
100 Ok(())
101 }
102
103 /// Marks when a field part (either name or value) has been decoded.
104 ///
105 /// The `len` parameter is the size in bytes of the decoded part.
106 ///
107 /// Must be called when a new field part has been decoded.
108 fn on_field_part_decoded(&mut self, len: u64) -> Result<()> {
109 self.remaining = self
110 .remaining
111 .checked_sub(len)
112 .ok_or(Error::HeaderListTooLarge)?;
113
114 Ok(())
115 }
116
117 /// The remaining number of bytes in the tracker.
118 fn left(&self) -> u64 {
119 self.remaining
120 }
121}
122
123/// A QPACK decoder.
124#[derive(Default)]
125pub struct Decoder {}
126
127impl Decoder {
128 /// Creates a new QPACK decoder.
129 pub fn new() -> Decoder {
130 Decoder::default()
131 }
132
133 /// Processes control instructions from the encoder.
134 pub fn control(&mut self, _buf: &mut [u8]) -> Result<()> {
135 // TODO: process control instructions
136 Ok(())
137 }
138
139 /// Decodes a QPACK header block into a list of headers.
140 pub fn decode(&mut self, buf: &[u8], max_size: u64) -> Result<Vec<Header>> {
141 let mut b = octets::Octets::with_slice(buf);
142
143 let mut out = Vec::new();
144
145 let mut size_tracker = FieldListSizeTracker::new(max_size);
146
147 let req_insert_count = decode_int(&mut b, 8)?;
148 let base = decode_int(&mut b, 7)?;
149
150 trace!("Header count={req_insert_count} base={base}");
151
152 while b.cap() > 0 {
153 let first = b.peek_u8()?;
154
155 size_tracker.on_field_start()?;
156
157 match Representation::from_byte(first) {
158 Representation::Indexed => {
159 const STATIC: u8 = 0x40;
160
161 let s = first & STATIC == STATIC;
162 let index = decode_int(&mut b, 6)?;
163
164 trace!("Indexed index={index} static={s}");
165
166 if !s {
167 // TODO: implement dynamic table
168 return Err(Error::InvalidHeaderValue);
169 }
170
171 let (name, value) = lookup_static(index)?;
172
173 size_tracker.on_field_part_decoded(
174 (name.len() + value.len()) as u64,
175 )?;
176
177 let hdr = Header::new(name, value);
178 out.push(hdr);
179 },
180
181 Representation::IndexedWithPostBase => {
182 let index = decode_int(&mut b, 4)?;
183
184 trace!("Indexed With Post Base index={index}");
185
186 // TODO: implement dynamic table
187 return Err(Error::InvalidHeaderValue);
188 },
189
190 Representation::Literal => {
191 let name_huff = b.as_ref()[0] & 0x08 == 0x08;
192 let name_len = decode_int(&mut b, 3)? as usize;
193
194 let mut name = b.get_bytes(name_len)?;
195
196 let name = if name_huff {
197 name.get_huffman_decoded_with_max_length(
198 size_tracker.left() as usize,
199 )
200 .map_err(|_| Error::HeaderListTooLarge)?
201 } else {
202 if name_len > size_tracker.left() as usize {
203 return Err(Error::HeaderListTooLarge);
204 }
205 name.to_vec()
206 };
207
208 size_tracker.on_field_part_decoded(name.len() as u64)?;
209
210 let value = decode_str(&mut b, size_tracker.left() as usize)?;
211
212 trace!(
213 "Literal Without Name Reference name={name:?} value={value:?}",
214 );
215
216 size_tracker.on_field_part_decoded(value.len() as u64)?;
217
218 // Instead of calling Header::new(), create Header directly
219 // from `name` and `value`.
220 let hdr = Header(name, value);
221 out.push(hdr);
222 },
223
224 Representation::LiteralWithNameRef => {
225 const STATIC: u8 = 0x10;
226
227 let s = first & STATIC == STATIC;
228
229 if !s {
230 // TODO: implement dynamic table
231 return Err(Error::InvalidHeaderValue);
232 }
233
234 let name_idx = decode_int(&mut b, 4)?;
235
236 let (name, _) = lookup_static(name_idx)?;
237
238 size_tracker.on_field_part_decoded(name.len() as u64)?;
239
240 let value = decode_str(&mut b, size_tracker.left() as usize)?;
241
242 trace!(
243 "Literal name_idx={name_idx} static={s} value={value:?}"
244 );
245
246 size_tracker.on_field_part_decoded(value.len() as u64)?;
247
248 // Instead of calling Header::new(), create Header directly
249 // from `value`, but clone `name` as it is just a reference.
250 let hdr = Header(name.to_vec(), value);
251 out.push(hdr);
252 },
253
254 Representation::LiteralWithPostBase => {
255 trace!("Literal With Post Base");
256
257 // TODO: implement dynamic table
258 return Err(Error::InvalidHeaderValue);
259 },
260 }
261 }
262
263 Ok(out)
264 }
265}
266
267fn lookup_static(idx: u64) -> Result<(&'static [u8], &'static [u8])> {
268 if idx >= super::static_table::STATIC_DECODE_TABLE.len() as u64 {
269 return Err(Error::InvalidStaticTableIndex);
270 }
271
272 Ok(super::static_table::STATIC_DECODE_TABLE[idx as usize])
273}
274
275fn decode_int(b: &mut octets::Octets, prefix: usize) -> Result<u64> {
276 let mask = 2u64.pow(prefix as u32) - 1;
277
278 let mut val = u64::from(b.get_u8()?);
279 val &= mask;
280
281 if val < mask {
282 return Ok(val);
283 }
284
285 let mut shift = 0;
286
287 while b.cap() > 0 {
288 let byte = b.get_u8()?;
289
290 let inc = u64::from(byte & 0x7f)
291 .checked_shl(shift)
292 .ok_or(Error::BufferTooShort)?;
293
294 val = val.checked_add(inc).ok_or(Error::BufferTooShort)?;
295
296 shift += 7;
297
298 if byte & 0x80 == 0 {
299 return Ok(val);
300 }
301 }
302
303 Err(Error::BufferTooShort)
304}
305
306fn decode_str(b: &mut octets::Octets, max_len: usize) -> Result<Vec<u8>> {
307 let first = b.peek_u8()?;
308
309 let huff = first & 0x80 == 0x80;
310
311 let len = decode_int(b, 7)? as usize;
312
313 let mut val = b.get_bytes(len)?;
314
315 let val = if huff {
316 val.get_huffman_decoded_with_max_length(max_len)
317 .map_err(|_| Error::HeaderListTooLarge)?
318 } else {
319 if len > max_len {
320 return Err(Error::HeaderListTooLarge);
321 }
322 val.to_vec()
323 };
324
325 Ok(val)
326}
327
328#[cfg(test)]
329mod tests {
330 use super::*;
331
332 #[test]
333 fn decode_int1() {
334 let encoded = [0b01010, 0x02];
335 let mut b = octets::Octets::with_slice(&encoded);
336
337 assert_eq!(decode_int(&mut b, 5), Ok(10));
338 }
339
340 #[test]
341 fn decode_int2() {
342 let encoded = [0b11111, 0b10011010, 0b00001010];
343 let mut b = octets::Octets::with_slice(&encoded);
344
345 assert_eq!(decode_int(&mut b, 5), Ok(1337));
346 }
347
348 #[test]
349 fn decode_int3() {
350 let encoded = [0b101010];
351 let mut b = octets::Octets::with_slice(&encoded);
352
353 assert_eq!(decode_int(&mut b, 8), Ok(42));
354 }
355
356 /// LiteralWithNameRef with the dynamic table flag (S=0) must be
357 /// rejected since the dynamic table is not implemented.
358 #[test]
359 fn literal_with_name_ref_dynamic_table_rejected() {
360 let mut dec = Decoder::new();
361
362 // QPACK header block:
363 // [0x00, 0x00] required insert count=0, base=0
364 // [0x40] LiteralWithNameRef (0b0100_0000), S=0 (dynamic),
365 // name_idx=0
366 // [0x01, 0x61] value: non-huffman, length=1, 'a'
367 let encoded = [0x00, 0x00, 0x40, 0x01, 0x61];
368
369 assert_eq!(
370 dec.decode(&encoded, u64::MAX),
371 Err(Error::InvalidHeaderValue),
372 );
373 }
374
375 /// Non-Huffman value in decode_str must be rejected before allocation
376 /// when it exceeds the remaining budget (max_len).
377 ///
378 /// Uses a LiteralWithNameRef with static `:authority` (index 0, 10
379 /// bytes) and a non-Huffman value of 5 bytes. Budget is set so only
380 /// 4 bytes remain for the value after the name is charged.
381 #[test]
382 fn non_huffman_value_exceeding_budget_rejected() {
383 // QPACK header block:
384 // [0x00, 0x00] required insert count=0, base=0
385 // [0x50] LiteralWithNameRef, S=1 (static), name_idx=0
386 // (:authority, 10 bytes)
387 // [0x05] value: non-huffman (bit 7=0), length=5
388 // "abcde" value bytes
389 let encoded = [0x00, 0x00, 0x50, 0x05, 0x61, 0x62, 0x63, 0x64, 0x65];
390
391 // Exact fit: 32 (overhead) + 10 (name) + 5 (value) = 47.
392 assert!(Decoder::new().decode(&encoded, 47).is_ok());
393
394 // One byte too small: budget = 46.
395 // After overhead (32) and name (10): 4 bytes remain, but
396 // value is 5 bytes → rejected.
397 assert_eq!(
398 Decoder::new().decode(&encoded, 46),
399 Err(Error::HeaderListTooLarge),
400 );
401 }
402
403 /// Non-Huffman name in the Literal arm must be rejected before
404 /// allocation when it exceeds the remaining budget.
405 ///
406 /// Uses a Literal with a 10-byte non-Huffman name and a 1-byte
407 /// non-Huffman value. Budget is set so only 9 bytes remain for the
408 /// name after overhead.
409 #[test]
410 fn non_huffman_name_exceeding_budget_rejected() {
411 // QPACK header block:
412 // [0x00, 0x00] required insert count=0, base=0
413 // [0x27, 0x03] Literal (0b0010_0000), N=0, H=0 (no huffman),
414 // name_len=10 (3-bit prefix 0b111 + overflow 0x03)
415 // "x-custom99" name bytes (10 bytes)
416 // [0x01, 0x61] value: non-huffman, length=1, 'a'
417 let encoded = [
418 0x00, 0x00, // header block prefix
419 0x27, 0x03, // Literal, name_len=10
420 0x78, 0x2d, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x39,
421 0x39, // "x-custom99"
422 0x01, 0x61, // value: length=1, 'a'
423 ];
424
425 // Exact fit: 32 (overhead) + 10 (name) + 1 (value) = 43.
426 assert!(Decoder::new().decode(&encoded, 43).is_ok());
427
428 // Two bytes too small: budget = 41.
429 // After overhead (32): 9 bytes remain, name is 10 bytes →
430 // rejected at name check.
431 assert_eq!(
432 Decoder::new().decode(&encoded, 41),
433 Err(Error::HeaderListTooLarge),
434 );
435 }
436
437 /// Verify that both Literal and LiteralWithNameRef charge the name to
438 /// the budget *before* Huffman-decoding the value, so the max_len
439 /// passed to the Huffman decoder is equally strict in both paths.
440 #[test]
441 fn literal_with_name_ref_value_budget_ordering() {
442 use crate::h3::qpack;
443
444 // Static table index 0 = `:authority` (10 bytes).
445 // We'll encode the same value two ways:
446 // 1. LiteralWithNameRef using `:authority` (encoder matches static
447 // table)
448 // 2. Literal using a custom 10-byte name not in the static table
449 //
450 // Both have name_len=10, so budget arithmetic is comparable.
451 let value = b"aaaaaaaaaaaaaaaa"; // 16 bytes; Huffman compresses to 10
452
453 // -- Encode as LiteralWithNameRef --
454 let headers_nameref = vec![Header::new(b":authority", value)];
455 let mut buf = [0u8; 64];
456 let mut enc = qpack::Encoder::new();
457 let nameref_len = enc.encode(&headers_nameref, &mut buf).unwrap();
458 let encoded_nameref = buf[..nameref_len].to_vec();
459
460 // -- Encode as Literal (name not in static table) --
461 let headers_literal = vec![Header::new(b"x-custom99", value)];
462 let mut buf = [0u8; 64];
463 let mut enc = qpack::Encoder::new();
464 let literal_len = enc.encode(&headers_literal, &mut buf).unwrap();
465 let encoded_literal = buf[..literal_len].to_vec();
466
467 // Exact budget: 32 (overhead) + 10 (name) + 16 (value) = 58.
468 // Both representations succeed.
469 assert_eq!(
470 Decoder::new().decode(&encoded_nameref, 58),
471 Ok(headers_nameref.clone()),
472 );
473 assert_eq!(
474 Decoder::new().decode(&encoded_literal, 58),
475 Ok(headers_literal.clone()),
476 );
477
478 // One byte too small: budget = 57.
479 // Total decoded field size = 10 + 16 = 26, overhead = 32,
480 // so 32 + 26 = 58 > 57. Both reject with HeaderListTooLarge.
481 //
482 // Both paths now follow the same budget ordering:
483 // 1. name charged first (10 bytes) → remaining = 15
484 // 2. decode_str max_len = 15
485 // 3. Huffman decode produces 16 bytes > 15 → rejected
486 assert_eq!(
487 Decoder::new().decode(&encoded_nameref, 57),
488 Err(Error::HeaderListTooLarge),
489 );
490 assert_eq!(
491 Decoder::new().decode(&encoded_literal, 57),
492 Err(Error::HeaderListTooLarge),
493 );
494 }
495}