Skip to main content

ringline_h2/
hpack.rs

1//! HPACK header compression (RFC 7541).
2//!
3//! Implements the full HPACK encoder and decoder with:
4//! - 61-entry static table (RFC 7541 Appendix A)
5//! - Dynamic table with size management
6//! - Huffman encoding/decoding
7//! - Prefix integer codec
8
9use std::collections::VecDeque;
10
11use crate::error::H2Error;
12
13/// Hard cap on the number of header fields decoded from a single HPACK
14/// block. Defends against the indexed-literal amplification attack: a 16 KiB
15/// block of all-indexed entries (1 byte each) would otherwise produce ~16k
16/// `HeaderField` allocations at ≥32 bytes each (the HPACK accounting size),
17/// for ~500 KiB of heap per frame. 256 is generous for real HTTP traffic.
18pub const MAX_HEADER_FIELDS: usize = 256;
19
20/// A single header name-value pair.
21#[derive(Debug, Clone, PartialEq, Eq)]
22pub struct HeaderField {
23    pub name: Vec<u8>,
24    pub value: Vec<u8>,
25}
26
27impl HeaderField {
28    pub fn new(name: impl Into<Vec<u8>>, value: impl Into<Vec<u8>>) -> Self {
29        Self {
30            name: name.into(),
31            value: value.into(),
32        }
33    }
34
35    /// Size of this header field for dynamic table accounting (RFC 7541 Section 4.1).
36    /// Size = len(name) + len(value) + 32
37    fn size(&self) -> usize {
38        self.name.len() + self.value.len() + 32
39    }
40}
41
42// -- HPACK prefix integer codec (RFC 7541 Section 5.1) --
43
44pub(crate) fn encode_prefix_int(buf: &mut Vec<u8>, value: u64, prefix_bits: u8, pattern: u8) {
45    let max = (1u64 << prefix_bits) - 1;
46    if value < max {
47        buf.push(pattern | value as u8);
48    } else {
49        buf.push(pattern | max as u8);
50        let mut remaining = value - max;
51        while remaining >= 128 {
52            buf.push(0x80 | (remaining & 0x7f) as u8);
53            remaining >>= 7;
54        }
55        buf.push(remaining as u8);
56    }
57}
58
59pub(crate) fn decode_prefix_int(buf: &[u8], prefix_bits: u8) -> Option<(u64, usize)> {
60    if buf.is_empty() {
61        return None;
62    }
63    let max = (1u64 << prefix_bits) - 1;
64    let value = u64::from(buf[0]) & max;
65    if value < max {
66        return Some((value, 1));
67    }
68    let mut value = max;
69    let mut shift = 0u32;
70    for (i, &b) in buf[1..].iter().enumerate() {
71        value += u64::from(b & 0x7f) << shift;
72        shift += 7;
73        if b & 0x80 == 0 {
74            return Some((value, i + 2));
75        }
76        if shift > 56 {
77            return None; // overflow protection
78        }
79    }
80    None // incomplete
81}
82
83// -- Static table (RFC 7541 Appendix A) --
84
85/// HPACK static table entries: (name, value). 61 entries indexed 1..61.
86const STATIC_TABLE: &[(&[u8], &[u8])] = &[
87    (b":authority", b""),                   // 1
88    (b":method", b"GET"),                   // 2
89    (b":method", b"POST"),                  // 3
90    (b":path", b"/"),                       // 4
91    (b":path", b"/index.html"),             // 5
92    (b":scheme", b"http"),                  // 6
93    (b":scheme", b"https"),                 // 7
94    (b":status", b"200"),                   // 8
95    (b":status", b"204"),                   // 9
96    (b":status", b"206"),                   // 10
97    (b":status", b"304"),                   // 11
98    (b":status", b"400"),                   // 12
99    (b":status", b"404"),                   // 13
100    (b":status", b"500"),                   // 14
101    (b"accept-charset", b""),               // 15
102    (b"accept-encoding", b"gzip, deflate"), // 16
103    (b"accept-language", b""),              // 17
104    (b"accept-ranges", b""),                // 18
105    (b"accept", b""),                       // 19
106    (b"access-control-allow-origin", b""),  // 20
107    (b"age", b""),                          // 21
108    (b"allow", b""),                        // 22
109    (b"authorization", b""),                // 23
110    (b"cache-control", b""),                // 24
111    (b"content-disposition", b""),          // 25
112    (b"content-encoding", b""),             // 26
113    (b"content-language", b""),             // 27
114    (b"content-length", b""),               // 28
115    (b"content-location", b""),             // 29
116    (b"content-range", b""),                // 30
117    (b"content-type", b""),                 // 31
118    (b"cookie", b""),                       // 32
119    (b"date", b""),                         // 33
120    (b"etag", b""),                         // 34
121    (b"expect", b""),                       // 35
122    (b"expires", b""),                      // 36
123    (b"from", b""),                         // 37
124    (b"host", b""),                         // 38
125    (b"if-match", b""),                     // 39
126    (b"if-modified-since", b""),            // 40
127    (b"if-none-match", b""),                // 41
128    (b"if-range", b""),                     // 42
129    (b"if-unmodified-since", b""),          // 43
130    (b"last-modified", b""),                // 44
131    (b"link", b""),                         // 45
132    (b"location", b""),                     // 46
133    (b"max-forwards", b""),                 // 47
134    (b"proxy-authenticate", b""),           // 48
135    (b"proxy-authorization", b""),          // 49
136    (b"range", b""),                        // 50
137    (b"referer", b""),                      // 51
138    (b"refresh", b""),                      // 52
139    (b"retry-after", b""),                  // 53
140    (b"server", b""),                       // 54
141    (b"set-cookie", b""),                   // 55
142    (b"strict-transport-security", b""),    // 56
143    (b"transfer-encoding", b""),            // 57
144    (b"user-agent", b""),                   // 58
145    (b"vary", b""),                         // 59
146    (b"via", b""),                          // 60
147    (b"www-authenticate", b""),             // 61
148];
149
150/// Find a static table entry matching both name and value.
151/// Returns the 1-based index if found.
152fn find_static_name_value(name: &[u8], value: &[u8]) -> Option<usize> {
153    STATIC_TABLE
154        .iter()
155        .position(|(n, v)| *n == name && *v == value)
156        .map(|i| i + 1) // HPACK static table is 1-indexed
157}
158
159/// Find a static table entry matching just the name.
160/// Returns the 1-based index of the first match.
161fn find_static_name(name: &[u8]) -> Option<usize> {
162    STATIC_TABLE
163        .iter()
164        .position(|(n, _)| *n == name)
165        .map(|i| i + 1)
166}
167
168// -- Dynamic table --
169
170/// HPACK dynamic table (RFC 7541 Section 2.3.2).
171///
172/// Entries are stored newest-first. Index 0 of the VecDeque corresponds to
173/// HPACK dynamic table index (static_table_len + 1).
174pub struct DynamicTable {
175    entries: VecDeque<HeaderField>,
176    size: usize,
177    max_size: usize,
178}
179
180impl DynamicTable {
181    pub fn new(max_size: usize) -> Self {
182        Self {
183            entries: VecDeque::new(),
184            size: 0,
185            max_size,
186        }
187    }
188
189    /// Get an entry by 0-based dynamic table index.
190    pub fn get(&self, index: usize) -> Option<&HeaderField> {
191        self.entries.get(index)
192    }
193
194    /// Insert a new entry at the beginning of the dynamic table.
195    pub fn insert(&mut self, field: HeaderField) {
196        let entry_size = field.size();
197        // Evict entries to make room (RFC 7541 Section 4.4).
198        while self.size + entry_size > self.max_size && !self.entries.is_empty() {
199            if let Some(evicted) = self.entries.pop_back() {
200                self.size -= evicted.size();
201            }
202        }
203        // If the entry itself is larger than the max, don't add it but clear the table.
204        if entry_size > self.max_size {
205            self.entries.clear();
206            self.size = 0;
207            return;
208        }
209        self.entries.push_front(field);
210        self.size += entry_size;
211    }
212
213    /// Update the maximum table size, evicting entries as needed.
214    pub fn set_max_size(&mut self, max_size: usize) {
215        self.max_size = max_size;
216        while self.size > self.max_size && !self.entries.is_empty() {
217            if let Some(evicted) = self.entries.pop_back() {
218                self.size -= evicted.size();
219            }
220        }
221    }
222
223    /// Find a dynamic table entry matching both name and value.
224    /// Returns the HPACK index (62 + position) if found.
225    fn find_name_value(&self, name: &[u8], value: &[u8]) -> Option<usize> {
226        self.entries
227            .iter()
228            .position(|h| h.name == name && h.value == value)
229            .map(|i| i + 62) // 61 static + 1-indexed
230    }
231
232    /// Find a dynamic table entry matching just the name.
233    /// Returns the HPACK index (62 + position) if found.
234    fn find_name(&self, name: &[u8]) -> Option<usize> {
235        self.entries
236            .iter()
237            .position(|h| h.name == name)
238            .map(|i| i + 62)
239    }
240
241    pub fn len(&self) -> usize {
242        self.entries.len()
243    }
244
245    pub fn is_empty(&self) -> bool {
246        self.entries.is_empty()
247    }
248}
249
250// -- String literal encoding/decoding --
251
252/// Encode a string literal with optional Huffman compression.
253fn encode_string_literal(buf: &mut Vec<u8>, data: &[u8]) {
254    let huf_len = crate::huffman::encoded_len(data);
255    if huf_len < data.len() {
256        // Huffman is shorter -- set H bit (0x80).
257        encode_prefix_int(buf, huf_len as u64, 7, 0x80);
258        crate::huffman::encode(data, buf);
259    } else {
260        // Raw is shorter or equal -- no H bit.
261        encode_prefix_int(buf, data.len() as u64, 7, 0x00);
262        buf.extend_from_slice(data);
263    }
264}
265
266/// Decode a string literal (Huffman or raw).
267fn decode_string_literal(buf: &[u8]) -> Result<(Vec<u8>, usize), H2Error> {
268    if buf.is_empty() {
269        return Err(H2Error::CompressionError);
270    }
271    let huffman = buf[0] & 0x80 != 0;
272    let (str_len, n) = decode_prefix_int(buf, 7).ok_or(H2Error::CompressionError)?;
273    let str_len = str_len as usize;
274    let total = n + str_len;
275    if buf.len() < total {
276        return Err(H2Error::CompressionError);
277    }
278    let data = &buf[n..total];
279    let value = if huffman {
280        crate::huffman::decode(data)?
281    } else {
282        data.to_vec()
283    };
284    Ok((value, total))
285}
286
287// -- Encoder --
288
289/// HPACK encoder with dynamic table.
290pub struct Encoder {
291    dynamic_table: DynamicTable,
292    /// Pending table size updates to emit at the start of the next header
293    /// block. RFC 7541 §4.2: when multiple updates occur between header
294    /// blocks, the smallest value seen MUST be signaled, and the final
295    /// value MUST be signaled — at most two updates per block.
296    pending_min: Option<usize>,
297    pending_latest: Option<usize>,
298}
299
300impl Encoder {
301    pub fn new(max_table_size: usize) -> Self {
302        Encoder {
303            dynamic_table: DynamicTable::new(max_table_size),
304            pending_min: None,
305            pending_latest: None,
306        }
307    }
308
309    /// Queue a table size update to be emitted at the start of the next
310    /// header block (per RFC 7541 Section 6.3).
311    pub fn set_max_table_size(&mut self, new_size: usize, buf: &mut Vec<u8>) {
312        self.dynamic_table.set_max_size(new_size);
313        // Dynamic table size update (RFC 7541 Section 6.3):
314        // pattern 001xxxxx, 5-bit prefix.
315        encode_prefix_int(buf, new_size as u64, 5, 0x20);
316    }
317
318    /// Update the encoder's maximum table size from a SETTINGS change.
319    /// The size update will be emitted at the start of the next `encode()`
320    /// call. Multiple updates between encodes collapse to (min, final) per
321    /// RFC 7541 §4.2.
322    pub fn update_max_table_size(&mut self, new_size: usize) {
323        self.pending_min = Some(match self.pending_min {
324            Some(m) => m.min(new_size),
325            None => new_size,
326        });
327        self.pending_latest = Some(new_size);
328    }
329
330    /// Encode a list of headers into an HPACK header block.
331    pub fn encode(&mut self, headers: &[HeaderField], buf: &mut Vec<u8>) {
332        // Emit pending table size update(s) at the start of the header
333        // block (RFC 7541 §4.2). When the latest value matches the min,
334        // a single update suffices.
335        if let Some(latest) = self.pending_latest.take() {
336            let min = self.pending_min.take().unwrap_or(latest);
337            if min != latest {
338                self.dynamic_table.set_max_size(min);
339                encode_prefix_int(buf, min as u64, 5, 0x20);
340            }
341            self.dynamic_table.set_max_size(latest);
342            encode_prefix_int(buf, latest as u64, 5, 0x20);
343        }
344        for header in headers {
345            self.encode_header(header, buf);
346        }
347    }
348
349    fn encode_header(&mut self, header: &HeaderField, buf: &mut Vec<u8>) {
350        // 1. Check for exact match in static table.
351        if let Some(index) = find_static_name_value(&header.name, &header.value) {
352            // Indexed header field (RFC 7541 Section 6.1): pattern 1xxxxxxx, 7-bit index.
353            encode_prefix_int(buf, index as u64, 7, 0x80);
354            return;
355        }
356
357        // 2. Check for exact match in dynamic table.
358        if let Some(index) = self
359            .dynamic_table
360            .find_name_value(&header.name, &header.value)
361        {
362            encode_prefix_int(buf, index as u64, 7, 0x80);
363            return;
364        }
365
366        // 3. Check for name match in static table -- use incremental indexing.
367        if let Some(name_index) = find_static_name(&header.name) {
368            // Literal with incremental indexing (RFC 7541 Section 6.2.1):
369            // pattern 01xxxxxx, 6-bit name index.
370            encode_prefix_int(buf, name_index as u64, 6, 0x40);
371            encode_string_literal(buf, &header.value);
372            self.dynamic_table.insert(header.clone());
373            return;
374        }
375
376        // 4. Check for name match in dynamic table.
377        if let Some(name_index) = self.dynamic_table.find_name(&header.name) {
378            encode_prefix_int(buf, name_index as u64, 6, 0x40);
379            encode_string_literal(buf, &header.value);
380            self.dynamic_table.insert(header.clone());
381            return;
382        }
383
384        // 5. Literal with incremental indexing, new name.
385        // Pattern 0100_0000 = 0x40, 6-bit index = 0.
386        buf.push(0x40);
387        encode_string_literal(buf, &header.name);
388        encode_string_literal(buf, &header.value);
389        self.dynamic_table.insert(header.clone());
390    }
391}
392
393// -- Decoder --
394
395/// HPACK decoder with dynamic table.
396pub struct Decoder {
397    dynamic_table: DynamicTable,
398    max_table_size: usize,
399}
400
401impl Decoder {
402    pub fn new(max_table_size: usize) -> Self {
403        Self {
404            dynamic_table: DynamicTable::new(max_table_size),
405            max_table_size,
406        }
407    }
408
409    /// Decode an HPACK header block.
410    pub fn decode(&mut self, buf: &[u8]) -> Result<Vec<HeaderField>, H2Error> {
411        let mut headers = Vec::new();
412        let mut pos = 0;
413        // RFC 7541 §4.2: dynamic table size updates MUST occur at the
414        // beginning of the first header block; once any header has been
415        // processed, a subsequent size update is a compression error.
416        let mut seen_header = false;
417
418        while pos < buf.len() {
419            let first = buf[pos];
420
421            if first & 0x80 != 0 {
422                // Indexed header field (Section 6.1): pattern 1xxxxxxx.
423                let (index, n) =
424                    decode_prefix_int(&buf[pos..], 7).ok_or(H2Error::CompressionError)?;
425                pos += n;
426                if headers.len() >= MAX_HEADER_FIELDS {
427                    return Err(H2Error::MaxSizeExceeded(format!(
428                        "HPACK block exceeds MAX_HEADER_FIELDS ({MAX_HEADER_FIELDS})"
429                    )));
430                }
431                let field = self.get_indexed(index as usize)?;
432                headers.push(field);
433                seen_header = true;
434            } else if first & 0x40 != 0 {
435                // Literal with incremental indexing (Section 6.2.1): pattern 01xxxxxx.
436                let (name_index, n) =
437                    decode_prefix_int(&buf[pos..], 6).ok_or(H2Error::CompressionError)?;
438                pos += n;
439                let name = if name_index > 0 {
440                    self.get_name(name_index as usize)?
441                } else {
442                    let (name, consumed) = decode_string_literal(&buf[pos..])?;
443                    pos += consumed;
444                    name
445                };
446                let (value, consumed) = decode_string_literal(&buf[pos..])?;
447                pos += consumed;
448                if headers.len() >= MAX_HEADER_FIELDS {
449                    return Err(H2Error::MaxSizeExceeded(format!(
450                        "HPACK block exceeds MAX_HEADER_FIELDS ({MAX_HEADER_FIELDS})"
451                    )));
452                }
453                let field = HeaderField {
454                    name: name.clone(),
455                    value,
456                };
457                self.dynamic_table.insert(field.clone());
458                headers.push(field);
459                seen_header = true;
460            } else if first & 0x20 != 0 {
461                // Dynamic table size update (Section 6.3): pattern 001xxxxx.
462                if seen_header {
463                    return Err(H2Error::CompressionError);
464                }
465                let (new_size, n) =
466                    decode_prefix_int(&buf[pos..], 5).ok_or(H2Error::CompressionError)?;
467                pos += n;
468                let new_size = new_size as usize;
469                if new_size > self.max_table_size {
470                    return Err(H2Error::CompressionError);
471                }
472                self.dynamic_table.set_max_size(new_size);
473            } else if first & 0x10 != 0 {
474                // Literal never indexed (Section 6.2.3): pattern 0001xxxx.
475                let (name_index, n) =
476                    decode_prefix_int(&buf[pos..], 4).ok_or(H2Error::CompressionError)?;
477                pos += n;
478                let name = if name_index > 0 {
479                    self.get_name(name_index as usize)?
480                } else {
481                    let (name, consumed) = decode_string_literal(&buf[pos..])?;
482                    pos += consumed;
483                    name
484                };
485                let (value, consumed) = decode_string_literal(&buf[pos..])?;
486                pos += consumed;
487                if headers.len() >= MAX_HEADER_FIELDS {
488                    return Err(H2Error::MaxSizeExceeded(format!(
489                        "HPACK block exceeds MAX_HEADER_FIELDS ({MAX_HEADER_FIELDS})"
490                    )));
491                }
492                headers.push(HeaderField { name, value });
493                seen_header = true;
494                // Never indexed: do NOT add to dynamic table.
495            } else {
496                // Literal without indexing (Section 6.2.2): pattern 0000xxxx.
497                let (name_index, n) =
498                    decode_prefix_int(&buf[pos..], 4).ok_or(H2Error::CompressionError)?;
499                pos += n;
500                let name = if name_index > 0 {
501                    self.get_name(name_index as usize)?
502                } else {
503                    let (name, consumed) = decode_string_literal(&buf[pos..])?;
504                    pos += consumed;
505                    name
506                };
507                let (value, consumed) = decode_string_literal(&buf[pos..])?;
508                pos += consumed;
509                if headers.len() >= MAX_HEADER_FIELDS {
510                    return Err(H2Error::MaxSizeExceeded(format!(
511                        "HPACK block exceeds MAX_HEADER_FIELDS ({MAX_HEADER_FIELDS})"
512                    )));
513                }
514                headers.push(HeaderField { name, value });
515                seen_header = true;
516                // Without indexing: do NOT add to dynamic table.
517            }
518        }
519
520        Ok(headers)
521    }
522
523    /// Look up an indexed header field (static or dynamic).
524    fn get_indexed(&self, index: usize) -> Result<HeaderField, H2Error> {
525        if index == 0 {
526            return Err(H2Error::CompressionError);
527        }
528        if index <= STATIC_TABLE.len() {
529            let (name, value) = STATIC_TABLE[index - 1];
530            Ok(HeaderField {
531                name: name.to_vec(),
532                value: value.to_vec(),
533            })
534        } else {
535            let dyn_index = index - STATIC_TABLE.len() - 1;
536            self.dynamic_table
537                .get(dyn_index)
538                .cloned()
539                .ok_or(H2Error::CompressionError)
540        }
541    }
542
543    /// Look up only the name from an indexed entry.
544    fn get_name(&self, index: usize) -> Result<Vec<u8>, H2Error> {
545        if index == 0 {
546            return Err(H2Error::CompressionError);
547        }
548        if index <= STATIC_TABLE.len() {
549            Ok(STATIC_TABLE[index - 1].0.to_vec())
550        } else {
551            let dyn_index = index - STATIC_TABLE.len() - 1;
552            self.dynamic_table
553                .get(dyn_index)
554                .map(|h| h.name.clone())
555                .ok_or(H2Error::CompressionError)
556        }
557    }
558
559    /// Update the maximum dynamic table size allowed by SETTINGS.
560    pub fn set_max_table_size(&mut self, max_size: usize) {
561        self.max_table_size = max_size;
562        // The actual resize happens when we receive a dynamic table size update
563        // instruction in the header block.
564    }
565}
566
567#[cfg(test)]
568mod tests {
569    use super::*;
570
571    #[test]
572    fn prefix_int_round_trip() {
573        for &(value, prefix_bits, pattern) in &[
574            (0u64, 7, 0x80u8),
575            (5, 7, 0x80),
576            (126, 7, 0x80),
577            (127, 7, 0x80),
578            (128, 7, 0x80),
579            (1000, 7, 0x80),
580            (0, 6, 0x40),
581            (62, 6, 0x40),
582            (63, 6, 0x40),
583            (64, 6, 0x40),
584            (255, 6, 0x40),
585            (0, 5, 0x20),
586            (31, 5, 0x20),
587            (32, 5, 0x20),
588            (4096, 5, 0x20),
589            (0, 4, 0x00),
590            (15, 4, 0x00),
591            (16, 4, 0x00),
592        ] {
593            let mut buf = Vec::new();
594            encode_prefix_int(&mut buf, value, prefix_bits, pattern);
595            let (decoded, len) = decode_prefix_int(&buf, prefix_bits).unwrap();
596            assert_eq!(
597                decoded, value,
598                "mismatch for value={value} prefix={prefix_bits}"
599            );
600            assert_eq!(len, buf.len());
601            let mask = !((1u8 << prefix_bits) - 1);
602            assert_eq!(buf[0] & mask, pattern & mask);
603        }
604    }
605
606    #[test]
607    fn static_table_size() {
608        assert_eq!(STATIC_TABLE.len(), 61);
609    }
610
611    #[test]
612    fn encode_decode_indexed() {
613        // :method GET is static index 2.
614        let mut encoder = Encoder::new(4096);
615        let mut decoder = Decoder::new(4096);
616        let headers = vec![HeaderField::new(b":method", b"GET")];
617        let mut buf = Vec::new();
618        encoder.encode(&headers, &mut buf);
619        let decoded = decoder.decode(&buf).unwrap();
620        assert_eq!(decoded, headers);
621    }
622
623    #[test]
624    fn encode_decode_name_reference() {
625        // :path /foo -- :path is at index 4 with value "/".
626        let mut encoder = Encoder::new(4096);
627        let mut decoder = Decoder::new(4096);
628        let headers = vec![HeaderField::new(b":path", b"/foo")];
629        let mut buf = Vec::new();
630        encoder.encode(&headers, &mut buf);
631        let decoded = decoder.decode(&buf).unwrap();
632        assert_eq!(decoded, headers);
633    }
634
635    #[test]
636    fn encode_decode_literal() {
637        let mut encoder = Encoder::new(4096);
638        let mut decoder = Decoder::new(4096);
639        let headers = vec![HeaderField::new(b"x-custom", b"value123")];
640        let mut buf = Vec::new();
641        encoder.encode(&headers, &mut buf);
642        let decoded = decoder.decode(&buf).unwrap();
643        assert_eq!(decoded, headers);
644    }
645
646    #[test]
647    fn encode_decode_multiple_headers() {
648        let mut encoder = Encoder::new(4096);
649        let mut decoder = Decoder::new(4096);
650        let headers = vec![
651            HeaderField::new(b":method", b"GET"),
652            HeaderField::new(b":path", b"/"),
653            HeaderField::new(b":scheme", b"https"),
654            HeaderField::new(b":authority", b"example.com"),
655            HeaderField::new(b"accept", b"*/*"),
656            HeaderField::new(b"x-request-id", b"abc123"),
657        ];
658        let mut buf = Vec::new();
659        encoder.encode(&headers, &mut buf);
660        let decoded = decoder.decode(&buf).unwrap();
661        assert_eq!(decoded, headers);
662    }
663
664    #[test]
665    fn dynamic_table_reuse() {
666        let mut encoder = Encoder::new(4096);
667        let mut decoder = Decoder::new(4096);
668
669        // First request encodes custom header -- adds to dynamic table.
670        let headers1 = vec![
671            HeaderField::new(b":method", b"GET"),
672            HeaderField::new(b"x-token", b"abc"),
673        ];
674        let mut buf1 = Vec::new();
675        encoder.encode(&headers1, &mut buf1);
676        let decoded1 = decoder.decode(&buf1).unwrap();
677        assert_eq!(decoded1, headers1);
678
679        // Second request reuses the same custom header -- should use dynamic table index.
680        let headers2 = vec![
681            HeaderField::new(b":method", b"GET"),
682            HeaderField::new(b"x-token", b"abc"),
683        ];
684        let mut buf2 = Vec::new();
685        encoder.encode(&headers2, &mut buf2);
686        let decoded2 = decoder.decode(&buf2).unwrap();
687        assert_eq!(decoded2, headers2);
688
689        // Second encoding should be shorter (uses indexed representation).
690        assert!(buf2.len() <= buf1.len());
691    }
692
693    #[test]
694    fn dynamic_table_eviction() {
695        // Tiny max size to force eviction.
696        let mut encoder = Encoder::new(64);
697        let mut decoder = Decoder::new(64);
698
699        let headers = vec![HeaderField::new(
700            b"x-long-header-name",
701            b"a-somewhat-long-value",
702        )];
703        let mut buf = Vec::new();
704        encoder.encode(&headers, &mut buf);
705        let decoded = decoder.decode(&buf).unwrap();
706        assert_eq!(decoded, headers);
707    }
708
709    #[test]
710    fn encode_decode_status_200() {
711        let mut encoder = Encoder::new(4096);
712        let mut decoder = Decoder::new(4096);
713        let headers = vec![
714            HeaderField::new(b":status", b"200"),
715            HeaderField::new(b"content-type", b"text/plain"),
716        ];
717        let mut buf = Vec::new();
718        encoder.encode(&headers, &mut buf);
719        let decoded = decoder.decode(&buf).unwrap();
720        assert_eq!(decoded, headers);
721    }
722
723    #[test]
724    fn table_size_update() {
725        let mut encoder = Encoder::new(4096);
726        let mut decoder = Decoder::new(4096);
727
728        let mut buf = Vec::new();
729        encoder.set_max_table_size(256, &mut buf);
730        encoder.encode(&[HeaderField::new(b":method", b"GET")], &mut buf);
731        let decoded = decoder.decode(&buf).unwrap();
732        assert_eq!(decoded, vec![HeaderField::new(b":method", b"GET")]);
733    }
734
735    #[test]
736    fn encoder_emits_min_then_latest_when_multiple_updates_pending() {
737        // RFC 7541 §4.2: when multiple table size updates happen between
738        // header blocks, the encoder must signal the minimum value seen
739        // followed by the final value.
740        let mut encoder = Encoder::new(4096);
741        encoder.update_max_table_size(4096);
742        encoder.update_max_table_size(1024); // min
743        encoder.update_max_table_size(2048); // latest
744
745        let mut buf = Vec::new();
746        encoder.encode(&[HeaderField::new(b":method", b"GET")], &mut buf);
747
748        // Decode the two size updates that should appear at the start.
749        let (first, n) = decode_prefix_int(&buf, 5).unwrap();
750        assert_eq!(first, 1024, "expected min (1024) first");
751        let (second, _) = decode_prefix_int(&buf[n..], 5).unwrap();
752        assert_eq!(second, 2048, "expected latest (2048) second");
753    }
754
755    #[test]
756    fn encoder_single_update_when_min_equals_latest() {
757        let mut encoder = Encoder::new(4096);
758        encoder.update_max_table_size(2048);
759        encoder.update_max_table_size(2048); // same, no min/latest gap
760
761        let mut buf = Vec::new();
762        encoder.encode(&[HeaderField::new(b":method", b"GET")], &mut buf);
763
764        // Only one size update should be emitted before the header.
765        let (size, n) = decode_prefix_int(&buf, 5).unwrap();
766        assert_eq!(size, 2048);
767        // Next byte should be the header (0x82 = :method GET), not another
768        // size update (0x20 prefix).
769        assert_eq!(buf[n], 0x82);
770    }
771
772    #[test]
773    fn decoder_caps_at_max_header_fields() {
774        // Craft a block of >MAX_HEADER_FIELDS indexed entries (single byte
775        // each — :method GET is static index 2 → 0x82).
776        let mut decoder = Decoder::new(4096);
777        let block: Vec<u8> = std::iter::repeat_n(0x82u8, MAX_HEADER_FIELDS + 1).collect();
778        let err = decoder.decode(&block).err().unwrap();
779        assert!(matches!(err, H2Error::MaxSizeExceeded(_)));
780    }
781
782    #[test]
783    fn decoder_rejects_table_size_update_after_header() {
784        // First a header (indexed :method GET = 0x82), then a dynamic table
785        // size update (pattern 001xxxxx) — must be rejected.
786        let mut decoder = Decoder::new(4096);
787        let block = vec![0x82, 0x20]; // header, then size update = 0
788        let err = decoder.decode(&block).err().unwrap();
789        assert!(matches!(err, H2Error::CompressionError));
790    }
791
792    #[test]
793    fn decoder_accepts_table_size_update_before_header() {
794        // Size update first, then header — must succeed.
795        let mut decoder = Decoder::new(4096);
796        let block = vec![0x20, 0x82]; // size update = 0, then :method GET
797        let headers = decoder.decode(&block).unwrap();
798        assert_eq!(headers.len(), 1);
799    }
800
801    #[test]
802    fn rfc7541_appendix_c1_integer_examples() {
803        // C.1.1: Encoding 10 using a 5-bit prefix.
804        let mut buf = Vec::new();
805        encode_prefix_int(&mut buf, 10, 5, 0x00);
806        assert_eq!(buf, vec![0x0a]);
807
808        // C.1.2: Encoding 1337 using a 5-bit prefix.
809        let mut buf = Vec::new();
810        encode_prefix_int(&mut buf, 1337, 5, 0x00);
811        assert_eq!(buf, vec![0x1f, 0x9a, 0x0a]);
812
813        // C.1.3: Encoding 42 starting at an octet boundary (8-bit prefix).
814        let mut buf = Vec::new();
815        encode_prefix_int(&mut buf, 42, 8, 0x00);
816        assert_eq!(buf, vec![0x2a]);
817    }
818}