v / vlib / net / http / h2_hpack_huffman.v
95 lines · 87 sloc · 3.11 KB · 95861b8bdeeddc71d79c3f09f56a66bf01106ecd
Raw
1// Copyright (c) 2019-2024 Alexander Medvednikov. All rights reserved.
2// Use of this source code is governed by an MIT license
3// that can be found in the LICENSE file.
4module http
5
6import hash.huffman
7
8// h2_huffman_eos is the index of the EOS (end-of-string) symbol in the
9// HPACK Huffman table (RFC 7541 Appendix B).
10const h2_huffman_eos = 256
11
12// h2_max_code_bits is the longest HPACK Huffman code length (RFC 7541
13// Appendix B). 30 bits is too wide for a flat decode table, so decoding goes
14// bit-at-a-time against h2_huffman_decode_map.
15const h2_max_code_bits = 30
16
17// h2_huffman_table holds the canonical HPACK Huffman codes, rebuilt at startup
18// from the per-symbol bit lengths via the shared hash.huffman builder. The
19// codes are MSB-first (the order they appear on the wire).
20const h2_huffman_table = build_h2_huffman_table()
21
22// h2_huffman_decode_map maps a (bit_length, code) pair, packed as
23// `(bit_length << 32) | code`, to its symbol. It is built once at startup
24// from the canonical code table and is read-only afterwards.
25const h2_huffman_decode_map = h2_huffman_table.decode_map() or { panic('hpack: ${err}') }
26
27fn build_h2_huffman_table() huffman.Table {
28 return huffman.build(
29 lengths: h2_huffman_code_lens[..].map(int(it))
30 max_bits: h2_max_code_bits
31 bit_order: .msb_first
32 ) or { panic('hpack: ${err}') }
33}
34
35// h2_huffman_encode returns the HPACK Huffman encoding of `input`
36// (RFC 7541 Section 5.2). The final byte is padded with the most significant
37// bits of the EOS code, i.e. all ones.
38fn h2_huffman_encode(input []u8) []u8 {
39 mut out := []u8{cap: input.len}
40 mut acc := u64(0)
41 mut nbits := 0
42 for b in input {
43 code := u64(h2_huffman_table.codes[b])
44 clen := h2_huffman_table.lengths[b]
45 acc = (acc << clen) | code
46 nbits += clen
47 for nbits >= 8 {
48 nbits -= 8
49 out << u8((acc >> nbits) & 0xff)
50 }
51 // Keep only the still-buffered low bits, so acc stays bounded.
52 acc = if nbits == 0 { u64(0) } else { acc & ((u64(1) << nbits) - 1) }
53 }
54 if nbits > 0 {
55 pad := 8 - nbits
56 out << u8(((acc << pad) | ((u64(1) << pad) - 1)) & 0xff)
57 }
58 return out
59}
60
61// h2_huffman_decode reverses h2_huffman_encode. It returns an error for the
62// invalid cases called out by RFC 7541 Section 5.2: an explicit EOS symbol,
63// padding longer than 7 bits, or padding that is not all ones.
64fn h2_huffman_decode(input []u8) ![]u8 {
65 mut out := []u8{cap: input.len + input.len / 2}
66 mut cur := u64(0)
67 mut cur_len := 0
68 for b in input {
69 for bit := 7; bit >= 0; bit-- {
70 cur = (cur << 1) | u64((b >> u8(bit)) & 1)
71 cur_len++
72 if cur_len > h2_max_code_bits {
73 return error('hpack: invalid huffman code (no symbol within ${h2_max_code_bits} bits)')
74 }
75 if sym := h2_huffman_decode_map[(u64(cur_len) << 32) | cur] {
76 if sym == h2_huffman_eos {
77 return error('hpack: EOS symbol encountered in huffman string')
78 }
79 out << u8(sym)
80 cur = 0
81 cur_len = 0
82 }
83 }
84 }
85 if cur_len >= 8 {
86 return error('hpack: invalid huffman padding (more than 7 bits left)')
87 }
88 if cur_len > 0 {
89 mask := (u64(1) << cur_len) - 1
90 if cur & mask != mask {
91 return error('hpack: invalid huffman padding (not all ones)')
92 }
93 }
94 return out
95}
96