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.
368pub fn decode_integer(buf: &[u8], prefix_bits: u8) -> Option<(u64, usize)> {
369    if buf.is_empty() || prefix_bits == 0 || prefix_bits > 8 {
370        return None;
371    }
372
373    let prefix_max = (1u32 << prefix_bits) - 1;
374    let first = (buf[0] & (prefix_max as u8)) as u64;
375
376    if first < prefix_max as u64 {
377        // Small value that fits in prefix
378        return Some((first, 1));
379    }
380
381    // Value is >= prefix_max, read continuation bytes
382    let mut value = prefix_max as u64;
383    let mut shift = 0u64;
384    let mut i = 1;
385
386    loop {
387        if i >= buf.len() {
388            return None;
389        }
390        let byte = buf[i];
391        i += 1;
392        value += ((byte & 0x7F) as u64) << shift;
393        shift += 7;
394
395        if (byte & 0x80) == 0 {
396            break;
397        }
398
399        if shift > 63 {
400            // Overflow protection
401            return None;
402        }
403    }
404
405    Some((value, i))
406}
407
408/// Encode an HPACK integer with N-bit prefix.
409///
410/// # Arguments
411/// - `value`: the integer to encode
412/// - `prefix_bits`: number of prefix bits (1-8)
413/// - `prefix_byte`: the high bits to set in the first byte (the representation indicator bits)
414///
415/// # Returns
416/// A `Vec<u8>` containing the encoded integer.
417pub fn encode_integer(value: u64, prefix_bits: u8, prefix_byte: u8) -> Vec<u8> {
418    let prefix_max = (1u64 << prefix_bits) - 1;
419
420    if value < prefix_max {
421        // Fits in prefix
422        return vec![prefix_byte | (value as u8)];
423    }
424
425    // Does not fit in prefix
426    let mut out = vec![prefix_byte | (prefix_max as u8)];
427    let mut remaining = value - prefix_max;
428
429    loop {
430        if remaining < 128 {
431            out.push(remaining as u8);
432            break;
433        }
434        out.push((remaining & 0x7F) as u8 | 0x80);
435        remaining >>= 7;
436    }
437
438    out
439}
440
441// ============================================================================
442// Huffman Node for decoding
443// ============================================================================
444
445/// A node in the Huffman decoding tree.
446#[derive(Default)]
447struct HuffmanNode {
448    symbol: Option<u16>, // Some(sym) if this is a leaf
449    children: [Option<Box<HuffmanNode>>; 2],
450}
451
452impl HuffmanNode {
453    fn new() -> Self {
454        HuffmanNode {
455            symbol: None,
456            children: [None, None],
457        }
458    }
459}
460
461/// Build the Huffman decoding tree from the HUFFMAN_TABLE.
462fn build_huffman_tree() -> HuffmanNode {
463    let mut root = HuffmanNode::new();
464
465    for (sym, &(code, nbits)) in HUFFMAN_TABLE.iter().enumerate() {
466        if nbits == 0 || nbits > 32 {
467            continue;
468        }
469        // Skip impossible codes (EOS is sym 256, but we include it for EOS detection)
470        let mut node = &mut root;
471        for bit_idx in (0..nbits).rev() {
472            let bit = ((code >> bit_idx) & 1) as usize;
473            if node.children[bit].is_none() {
474                node.children[bit] = Some(Box::new(HuffmanNode::new()));
475            }
476            node = node.children[bit].as_mut().unwrap();
477        }
478        node.symbol = Some(sym as u16);
479    }
480
481    root
482}
483
484// ============================================================================
485// Huffman Decode / Encode (RFC 7541 Appendix B)
486// ============================================================================
487
488/// Decode Huffman-encoded data (RFC 7541 Appendix B).
489///
490/// Returns the decoded bytes on success, or `None` if the data is malformed.
491/// Padding bits (at most 7) must all be 1s as per the spec.
492pub fn huffman_decode(encoded: &[u8]) -> Option<Vec<u8>> {
493    let root = build_huffman_tree();
494    let mut output = Vec::new();
495    let mut node = &root;
496
497    for &byte in encoded {
498        for bit_idx in (0..8).rev() {
499            let bit = ((byte >> bit_idx) & 1) as usize;
500            node = node.children[bit].as_ref()?;
501
502            if let Some(sym) = node.symbol {
503                if sym == 256 {
504                    // EOS encountered
505                    return None;
506                }
507                output.push(sym as u8);
508                node = &root;
509            }
510        }
511    }
512
513    // After all bytes consumed, we should be at root or at a padding position.
514    // Padding must be high bits of a partial byte (all 1s). The tree traversal
515    // handles this: if we're not at root, the partial byte's remaining bits
516    // must form a path to a non-leaf node (valid padding).
517    // If we ended on a leaf that's EOS, that's an error (already handled).
518    // Being at a non-root position is acceptable as long as remaining bits were all 1.
519    // The loop above handles this correctly since we only follow actual bit paths.
520
521    Some(output)
522}
523
524/// Encode data using Huffman coding (RFC 7541 Appendix B).
525///
526/// Returns a byte vector. The output is padded with 1-bits to the next byte boundary.
527pub fn huffman_encode(data: &[u8]) -> Vec<u8> {
528    let mut bit_buf: u64 = 0;
529    let mut bit_count = 0u32;
530    let mut output = Vec::new();
531
532    for &byte in data {
533        let (code, nbits) = HUFFMAN_TABLE[byte as usize];
534        let nbits = nbits as u32;
535
536        bit_buf = (bit_buf << nbits) | (code as u64);
537        bit_count += nbits;
538
539        while bit_count >= 8 {
540            bit_count -= 8;
541            output.push(((bit_buf >> bit_count) & 0xFF) as u8);
542        }
543    }
544
545    // Flush remaining bits with 1-bit padding
546    if bit_count > 0 {
547        let padding = 8 - bit_count;
548        bit_buf = (bit_buf << padding) | ((1u64 << padding) - 1);
549        output.push((bit_buf & 0xFF) as u8);
550    }
551
552    output
553}
554
555// ============================================================================
556// String Literal Decode/Encode (RFC 7541 Section 5.2)
557// ============================================================================
558
559/// Decode a string literal from an HPACK-encoded buffer.
560///
561/// Format:
562/// ```text
563/// +---+---+---+---+---+---+---+---+
564/// | H |    String Length (7+)      |
565/// +---+---------------------------+
566/// | String Data (Length octets)   |
567/// +-------------------------------+
568/// ```
569///
570/// # Returns
571/// `Some((string_bytes, bytes_consumed))` or `None` if the buffer is too short.
572pub fn decode_string(buf: &[u8]) -> Option<(Vec<u8>, usize)> {
573    if buf.is_empty() {
574        return None;
575    }
576
577    let huffman_flag = (buf[0] & 0x80) != 0;
578    let (length, consumed) = decode_integer(buf, 7)?;
579    let length = length as usize;
580
581    if consumed + length > buf.len() {
582        return None;
583    }
584
585    let string_data = &buf[consumed..consumed + length];
586
587    let result = if huffman_flag {
588        huffman_decode(string_data)?
589    } else {
590        string_data.to_vec()
591    };
592
593    Some((result, consumed + length))
594}
595
596/// Encode a string literal without Huffman coding.
597///
598/// Returns bytes in literal (non-Huffman) format.
599pub fn encode_string_literal(data: &[u8]) -> Vec<u8> {
600    let mut out = encode_integer(data.len() as u64, 7, 0x00); // H=0
601    out.extend_from_slice(data);
602    out
603}
604
605/// Encode a string literal with Huffman coding if beneficial.
606///
607/// Uses Huffman encoding and sets H=1. Always Huffman-encodes for simplicity.
608pub fn encode_string_huffman(data: &[u8]) -> Vec<u8> {
609    let encoded = huffman_encode(data);
610    let mut out = encode_integer(encoded.len() as u64, 7, 0x80); // H=1
611    out.extend_from_slice(&encoded);
612    out
613}
614
615// ============================================================================
616// HPACK Decoder (RFC 7541)
617// ============================================================================
618
619/// HPACK dynamic table entry size = name length + value length + 32 bytes overhead.
620fn entry_size(name: &str, value: &str) -> usize {
621    name.len() + value.len() + 32
622}
623
624/// HPACK decoder with dynamic table state.
625///
626/// This decoder maintains a dynamic table across multiple calls, allowing
627/// header compression state to be shared within an HTTP/2 connection.
628pub struct HpackDecoder {
629    /// Dynamic table entries (most recently added first).
630    dynamic_table: Vec<(String, String)>,
631    /// Maximum dynamic table size (bytes, per SETTINGS_HEADER_TABLE_SIZE).
632    max_table_size: usize,
633    /// Current dynamic table size (sum of entry sizes).
634    current_size: usize,
635}
636
637impl Default for HpackDecoder {
638    fn default() -> Self {
639        Self::new()
640    }
641}
642
643impl HpackDecoder {
644    /// Create a new HPACK decoder with default max table size of 4096 bytes.
645    pub fn new() -> Self {
646        HpackDecoder {
647            dynamic_table: Vec::new(),
648            max_table_size: 4096,
649            current_size: 0,
650        }
651    }
652
653    /// Create a new HPACK decoder with a specific max table size.
654    pub fn with_max_size(max_table_size: usize) -> Self {
655        HpackDecoder {
656            dynamic_table: Vec::new(),
657            max_table_size,
658            current_size: 0,
659        }
660    }
661
662    /// Update the maximum table size (e.g., from SETTINGS_HEADER_TABLE_SIZE).
663    pub fn set_max_table_size(&mut self, size: usize) {
664        self.max_table_size = size;
665        self.evict_to_fit(0);
666    }
667
668    /// Returns the number of entries currently in the dynamic table.
669    pub fn dynamic_table_len(&self) -> usize {
670        self.dynamic_table.len()
671    }
672
673    /// Returns the current size (in bytes) of the dynamic table.
674    pub fn dynamic_table_current_size(&self) -> usize {
675        self.current_size
676    }
677
678    /// Returns the entries in the dynamic table as (name, value) string slices.
679    /// Entries are ordered most-recently-added first (index 62 = first entry).
680    pub fn dynamic_table_entries(&self) -> &[(String, String)] {
681        &self.dynamic_table
682    }
683
684    /// Decode a block of HPACK-encoded headers.
685    ///
686    /// Returns a list of `(name, value)` pairs in the order they were decoded.
687    pub fn decode(&mut self, buf: &[u8]) -> Result<Vec<(String, String)>, String> {
688        let mut headers = Vec::new();
689        let mut pos = 0;
690
691        while pos < buf.len() {
692            let first = buf[pos];
693
694            if (first & 0x80) != 0 {
695                // Indexed Header Field Representation (RFC 7541 Section 6.1)
696                // Pattern: 1xxxxxxx
697                let (idx, consumed) = decode_integer(&buf[pos..], 7)
698                    .ok_or_else(|| "Failed to decode indexed header index".to_string())?;
699                pos += consumed;
700
701                let (name, value) = self
702                    .table_entry(idx as usize)
703                    .ok_or_else(|| format!("Invalid header table index: {}", idx))?;
704                headers.push((name, value));
705            } else if (first & 0x40) != 0 {
706                // Literal Header Field with Incremental Indexing (RFC 7541 Section 6.2.1)
707                // Pattern: 01xxxxxx
708                let (idx, consumed) = decode_integer(&buf[pos..], 6)
709                    .ok_or_else(|| "Failed to decode literal indexed name index".to_string())?;
710                pos += consumed;
711
712                let name = if idx == 0 {
713                    // Name is a literal string
714                    let (name_bytes, nc) = decode_string(&buf[pos..])
715                        .ok_or_else(|| "Failed to decode literal name string".to_string())?;
716                    pos += nc;
717                    String::from_utf8(name_bytes)
718                        .map_err(|e| format!("Invalid UTF-8 in header name: {}", e))?
719                } else {
720                    let (n, _) = self
721                        .table_entry(idx as usize)
722                        .ok_or_else(|| format!("Invalid header table index: {}", idx))?;
723                    n
724                };
725
726                let (value_bytes, vc) = decode_string(&buf[pos..])
727                    .ok_or_else(|| "Failed to decode literal value string".to_string())?;
728                pos += vc;
729                let value = String::from_utf8(value_bytes)
730                    .map_err(|e| format!("Invalid UTF-8 in header value: {}", e))?;
731
732                self.add_to_dynamic_table(name.clone(), value.clone());
733                headers.push((name, value));
734            } else if (first & 0x20) != 0 {
735                // Dynamic Table Size Update (RFC 7541 Section 6.3)
736                // Pattern: 001xxxxx
737                let (new_size, consumed) = decode_integer(&buf[pos..], 5)
738                    .ok_or_else(|| "Failed to decode table size update".to_string())?;
739                pos += consumed;
740
741                if new_size as usize > self.max_table_size {
742                    return Err(format!(
743                        "Table size update {} exceeds max {}",
744                        new_size, self.max_table_size
745                    ));
746                }
747                self.evict_to_fit(0);
748                // Trim to new size
749                while self.current_size > new_size as usize {
750                    if let Some(entry) = self.dynamic_table.pop() {
751                        self.current_size = self
752                            .current_size
753                            .saturating_sub(entry_size(&entry.0, &entry.1));
754                    } else {
755                        break;
756                    }
757                }
758            } else if (first & 0x10) != 0 {
759                // Literal Header Field Never Indexed (RFC 7541 Section 6.2.3)
760                // Pattern: 0001xxxx
761                let (idx, consumed) = decode_integer(&buf[pos..], 4)
762                    .ok_or_else(|| "Failed to decode never-indexed name index".to_string())?;
763                pos += consumed;
764
765                let name = if idx == 0 {
766                    let (name_bytes, nc) = decode_string(&buf[pos..])
767                        .ok_or_else(|| "Failed to decode never-indexed name string".to_string())?;
768                    pos += nc;
769                    String::from_utf8(name_bytes)
770                        .map_err(|e| format!("Invalid UTF-8 in header name: {}", e))?
771                } else {
772                    let (n, _) = self
773                        .table_entry(idx as usize)
774                        .ok_or_else(|| format!("Invalid header table index: {}", idx))?;
775                    n
776                };
777
778                let (value_bytes, vc) = decode_string(&buf[pos..])
779                    .ok_or_else(|| "Failed to decode never-indexed value string".to_string())?;
780                pos += vc;
781                let value = String::from_utf8(value_bytes)
782                    .map_err(|e| format!("Invalid UTF-8 in header value: {}", e))?;
783
784                // Never indexed — do not add to dynamic table
785                headers.push((name, value));
786            } else {
787                // Literal Header Field Without Indexing (RFC 7541 Section 6.2.2)
788                // Pattern: 0000xxxx
789                let (idx, consumed) = decode_integer(&buf[pos..], 4)
790                    .ok_or_else(|| "Failed to decode without-indexing name index".to_string())?;
791                pos += consumed;
792
793                let name = if idx == 0 {
794                    let (name_bytes, nc) = decode_string(&buf[pos..]).ok_or_else(|| {
795                        "Failed to decode without-indexing name string".to_string()
796                    })?;
797                    pos += nc;
798                    String::from_utf8(name_bytes)
799                        .map_err(|e| format!("Invalid UTF-8 in header name: {}", e))?
800                } else {
801                    let (n, _) = self
802                        .table_entry(idx as usize)
803                        .ok_or_else(|| format!("Invalid header table index: {}", idx))?;
804                    n
805                };
806
807                let (value_bytes, vc) = decode_string(&buf[pos..])
808                    .ok_or_else(|| "Failed to decode without-indexing value string".to_string())?;
809                pos += vc;
810                let value = String::from_utf8(value_bytes)
811                    .map_err(|e| format!("Invalid UTF-8 in header value: {}", e))?;
812
813                // Without indexing — do not add to dynamic table
814                headers.push((name, value));
815            }
816        }
817
818        Ok(headers)
819    }
820
821    /// Look up a dynamic table entry (0-indexed from the front, most recent first).
822    fn dynamic_table_entry(&self, index: usize) -> Option<(&str, &str)> {
823        self.dynamic_table
824            .get(index)
825            .map(|(n, v)| (n.as_str(), v.as_str()))
826    }
827
828    /// Look up a static table entry by 1-based index.
829    fn static_table_entry(index: usize) -> Option<(&'static str, &'static str)> {
830        if index >= 1 && index <= STATIC_TABLE.len() {
831            Some(STATIC_TABLE[index - 1])
832        } else {
833            None
834        }
835    }
836
837    /// Look up a combined (static + dynamic) table entry.
838    ///
839    /// Indices 1..=61 are static table. Indices 62+ are dynamic table.
840    fn table_entry(&self, index: usize) -> Option<(String, String)> {
841        if index == 0 {
842            return None;
843        }
844
845        if index <= STATIC_TABLE.len() {
846            let (n, v) = Self::static_table_entry(index)?;
847            return Some((n.to_string(), v.to_string()));
848        }
849
850        let dynamic_idx = index - STATIC_TABLE.len() - 1;
851        let (n, v) = self.dynamic_table_entry(dynamic_idx)?;
852        Some((n.to_string(), v.to_string()))
853    }
854
855    /// Add a new entry to the dynamic table (prepended, i.e., most recent first).
856    fn add_to_dynamic_table(&mut self, name: String, value: String) {
857        let size = entry_size(&name, &value);
858        self.evict_to_fit(size);
859        if size <= self.max_table_size {
860            self.current_size += size;
861            self.dynamic_table.insert(0, (name, value));
862        }
863    }
864
865    /// Evict entries from the back of the dynamic table until the table can
866    /// accommodate `new_entry_size` more bytes.
867    fn evict_to_fit(&mut self, new_entry_size: usize) {
868        while !self.dynamic_table.is_empty()
869            && self.current_size + new_entry_size > self.max_table_size
870        {
871            if let Some(entry) = self.dynamic_table.pop() {
872                self.current_size = self
873                    .current_size
874                    .saturating_sub(entry_size(&entry.0, &entry.1));
875            }
876        }
877    }
878}
879
880// ============================================================================
881// HPACK Encoder
882// ============================================================================
883
884/// HPACK encoder.
885///
886/// Currently uses literal header fields without indexing for simplicity.
887/// An optimized version would use the static and dynamic tables to produce
888/// smaller output.
889pub struct HpackEncoder {
890    /// Dynamic table entries (most recently added first).
891    #[allow(dead_code)]
892    dynamic_table: Vec<(String, String)>,
893    /// Maximum table size.
894    #[allow(dead_code)]
895    max_table_size: usize,
896}
897
898impl Default for HpackEncoder {
899    fn default() -> Self {
900        Self::new()
901    }
902}
903
904impl HpackEncoder {
905    /// Create a new HPACK encoder with default max table size of 4096 bytes.
906    pub fn new() -> Self {
907        HpackEncoder {
908            dynamic_table: Vec::new(),
909            max_table_size: 4096,
910        }
911    }
912
913    /// Encode a list of header name-value pairs using HPACK.
914    ///
915    /// This implementation uses literal header fields without indexing
916    /// (pattern 0000xxxx with index=0) for simplicity and correctness.
917    /// It checks the static table for name-only matches to save space.
918    pub fn encode(&self, headers: &[(&str, &str)]) -> Vec<u8> {
919        let mut out = Vec::new();
920
921        for &(name, value) in headers {
922            // Check static table for exact match (both name and value)
923            if let Some(static_idx) = self.find_static_exact(name, value) {
924                // Indexed header field representation (Section 6.1)
925                // Pattern: 1xxxxxxx
926                out.extend_from_slice(&encode_integer(static_idx as u64, 7, 0x80));
927                continue;
928            }
929
930            // Check static table for name-only match
931            if let Some(name_idx) = self.find_static_name(name) {
932                // Literal header field with incremental indexing (Section 6.2.1)
933                // Pattern: 01xxxxxx with name index
934                out.extend_from_slice(&encode_integer(name_idx as u64, 6, 0x40));
935                out.extend_from_slice(&encode_string_literal(value.as_bytes()));
936                continue;
937            }
938
939            // Literal header field without indexing (Section 6.2.2)
940            // Pattern: 00000000 followed by name and value strings
941            out.push(0x00);
942            out.extend_from_slice(&encode_string_literal(name.as_bytes()));
943            out.extend_from_slice(&encode_string_literal(value.as_bytes()));
944        }
945
946        out
947    }
948
949    /// Encode headers using Huffman encoding for string values.
950    pub fn encode_huffman(&self, headers: &[(&str, &str)]) -> Vec<u8> {
951        let mut out = Vec::new();
952
953        for &(name, value) in headers {
954            // Check static table for exact match
955            if let Some(static_idx) = self.find_static_exact(name, value) {
956                out.extend_from_slice(&encode_integer(static_idx as u64, 7, 0x80));
957                continue;
958            }
959
960            // Literal without indexing with Huffman-encoded strings
961            out.push(0x00);
962            out.extend_from_slice(&encode_string_huffman(name.as_bytes()));
963            out.extend_from_slice(&encode_string_huffman(value.as_bytes()));
964        }
965
966        out
967    }
968
969    /// Find a static table index for an exact (name, value) match.
970    /// Returns 1-based index or None.
971    fn find_static_exact(&self, name: &str, value: &str) -> Option<usize> {
972        for (i, &(n, v)) in STATIC_TABLE.iter().enumerate() {
973            if n == name && v == value {
974                return Some(i + 1);
975            }
976        }
977        None
978    }
979
980    /// Find a static table index for a name-only match.
981    /// Returns 1-based index of first matching entry or None.
982    fn find_static_name(&self, name: &str) -> Option<usize> {
983        for (i, &(n, _)) in STATIC_TABLE.iter().enumerate() {
984            if n == name {
985                return Some(i + 1);
986            }
987        }
988        None
989    }
990}
991
992// ============================================================================
993// Tests
994// ============================================================================
995
996#[cfg(test)]
997mod tests {
998    use super::*;
999
1000    // -------------------------------------------------------------------------
1001    // Integer encode/decode
1002    // -------------------------------------------------------------------------
1003
1004    #[test]
1005    fn test_decode_integer_small() {
1006        // Value 5 with 5-bit prefix: 0b00000101 = 0x05
1007        let buf = [0x05u8];
1008        let (val, consumed) = decode_integer(&buf, 5).unwrap();
1009        assert_eq!(val, 5);
1010        assert_eq!(consumed, 1);
1011    }
1012
1013    #[test]
1014    fn test_decode_integer_multi_byte() {
1015        // RFC 7541 Section 5.1 example: value 1337 with 5-bit prefix
1016        // First byte: 0b00011111 = 31 (all prefix bits set)
1017        // Continuation: 0b10011010 = 154, 0b00001010 = 10
1018        // 1337 - 31 = 1306 = 0b10100011010
1019        // First continuation: 1306 & 0x7F = 26 (0b0011010), MSB=1 → 0b10011010 = 0x9A... wait
1020        // Let me use spec values: 1337 with 5-bit prefix
1021        // 1337 - 31 = 1306
1022        // 1306 % 128 = 26 | 0x80 = 0x9A, 1306 / 128 = 10 = 0x0A
1023        let buf = [0b00011111u8, 0x9A, 0x0A];
1024        let (val, consumed) = decode_integer(&buf, 5).unwrap();
1025        assert_eq!(val, 1337);
1026        assert_eq!(consumed, 3);
1027    }
1028
1029    #[test]
1030    fn test_encode_integer_small() {
1031        let encoded = encode_integer(5, 5, 0x00);
1032        assert_eq!(encoded, vec![0x05]);
1033    }
1034
1035    #[test]
1036    fn test_encode_integer_large() {
1037        // 1337 with 5-bit prefix
1038        let encoded = encode_integer(1337, 5, 0x00);
1039        assert_eq!(encoded, vec![0b00011111, 0x9A, 0x0A]);
1040    }
1041
1042    #[test]
1043    fn test_encode_decode_integer_roundtrip() {
1044        for val in [0u64, 1, 30, 31, 127, 128, 255, 1000, 65535, 1337] {
1045            for prefix in [4u8, 5, 6, 7] {
1046                let encoded = encode_integer(val, prefix, 0x00);
1047                let (decoded, _) = decode_integer(&encoded, prefix).unwrap();
1048                assert_eq!(
1049                    val, decoded,
1050                    "Roundtrip failed for val={} prefix={}",
1051                    val, prefix
1052                );
1053            }
1054        }
1055    }
1056
1057    // -------------------------------------------------------------------------
1058    // String literals
1059    // -------------------------------------------------------------------------
1060
1061    #[test]
1062    fn test_decode_string_literal() {
1063        // Non-Huffman string: length=5, "hello"
1064        let mut buf = vec![0x05u8]; // H=0, length=5
1065        buf.extend_from_slice(b"hello");
1066        let (s, consumed) = decode_string(&buf).unwrap();
1067        assert_eq!(s, b"hello");
1068        assert_eq!(consumed, 6);
1069    }
1070
1071    #[test]
1072    fn test_huffman_encode_decode_roundtrip() {
1073        let original = b"www.example.com";
1074        let encoded = huffman_encode(original);
1075        let decoded = huffman_decode(&encoded).unwrap();
1076        assert_eq!(decoded, original);
1077    }
1078
1079    #[test]
1080    fn test_huffman_encode_empty() {
1081        let encoded = huffman_encode(b"");
1082        assert!(encoded.is_empty());
1083    }
1084
1085    #[test]
1086    fn test_huffman_decode_rfc7541_example() {
1087        // RFC 7541 Appendix C.4.1 example: "www.example.com" Huffman-encoded
1088        // Known encoding from the RFC: f1e3 c2e5 f23a 6ba0 ab90 f4ff
1089        let encoded = [
1090            0xf1u8, 0xe3, 0xc2, 0xe5, 0xf2, 0x3a, 0x6b, 0xa0, 0xab, 0x90, 0xf4, 0xff,
1091        ];
1092        let decoded = huffman_decode(&encoded).unwrap();
1093        assert_eq!(decoded, b"www.example.com");
1094    }
1095
1096    #[test]
1097    fn test_decode_string_huffman() {
1098        let original = b"no-cache";
1099        let encoded = huffman_encode(original);
1100        // Build HPACK string format: H=1, length, data
1101        let mut buf = encode_integer(encoded.len() as u64, 7, 0x80);
1102        buf.extend_from_slice(&encoded);
1103        let (decoded, _) = decode_string(&buf).unwrap();
1104        assert_eq!(decoded, original);
1105    }
1106
1107    // -------------------------------------------------------------------------
1108    // Static table
1109    // -------------------------------------------------------------------------
1110
1111    #[test]
1112    fn test_static_table_size() {
1113        assert_eq!(STATIC_TABLE.len(), 61);
1114    }
1115
1116    #[test]
1117    fn test_static_table_first_entry() {
1118        assert_eq!(STATIC_TABLE[0], (":authority", ""));
1119    }
1120
1121    #[test]
1122    fn test_static_table_method_get() {
1123        assert_eq!(STATIC_TABLE[1], (":method", "GET"));
1124    }
1125
1126    // -------------------------------------------------------------------------
1127    // HPACK decoder
1128    // -------------------------------------------------------------------------
1129
1130    #[test]
1131    fn test_decoder_indexed_static() {
1132        // Index 2 = ":method: GET"
1133        // Indexed representation: 1xxxxxxx where x=2 → 0x82
1134        let buf = [0x82u8];
1135        let mut decoder = HpackDecoder::new();
1136        let headers = decoder.decode(&buf).unwrap();
1137        assert_eq!(headers.len(), 1);
1138        assert_eq!(headers[0], (":method".to_string(), "GET".to_string()));
1139    }
1140
1141    #[test]
1142    fn test_decoder_literal_with_indexing() {
1143        // Literal with incremental indexing: 01xxxxxx
1144        // name index = 0, name = "custom-key", value = "custom-header"
1145        let mut buf = vec![0x40u8]; // 01 000000 = new name, incremental indexing
1146        buf.extend_from_slice(&encode_string_literal(b"custom-key"));
1147        buf.extend_from_slice(&encode_string_literal(b"custom-header"));
1148
1149        let mut decoder = HpackDecoder::new();
1150        let headers = decoder.decode(&buf).unwrap();
1151        assert_eq!(headers.len(), 1);
1152        assert_eq!(
1153            headers[0],
1154            ("custom-key".to_string(), "custom-header".to_string())
1155        );
1156    }
1157
1158    #[test]
1159    fn test_decoder_literal_without_indexing() {
1160        // Literal without indexing: 0000xxxx with idx=0 (new name)
1161        let mut buf = vec![0x00u8];
1162        buf.extend_from_slice(&encode_string_literal(b"x-custom"));
1163        buf.extend_from_slice(&encode_string_literal(b"value"));
1164
1165        let mut decoder = HpackDecoder::new();
1166        let headers = decoder.decode(&buf).unwrap();
1167        assert_eq!(headers.len(), 1);
1168        assert_eq!(headers[0], ("x-custom".to_string(), "value".to_string()));
1169    }
1170
1171    #[test]
1172    fn test_decoder_dynamic_table_eviction() {
1173        let mut decoder = HpackDecoder::with_max_size(64);
1174        // Add an entry that barely fits
1175        let mut buf = vec![0x40u8];
1176        buf.extend_from_slice(&encode_string_literal(b"x-a"));
1177        buf.extend_from_slice(&encode_string_literal(b"b"));
1178        decoder.decode(&buf).unwrap();
1179        assert_eq!(decoder.dynamic_table.len(), 1);
1180
1181        // Add another entry that should evict the first
1182        let mut buf2 = vec![0x40u8];
1183        buf2.extend_from_slice(&encode_string_literal(b"x-c"));
1184        buf2.extend_from_slice(&encode_string_literal(b"d"));
1185        decoder.decode(&buf2).unwrap();
1186        // First entry should be evicted since total would exceed 64
1187        assert!(decoder.dynamic_table.len() <= 2);
1188    }
1189
1190    // -------------------------------------------------------------------------
1191    // HPACK encoder
1192    // -------------------------------------------------------------------------
1193
1194    #[test]
1195    fn test_encoder_static_exact_match() {
1196        let encoder = HpackEncoder::new();
1197        // ":method: GET" is static index 2 → should encode as 0x82
1198        let encoded = encoder.encode(&[(":method", "GET")]);
1199        assert_eq!(encoded, vec![0x82]);
1200    }
1201
1202    #[test]
1203    fn test_encoder_static_name_match() {
1204        let encoder = HpackEncoder::new();
1205        // ":method" matches static index 2 with value "GET", but "DELETE" won't match exact
1206        let encoded = encoder.encode(&[(":method", "DELETE")]);
1207        // Should use name-indexed literal: 0x40 | idx(2) = 0x42, then value
1208        assert_eq!(encoded[0], 0x42); // 01 000010 = name-indexed literal with static index 2
1209    }
1210
1211    #[test]
1212    fn test_encoder_roundtrip() {
1213        let encoder = HpackEncoder::new();
1214        let mut decoder = HpackDecoder::new();
1215
1216        let headers = vec![
1217            (":method", "GET"),
1218            (":path", "/"),
1219            (":scheme", "https"),
1220            ("user-agent", "test/1.0"),
1221            ("x-custom", "value"),
1222        ];
1223        let encoded = encoder.encode(&headers);
1224        let decoded = decoder.decode(&encoded).unwrap();
1225
1226        assert_eq!(decoded.len(), headers.len());
1227        for (i, (name, value)) in headers.iter().enumerate() {
1228            assert_eq!(decoded[i].0, *name);
1229            assert_eq!(decoded[i].1, *value);
1230        }
1231    }
1232
1233    #[test]
1234    fn test_encoder_huffman_roundtrip() {
1235        let encoder = HpackEncoder::new();
1236        let mut decoder = HpackDecoder::new();
1237
1238        let headers = vec![
1239            ("x-request-id", "abc-123"),
1240            ("content-type", "application/json"),
1241        ];
1242        let encoded = encoder.encode_huffman(&headers);
1243        let decoded = decoder.decode(&encoded).unwrap();
1244
1245        assert_eq!(decoded.len(), headers.len());
1246        for (i, (name, value)) in headers.iter().enumerate() {
1247            assert_eq!(decoded[i].0, *name);
1248            assert_eq!(decoded[i].1, *value);
1249        }
1250    }
1251}