Skip to content

Commit 700a5df

Browse files
committed
Desloppify code
1 parent 8380793 commit 700a5df

3 files changed

Lines changed: 374 additions & 391 deletions

File tree

src/hpack/huffman/decode.rs

Lines changed: 293 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,293 @@
1+
//! Huffman decoding for HPACK (RFC 7541 §5.2).
2+
//!
3+
//! The decoder has a fast path which decodes 12 bits a time. The decoder
4+
//! looks the next 12 bits up in `FAST_TABLE`, which maps directly to either
5+
//! one or two symbols to decode. Since ASCII letters and digits have 5-7 bit
6+
//! codes, one lookup usually decodes two symbols at once.
7+
//!
8+
//! Two situations fall off this fast path:
9+
//!
10+
//! - Codes longer than 12 bits: control characters and bytes >= 0x80
11+
//! - The end of the input, when fewer than 12 bits remain.
12+
//!
13+
//! These are decoded by walking the generated `DECODE_TABLE` one input byte at a time.
14+
15+
use crate::hpack::huffman::table::{DECODE_TABLE, ENCODE_TABLE};
16+
use crate::hpack::DecoderError;
17+
18+
use bytes::BytesMut;
19+
20+
// DECODE_TABLE (in the generated `table.rs`) is a series of 256-entry
21+
// tables, walked one input byte at a time. A leaf entry (BRANCH bit clear)
22+
// holds a decoded symbol and the number of bits its code used. A branch
23+
// entry (BRANCH bit set) holds the index of the table for the next byte.
24+
// No valid code leads back to table 0, so a branch to 0 means the input is
25+
// not a valid code.
26+
const BRANCH: u16 = 0x8000;
27+
const TABLE_INDEX_MASK: u16 = 0x7f00;
28+
const TABLE_WIDTH: usize = 256;
29+
30+
// Maps each possible 12-bit chunk of input to the symbol(s) it starts
31+
// with. Entry layout (u32):
32+
//
33+
// bits 0..8 first decoded byte
34+
// bits 8..16 second decoded byte (if any)
35+
// bits 16..24 number of symbols decoded: 1, 2, or 0, where 0 means the
36+
// code is longer than 12 bits and the slow path must run
37+
// bits 24..32 how many of the 12 bits the decoded symbols used
38+
const FAST_BITS: usize = 12;
39+
const FAST_DECODE_TABLE: [u32; 1 << FAST_BITS] = build_fast_table();
40+
41+
const fn build_fast_table() -> [u32; 1 << FAST_BITS] {
42+
let mut table = [0; 1 << FAST_BITS];
43+
44+
// First fill in every index that starts with one whole code
45+
let mut a = 0;
46+
while a < 256 {
47+
let (len1, code1) = ENCODE_TABLE[a];
48+
if len1 <= FAST_BITS {
49+
let rem = FAST_BITS - len1;
50+
let base = (code1 as usize) << rem;
51+
let single = (1 << 16) | ((len1 as u32) << 24) | a as u32;
52+
let mut i = 0;
53+
while i < (1 << rem) {
54+
table[base + i] = single;
55+
i += 1;
56+
}
57+
}
58+
a += 1;
59+
}
60+
61+
// Overwrite the indices where a second whole code fit right after the first
62+
let mut a = 0;
63+
while a < 256 {
64+
let (len1, code1) = ENCODE_TABLE[a];
65+
// A second code needs at least 5 more bits (the shortest code)
66+
if len1 + 5 <= FAST_BITS {
67+
let rem = FAST_BITS - len1;
68+
let mut b = 0;
69+
while b < 256 {
70+
let (len2, code2) = ENCODE_TABLE[b];
71+
if len2 <= rem {
72+
let rem2 = rem - len2;
73+
let base = ((code1 as usize) << rem) | ((code2 as usize) << rem2);
74+
let pair =
75+
(2 << 16) | (((len1 + len2) as u32) << 24) | ((b as u32) << 8) | a as u32;
76+
let mut i = 0;
77+
while i < (1 << rem2) {
78+
table[base + i] = pair;
79+
i += 1;
80+
}
81+
}
82+
b += 1;
83+
}
84+
}
85+
a += 1;
86+
}
87+
88+
table
89+
}
90+
91+
// Decodes one code of any length by walking DECODE_TABLE one input byte at
92+
// a time, and writes the symbol to `dst[*o]`. Used for codes longer than
93+
// FAST_BITS bits and for the tail of the input.
94+
//
95+
// Inlining avoids a 35% performance regression on ASCII benchmarks.
96+
#[inline(always)]
97+
fn decode_code_slow(
98+
acc: &mut u64,
99+
bits: &mut usize,
100+
dst: &mut [u8],
101+
o: &mut usize,
102+
) -> Result<(), DecoderError> {
103+
let mut table = 0;
104+
loop {
105+
let e = DECODE_TABLE[table * TABLE_WIDTH + (*acc >> 56) as usize];
106+
if e & BRANCH == 0 {
107+
let used = (e >> 8) as usize;
108+
if used > *bits {
109+
return Err(DecoderError::InvalidHuffmanCode);
110+
}
111+
dst[*o] = e as u8;
112+
*o += 1;
113+
*acc <<= used;
114+
*bits -= used;
115+
return Ok(());
116+
}
117+
if *bits < 8 {
118+
return Err(DecoderError::InvalidHuffmanCode);
119+
}
120+
table = ((e & TABLE_INDEX_MASK) >> 8) as usize;
121+
if table == 0 {
122+
return Err(DecoderError::InvalidHuffmanCode);
123+
}
124+
*acc <<= 8;
125+
*bits -= 8;
126+
}
127+
}
128+
129+
// Decodes a Huffman encoded string into the provided buffer.
130+
pub fn decode(src: &[u8], buf: &mut BytesMut) -> Result<BytesMut, DecoderError> {
131+
let len = src.len();
132+
let base_len = buf.len();
133+
134+
// Reserve the worst case output size. Every code is at least 5 bits,
135+
// so `len` input bytes can't decode to more than `len * 8 / 5` output
136+
// bytes. One more byte covers the fast path always writing two bytes
137+
// even when it decoded only one symbol.
138+
buf.resize(base_len + len * 8 / 5 + 1, 0);
139+
let dst = &mut buf[base_len..];
140+
let mut o = 0;
141+
142+
let mut acc: u64 = 0;
143+
let mut bits: usize = 0;
144+
let mut pos: usize = 0;
145+
146+
'outer: loop {
147+
// Fill the bit buffer. While 8 or more input bytes remain, load
148+
// the next 8 in one go and count as many whole bytes as fit under
149+
// the bits already in the buffer (bringing it to 56-63 bits). The
150+
// bytes that didn't fit still land in the low end of `acc`; the
151+
// next refill just ORs the same values over them, so no harm done.
152+
if pos + 8 <= len {
153+
let word = u64::from_be_bytes(src[pos..pos + 8].try_into().unwrap());
154+
acc |= word >> bits;
155+
pos += (63 - bits) >> 3; // bytes now accounted for in `bits`
156+
bits |= 56; // equals bits + 8 * (bytes added)
157+
} else {
158+
while bits <= 56 && pos < len {
159+
acc |= (src[pos] as u64) << (56 - bits);
160+
pos += 1;
161+
bits += 8;
162+
}
163+
}
164+
165+
while bits >= FAST_BITS {
166+
let entry = FAST_DECODE_TABLE[(acc >> (64 - FAST_BITS)) as usize];
167+
let count = (entry >> 16) & 0xff;
168+
if count == 0 {
169+
// The next code is longer than 12 bits. Refill first if it
170+
// might not be in the buffer yet (the longest code is 30
171+
// bits), then decode it by walking DECODE_TABLE.
172+
if bits < 30 && pos < len {
173+
continue 'outer;
174+
}
175+
decode_code_slow(&mut acc, &mut bits, dst, &mut o)?;
176+
continue;
177+
}
178+
let consumed = (entry >> 24) as usize;
179+
dst[o] = entry as u8;
180+
dst[o + 1] = (entry >> 8) as u8;
181+
o += count as usize;
182+
acc <<= consumed;
183+
bits -= consumed;
184+
}
185+
186+
if pos >= len {
187+
break;
188+
}
189+
}
190+
191+
// Tail: the input is exhausted and fewer than 12 bits remain.
192+
while bits > 0 {
193+
// The encoder pads the last byte with up to 7 one-bits, which are
194+
// valid only between symbols. Anything else must decode.
195+
if bits < 8 && (acc >> (64 - bits)) == (1 << bits) - 1 {
196+
break;
197+
}
198+
decode_code_slow(&mut acc, &mut bits, dst, &mut o)?;
199+
}
200+
201+
buf.truncate(base_len + o);
202+
Ok(buf.split())
203+
}
204+
205+
#[cfg(test)]
206+
mod test {
207+
use super::*;
208+
209+
use bytes::BufMut;
210+
use rand::rngs::StdRng;
211+
use rand::{Rng, SeedableRng};
212+
213+
fn decode(src: &[u8]) -> Result<BytesMut, DecoderError> {
214+
let mut buf = BytesMut::new();
215+
super::decode(src, &mut buf)
216+
}
217+
218+
// The simplest decoder we can write, used to double-check the real
219+
// one: turn the input into a literal string of '0'/'1' characters and
220+
// repeatedly strip off the first code that matches. No code is a
221+
// prefix of another, so at most one can match. EOS (index 256) is left
222+
// out on purpose: encoded EOS is an error per RFC 7541 §5.2.
223+
fn reference_decode(src: &[u8]) -> Result<BytesMut, DecoderError> {
224+
// Each symbol's code as a string of '0' and '1', e.g. "11111000".
225+
let codes: Vec<String> = ENCODE_TABLE[..256]
226+
.iter()
227+
.map(|&(len, code)| format!("{code:0len$b}"))
228+
.collect();
229+
let bits: String = src.iter().map(|byte| format!("{byte:08b}")).collect();
230+
231+
let mut decoded = BytesMut::new();
232+
let mut remaining_bits: &str = &bits;
233+
while let Some(sym) = (0..256).find(|&sym| remaining_bits.starts_with(&codes[sym])) {
234+
decoded.put_u8(sym as u8);
235+
remaining_bits = &remaining_bits[codes[sym].len()..];
236+
}
237+
// We expect anything that cannot be matched to be padding. Padding is
238+
// fewer than 8 bits, and all ones.
239+
if remaining_bits.len() >= 8 || remaining_bits.chars().any(|bit| bit == '0') {
240+
return Err(DecoderError::InvalidHuffmanCode);
241+
}
242+
Ok(decoded)
243+
}
244+
245+
#[test]
246+
fn decode_single_byte() {
247+
assert_eq!("o", decode(&[0b00111111]).unwrap());
248+
assert_eq!("0", decode(&[7]).unwrap());
249+
assert_eq!("A", decode(&[(0x21 << 2) + 3]).unwrap());
250+
}
251+
252+
#[test]
253+
fn single_char_multi_byte() {
254+
assert_eq!("#", decode(&[255, 160 + 15]).unwrap());
255+
assert_eq!("$", decode(&[255, 200 + 7]).unwrap());
256+
assert_eq!("\x0a", decode(&[255, 255, 255, 240 + 3]).unwrap());
257+
}
258+
259+
#[test]
260+
fn multi_char() {
261+
assert_eq!("!0", decode(&[254, 1]).unwrap());
262+
assert_eq!(" !", decode(&[0b01010011, 0b11111000]).unwrap());
263+
}
264+
265+
// Random (mostly invalid) byte strings must produce identical results to reference impl
266+
#[test]
267+
fn matches_reference_on_arbitrary_bytes() {
268+
let mut rng = StdRng::seed_from_u64(0xdeadbeefcafe);
269+
270+
for _ in 0..10_000 {
271+
let len = rng.gen_range(0..40);
272+
let src: Vec<u8> = (0..len).map(|_| rng.gen()).collect();
273+
assert_eq!(reference_decode(&src), decode(&src), "src={:?}", src);
274+
}
275+
276+
// Bias toward high bytes to exercise long codes and EOS prefixes.
277+
for _ in 0..10_000 {
278+
let len = rng.gen_range(0..40);
279+
let src: Vec<u8> = (0..len).map(|_| rng.gen::<u8>() | 0xe0).collect();
280+
assert_eq!(reference_decode(&src), decode(&src), "src={:?}", src);
281+
}
282+
}
283+
284+
#[test]
285+
fn rejects_eos_and_invalid_padding() {
286+
assert_eq!(decode(&[0xff]), Err(DecoderError::InvalidHuffmanCode));
287+
assert_eq!(
288+
decode(&[0xff, 0xff, 0xff, 0xff]),
289+
Err(DecoderError::InvalidHuffmanCode)
290+
);
291+
assert_eq!(decode(&[0]), Err(DecoderError::InvalidHuffmanCode));
292+
}
293+
}

src/hpack/huffman/encode.rs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
use crate::hpack::huffman::table::ENCODE_TABLE;
2+
3+
use bytes::{BufMut, BytesMut};
4+
5+
pub fn encode(src: &[u8], dst: &mut BytesMut) {
6+
let mut bits: u64 = 0;
7+
let mut bits_left = 40;
8+
9+
for &b in src {
10+
let (nbits, code) = ENCODE_TABLE[b as usize];
11+
12+
bits |= code << (bits_left - nbits);
13+
bits_left -= nbits;
14+
15+
while bits_left <= 32 {
16+
dst.put_u8((bits >> 32) as u8);
17+
18+
bits <<= 8;
19+
bits_left += 8;
20+
}
21+
}
22+
23+
if bits_left != 40 {
24+
// This writes the EOS token
25+
bits |= (1 << bits_left) - 1;
26+
dst.put_u8((bits >> 32) as u8);
27+
}
28+
}
29+
30+
#[cfg(test)]
31+
mod test {
32+
use super::*;
33+
34+
#[test]
35+
fn encode_single_byte() {
36+
let mut dst = BytesMut::with_capacity(1);
37+
38+
encode(b"o", &mut dst);
39+
assert_eq!(&dst[..], &[0b00111111]);
40+
41+
dst.clear();
42+
encode(b"0", &mut dst);
43+
assert_eq!(&dst[..], &[7]);
44+
45+
dst.clear();
46+
encode(b"A", &mut dst);
47+
assert_eq!(&dst[..], &[(0x21 << 2) + 3]);
48+
}
49+
}

0 commit comments

Comments
 (0)