| 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. |
| 4 | module http |
| 5 | |
| 6 | import 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). |
| 10 | const 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. |
| 15 | const 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). |
| 20 | const 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. |
| 25 | const h2_huffman_decode_map = h2_huffman_table.decode_map() or { panic('hpack: ${err}') } |
| 26 | |
| 27 | fn 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. |
| 38 | fn 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. |
| 64 | fn 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 | |