use std::cell::UnsafeCell;
use super::InflateError;
pub const LITLEN_TABLEBITS: u32 = 11;
pub const LITLEN_TABLE_SIZE: usize = 2342;
pub const DIST_TABLEBITS: u32 = 8;
pub const DIST_TABLE_SIZE: usize = 402;
pub const PRECODE_TABLEBITS: u32 = 7;
pub const PRECODE_TABLE_SIZE: usize = 128;
pub const HUFFDEC_LITERAL: u32 = 1 << 31;
pub const HUFFDEC_SUBTABLE: u32 = 1 << 15;
pub const HUFFDEC_END_OF_BLOCK: u32 = 1 << 30;
#[inline(always)]
pub const fn pack_literal(byte_val: u8, code_len: u8) -> u32 {
HUFFDEC_LITERAL | ((byte_val as u32) << 16) | (code_len as u32)
}
#[inline(always)]
pub const fn pack_length(length_base: u16, code_len: u8, extra_bits: u8) -> u32 {
((length_base as u32) << 16) | ((code_len as u32) << 8) | ((code_len + extra_bits) as u32)
}
#[inline(always)]
pub const fn pack_eob(code_len: u8) -> u32 {
HUFFDEC_END_OF_BLOCK | (code_len as u32)
}
#[inline(always)]
pub const fn pack_distance(dist_base: u16, code_len: u8, extra_bits: u8) -> u32 {
((dist_base as u32) << 16) | ((code_len as u32) << 8) | ((code_len + extra_bits) as u32)
}
#[inline(always)]
pub const fn pack_subtable(offset: u16, subtable_bits: u8, main_bits: u8) -> u32 {
((offset as u32) << 16) | HUFFDEC_SUBTABLE | ((subtable_bits as u32) << 8) | (main_bits as u32)
}
pub static LENGTH_BASE: [u16; 29] = [
3, 4, 5, 6, 7, 8, 9, 10, 11, 13,
15, 17, 19, 23, 27, 31, 35, 43, 51, 59,
67, 83, 99, 115, 131, 163, 195, 227, 258,
];
pub static LENGTH_EXTRA: [u8; 29] = [
0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
1, 1, 2, 2, 2, 2, 3, 3, 3, 3,
4, 4, 4, 4, 5, 5, 5, 5, 0,
];
pub static DIST_BASE: [u16; 30] = [
1, 2, 3, 4, 5, 7, 9, 13, 17, 25,
33, 49, 65, 97, 129, 193, 257, 385, 513, 769,
1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577,
];
pub static DIST_EXTRA: [u8; 30] = [
0, 0, 0, 0, 1, 1, 2, 2, 3, 3,
4, 4, 5, 5, 6, 6, 7, 7, 8, 8,
9, 9, 10, 10, 11, 11, 12, 12, 13, 13,
];
pub struct DecompressTables {
pub litlen: [u32; LITLEN_TABLE_SIZE],
pub dist: [u32; DIST_TABLE_SIZE],
pub precode: [u32; PRECODE_TABLE_SIZE],
}
impl DecompressTables {
pub fn zeroed() -> Self {
Self {
litlen: [0u32; LITLEN_TABLE_SIZE],
dist: [0u32; DIST_TABLE_SIZE],
precode: [0u32; PRECODE_TABLE_SIZE],
}
}
}
thread_local! {
static TABLES: UnsafeCell<Option<Box<DecompressTables>>> = const { UnsafeCell::new(None) };
}
pub fn with_tables<R>(f: impl FnOnce(&mut DecompressTables) -> R) -> R {
TABLES.with(|cell| {
let opt = unsafe { &mut *cell.get() };
let tables = opt.get_or_insert_with(|| Box::new(DecompressTables::zeroed()));
f(tables)
})
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum TableKind {
Litlen,
Dist,
Precode,
}
pub fn build_decode_table(
lens: &[u8],
table: &mut [u32],
table_bits: u32,
kind: TableKind,
) -> Result<usize, InflateError> {
let num_syms = lens.len();
let table_size = 1usize << table_bits;
let mut bl_count = [0u32; 16]; let mut max_len: u32 = 0;
for &len in lens.iter() {
if len > 0 {
bl_count[len as usize] += 1;
if (len as u32) > max_len {
max_len = len as u32;
}
}
}
if max_len == 0 {
for entry in table[..table_size].iter_mut() {
*entry = 0;
}
return Ok(table_size);
}
{
let mut left: i32 = 1;
for bits in 1..=15usize {
left <<= 1;
left -= bl_count[bits] as i32;
if left < 0 {
return Err(InflateError::InvalidHuffmanTable);
}
}
}
let mut next_code = [0u32; 16];
{
let mut code = 0u32;
for bits in 1..=15 {
code = (code + bl_count[bits - 1]) << 1;
next_code[bits] = code;
}
}
for entry in table[..table_size].iter_mut() {
*entry = 0;
}
let mut table_end = table_size;
let mut subtable_max_bits = [0u8; 2048]; debug_assert!(table_size <= 2048);
if max_len > table_bits {
let mut codes_tmp = next_code;
for sym in 0..num_syms {
let len = lens[sym] as u32;
if len <= table_bits || len == 0 { continue; }
let code = codes_tmp[len as usize];
codes_tmp[len as usize] += 1;
let reversed = bit_reverse(code, len);
let main_idx = (reversed & ((1u32 << table_bits) - 1)) as usize;
let sub_bits = (len - table_bits) as u8;
if sub_bits > subtable_max_bits[main_idx] {
subtable_max_bits[main_idx] = sub_bits;
}
}
for main_idx in 0..table_size {
let max_sub = subtable_max_bits[main_idx];
if max_sub > 0 {
let sub_size = 1usize << max_sub;
let sub_offset = table_end;
table_end += sub_size;
if table_end > table.len() {
return Err(InflateError::InvalidHuffmanTable);
}
for e in table[sub_offset..sub_offset + sub_size].iter_mut() {
*e = 0;
}
table[main_idx] = pack_subtable(
sub_offset as u16,
max_sub,
table_bits as u8,
);
}
}
}
let mut next_code2 = next_code;
for sym in 0..num_syms {
let len = lens[sym] as u32;
if len == 0 { continue; }
let code = next_code2[len as usize];
next_code2[len as usize] += 1;
let entry = match kind {
TableKind::Litlen => pack_litlen_entry(sym, len as u8),
TableKind::Dist => pack_dist_entry(sym, len as u8),
TableKind::Precode => pack_literal(sym as u8, len as u8),
};
let reversed = bit_reverse(code, len);
if len <= table_bits {
let step = 1u32 << len;
let mut idx = reversed;
while (idx as usize) < table_size {
table[idx as usize] = entry;
idx += step;
}
} else {
let main_idx = (reversed & ((1u32 << table_bits) - 1)) as usize;
let sub_entry = table[main_idx];
let sub_offset = ((sub_entry >> 16) & 0x7FFF) as usize;
let max_sub_bits = ((sub_entry >> 8) & 0x7F) as u32;
let sub_bits = len - table_bits;
let sub_idx = (reversed >> table_bits) & ((1u32 << max_sub_bits) - 1);
let step = 1u32 << sub_bits;
let sub_size = 1u32 << max_sub_bits;
let mut idx = sub_idx;
while idx < sub_size {
table[sub_offset + idx as usize] = entry;
idx += step;
}
}
}
Ok(table_end)
}
#[cfg(test)]
fn compute_code_for_sym(lens: &[u8], sym: usize) -> u32 {
let len = lens[sym] as u32;
if len == 0 { return 0; }
let mut bl_count = [0u32; 16];
for &l in lens.iter() {
if l > 0 { bl_count[l as usize] += 1; }
}
let mut next_code = [0u32; 16];
let mut code = 0u32;
for bits in 1..=15 {
code = (code + bl_count[bits - 1]) << 1;
next_code[bits] = code;
}
next_code[len as usize] + (0..sym).filter(|&i| lens[i] as u32 == len).count() as u32
}
#[inline(always)]
fn bit_reverse(code: u32, len: u32) -> u32 {
code.reverse_bits() >> (32 - len)
}
fn pack_litlen_entry(sym: usize, code_len: u8) -> u32 {
if sym < 256 {
pack_literal(sym as u8, code_len)
} else if sym == 256 {
pack_eob(code_len)
} else if sym <= 285 {
let idx = sym - 257;
pack_length(LENGTH_BASE[idx], code_len, LENGTH_EXTRA[idx])
} else {
0 }
}
fn pack_dist_entry(sym: usize, code_len: u8) -> u32 {
if sym < 30 {
pack_distance(DIST_BASE[sym], code_len, DIST_EXTRA[sym])
} else {
0 }
}
#[inline(always)]
pub fn decode_entry(table: &[u32], bits_buf: u64, table_bits: u32) -> u32 {
let idx = (bits_buf as u32) & ((1u32 << table_bits) - 1);
let entry = table[idx as usize];
if entry & HUFFDEC_SUBTABLE == 0 {
return entry;
}
let main_bits = (entry & 0xF) as u32;
let sub_bits = ((entry >> 8) & 0x7F) as u32;
let sub_offset = ((entry >> 16) & 0x7FFF) as usize;
let sub_idx = ((bits_buf >> main_bits) as u32) & ((1u32 << sub_bits) - 1);
table[sub_offset + sub_idx as usize]
}
#[inline(always)]
pub fn entry_code_len(entry: u32) -> u32 {
entry & 0xF
}
#[inline(always)]
pub fn entry_total_bits(entry: u32) -> u32 {
entry & 0x1F
}
#[inline(always)]
pub fn entry_literal(entry: u32) -> u8 {
(entry >> 16) as u8
}
#[inline(always)]
pub fn entry_base(entry: u32) -> u32 {
(entry >> 16) & 0xFFFF
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn pack_unpack_literal() {
let entry = pack_literal(b'A', 7);
assert!(entry & HUFFDEC_LITERAL != 0);
assert_eq!(entry_literal(entry), b'A');
assert_eq!(entry_code_len(entry), 7);
}
#[test]
fn pack_unpack_length() {
let entry = pack_length(3, 7, 0);
assert!(entry & HUFFDEC_LITERAL == 0);
assert!(entry & HUFFDEC_END_OF_BLOCK == 0);
assert_eq!(entry_base(entry), 3);
assert_eq!((entry >> 8) & 0xF, 7); assert_eq!(entry_total_bits(entry), 7); }
#[test]
fn pack_unpack_eob() {
let entry = pack_eob(7);
assert!(entry & HUFFDEC_END_OF_BLOCK != 0);
assert!(entry & HUFFDEC_LITERAL == 0);
assert_eq!(entry_code_len(entry), 7);
}
#[test]
fn bit_reverse_basic() {
assert_eq!(bit_reverse(0b110, 3), 0b011);
assert_eq!(bit_reverse(0b1010, 4), 0b0101);
assert_eq!(bit_reverse(0b1, 1), 0b1);
}
#[test]
fn build_simple_table() {
let lens = [1u8, 1];
let mut table = [0u32; 4]; let result = build_decode_table(&lens, &mut table, 2, TableKind::Litlen);
assert!(result.is_ok());
assert!(table[0] & HUFFDEC_LITERAL != 0);
assert_eq!(entry_literal(table[0]), 0);
assert!(table[2] & HUFFDEC_LITERAL != 0);
assert_eq!(entry_literal(table[2]), 0);
assert!(table[1] & HUFFDEC_LITERAL != 0);
assert_eq!(entry_literal(table[1]), 1);
assert!(table[3] & HUFFDEC_LITERAL != 0);
assert_eq!(entry_literal(table[3]), 1);
}
#[test]
fn build_fixed_litlen_table() {
let mut lens = [0u8; 288];
for i in 0..=143 { lens[i] = 8; }
for i in 144..=255 { lens[i] = 9; }
for i in 256..=279 { lens[i] = 7; }
for i in 280..=287 { lens[i] = 8; }
let mut table = [0u32; LITLEN_TABLE_SIZE];
let result = build_decode_table(&lens, &mut table, LITLEN_TABLEBITS, TableKind::Litlen);
assert!(result.is_ok());
let eob_code = compute_code_for_sym(&lens, 256);
let eob_rev = bit_reverse(eob_code, 7);
let entry = table[eob_rev as usize];
assert!(entry & HUFFDEC_END_OF_BLOCK != 0, "EOB entry should have EOB flag");
}
}