1use std::sync::OnceLock;
22
23pub const HUFFMAN_TABLE: [(u32, u8); 257] = [
36 (0x1ff8, 13), (0x7fffd8, 23), (0xfffffe2, 28), (0xfffffe3, 28),
37 (0xfffffe4, 28), (0xfffffe5, 28), (0xfffffe6, 28), (0xfffffe7, 28),
38 (0xfffffe8, 28), (0xffffea, 24), (0x3ffffffc, 30), (0xfffffe9, 28),
39 (0xfffffea, 28), (0x3ffffffd, 30), (0xfffffeb, 28), (0xfffffec, 28),
40 (0xfffffed, 28), (0xfffffee, 28), (0xfffffef, 28), (0xffffff0, 28),
41 (0xffffff1, 28), (0xffffff2, 28), (0x3ffffffe, 30), (0xffffff3, 28),
42 (0xffffff4, 28), (0xffffff5, 28), (0xffffff6, 28), (0xffffff7, 28),
43 (0xffffff8, 28), (0xffffff9, 28), (0xffffffa, 28), (0xffffffb, 28),
44 (0x14, 6), (0x3f8, 10), (0x3f9, 10), (0xffa, 12),
45 (0x1ff9, 13), (0x15, 6), (0xf8, 8), (0x7fa, 11),
46 (0x3fa, 10), (0x3fb, 10), (0xf9, 8), (0x7fb, 11),
47 (0xfa, 8), (0x16, 6), (0x17, 6), (0x18, 6),
48 (0x0, 5), (0x1, 5), (0x2, 5), (0x19, 6),
49 (0x1a, 6), (0x1b, 6), (0x1c, 6), (0x1d, 6),
50 (0x1e, 6), (0x1f, 6), (0x5c, 7), (0xfb, 8),
51 (0x7ffc, 15), (0x20, 6), (0xffb, 12), (0x3fc, 10),
52 (0x1ffa, 13), (0x21, 6), (0x5d, 7), (0x5e, 7),
53 (0x5f, 7), (0x60, 7), (0x61, 7), (0x62, 7),
54 (0x63, 7), (0x64, 7), (0x65, 7), (0x66, 7),
55 (0x67, 7), (0x68, 7), (0x69, 7), (0x6a, 7),
56 (0x6b, 7), (0x6c, 7), (0x6d, 7), (0x6e, 7),
57 (0x6f, 7), (0x70, 7), (0x71, 7), (0x72, 7),
58 (0xfc, 8), (0x73, 7), (0xfd, 8), (0x1ffb, 13),
59 (0x7fff0, 19), (0x1ffc, 13), (0x3ffc, 14), (0x22, 6),
60 (0x7ffd, 15), (0x3, 5), (0x23, 6), (0x4, 5),
61 (0x24, 6), (0x5, 5), (0x25, 6), (0x26, 6),
62 (0x27, 6), (0x6, 5), (0x74, 7), (0x75, 7),
63 (0x28, 6), (0x29, 6), (0x2a, 6), (0x7, 5),
64 (0x2b, 6), (0x76, 7), (0x2c, 6), (0x8, 5),
65 (0x9, 5), (0x2d, 6), (0x77, 7), (0x78, 7),
66 (0x79, 7), (0x7a, 7), (0x7b, 7), (0x7ffe, 15),
67 (0x7fc, 11), (0x3ffd, 14), (0x1ffd, 13), (0xffffffc, 28),
68 (0xfffe6, 20), (0x3fffd2, 22), (0xfffe7, 20), (0xfffe8, 20),
69 (0x3fffd3, 22), (0x3fffd4, 22), (0x3fffd5, 22), (0x7fffd9, 23),
70 (0x3fffd6, 22), (0x7fffda, 23), (0x7fffdb, 23), (0x7fffdc, 23),
71 (0x7fffdd, 23), (0x7fffde, 23), (0xffffeb, 24), (0x7fffdf, 23),
72 (0xffffec, 24), (0xffffed, 24), (0x3fffd7, 22), (0x7fffe0, 23),
73 (0xffffee, 24), (0x7fffe1, 23), (0x7fffe2, 23), (0x7fffe3, 23),
74 (0x7fffe4, 23), (0x1fffdc, 21), (0x3fffd8, 22), (0x7fffe5, 23),
75 (0x3fffd9, 22), (0x7fffe6, 23), (0x7fffe7, 23), (0xffffef, 24),
76 (0x3fffda, 22), (0x1fffdd, 21), (0xfffe9, 20), (0x3fffdb, 22),
77 (0x3fffdc, 22), (0x7fffe8, 23), (0x7fffe9, 23), (0x1fffde, 21),
78 (0x7fffea, 23), (0x3fffdd, 22), (0x3fffde, 22), (0xfffff0, 24),
79 (0x1fffdf, 21), (0x3fffdf, 22), (0x7fffeb, 23), (0x7fffec, 23),
80 (0x1fffe0, 21), (0x1fffe1, 21), (0x3fffe0, 22), (0x1fffe2, 21),
81 (0x7fffed, 23), (0x3fffe1, 22), (0x7fffee, 23), (0x7fffef, 23),
82 (0xfffea, 20), (0x3fffe2, 22), (0x3fffe3, 22), (0x3fffe4, 22),
83 (0x7ffff0, 23), (0x3fffe5, 22), (0x3fffe6, 22), (0x7ffff1, 23),
84 (0x3ffffe0, 26), (0x3ffffe1, 26), (0xfffeb, 20), (0x7fff1, 19),
85 (0x3fffe7, 22), (0x7ffff2, 23), (0x3fffe8, 22), (0x1ffffec, 25),
86 (0x3ffffe2, 26), (0x3ffffe3, 26), (0x3ffffe4, 26), (0x7ffffde, 27),
87 (0x7ffffdf, 27), (0x3ffffe5, 26), (0xfffff1, 24), (0x1ffffed, 25),
88 (0x7fff2, 19), (0x1fffe3, 21), (0x3ffffe6, 26), (0x7ffffe0, 27),
89 (0x7ffffe1, 27), (0x3ffffe7, 26), (0x7ffffe2, 27), (0xfffff2, 24),
90 (0x1fffe4, 21), (0x1fffe5, 21), (0x3ffffe8, 26), (0x3ffffe9, 26),
91 (0xffffffd, 28), (0x7ffffe3, 27), (0x7ffffe4, 27), (0x7ffffe5, 27),
92 (0xfffec, 20), (0xfffff3, 24), (0xfffed, 20), (0x1fffe6, 21),
93 (0x3fffe9, 22), (0x1fffe7, 21), (0x1fffe8, 21), (0x7ffff3, 23),
94 (0x3fffea, 22), (0x3fffeb, 22), (0x1ffffee, 25), (0x1ffffef, 25),
95 (0xfffff4, 24), (0xfffff5, 24), (0x3ffffea, 26), (0x7ffff4, 23),
96 (0x3ffffeb, 26), (0x7ffffe6, 27), (0x3ffffec, 26), (0x3ffffed, 26),
97 (0x7ffffe7, 27), (0x7ffffe8, 27), (0x7ffffe9, 27), (0x7ffffea, 27),
98 (0x7ffffeb, 27), (0xffffffe, 28), (0x7ffffec, 27), (0x7ffffed, 27),
99 (0x7ffffee, 27), (0x7ffffef, 27), (0x7fffff0, 27), (0x3ffffee, 26),
100 (0x3fffffff, 30), ];
102
103const MAX_HUFFMAN_DECODE_OUTPUT: usize = 1_048_576;
110
111const MAX_CODE_LEN: u32 = 30;
113
114const MIN_CODE_LEN: u32 = 5;
116
117type GroupedTable = Vec<Vec<(u32, u16)>>;
128
129#[inline]
131fn grouped_table() -> &'static GroupedTable {
132 static TABLE: OnceLock<GroupedTable> = OnceLock::new();
133 TABLE.get_or_init(|| {
134 let mut grouped: GroupedTable = vec![Vec::new(); (MAX_CODE_LEN + 1) as usize];
135 for (symbol, &(code, len)) in HUFFMAN_TABLE.iter().enumerate() {
136 let len_idx = len as usize;
137 if len_idx < grouped.len() {
138 grouped[len_idx].push((code, symbol as u16));
139 }
140 }
141 for group in grouped.iter_mut() {
143 group.sort_unstable_by_key(|&(code, _)| code);
144 }
145 grouped
146 })
147}
148
149#[derive(Clone, Copy, Debug)]
159struct PrefixEntry {
160 symbol: u8,
161 bits_consumed: u8,
162}
163
164#[inline]
169fn prefix_table() -> &'static [PrefixEntry; 256] {
170 static TABLE: OnceLock<[PrefixEntry; 256]> = OnceLock::new();
171 TABLE.get_or_init(|| {
172 let mut table = [PrefixEntry {
173 symbol: 0,
174 bits_consumed: 0,
175 }; 256];
176
177 for (symbol, &(code, len)) in HUFFMAN_TABLE.iter().enumerate() {
178 if symbol == 256 || len > 8 {
180 continue;
181 }
182
183 let len_usize = len as usize;
184 let shift = 8 - len_usize;
185 let prefix = (code as usize) << shift;
187 let count = 1usize << shift;
188
189 for i in 0..count {
190 let idx = prefix + i;
191 table[idx] = PrefixEntry {
192 symbol: symbol as u8,
193 bits_consumed: len,
194 };
195 }
196 }
197
198 table
199 })
200}
201
202#[derive(Debug, Clone)]
208pub struct HuffmanEncoder;
209
210impl HuffmanEncoder {
211 #[inline]
215 pub fn encode(input: &[u8]) -> Vec<u8> {
216 if input.is_empty() {
217 return Vec::new();
218 }
219
220 let max_out = input.len().saturating_mul(4);
222 let mut bits: Vec<u8> = Vec::with_capacity(max_out);
223 let mut acc: u64 = 0;
224 let mut nbits: u32 = 0;
225
226 for &byte in input {
227 let (code, len) = HUFFMAN_TABLE[byte as usize];
228 acc = (acc << len as u32) | code as u64;
229 nbits = nbits.saturating_add(len as u32);
230
231 while nbits >= 8 {
233 nbits -= 8;
234 bits.push((acc >> nbits) as u8);
235 }
236 }
237
238 if nbits > 0 {
240 let pad = 8u32.saturating_sub(nbits);
241 acc = (acc << pad) | (1u64 << pad).saturating_sub(1);
242 bits.push(acc as u8);
243 }
244
245 bits
246 }
247
248 #[inline]
250 pub fn encoded_len(input: &[u8]) -> usize {
251 if input.is_empty() {
252 return 0;
253 }
254 let mut total_bits: u64 = 0;
255 for &byte in input {
256 let (_, len) = HUFFMAN_TABLE[byte as usize];
257 total_bits = total_bits.saturating_add(len as u64);
258 }
259 total_bits.div_ceil(8) as usize
261 }
262
263 pub fn encode_into(input: &[u8], out: &mut Vec<u8>) -> usize {
270 let start = out.len();
271 if input.is_empty() {
272 return 0;
273 }
274
275 let mut acc: u64 = 0;
276 let mut nbits: u32 = 0;
277
278 for &byte in input {
279 let (code, len) = HUFFMAN_TABLE[byte as usize];
280 acc = (acc << len as u32) | code as u64;
281 nbits = nbits.saturating_add(len as u32);
282
283 while nbits >= 8 {
285 nbits -= 8;
286 out.push((acc >> nbits) as u8);
287 }
288 }
289
290 if nbits > 0 {
291 let pad = 8u32.saturating_sub(nbits);
292 acc = (acc << pad) | (1u64 << pad).saturating_sub(1);
293 out.push(acc as u8);
294 }
295
296 out.len() - start
297 }
298}
299
300#[derive(Debug, Clone, PartialEq, Eq)]
306pub enum HuffmanDecodeError {
307 Truncated,
309 EosSymbol,
311 InvalidCode,
313 OutputTooLarge,
315 InvalidPadding,
317}
318
319impl std::fmt::Display for HuffmanDecodeError {
320 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
321 match self {
322 Self::Truncated => write!(f, "Huffman input truncated"),
323 Self::EosSymbol => write!(f, "Huffman EOS symbol encountered"),
324 Self::InvalidCode => write!(f, "Huffman invalid code"),
325 Self::OutputTooLarge => write!(f, "Huffman output exceeds maximum length"),
326 Self::InvalidPadding => write!(f, "Huffman padding contains 0 bits"),
327 }
328 }
329}
330
331impl std::error::Error for HuffmanDecodeError {}
332
333#[derive(Debug, Clone)]
343pub struct HuffmanDecoder;
344
345impl HuffmanDecoder {
346 pub fn decode(input: &[u8]) -> Result<Vec<u8>, HuffmanDecodeError> {
355 if input.is_empty() {
356 return Ok(Vec::new());
357 }
358
359 let mut output: Vec<u8> = Vec::with_capacity(input.len());
360 Self::decode_into(input, &mut output)?;
361 Ok(output)
362 }
363
364 pub fn decode_into(input: &[u8], output: &mut Vec<u8>) -> Result<(), HuffmanDecodeError> {
369 if input.is_empty() {
370 return Ok(());
371 }
372
373 let mut bit_pos: usize = 0;
374 let total_bits = input.len().checked_mul(8).ok_or(HuffmanDecodeError::Truncated)?;
375
376 let prefix_tbl = prefix_table();
378
379 while bit_pos < total_bits {
380 let remaining = total_bits - bit_pos;
381
382 let start_len = if remaining >= 8 {
386 let bits8 = Self::read_bits(input, bit_pos, 8)? as usize;
387 let entry = prefix_tbl[bits8];
388 if entry.bits_consumed > 0 {
389 output.push(entry.symbol);
391
392 if output.len() > MAX_HUFFMAN_DECODE_OUTPUT {
394 return Err(HuffmanDecodeError::OutputTooLarge);
395 }
396
397 bit_pos += entry.bits_consumed as usize;
398 continue;
399 }
400 9
402 } else {
403 MIN_CODE_LEN
405 };
406
407 let max_len = if remaining < MAX_CODE_LEN as usize {
409 remaining as u32
410 } else {
411 MAX_CODE_LEN
412 };
413
414 let mut matched = false;
415
416 if max_len >= start_len {
417 let table = grouped_table();
418
419 for len in start_len..=max_len {
420 let bits = Self::read_bits(input, bit_pos, len as u8)?;
421
422 let group = &table[len as usize];
424 if let Ok(idx) = group.binary_search_by_key(&bits, |&(c, _)| c) {
425 let (_, symbol) = group[idx];
426 if symbol == 256 {
428 return Err(HuffmanDecodeError::EosSymbol);
429 }
430 output.push(symbol as u8);
431
432 if output.len() > MAX_HUFFMAN_DECODE_OUTPUT {
434 return Err(HuffmanDecodeError::OutputTooLarge);
435 }
436
437 bit_pos = bit_pos.checked_add(len as usize)
438 .ok_or(HuffmanDecodeError::Truncated)?;
439 matched = true;
440 break;
441 }
442 }
443 }
444
445 if !matched {
446 if remaining <= 7 {
448 let pad_val = Self::read_bits(input, bit_pos, remaining as u8)?;
449 let expected = (1u32 << remaining).saturating_sub(1);
450 if pad_val == expected {
451 break;
453 } else {
454 return Err(HuffmanDecodeError::InvalidPadding);
455 }
456 } else {
457 return Err(HuffmanDecodeError::InvalidCode);
459 }
460 }
461 }
462
463 Ok(())
464 }
465
466 #[inline]
471 fn read_bits(input: &[u8], bit_pos: usize, len: u8) -> Result<u32, HuffmanDecodeError> {
472 if len == 0 {
473 return Ok(0);
474 }
475 if len > 30 {
476 return Err(HuffmanDecodeError::InvalidCode);
477 }
478
479 let len_usize = len as usize;
480 let total_bits = input.len().checked_mul(8).ok_or(HuffmanDecodeError::Truncated)?;
481 let end_pos = bit_pos.checked_add(len_usize).ok_or(HuffmanDecodeError::Truncated)?;
482 if end_pos > total_bits {
483 return Err(HuffmanDecodeError::Truncated);
484 }
485
486 let byte_idx = bit_pos / 8;
487 let bit_offset = bit_pos % 8;
488
489 let avail = input.len() - byte_idx;
491 let to_read = if avail >= 8 { 8 } else { avail };
492
493 let mut buf = [0u8; 8];
494 buf[..to_read].copy_from_slice(&input[byte_idx..byte_idx + to_read]);
495 let val = u64::from_be_bytes(buf);
496
497 let shift = 64u32
502 .checked_sub(bit_offset as u32)
503 .and_then(|s| s.checked_sub(len as u32))
504 .ok_or(HuffmanDecodeError::Truncated)?;
505
506 let result = if shift >= 64 {
507 0u64
508 } else {
509 val >> shift
510 };
511
512 let mask = if len == 30 {
514 0x3FFFFFFFu64
515 } else {
516 (1u64 << len_usize).saturating_sub(1)
517 };
518
519 Ok((result & mask) as u32)
520 }
521}
522
523#[cfg(test)]
528mod tests {
529 use super::*;
530
531 #[test]
532 fn test_encode_empty() {
533 assert!(HuffmanEncoder::encode(b"").is_empty());
534 }
535
536 #[test]
537 fn test_decode_empty() {
538 assert!(HuffmanDecoder::decode(b"").unwrap().is_empty());
539 }
540
541 #[test]
542 fn test_encode_decode_simple_ascii() {
543 let input = b"hello world";
544 let encoded = HuffmanEncoder::encode(input);
545 let decoded = HuffmanDecoder::decode(&encoded).unwrap();
546 assert_eq!(decoded, input);
547 }
548
549 #[test]
550 fn test_encode_decode_url() {
551 let input = b"https://example.com/path?query=1";
552 let encoded = HuffmanEncoder::encode(input);
553 let decoded = HuffmanDecoder::decode(&encoded).unwrap();
554 assert_eq!(decoded, input);
555 }
556
557 #[test]
558 fn test_encode_decode_http_header() {
559 let input = b"application/json; charset=utf-8";
560 let encoded = HuffmanEncoder::encode(input);
561 let decoded = HuffmanDecoder::decode(&encoded).unwrap();
562 assert_eq!(decoded, input);
563 }
564
565 #[test]
566 fn test_encode_decode_all_bytes() {
567 let input: Vec<u8> = (0..=255u8).collect();
569 let encoded = HuffmanEncoder::encode(&input);
570 let decoded = HuffmanDecoder::decode(&encoded).unwrap();
571 assert_eq!(decoded, input);
572 }
573
574 #[test]
575 fn test_encode_compression_ratio() {
576 let input = b"application/javascript";
578 let encoded = HuffmanEncoder::encode(input);
579 assert!(encoded.len() < input.len(), "Huffman should compress '{}': {} -> {}",
580 String::from_utf8_lossy(input), input.len(), encoded.len());
581 }
582
583 #[test]
584 fn test_encoded_len_accuracy() {
585 let input = b"content-type: text/html";
586 let predicted = HuffmanEncoder::encoded_len(input);
587 let actual = HuffmanEncoder::encode(input).len();
588 assert_eq!(predicted, actual);
589 }
590
591 #[test]
592 fn test_encode_into_matches_encode() {
593 let inputs: &[&[u8]] = &[
595 b"",
596 b"a",
597 b"hello world",
598 b"content-type: text/html",
599 b"Accept-Encoding: gzip, deflate, br",
600 ];
601 for input in inputs {
602 let expected = HuffmanEncoder::encode(input);
603 let mut buf = Vec::with_capacity(64);
604 let written = HuffmanEncoder::encode_into(input, &mut buf);
605 assert_eq!(buf, expected, "encode_into mismatch for {:?}", String::from_utf8_lossy(input));
606 assert_eq!(written, expected.len());
607 }
608 let all: Vec<u8> = (0..=255u8).collect();
610 let expected = HuffmanEncoder::encode(&all);
611 let mut buf = Vec::with_capacity(expected.len());
612 let written = HuffmanEncoder::encode_into(&all, &mut buf);
613 assert_eq!(buf, expected);
614 assert_eq!(written, expected.len());
615 }
616
617 #[test]
618 fn test_decode_into_matches_decode() {
619 for b in 0u8..=255u8 {
621 let input = [b];
622 let encoded = HuffmanEncoder::encode(&input);
623 let expected = HuffmanDecoder::decode(&encoded).unwrap();
624 let mut buf = Vec::new();
625 HuffmanDecoder::decode_into(&encoded, &mut buf).unwrap();
626 assert_eq!(buf, expected, "decode_into mismatch for byte {b}");
627 }
628 let mut buf = Vec::new();
630 HuffmanDecoder::decode_into(b"", &mut buf).unwrap();
631 assert!(buf.is_empty());
632 }
633
634 #[test]
635 fn test_decode_invalid_data() {
636 let result = HuffmanDecoder::decode(&[0xFF, 0xFF, 0xFF, 0xFF]);
637 let _ = result;
638 }
639
640 #[test]
641 fn test_decode_eos_rejected() {
642 let eos_bytes = [0xFF, 0xFF, 0xFF, 0xFF];
643 let result = HuffmanDecoder::decode(&eos_bytes);
644 let _ = result;
645 }
646
647 #[test]
648 fn test_decode_truncated() {
649 assert!(HuffmanDecoder::decode(b"").unwrap().is_empty());
650 }
651
652 #[test]
653 fn test_roundtrip_random_data() {
654 let inputs: &[&[u8]] = &[
655 b"a",
656 b"ab",
657 b"abc",
658 b"GET / HTTP/1.1",
659 b"Host: example.com",
660 b"Accept-Encoding: gzip, deflate, br",
661 b"User-Agent: Mozilla/5.0 (X11; Linux x86_64)",
662 b"Content-Type: application/json; charset=utf-8",
663 b"set-cookie: session=abc123; Path=/; HttpOnly; Secure",
664 ];
665 for input in inputs {
666 let encoded = HuffmanEncoder::encode(input);
667 let decoded = HuffmanDecoder::decode(&encoded).unwrap();
668 assert_eq!(decoded, *input, "roundtrip failed for: {}", String::from_utf8_lossy(input));
669 }
670 }
671
672 #[test]
673 fn test_read_bits_msb_first() {
674 assert_eq!(HuffmanDecoder::read_bits(&[0xA0], 0, 1).unwrap(), 0b1);
675 assert_eq!(HuffmanDecoder::read_bits(&[0xA0], 1, 1).unwrap(), 0b0);
676 assert_eq!(HuffmanDecoder::read_bits(&[0xA0], 2, 1).unwrap(), 0b1);
677 assert_eq!(HuffmanDecoder::read_bits(&[0xA0], 0, 3).unwrap(), 0b101);
678 assert_eq!(HuffmanDecoder::read_bits(&[0xA0, 0xFF], 0, 8).unwrap(), 0xA0);
679 assert_eq!(HuffmanDecoder::read_bits(&[0xA0, 0xFF], 4, 8).unwrap(), 0x0F);
680 assert_eq!(HuffmanDecoder::read_bits(&[0xA0, 0xFF], 4, 12).unwrap(), 0x0FF);
681 }
682
683 #[test]
684 fn test_padding_validation() {
685 let result = HuffmanDecoder::decode(&[0x00]);
686 assert!(matches!(result, Err(HuffmanDecodeError::InvalidPadding)));
687 }
688
689 #[test]
690 fn test_single_byte_roundtrip() {
691 for b in 0u8..=255u8 {
692 let input = [b];
693 let encoded = HuffmanEncoder::encode(&input);
694 let decoded = HuffmanDecoder::decode(&encoded).unwrap_or_default();
695 assert_eq!(decoded, input, "roundtrip failed for byte {}", b);
696 }
697 }
698
699 #[test]
700 fn test_huffman_table_completeness() {
701 assert_eq!(HUFFMAN_TABLE.len(), 257);
702 for &(_, len) in HUFFMAN_TABLE.iter() {
703 assert!((5..=30).contains(&len), "invalid Huffman code length: {}", len);
704 }
705 }
706
707 #[test]
708 fn test_grouped_table_initialization() {
709 let table = grouped_table();
710 assert_eq!(table.len(), 31);
711
712 assert!(!table[5].is_empty());
713 for group in table.iter() {
714 for w in group.windows(2) {
715 assert!(w[0].0 <= w[1].0, "group not sorted");
716 }
717 }
718 }
719
720 #[test]
721 fn test_prefix_table_construction() {
722 let tbl = prefix_table();
723
724 for (i, entry) in tbl.iter().enumerate().take(0x1F + 1).skip(0x18) {
725 assert_eq!(entry.symbol, b'a', "prefix table entry 0x{:02X} should be 'a'", i);
726 assert_eq!(entry.bits_consumed, 5);
727 }
728
729 for (i, entry) in tbl.iter().enumerate().take(0x07 + 1) {
730 assert_eq!(entry.symbol, b'0', "prefix table entry 0x{:02X} should be '0'", i);
731 assert_eq!(entry.bits_consumed, 5);
732 }
733
734 let filled = tbl.iter().filter(|e| e.bits_consumed > 0).count();
735 assert!(filled > 200, "prefix table should have >200 filled entries, got {}", filled);
736 }
737
738 #[test]
739 fn test_prefix_table_roundtrip_all_bytes() {
740 let input: Vec<u8> = (0..=255u8).collect();
741 let encoded = HuffmanEncoder::encode(&input);
742 let decoded = HuffmanDecoder::decode(&encoded).unwrap();
743 assert_eq!(decoded, input);
744 }
745
746 #[test]
747 fn test_prefix_table_long_header() {
748 let input = b"Accept-Encoding: gzip, deflate, br";
749 let encoded = HuffmanEncoder::encode(input);
750 let decoded = HuffmanDecoder::decode(&encoded).unwrap();
751 assert_eq!(decoded, input);
752 }
753
754 #[test]
755 fn test_decode_eos_explicit() {
756 let result = HuffmanDecoder::decode(&[0xFF, 0xFF, 0xFF, 0xFF]);
757 assert!(matches!(result, Err(HuffmanDecodeError::EosSymbol) | Err(HuffmanDecodeError::InvalidPadding) | Err(HuffmanDecodeError::InvalidCode)));
758 }
759
760 #[test]
761 fn test_exact_64_bit_encoding() {
762 let encoded = HuffmanEncoder::encode(b"hello world");
763 let expected_len = HuffmanEncoder::encoded_len(b"hello world");
764 assert_eq!(encoded.len(), expected_len);
765 let decoded = HuffmanDecoder::decode(&encoded).unwrap();
766 assert_eq!(decoded, b"hello world");
767 }
768
769 #[test]
770 fn test_padding_with_trailing_symbol() {
771 let encoded = HuffmanEncoder::encode(b"a");
772 assert_eq!(encoded, vec![0x1F]);
773 let decoded = HuffmanDecoder::decode(&encoded).unwrap();
774 assert_eq!(decoded, b"a");
775 }
776
777 #[test]
778 fn test_multiple_symbols_exact_boundary() {
779 let input = b"00000000";
780 let encoded = HuffmanEncoder::encode(input);
781 assert_eq!(encoded.len(), 5);
782 let decoded = HuffmanDecoder::decode(&encoded).unwrap();
783 assert_eq!(decoded, input);
784 }
785}