Skip to main content

stackforge_core/layer/http2/
hpack.rs

1//! HPACK header compression for HTTP/2 (RFC 7541).
2//!
3//! Implements the HPACK header compression algorithm used in HTTP/2 HEADERS frames.
4//! Includes:
5//! - Static table (61 entries per RFC 7541 Appendix A)
6//! - Dynamic table with eviction
7//! - Integer encoding/decoding with N-bit prefix
8//! - String literal encoding/decoding
9//! - Huffman encoding/decoding (RFC 7541 Appendix B)
10//! - `HpackDecoder` and `HpackEncoder` types
11
12// ============================================================================
13// Static Table (RFC 7541 Appendix A)
14// ============================================================================
15
16/// Static HPACK table from RFC 7541 Appendix A.
17/// 61 entries, 1-indexed in the spec (index 0 here = spec index 1).
18pub const STATIC_TABLE: &[(&str, &str)] = &[
19    (":authority", ""),
20    (":method", "GET"),
21    (":method", "POST"),
22    (":path", "/"),
23    (":path", "/index.html"),
24    (":scheme", "http"),
25    (":scheme", "https"),
26    (":status", "200"),
27    (":status", "204"),
28    (":status", "206"),
29    (":status", "304"),
30    (":status", "400"),
31    (":status", "404"),
32    (":status", "500"),
33    ("accept-charset", ""),
34    ("accept-encoding", "gzip, deflate"),
35    ("accept-language", ""),
36    ("accept-ranges", ""),
37    ("accept", ""),
38    ("access-control-allow-origin", ""),
39    ("age", ""),
40    ("allow", ""),
41    ("authorization", ""),
42    ("cache-control", ""),
43    ("content-disposition", ""),
44    ("content-encoding", ""),
45    ("content-language", ""),
46    ("content-length", ""),
47    ("content-location", ""),
48    ("content-range", ""),
49    ("content-type", ""),
50    ("cookie", ""),
51    ("date", ""),
52    ("etag", ""),
53    ("expect", ""),
54    ("expires", ""),
55    ("from", ""),
56    ("host", ""),
57    ("if-match", ""),
58    ("if-modified-since", ""),
59    ("if-none-match", ""),
60    ("if-range", ""),
61    ("if-unmodified-since", ""),
62    ("last-modified", ""),
63    ("link", ""),
64    ("location", ""),
65    ("max-forwards", ""),
66    ("proxy-authenticate", ""),
67    ("proxy-authorization", ""),
68    ("range", ""),
69    ("referer", ""),
70    ("refresh", ""),
71    ("retry-after", ""),
72    ("server", ""),
73    ("set-cookie", ""),
74    ("strict-transport-security", ""),
75    ("transfer-encoding", ""),
76    ("user-agent", ""),
77    ("vary", ""),
78    ("via", ""),
79    ("www-authenticate", ""),
80];
81
82// ============================================================================
83// Huffman Table (RFC 7541 Appendix B)
84// All 256 byte values + EOS (symbol 256)
85// Each entry is (code: u32, nbits: u8)
86// ============================================================================
87
88/// Full Huffman code table from RFC 7541 Appendix B.
89/// Index is the symbol value (0-255 for bytes, 256 for EOS).
90/// Each entry is (code: u32, nbits: u8).
91#[rustfmt::skip]
92pub const HUFFMAN_TABLE: &[(u32, u8)] = &[
93    (0x1ff8, 13),      // 0
94    (0x7fffd8, 23),    // 1
95    (0xfffffe2, 28),   // 2
96    (0xfffffe3, 28),   // 3
97    (0xfffffe4, 28),   // 4
98    (0xfffffe5, 28),   // 5
99    (0xfffffe6, 28),   // 6
100    (0xfffffe7, 28),   // 7
101    (0xfffffe8, 28),   // 8
102    (0xffffea, 24),    // 9
103    (0x3ffffffc, 30),  // 10
104    (0xfffffe9, 28),   // 11
105    (0xfffffea, 28),   // 12
106    (0x3ffffffd, 30),  // 13
107    (0xfffffeb, 28),   // 14
108    (0xfffffec, 28),   // 15
109    (0xfffffed, 28),   // 16
110    (0xfffffee, 28),   // 17
111    (0xfffffef, 28),   // 18
112    (0xffffff0, 28),   // 19
113    (0xffffff1, 28),   // 20
114    (0xffffff2, 28),   // 21
115    (0x3ffffffe, 30),  // 22
116    (0xffffff3, 28),   // 23
117    (0xffffff4, 28),   // 24
118    (0xffffff5, 28),   // 25
119    (0xffffff6, 28),   // 26
120    (0xffffff7, 28),   // 27
121    (0xffffff8, 28),   // 28
122    (0xffffff9, 28),   // 29
123    (0xffffffa, 28),   // 30
124    (0xffffffb, 28),   // 31
125    (0x14, 6),         // 32 ' '
126    (0x3f8, 10),       // 33 '!'
127    (0x3f9, 10),       // 34 '"'
128    (0xffa, 12),       // 35 '#'
129    (0x1ff9, 13),      // 36 '$'
130    (0x15, 6),         // 37 '%'
131    (0xf8, 8),         // 38 '&'
132    (0x7fa, 11),       // 39 '\''
133    (0x3fa, 10),       // 40 '('
134    (0x3fb, 10),       // 41 ')'
135    (0xf9, 8),         // 42 '*'
136    (0x7fb, 11),       // 43 '+'
137    (0xfa, 8),         // 44 ','
138    (0x16, 6),         // 45 '-'
139    (0x17, 6),         // 46 '.'
140    (0x18, 6),         // 47 '/'
141    (0x00, 5),         // 48 '0'
142    (0x01, 5),         // 49 '1'
143    (0x02, 5),         // 50 '2'
144    (0x19, 6),         // 51 '3'
145    (0x1a, 6),         // 52 '4'
146    (0x1b, 6),         // 53 '5'
147    (0x1c, 6),         // 54 '6'
148    (0x1d, 6),         // 55 '7'
149    (0x1e, 6),         // 56 '8'
150    (0x1f, 6),         // 57 '9'
151    (0x5c, 7),         // 58 ':'
152    (0xfb, 8),         // 59 ';'
153    (0x7ffc, 15),      // 60 '<'
154    (0x20, 6),         // 61 '='
155    (0xffb, 12),       // 62 '>'
156    (0x3fc, 10),       // 63 '?'
157    (0x1ffa, 13),      // 64 '@'
158    (0x21, 6),         // 65 'A'
159    (0x5d, 7),         // 66 'B'
160    (0x5e, 7),         // 67 'C'
161    (0x5f, 7),         // 68 'D'
162    (0x60, 7),         // 69 'E'
163    (0x61, 7),         // 70 'F'
164    (0x62, 7),         // 71 'G'
165    (0x63, 7),         // 72 'H'
166    (0x64, 7),         // 73 'I'
167    (0x65, 7),         // 74 'J'
168    (0x66, 7),         // 75 'K'
169    (0x67, 7),         // 76 'L'
170    (0x68, 7),         // 77 'M'
171    (0x69, 7),         // 78 'N'
172    (0x6a, 7),         // 79 'O'
173    (0x6b, 7),         // 80 'P'
174    (0x6c, 7),         // 81 'Q'
175    (0x6d, 7),         // 82 'R'
176    (0x6e, 7),         // 83 'S'
177    (0x6f, 7),         // 84 'T'
178    (0x70, 7),         // 85 'U'
179    (0x71, 7),         // 86 'V'
180    (0x72, 7),         // 87 'W'
181    (0xfc, 8),         // 88 'X'
182    (0x73, 7),         // 89 'Y'
183    (0xfd, 8),         // 90 'Z'
184    (0x1ffb, 13),      // 91 '['
185    (0x7fff0, 19),     // 92 '\\'
186    (0x1ffc, 13),      // 93 ']'
187    (0x3ffc, 14),      // 94 '^'
188    (0x22, 6),         // 95 '_'
189    (0x7ffd, 15),      // 96 '`'
190    (0x03, 5),         // 97 'a'
191    (0x23, 6),         // 98 'b'
192    (0x04, 5),         // 99 'c'
193    (0x24, 6),         // 100 'd'
194    (0x05, 5),         // 101 'e'
195    (0x25, 6),         // 102 'f'
196    (0x26, 6),         // 103 'g'
197    (0x27, 6),         // 104 'h'
198    (0x06, 5),         // 105 'i'
199    (0x74, 7),         // 106 'j'
200    (0x75, 7),         // 107 'k'
201    (0x28, 6),         // 108 'l'
202    (0x29, 6),         // 109 'm'
203    (0x2a, 6),         // 110 'n'
204    (0x07, 5),         // 111 'o'
205    (0x2b, 6),         // 112 'p'
206    (0x76, 7),         // 113 'q'
207    (0x2c, 6),         // 114 'r'
208    (0x08, 5),         // 115 's'
209    (0x09, 5),         // 116 't'
210    (0x2d, 6),         // 117 'u'
211    (0x77, 7),         // 118 'v'
212    (0x78, 7),         // 119 'w'
213    (0x79, 7),         // 120 'x'
214    (0x7a, 7),         // 121 'y'
215    (0x7b, 7),         // 122 'z'
216    (0x7ffe, 15),      // 123 '{'
217    (0x7fc, 11),       // 124 '|'
218    (0x3ffd, 14),      // 125 '}'
219    (0x1ffd, 13),      // 126 '~'
220    (0xffffffc, 28),   // 127
221    (0xfffe6, 20),     // 128
222    (0x3fffd2, 22),    // 129
223    (0xfffe7, 20),     // 130
224    (0xfffe8, 20),     // 131
225    (0x3fffd3, 22),    // 132
226    (0x3fffd4, 22),    // 133
227    (0x3fffd5, 22),    // 134
228    (0x7fffd9, 23),    // 135
229    (0x3fffd6, 22),    // 136
230    (0x7fffda, 23),    // 137
231    (0x7fffdb, 23),    // 138
232    (0x7fffdc, 23),    // 139
233    (0x7fffdd, 23),    // 140
234    (0x7fffde, 23),    // 141
235    (0xffffeb, 24),    // 142
236    (0x7fffdf, 23),    // 143
237    (0xffffec, 24),    // 144
238    (0xffffed, 24),    // 145
239    (0x3fffd7, 22),    // 146
240    (0x7fffe0, 23),    // 147
241    (0xffffee, 24),    // 148
242    (0x7fffe1, 23),    // 149
243    (0x7fffe2, 23),    // 150
244    (0x7fffe3, 23),    // 151
245    (0x7fffe4, 23),    // 152
246    (0x1fffdc, 21),    // 153
247    (0x3fffd8, 22),    // 154
248    (0x7fffe5, 23),    // 155
249    (0x3fffd9, 22),    // 156
250    (0x7fffe6, 23),    // 157
251    (0x7fffe7, 23),    // 158
252    (0xffffef, 24),    // 159
253    (0x3fffda, 22),    // 160
254    (0x1fffdd, 21),    // 161
255    (0xfffe9, 20),     // 162
256    (0x3fffdb, 22),    // 163
257    (0x3fffdc, 22),    // 164
258    (0x7fffe8, 23),    // 165
259    (0x7fffe9, 23),    // 166
260    (0x1fffde, 21),    // 167
261    (0x7fffea, 23),    // 168
262    (0x3fffdd, 22),    // 169
263    (0x3fffde, 22),    // 170
264    (0xfffff0, 24),    // 171
265    (0x1fffdf, 21),    // 172
266    (0x3fffdf, 22),    // 173
267    (0x7fffeb, 23),    // 174
268    (0x7fffec, 23),    // 175
269    (0x1fffe0, 21),    // 176
270    (0x1fffe1, 21),    // 177
271    (0x3fffe0, 22),    // 178
272    (0x1fffe2, 21),    // 179
273    (0x7fffed, 23),    // 180
274    (0x3fffe1, 22),    // 181
275    (0x7fffee, 23),    // 182
276    (0x7fffef, 23),    // 183
277    (0xfffea, 20),     // 184
278    (0x3fffe2, 22),    // 185
279    (0x3fffe3, 22),    // 186
280    (0x3fffe4, 22),    // 187
281    (0x7ffff0, 23),    // 188
282    (0x3fffe5, 22),    // 189
283    (0x3fffe6, 22),    // 190
284    (0x7ffff1, 23),    // 191
285    (0x3ffffe0, 26),   // 192
286    (0x3ffffe1, 26),   // 193
287    (0xfffeb, 20),     // 194
288    (0x7fff1, 19),     // 195
289    (0x3fffe7, 22),    // 196
290    (0x7ffff2, 23),    // 197
291    (0x3fffe8, 22),    // 198
292    (0x1ffffec, 25),   // 199
293    (0x3ffffe2, 26),   // 200
294    (0x3ffffe3, 26),   // 201
295    (0x3ffffe4, 26),   // 202
296    (0x7ffffde, 27),   // 203
297    (0x7ffffdf, 27),   // 204
298    (0x3ffffe5, 26),   // 205
299    (0xfffff1, 24),    // 206
300    (0x1ffffed, 25),   // 207
301    (0x7fff2, 19),     // 208
302    (0x1fffe3, 21),    // 209
303    (0x3ffffe6, 26),   // 210
304    (0x7ffffe0, 27),   // 211
305    (0x7ffffe1, 27),   // 212
306    (0x3ffffe7, 26),   // 213
307    (0x7ffffe2, 27),   // 214
308    (0xfffff2, 24),    // 215
309    (0x1fffe4, 21),    // 216
310    (0x1fffe5, 21),    // 217
311    (0x3ffffe8, 26),   // 218
312    (0x3ffffe9, 26),   // 219
313    (0xffffffd, 28),   // 220
314    (0x7ffffe3, 27),   // 221
315    (0x7ffffe4, 27),   // 222
316    (0x7ffffe5, 27),   // 223
317    (0xfffec, 20),     // 224
318    (0xfffff3, 24),    // 225
319    (0xfffed, 20),     // 226
320    (0x1fffe6, 21),    // 227
321    (0x3fffe9, 22),    // 228
322    (0x1fffe7, 21),    // 229
323    (0x1fffe8, 21),    // 230
324    (0x7ffff3, 23),    // 231
325    (0x3fffea, 22),    // 232
326    (0x3fffeb, 22),    // 233
327    (0x1ffffee, 25),   // 234
328    (0x1ffffef, 25),   // 235
329    (0xfffff4, 24),    // 236
330    (0xfffff5, 24),    // 237
331    (0x3ffffea, 26),   // 238
332    (0x7ffff4, 23),    // 239
333    (0x3ffffeb, 26),   // 240
334    (0x7ffffe6, 27),   // 241
335    (0x3ffffec, 26),   // 242
336    (0x3ffffed, 26),   // 243
337    (0x7ffffe7, 27),   // 244
338    (0x7ffffe8, 27),   // 245
339    (0x7ffffe9, 27),   // 246
340    (0x7ffffea, 27),   // 247
341    (0x7ffffeb, 27),   // 248
342    (0xffffffe, 28),   // 249
343    (0x7ffffec, 27),   // 250
344    (0x7ffffed, 27),   // 251
345    (0x7ffffee, 27),   // 252
346    (0x7ffffef, 27),   // 253
347    (0x7fffff0, 27),   // 254
348    (0x3ffffee, 26),   // 255
349    (0x3fffffff, 30),  // 256 EOS
350];
351
352// ============================================================================
353// Integer Encoding/Decoding (RFC 7541 Section 5.1)
354// ============================================================================
355
356/// Decode an HPACK integer with N-bit prefix.
357///
358/// The first byte is the prefix byte, where the top (8 - `prefix_bits`) bits
359/// may contain a representation indicator.
360///
361/// # Arguments
362/// - `buf`: input bytes starting at the prefix byte
363/// - `prefix_bits`: number of bits in the integer prefix (1-8)
364///
365/// # Returns
366/// `Some((value, bytes_consumed))` or `None` if the buffer is too short or
367/// the integer is malformed.
368#[must_use]
369pub fn decode_integer(buf: &[u8], prefix_bits: u8) -> Option<(u64, usize)> {
370    if buf.is_empty() || prefix_bits == 0 || prefix_bits > 8 {
371        return None;
372    }
373
374    let prefix_max = (1u32 << prefix_bits) - 1;
375    let first = u64::from(buf[0] & (prefix_max as u8));
376
377    if first < u64::from(prefix_max) {
378        // Small value that fits in prefix
379        return Some((first, 1));
380    }
381
382    // Value is >= prefix_max, read continuation bytes
383    let mut value = u64::from(prefix_max);
384    let mut shift = 0u64;
385    let mut i = 1;
386
387    loop {
388        if i >= buf.len() {
389            return None;
390        }
391        let byte = buf[i];
392        i += 1;
393        value += u64::from(byte & 0x7F) << shift;
394        shift += 7;
395
396        if (byte & 0x80) == 0 {
397            break;
398        }
399
400        if shift > 63 {
401            // Overflow protection
402            return None;
403        }
404    }
405
406    Some((value, i))
407}
408
409/// Encode an HPACK integer with N-bit prefix.
410///
411/// # Arguments
412/// - `value`: the integer to encode
413/// - `prefix_bits`: number of prefix bits (1-8)
414/// - `prefix_byte`: the high bits to set in the first byte (the representation indicator bits)
415///
416/// # Returns
417/// A `Vec<u8>` containing the encoded integer.
418#[must_use]
419pub fn encode_integer(value: u64, prefix_bits: u8, prefix_byte: u8) -> Vec<u8> {
420    let prefix_max = (1u64 << prefix_bits) - 1;
421
422    if value < prefix_max {
423        // Fits in prefix
424        return vec![prefix_byte | (value as u8)];
425    }
426
427    // Does not fit in prefix
428    let mut out = vec![prefix_byte | (prefix_max as u8)];
429    let mut remaining = value - prefix_max;
430
431    loop {
432        if remaining < 128 {
433            out.push(remaining as u8);
434            break;
435        }
436        out.push((remaining & 0x7F) as u8 | 0x80);
437        remaining >>= 7;
438    }
439
440    out
441}
442
443// ============================================================================
444// Huffman Node for decoding
445// ============================================================================
446
447/// A node in the Huffman decoding tree.
448#[derive(Default)]
449struct HuffmanNode {
450    symbol: Option<u16>, // Some(sym) if this is a leaf
451    children: [Option<Box<HuffmanNode>>; 2],
452}
453
454impl HuffmanNode {
455    fn new() -> Self {
456        HuffmanNode {
457            symbol: None,
458            children: [None, None],
459        }
460    }
461}
462
463/// Build the Huffman decoding tree from the `HUFFMAN_TABLE`.
464fn build_huffman_tree() -> HuffmanNode {
465    let mut root = HuffmanNode::new();
466
467    for (sym, &(code, nbits)) in HUFFMAN_TABLE.iter().enumerate() {
468        if nbits == 0 || nbits > 32 {
469            continue;
470        }
471        // Skip impossible codes (EOS is sym 256, but we include it for EOS detection)
472        let mut node = &mut root;
473        for bit_idx in (0..nbits).rev() {
474            let bit = ((code >> bit_idx) & 1) as usize;
475            if node.children[bit].is_none() {
476                node.children[bit] = Some(Box::new(HuffmanNode::new()));
477            }
478            node = node.children[bit].as_mut().unwrap();
479        }
480        node.symbol = Some(sym as u16);
481    }
482
483    root
484}
485
486// ============================================================================
487// Huffman Decode / Encode (RFC 7541 Appendix B)
488// ============================================================================
489
490/// Decode Huffman-encoded data (RFC 7541 Appendix B).
491///
492/// Returns the decoded bytes on success, or `None` if the data is malformed.
493/// Padding bits (at most 7) must all be 1s as per the spec.
494#[must_use]
495pub fn huffman_decode(encoded: &[u8]) -> Option<Vec<u8>> {
496    let root = build_huffman_tree();
497    let mut output = Vec::new();
498    let mut node = &root;
499
500    for &byte in encoded {
501        for bit_idx in (0..8).rev() {
502            let bit = ((byte >> bit_idx) & 1) as usize;
503            node = node.children[bit].as_ref()?;
504
505            if let Some(sym) = node.symbol {
506                if sym == 256 {
507                    // EOS encountered
508                    return None;
509                }
510                output.push(sym as u8);
511                node = &root;
512            }
513        }
514    }
515
516    // After all bytes consumed, we should be at root or at a padding position.
517    // Padding must be high bits of a partial byte (all 1s). The tree traversal
518    // handles this: if we're not at root, the partial byte's remaining bits
519    // must form a path to a non-leaf node (valid padding).
520    // If we ended on a leaf that's EOS, that's an error (already handled).
521    // Being at a non-root position is acceptable as long as remaining bits were all 1.
522    // The loop above handles this correctly since we only follow actual bit paths.
523
524    Some(output)
525}
526
527/// Encode data using Huffman coding (RFC 7541 Appendix B).
528///
529/// Returns a byte vector. The output is padded with 1-bits to the next byte boundary.
530#[must_use]
531pub fn huffman_encode(data: &[u8]) -> Vec<u8> {
532    let mut bit_buf: u64 = 0;
533    let mut bit_count = 0u32;
534    let mut output = Vec::new();
535
536    for &byte in data {
537        let (code, nbits) = HUFFMAN_TABLE[byte as usize];
538        let nbits = u32::from(nbits);
539
540        bit_buf = (bit_buf << nbits) | u64::from(code);
541        bit_count += nbits;
542
543        while bit_count >= 8 {
544            bit_count -= 8;
545            output.push(((bit_buf >> bit_count) & 0xFF) as u8);
546        }
547    }
548
549    // Flush remaining bits with 1-bit padding
550    if bit_count > 0 {
551        let padding = 8 - bit_count;
552        bit_buf = (bit_buf << padding) | ((1u64 << padding) - 1);
553        output.push((bit_buf & 0xFF) as u8);
554    }
555
556    output
557}
558
559// ============================================================================
560// String Literal Decode/Encode (RFC 7541 Section 5.2)
561// ============================================================================
562
563/// Decode a string literal from an HPACK-encoded buffer.
564///
565/// Format:
566/// ```text
567/// +---+---+---+---+---+---+---+---+
568/// | H |    String Length (7+)      |
569/// +---+---------------------------+
570/// | String Data (Length octets)   |
571/// +-------------------------------+
572/// ```
573///
574/// # Returns
575/// `Some((string_bytes, bytes_consumed))` or `None` if the buffer is too short.
576#[must_use]
577pub fn decode_string(buf: &[u8]) -> Option<(Vec<u8>, usize)> {
578    if buf.is_empty() {
579        return None;
580    }
581
582    let huffman_flag = (buf[0] & 0x80) != 0;
583    let (length, consumed) = decode_integer(buf, 7)?;
584    let length = length as usize;
585
586    if consumed + length > buf.len() {
587        return None;
588    }
589
590    let string_data = &buf[consumed..consumed + length];
591
592    let result = if huffman_flag {
593        huffman_decode(string_data)?
594    } else {
595        string_data.to_vec()
596    };
597
598    Some((result, consumed + length))
599}
600
601/// Encode a string literal without Huffman coding.
602///
603/// Returns bytes in literal (non-Huffman) format.
604#[must_use]
605pub fn encode_string_literal(data: &[u8]) -> Vec<u8> {
606    let mut out = encode_integer(data.len() as u64, 7, 0x00); // H=0
607    out.extend_from_slice(data);
608    out
609}
610
611/// Encode a string literal with Huffman coding if beneficial.
612///
613/// Uses Huffman encoding and sets H=1. Always Huffman-encodes for simplicity.
614#[must_use]
615pub fn encode_string_huffman(data: &[u8]) -> Vec<u8> {
616    let encoded = huffman_encode(data);
617    let mut out = encode_integer(encoded.len() as u64, 7, 0x80); // H=1
618    out.extend_from_slice(&encoded);
619    out
620}
621
622// ============================================================================
623// HPACK Decoder (RFC 7541)
624// ============================================================================
625
626/// HPACK dynamic table entry size = name length + value length + 32 bytes overhead.
627fn entry_size(name: &str, value: &str) -> usize {
628    name.len() + value.len() + 32
629}
630
631/// HPACK decoder with dynamic table state.
632///
633/// This decoder maintains a dynamic table across multiple calls, allowing
634/// header compression state to be shared within an HTTP/2 connection.
635pub struct HpackDecoder {
636    /// Dynamic table entries (most recently added first).
637    dynamic_table: Vec<(String, String)>,
638    /// Maximum dynamic table size (bytes, per `SETTINGS_HEADER_TABLE_SIZE`).
639    max_table_size: usize,
640    /// Current dynamic table size (sum of entry sizes).
641    current_size: usize,
642}
643
644impl Default for HpackDecoder {
645    fn default() -> Self {
646        Self::new()
647    }
648}
649
650impl HpackDecoder {
651    /// Create a new HPACK decoder with default max table size of 4096 bytes.
652    #[must_use]
653    pub fn new() -> Self {
654        HpackDecoder {
655            dynamic_table: Vec::new(),
656            max_table_size: 4096,
657            current_size: 0,
658        }
659    }
660
661    /// Create a new HPACK decoder with a specific max table size.
662    #[must_use]
663    pub fn with_max_size(max_table_size: usize) -> Self {
664        HpackDecoder {
665            dynamic_table: Vec::new(),
666            max_table_size,
667            current_size: 0,
668        }
669    }
670
671    /// Update the maximum table size (e.g., from `SETTINGS_HEADER_TABLE_SIZE`).
672    pub fn set_max_table_size(&mut self, size: usize) {
673        self.max_table_size = size;
674        self.evict_to_fit(0);
675    }
676
677    /// Returns the number of entries currently in the dynamic table.
678    #[must_use]
679    pub fn dynamic_table_len(&self) -> usize {
680        self.dynamic_table.len()
681    }
682
683    /// Returns the current size (in bytes) of the dynamic table.
684    #[must_use]
685    pub fn dynamic_table_current_size(&self) -> usize {
686        self.current_size
687    }
688
689    /// Returns the entries in the dynamic table as (name, value) string slices.
690    /// Entries are ordered most-recently-added first (index 62 = first entry).
691    #[must_use]
692    pub fn dynamic_table_entries(&self) -> &[(String, String)] {
693        &self.dynamic_table
694    }
695
696    /// Decode a block of HPACK-encoded headers.
697    ///
698    /// Returns a list of `(name, value)` pairs in the order they were decoded.
699    pub fn decode(&mut self, buf: &[u8]) -> Result<Vec<(String, String)>, String> {
700        let mut headers = Vec::new();
701        let mut pos = 0;
702
703        while pos < buf.len() {
704            let first = buf[pos];
705
706            if (first & 0x80) != 0 {
707                // Indexed Header Field Representation (RFC 7541 Section 6.1)
708                // Pattern: 1xxxxxxx
709                let (idx, consumed) = decode_integer(&buf[pos..], 7)
710                    .ok_or_else(|| "Failed to decode indexed header index".to_string())?;
711                pos += consumed;
712
713                let (name, value) = self
714                    .table_entry(idx as usize)
715                    .ok_or_else(|| format!("Invalid header table index: {idx}"))?;
716                headers.push((name, value));
717            } else if (first & 0x40) != 0 {
718                // Literal Header Field with Incremental Indexing (RFC 7541 Section 6.2.1)
719                // Pattern: 01xxxxxx
720                let (idx, consumed) = decode_integer(&buf[pos..], 6)
721                    .ok_or_else(|| "Failed to decode literal indexed name index".to_string())?;
722                pos += consumed;
723
724                let name = if idx == 0 {
725                    // Name is a literal string
726                    let (name_bytes, nc) = decode_string(&buf[pos..])
727                        .ok_or_else(|| "Failed to decode literal name string".to_string())?;
728                    pos += nc;
729                    String::from_utf8(name_bytes)
730                        .map_err(|e| format!("Invalid UTF-8 in header name: {e}"))?
731                } else {
732                    let (n, _) = self
733                        .table_entry(idx as usize)
734                        .ok_or_else(|| format!("Invalid header table index: {idx}"))?;
735                    n
736                };
737
738                let (value_bytes, vc) = decode_string(&buf[pos..])
739                    .ok_or_else(|| "Failed to decode literal value string".to_string())?;
740                pos += vc;
741                let value = String::from_utf8(value_bytes)
742                    .map_err(|e| format!("Invalid UTF-8 in header value: {e}"))?;
743
744                self.add_to_dynamic_table(name.clone(), value.clone());
745                headers.push((name, value));
746            } else if (first & 0x20) != 0 {
747                // Dynamic Table Size Update (RFC 7541 Section 6.3)
748                // Pattern: 001xxxxx
749                let (new_size, consumed) = decode_integer(&buf[pos..], 5)
750                    .ok_or_else(|| "Failed to decode table size update".to_string())?;
751                pos += consumed;
752
753                if new_size as usize > self.max_table_size {
754                    return Err(format!(
755                        "Table size update {} exceeds max {}",
756                        new_size, self.max_table_size
757                    ));
758                }
759                self.evict_to_fit(0);
760                // Trim to new size
761                while self.current_size > new_size as usize {
762                    if let Some(entry) = self.dynamic_table.pop() {
763                        self.current_size = self
764                            .current_size
765                            .saturating_sub(entry_size(&entry.0, &entry.1));
766                    } else {
767                        break;
768                    }
769                }
770            } else if (first & 0x10) != 0 {
771                // Literal Header Field Never Indexed (RFC 7541 Section 6.2.3)
772                // Pattern: 0001xxxx
773                let (idx, consumed) = decode_integer(&buf[pos..], 4)
774                    .ok_or_else(|| "Failed to decode never-indexed name index".to_string())?;
775                pos += consumed;
776
777                let name = if idx == 0 {
778                    let (name_bytes, nc) = decode_string(&buf[pos..])
779                        .ok_or_else(|| "Failed to decode never-indexed name string".to_string())?;
780                    pos += nc;
781                    String::from_utf8(name_bytes)
782                        .map_err(|e| format!("Invalid UTF-8 in header name: {e}"))?
783                } else {
784                    let (n, _) = self
785                        .table_entry(idx as usize)
786                        .ok_or_else(|| format!("Invalid header table index: {idx}"))?;
787                    n
788                };
789
790                let (value_bytes, vc) = decode_string(&buf[pos..])
791                    .ok_or_else(|| "Failed to decode never-indexed value string".to_string())?;
792                pos += vc;
793                let value = String::from_utf8(value_bytes)
794                    .map_err(|e| format!("Invalid UTF-8 in header value: {e}"))?;
795
796                // Never indexed — do not add to dynamic table
797                headers.push((name, value));
798            } else {
799                // Literal Header Field Without Indexing (RFC 7541 Section 6.2.2)
800                // Pattern: 0000xxxx
801                let (idx, consumed) = decode_integer(&buf[pos..], 4)
802                    .ok_or_else(|| "Failed to decode without-indexing name index".to_string())?;
803                pos += consumed;
804
805                let name = if idx == 0 {
806                    let (name_bytes, nc) = decode_string(&buf[pos..]).ok_or_else(|| {
807                        "Failed to decode without-indexing name string".to_string()
808                    })?;
809                    pos += nc;
810                    String::from_utf8(name_bytes)
811                        .map_err(|e| format!("Invalid UTF-8 in header name: {e}"))?
812                } else {
813                    let (n, _) = self
814                        .table_entry(idx as usize)
815                        .ok_or_else(|| format!("Invalid header table index: {idx}"))?;
816                    n
817                };
818
819                let (value_bytes, vc) = decode_string(&buf[pos..])
820                    .ok_or_else(|| "Failed to decode without-indexing value string".to_string())?;
821                pos += vc;
822                let value = String::from_utf8(value_bytes)
823                    .map_err(|e| format!("Invalid UTF-8 in header value: {e}"))?;
824
825                // Without indexing — do not add to dynamic table
826                headers.push((name, value));
827            }
828        }
829
830        Ok(headers)
831    }
832
833    /// Look up a dynamic table entry (0-indexed from the front, most recent first).
834    fn dynamic_table_entry(&self, index: usize) -> Option<(&str, &str)> {
835        self.dynamic_table
836            .get(index)
837            .map(|(n, v)| (n.as_str(), v.as_str()))
838    }
839
840    /// Look up a static table entry by 1-based index.
841    fn static_table_entry(index: usize) -> Option<(&'static str, &'static str)> {
842        if index >= 1 && index <= STATIC_TABLE.len() {
843            Some(STATIC_TABLE[index - 1])
844        } else {
845            None
846        }
847    }
848
849    /// Look up a combined (static + dynamic) table entry.
850    ///
851    /// Indices 1..=61 are static table. Indices 62+ are dynamic table.
852    fn table_entry(&self, index: usize) -> Option<(String, String)> {
853        if index == 0 {
854            return None;
855        }
856
857        if index <= STATIC_TABLE.len() {
858            let (n, v) = Self::static_table_entry(index)?;
859            return Some((n.to_string(), v.to_string()));
860        }
861
862        let dynamic_idx = index - STATIC_TABLE.len() - 1;
863        let (n, v) = self.dynamic_table_entry(dynamic_idx)?;
864        Some((n.to_string(), v.to_string()))
865    }
866
867    /// Add a new entry to the dynamic table (prepended, i.e., most recent first).
868    fn add_to_dynamic_table(&mut self, name: String, value: String) {
869        let size = entry_size(&name, &value);
870        self.evict_to_fit(size);
871        if size <= self.max_table_size {
872            self.current_size += size;
873            self.dynamic_table.insert(0, (name, value));
874        }
875    }
876
877    /// Evict entries from the back of the dynamic table until the table can
878    /// accommodate `new_entry_size` more bytes.
879    fn evict_to_fit(&mut self, new_entry_size: usize) {
880        while !self.dynamic_table.is_empty()
881            && self.current_size + new_entry_size > self.max_table_size
882        {
883            if let Some(entry) = self.dynamic_table.pop() {
884                self.current_size = self
885                    .current_size
886                    .saturating_sub(entry_size(&entry.0, &entry.1));
887            }
888        }
889    }
890}
891
892// ============================================================================
893// HPACK Encoder
894// ============================================================================
895
896/// HPACK encoder.
897///
898/// Currently uses literal header fields without indexing for simplicity.
899/// An optimized version would use the static and dynamic tables to produce
900/// smaller output.
901pub struct HpackEncoder {
902    /// Dynamic table entries (most recently added first).
903    #[allow(dead_code)]
904    dynamic_table: Vec<(String, String)>,
905    /// Maximum table size.
906    #[allow(dead_code)]
907    max_table_size: usize,
908}
909
910impl Default for HpackEncoder {
911    fn default() -> Self {
912        Self::new()
913    }
914}
915
916impl HpackEncoder {
917    /// Create a new HPACK encoder with default max table size of 4096 bytes.
918    #[must_use]
919    pub fn new() -> Self {
920        HpackEncoder {
921            dynamic_table: Vec::new(),
922            max_table_size: 4096,
923        }
924    }
925
926    /// Encode a list of header name-value pairs using HPACK.
927    ///
928    /// This implementation uses literal header fields without indexing
929    /// (pattern 0000xxxx with index=0) for simplicity and correctness.
930    /// It checks the static table for name-only matches to save space.
931    #[must_use]
932    pub fn encode(&self, headers: &[(&str, &str)]) -> Vec<u8> {
933        let mut out = Vec::new();
934
935        for &(name, value) in headers {
936            // Check static table for exact match (both name and value)
937            if let Some(static_idx) = self.find_static_exact(name, value) {
938                // Indexed header field representation (Section 6.1)
939                // Pattern: 1xxxxxxx
940                out.extend_from_slice(&encode_integer(static_idx as u64, 7, 0x80));
941                continue;
942            }
943
944            // Check static table for name-only match
945            if let Some(name_idx) = self.find_static_name(name) {
946                // Literal header field with incremental indexing (Section 6.2.1)
947                // Pattern: 01xxxxxx with name index
948                out.extend_from_slice(&encode_integer(name_idx as u64, 6, 0x40));
949                out.extend_from_slice(&encode_string_literal(value.as_bytes()));
950                continue;
951            }
952
953            // Literal header field without indexing (Section 6.2.2)
954            // Pattern: 00000000 followed by name and value strings
955            out.push(0x00);
956            out.extend_from_slice(&encode_string_literal(name.as_bytes()));
957            out.extend_from_slice(&encode_string_literal(value.as_bytes()));
958        }
959
960        out
961    }
962
963    /// Encode headers using Huffman encoding for string values.
964    #[must_use]
965    pub fn encode_huffman(&self, headers: &[(&str, &str)]) -> Vec<u8> {
966        let mut out = Vec::new();
967
968        for &(name, value) in headers {
969            // Check static table for exact match
970            if let Some(static_idx) = self.find_static_exact(name, value) {
971                out.extend_from_slice(&encode_integer(static_idx as u64, 7, 0x80));
972                continue;
973            }
974
975            // Literal without indexing with Huffman-encoded strings
976            out.push(0x00);
977            out.extend_from_slice(&encode_string_huffman(name.as_bytes()));
978            out.extend_from_slice(&encode_string_huffman(value.as_bytes()));
979        }
980
981        out
982    }
983
984    /// Find a static table index for an exact (name, value) match.
985    /// Returns 1-based index or None.
986    fn find_static_exact(&self, name: &str, value: &str) -> Option<usize> {
987        for (i, &(n, v)) in STATIC_TABLE.iter().enumerate() {
988            if n == name && v == value {
989                return Some(i + 1);
990            }
991        }
992        None
993    }
994
995    /// Find a static table index for a name-only match.
996    /// Returns 1-based index of first matching entry or None.
997    fn find_static_name(&self, name: &str) -> Option<usize> {
998        for (i, &(n, _)) in STATIC_TABLE.iter().enumerate() {
999            if n == name {
1000                return Some(i + 1);
1001            }
1002        }
1003        None
1004    }
1005}
1006
1007// ============================================================================
1008// Tests
1009// ============================================================================
1010
1011#[cfg(test)]
1012mod tests {
1013    use super::*;
1014
1015    // -------------------------------------------------------------------------
1016    // Integer encode/decode
1017    // -------------------------------------------------------------------------
1018
1019    #[test]
1020    fn test_decode_integer_small() {
1021        // Value 5 with 5-bit prefix: 0b00000101 = 0x05
1022        let buf = [0x05u8];
1023        let (val, consumed) = decode_integer(&buf, 5).unwrap();
1024        assert_eq!(val, 5);
1025        assert_eq!(consumed, 1);
1026    }
1027
1028    #[test]
1029    fn test_decode_integer_multi_byte() {
1030        // RFC 7541 Section 5.1 example: value 1337 with 5-bit prefix
1031        // First byte: 0b00011111 = 31 (all prefix bits set)
1032        // Continuation: 0b10011010 = 154, 0b00001010 = 10
1033        // 1337 - 31 = 1306 = 0b10100011010
1034        // First continuation: 1306 & 0x7F = 26 (0b0011010), MSB=1 → 0b10011010 = 0x9A... wait
1035        // Let me use spec values: 1337 with 5-bit prefix
1036        // 1337 - 31 = 1306
1037        // 1306 % 128 = 26 | 0x80 = 0x9A, 1306 / 128 = 10 = 0x0A
1038        let buf = [0b00011111u8, 0x9A, 0x0A];
1039        let (val, consumed) = decode_integer(&buf, 5).unwrap();
1040        assert_eq!(val, 1337);
1041        assert_eq!(consumed, 3);
1042    }
1043
1044    #[test]
1045    fn test_encode_integer_small() {
1046        let encoded = encode_integer(5, 5, 0x00);
1047        assert_eq!(encoded, vec![0x05]);
1048    }
1049
1050    #[test]
1051    fn test_encode_integer_large() {
1052        // 1337 with 5-bit prefix
1053        let encoded = encode_integer(1337, 5, 0x00);
1054        assert_eq!(encoded, vec![0b00011111, 0x9A, 0x0A]);
1055    }
1056
1057    #[test]
1058    fn test_encode_decode_integer_roundtrip() {
1059        for val in [0u64, 1, 30, 31, 127, 128, 255, 1000, 65535, 1337] {
1060            for prefix in [4u8, 5, 6, 7] {
1061                let encoded = encode_integer(val, prefix, 0x00);
1062                let (decoded, _) = decode_integer(&encoded, prefix).unwrap();
1063                assert_eq!(
1064                    val, decoded,
1065                    "Roundtrip failed for val={} prefix={}",
1066                    val, prefix
1067                );
1068            }
1069        }
1070    }
1071
1072    // -------------------------------------------------------------------------
1073    // String literals
1074    // -------------------------------------------------------------------------
1075
1076    #[test]
1077    fn test_decode_string_literal() {
1078        // Non-Huffman string: length=5, "hello"
1079        let mut buf = vec![0x05u8]; // H=0, length=5
1080        buf.extend_from_slice(b"hello");
1081        let (s, consumed) = decode_string(&buf).unwrap();
1082        assert_eq!(s, b"hello");
1083        assert_eq!(consumed, 6);
1084    }
1085
1086    #[test]
1087    fn test_huffman_encode_decode_roundtrip() {
1088        let original = b"www.example.com";
1089        let encoded = huffman_encode(original);
1090        let decoded = huffman_decode(&encoded).unwrap();
1091        assert_eq!(decoded, original);
1092    }
1093
1094    #[test]
1095    fn test_huffman_encode_empty() {
1096        let encoded = huffman_encode(b"");
1097        assert!(encoded.is_empty());
1098    }
1099
1100    #[test]
1101    fn test_huffman_decode_rfc7541_example() {
1102        // RFC 7541 Appendix C.4.1 example: "www.example.com" Huffman-encoded
1103        // Known encoding from the RFC: f1e3 c2e5 f23a 6ba0 ab90 f4ff
1104        let encoded = [
1105            0xf1u8, 0xe3, 0xc2, 0xe5, 0xf2, 0x3a, 0x6b, 0xa0, 0xab, 0x90, 0xf4, 0xff,
1106        ];
1107        let decoded = huffman_decode(&encoded).unwrap();
1108        assert_eq!(decoded, b"www.example.com");
1109    }
1110
1111    #[test]
1112    fn test_decode_string_huffman() {
1113        let original = b"no-cache";
1114        let encoded = huffman_encode(original);
1115        // Build HPACK string format: H=1, length, data
1116        let mut buf = encode_integer(encoded.len() as u64, 7, 0x80);
1117        buf.extend_from_slice(&encoded);
1118        let (decoded, _) = decode_string(&buf).unwrap();
1119        assert_eq!(decoded, original);
1120    }
1121
1122    // -------------------------------------------------------------------------
1123    // Static table
1124    // -------------------------------------------------------------------------
1125
1126    #[test]
1127    fn test_static_table_size() {
1128        assert_eq!(STATIC_TABLE.len(), 61);
1129    }
1130
1131    #[test]
1132    fn test_static_table_first_entry() {
1133        assert_eq!(STATIC_TABLE[0], (":authority", ""));
1134    }
1135
1136    #[test]
1137    fn test_static_table_method_get() {
1138        assert_eq!(STATIC_TABLE[1], (":method", "GET"));
1139    }
1140
1141    // -------------------------------------------------------------------------
1142    // HPACK decoder
1143    // -------------------------------------------------------------------------
1144
1145    #[test]
1146    fn test_decoder_indexed_static() {
1147        // Index 2 = ":method: GET"
1148        // Indexed representation: 1xxxxxxx where x=2 → 0x82
1149        let buf = [0x82u8];
1150        let mut decoder = HpackDecoder::new();
1151        let headers = decoder.decode(&buf).unwrap();
1152        assert_eq!(headers.len(), 1);
1153        assert_eq!(headers[0], (":method".to_string(), "GET".to_string()));
1154    }
1155
1156    #[test]
1157    fn test_decoder_literal_with_indexing() {
1158        // Literal with incremental indexing: 01xxxxxx
1159        // name index = 0, name = "custom-key", value = "custom-header"
1160        let mut buf = vec![0x40u8]; // 01 000000 = new name, incremental indexing
1161        buf.extend_from_slice(&encode_string_literal(b"custom-key"));
1162        buf.extend_from_slice(&encode_string_literal(b"custom-header"));
1163
1164        let mut decoder = HpackDecoder::new();
1165        let headers = decoder.decode(&buf).unwrap();
1166        assert_eq!(headers.len(), 1);
1167        assert_eq!(
1168            headers[0],
1169            ("custom-key".to_string(), "custom-header".to_string())
1170        );
1171    }
1172
1173    #[test]
1174    fn test_decoder_literal_without_indexing() {
1175        // Literal without indexing: 0000xxxx with idx=0 (new name)
1176        let mut buf = vec![0x00u8];
1177        buf.extend_from_slice(&encode_string_literal(b"x-custom"));
1178        buf.extend_from_slice(&encode_string_literal(b"value"));
1179
1180        let mut decoder = HpackDecoder::new();
1181        let headers = decoder.decode(&buf).unwrap();
1182        assert_eq!(headers.len(), 1);
1183        assert_eq!(headers[0], ("x-custom".to_string(), "value".to_string()));
1184    }
1185
1186    #[test]
1187    fn test_decoder_dynamic_table_eviction() {
1188        let mut decoder = HpackDecoder::with_max_size(64);
1189        // Add an entry that barely fits
1190        let mut buf = vec![0x40u8];
1191        buf.extend_from_slice(&encode_string_literal(b"x-a"));
1192        buf.extend_from_slice(&encode_string_literal(b"b"));
1193        decoder.decode(&buf).unwrap();
1194        assert_eq!(decoder.dynamic_table.len(), 1);
1195
1196        // Add another entry that should evict the first
1197        let mut buf2 = vec![0x40u8];
1198        buf2.extend_from_slice(&encode_string_literal(b"x-c"));
1199        buf2.extend_from_slice(&encode_string_literal(b"d"));
1200        decoder.decode(&buf2).unwrap();
1201        // First entry should be evicted since total would exceed 64
1202        assert!(decoder.dynamic_table.len() <= 2);
1203    }
1204
1205    // -------------------------------------------------------------------------
1206    // HPACK encoder
1207    // -------------------------------------------------------------------------
1208
1209    #[test]
1210    fn test_encoder_static_exact_match() {
1211        let encoder = HpackEncoder::new();
1212        // ":method: GET" is static index 2 → should encode as 0x82
1213        let encoded = encoder.encode(&[(":method", "GET")]);
1214        assert_eq!(encoded, vec![0x82]);
1215    }
1216
1217    #[test]
1218    fn test_encoder_static_name_match() {
1219        let encoder = HpackEncoder::new();
1220        // ":method" matches static index 2 with value "GET", but "DELETE" won't match exact
1221        let encoded = encoder.encode(&[(":method", "DELETE")]);
1222        // Should use name-indexed literal: 0x40 | idx(2) = 0x42, then value
1223        assert_eq!(encoded[0], 0x42); // 01 000010 = name-indexed literal with static index 2
1224    }
1225
1226    #[test]
1227    fn test_encoder_roundtrip() {
1228        let encoder = HpackEncoder::new();
1229        let mut decoder = HpackDecoder::new();
1230
1231        let headers = vec![
1232            (":method", "GET"),
1233            (":path", "/"),
1234            (":scheme", "https"),
1235            ("user-agent", "test/1.0"),
1236            ("x-custom", "value"),
1237        ];
1238        let encoded = encoder.encode(&headers);
1239        let decoded = decoder.decode(&encoded).unwrap();
1240
1241        assert_eq!(decoded.len(), headers.len());
1242        for (i, (name, value)) in headers.iter().enumerate() {
1243            assert_eq!(decoded[i].0, *name);
1244            assert_eq!(decoded[i].1, *value);
1245        }
1246    }
1247
1248    #[test]
1249    fn test_encoder_huffman_roundtrip() {
1250        let encoder = HpackEncoder::new();
1251        let mut decoder = HpackDecoder::new();
1252
1253        let headers = vec![
1254            ("x-request-id", "abc-123"),
1255            ("content-type", "application/json"),
1256        ];
1257        let encoded = encoder.encode_huffman(&headers);
1258        let decoded = decoder.decode(&encoded).unwrap();
1259
1260        assert_eq!(decoded.len(), headers.len());
1261        for (i, (name, value)) in headers.iter().enumerate() {
1262            assert_eq!(decoded[i].0, *name);
1263            assert_eq!(decoded[i].1, *value);
1264        }
1265    }
1266}