use super::huffman::HuffmanDecoder;
use bitstream_io::{BitRead, BitReader, Endianness, LittleEndian};
use std::io::{self, Cursor, Error, Read, Result};
const MAX_LEN_SYMBOL: u16 = 63;
const MAX_HUFFMAN_SYMBOLS: usize = 1 << 8;
const MAX_CODE_LENGTH: usize = 16;
const CODE_LENGTH_COUNT_SIZE: usize = MAX_CODE_LENGTH;
fn read_huffman_code<T: std::io::Read, E: Endianness>(
is: &mut BitReader<T, E>,
num_lens: usize,
) -> std::io::Result<Box<HuffmanDecoder>> {
let mut lens = [0; MAX_HUFFMAN_SYMBOLS];
let mut len_count = [0; CODE_LENGTH_COUNT_SIZE];
let byte = is.read::<8, u8>()?;
let num_bytes = usize::from(byte) + 1;
let mut codeword_idx = 0;
for _byte_idx in 0..num_bytes {
let byte = is.read::<8, u16>()?;
let codeword_len = (byte & 0xf) + 1;
let run_length = (byte >> 4) + 1;
debug_assert!((1..=16).contains(&codeword_len));
len_count[codeword_len as usize - 1] += run_length;
if (codeword_idx + run_length) as usize > num_lens {
return Err(Error::new(
io::ErrorKind::InvalidData,
"too many codeword lengths",
));
}
for _ in 0..run_length {
debug_assert!((codeword_idx as usize) < num_lens);
lens[codeword_idx as usize] = codeword_len as u8;
codeword_idx += 1;
}
}
debug_assert!(codeword_idx as usize <= num_lens);
if (codeword_idx as usize) < num_lens {
return Err(Error::new(
io::ErrorKind::InvalidData,
"not enough codeword lengths",
));
}
let mut avail_codewords = 1;
for i in 1..=16 {
debug_assert!(avail_codewords >= 0);
avail_codewords *= 2;
avail_codewords -= i32::from(len_count[i - 1]);
if avail_codewords < 0 {
return Err(Error::new(
io::ErrorKind::InvalidData,
"huffman tree is not full",
));
}
}
if avail_codewords != 0 {
return Err(Error::new(
io::ErrorKind::InvalidData,
"not all codewords were used",
));
}
let mut d = Box::new(HuffmanDecoder::default());
d.init(&lens, num_lens)?;
Ok(d)
}
struct ImplodeState<T> {
bit_length: u64,
bit_reader: BitReader<T, LittleEndian>,
lit_decoder_opt: Option<Box<HuffmanDecoder>>,
len_decoder: Box<HuffmanDecoder>,
dist_decoder: Box<HuffmanDecoder>,
min_len: u16,
dist_low_bits: u32,
uncomp_len: usize,
}
impl<T> std::fmt::Debug for ImplodeState<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ImplodeState")
.field("bit_length", &self.bit_length)
.field("uncomp_len", &self.uncomp_len)
.finish_non_exhaustive()
}
}
impl<T: std::io::Read + std::io::Seek> ImplodeState<T> {
fn new(
src: T,
bit_length: u64,
uncomp_len: usize,
large_wnd: bool,
lit_tree: bool,
pk101_bug_compat: bool,
) -> std::io::Result<Self> {
let mut bit_reader = BitReader::endian(src, LittleEndian);
let lit_decoder_opt = if lit_tree {
Some(read_huffman_code(&mut bit_reader, 256)?)
} else {
None
};
let len_decoder = read_huffman_code(&mut bit_reader, 64)?;
let dist_decoder = read_huffman_code(&mut bit_reader, 64)?;
let min_len = if (pk101_bug_compat && large_wnd) || (!pk101_bug_compat && lit_tree) {
3
} else {
2
};
let dist_low_bits = if large_wnd { 7 } else { 6 };
Ok(Self {
bit_length,
bit_reader,
lit_decoder_opt,
len_decoder,
dist_decoder,
min_len,
dist_low_bits,
uncomp_len,
})
}
fn explode_until(&mut self, target_len: usize, dst: &mut Vec<u8>) -> std::io::Result<()> {
let target_len = target_len.min(self.uncomp_len);
if dst.len() < target_len {
dst.try_reserve(
target_len
.saturating_sub(dst.len())
.min(crate::legacy::MAX_PREALLOC),
)
.map_err(|e| Error::new(io::ErrorKind::OutOfMemory, e))?;
}
while dst.len() < target_len {
let is_literal = self.bit_reader.read_bit()?;
if is_literal {
let sym = if let Some(lit_decoder) = &mut self.lit_decoder_opt {
lit_decoder.huffman_decode(self.bit_length, &mut self.bit_reader)?
} else {
u16::from(self.bit_reader.read::<8, u8>()?)
};
debug_assert!(sym <= u16::from(u8::MAX));
dst.push(sym as u8);
continue;
}
let mut dist = self.bit_reader.read_var::<u16>(self.dist_low_bits)?;
let sym = self
.dist_decoder
.huffman_decode(self.bit_length, &mut self.bit_reader)?;
dist |= sym << self.dist_low_bits;
dist += 1;
let sym = self
.len_decoder
.huffman_decode(self.bit_length, &mut self.bit_reader)?;
let mut len = (sym + self.min_len) as usize;
if sym == MAX_LEN_SYMBOL {
len += self.bit_reader.read::<8, u16>()? as usize;
}
let len = len.min(self.uncomp_len - dst.len());
if dist as usize <= dst.len() {
if dist as usize >= len {
let start = dst.len() - dist as usize;
dst.extend_from_within(start..start + len);
} else {
for _ in 0..len {
let byte = dst[dst.len() - dist as usize];
dst.push(byte);
}
}
} else {
for _ in 0..len {
if dist as usize > dst.len() {
dst.push(0);
} else {
let byte = dst[dst.len() - dist as usize];
dst.push(byte);
}
}
}
}
Ok(())
}
}
#[cfg(test)]
fn hwexplode(
src: &[u8],
uncomp_len: usize,
large_wnd: bool,
lit_tree: bool,
pk101_bug_compat: bool,
dst: &mut Vec<u8>,
) -> std::io::Result<()> {
let mut state = ImplodeState::new(
Cursor::new(&src),
src.len() as u64 * 8,
uncomp_len,
large_wnd,
lit_tree,
pk101_bug_compat,
)?;
state.explode_until(uncomp_len, dst)
}
#[derive(Debug)]
pub struct ImplodeDecoder<R> {
compressed_reader: R,
uncompressed_size: u64,
state: Option<ImplodeState<Cursor<Vec<u8>>>>,
large_wnd: bool,
lit_tree: bool,
stream: Vec<u8>,
read_pos: usize, }
impl<R: Read> ImplodeDecoder<R> {
pub fn new(inner: R, uncompressed_size: u64, flags: u16) -> Self {
let large_wnd = (flags & 2) != 0;
let lit_tree = (flags & 4) != 0;
ImplodeDecoder {
compressed_reader: inner,
uncompressed_size,
state: None,
large_wnd,
lit_tree,
stream: Vec::new(),
read_pos: 0,
}
}
pub fn into_inner(self) -> R {
self.compressed_reader
}
}
impl<R: Read> Read for ImplodeDecoder<R> {
fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
let state = match &mut self.state {
Some(state) => state,
None => {
let uncomp_len = usize::try_from(self.uncompressed_size).map_err(|_| {
Error::new(
io::ErrorKind::InvalidData,
"uncompressed size exceeds usize::MAX",
)
})?;
let mut compressed_bytes = Vec::new();
self.compressed_reader.read_to_end(&mut compressed_bytes)?;
let bit_length = compressed_bytes.len() as u64 * 8;
self.state.insert(ImplodeState::new(
Cursor::new(compressed_bytes),
bit_length,
uncomp_len,
self.large_wnd,
self.lit_tree,
false,
)?)
}
};
let target_len = self
.read_pos
.saturating_add(buf.len())
.min(state.uncomp_len);
if self.stream.len() < target_len {
state.explode_until(target_len, &mut self.stream)?;
}
let available = self.stream.len() - self.read_pos;
let bytes_to_read = available.min(buf.len());
buf[..bytes_to_read]
.copy_from_slice(&self.stream[self.read_pos..self.read_pos + bytes_to_read]);
self.read_pos += bytes_to_read;
Ok(bytes_to_read)
}
}
#[cfg(test)]
mod tests {
use super::hwexplode;
const HAMLET_256: &[u8; 249] = include_bytes!("../../tests/data/legacy/implode_hamlet_256.bin");
const HAMLET_256_OUT: &[u8; 256] =
include_bytes!("../../tests/data/legacy/implode_hamlet_256.out");
#[test]
fn test_explode_hamlet_256() {
let mut dst = Vec::new();
hwexplode(HAMLET_256, 256, false, false, false, &mut dst).unwrap();
assert_eq!(dst.len(), 256);
assert_eq!(&dst, &HAMLET_256_OUT);
}
#[test]
fn code_length_table_with_256_bytes() {
use bitstream_io::{BitWrite, BitWriter, LittleEndian};
let mut src = Vec::new();
{
let mut w = BitWriter::endian(&mut src, LittleEndian);
w.write::<8, u8>(0xff).unwrap();
for _ in 0..256 {
w.write::<8, u8>(0x07).unwrap();
}
w.byte_align().unwrap();
}
let mut is = super::BitReader::endian(super::Cursor::new(&src), super::LittleEndian);
super::read_huffman_code(&mut is, 256).expect("a 256-entry code is valid");
}
#[test]
fn truncated_stream_is_an_error() {
for cut in 0..HAMLET_256.len() {
let r = std::panic::catch_unwind(|| {
let mut dst = Vec::new();
let r = hwexplode(&HAMLET_256[..cut], 256, false, false, false, &mut dst);
(r.is_ok(), dst)
});
assert!(
r.is_ok(),
"hwexplode panicked on {cut} of {} bytes",
HAMLET_256.len()
);
let Ok((ok, dst)) = r else { continue };
assert!(
!ok || HAMLET_256_OUT.starts_with(&dst),
"cut {cut}: wrong output"
);
}
}
#[test]
fn test_implode_decoder_incremental_take() {
use super::ImplodeDecoder;
use std::io::Read;
let decoder = ImplodeDecoder::new(HAMLET_256.as_slice(), 256, 0);
let mut limited = decoder.take(16);
let mut out = Vec::new();
limited.read_to_end(&mut out).unwrap();
assert_eq!(out, &HAMLET_256_OUT[..16]);
assert!(limited.get_ref().stream.len() <= 32);
}
}