pub mod bitreader;
pub mod tables;
pub mod fixed;
pub mod copy;
pub mod fastloop;
use bitreader::BitReader;
use tables::DecompressTables;
pub const OVERWRITE_HEADROOM: usize = copy::CHUNK_SIZE + 258;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum InflateError {
InvalidBlockType,
InvalidStoredLength,
InvalidHuffmanTable,
InvalidDistance,
InvalidCodeLengths,
OutputOverflow,
UnexpectedEof,
DataError,
}
impl std::fmt::Display for InflateError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::InvalidBlockType => write!(f, "invalid DEFLATE block type"),
Self::InvalidStoredLength => write!(f, "invalid stored block length"),
Self::InvalidHuffmanTable => write!(f, "invalid Huffman table"),
Self::InvalidDistance => write!(f, "invalid back-reference distance"),
Self::InvalidCodeLengths => write!(f, "invalid code lengths"),
Self::OutputOverflow => write!(f, "output buffer overflow"),
Self::UnexpectedEof => write!(f, "unexpected end of input"),
Self::DataError => write!(f, "DEFLATE data error"),
}
}
}
impl std::error::Error for InflateError {}
pub fn inflate_into(
compressed: &[u8],
output: &mut [u8],
) -> Result<usize, InflateError> {
inflate_impl(compressed, output, false)
}
pub fn inflate_segment(
compressed: &[u8],
output: &mut [u8],
) -> Result<usize, InflateError> {
inflate_impl(compressed, output, true)
}
pub fn inflate_segment_with_prefix(
compressed: &[u8],
output: &mut [u8],
prefix_len: usize,
) -> Result<usize, InflateError> {
inflate_impl_at(compressed, output, prefix_len, true, 0)
}
pub fn inflate_segment_with_prefix_limited(
compressed: &[u8],
output: &mut [u8],
prefix_len: usize,
limit: usize,
) -> Result<usize, InflateError> {
inflate_impl_at(compressed, output, prefix_len, true, limit)
}
fn inflate_impl(
compressed: &[u8],
output: &mut [u8],
allow_partial: bool,
) -> Result<usize, InflateError> {
inflate_impl_at(compressed, output, 0, allow_partial, 0)
}
fn inflate_impl_at(
compressed: &[u8],
output: &mut [u8],
start_pos: usize,
allow_partial: bool,
limit: usize,
) -> Result<usize, InflateError> {
tables::with_tables(|tables| {
let mut bits = BitReader::new(compressed);
let mut out_pos = start_pos;
let stop_at = if limit > 0 { start_pos + limit } else { 0 };
loop {
if stop_at > 0 && out_pos >= stop_at {
return Ok(out_pos - start_pos);
}
unsafe { bits.refill() };
if bits.bits_remaining() < 3 {
if allow_partial && out_pos > start_pos {
return Ok(out_pos - start_pos);
}
return Err(InflateError::UnexpectedEof);
}
let bfinal = bits.take(1);
let btype = bits.take(2);
match btype {
0 => {
out_pos = decode_stored(&mut bits, output, out_pos)?;
}
1 => {
fixed::load_fixed_tables(tables);
let written = unsafe {
fastloop::inflate_fast(&mut bits, tables, output, out_pos)
}?;
out_pos += written;
}
2 => {
decode_dynamic_header(&mut bits, tables)?;
let written = unsafe {
fastloop::inflate_fast(&mut bits, tables, output, out_pos)
}?;
out_pos += written;
}
_ => return Err(InflateError::InvalidBlockType),
}
if bfinal != 0 {
break;
}
}
Ok(out_pos - start_pos)
})
}
pub fn inflate_to_vec(
compressed: &[u8],
expected_size: usize,
) -> Result<Vec<u8>, InflateError> {
let total = expected_size + OVERWRITE_HEADROOM;
let mut output = Vec::with_capacity(total);
#[allow(clippy::uninit_vec)]
unsafe {
output.set_len(total);
}
let written = inflate_into(compressed, &mut output)?;
output.truncate(written);
Ok(output)
}
fn decode_stored(
bits: &mut BitReader,
output: &mut [u8],
mut out_pos: usize,
) -> Result<usize, InflateError> {
bits.align_to_byte();
if bits.bits_remaining() < 32 {
unsafe { bits.refill() };
}
if bits.bits_remaining() < 32 {
return Err(InflateError::UnexpectedEof);
}
let len = bits.take_u16() as usize;
let nlen = bits.take_u16() as usize;
if len != (!nlen & 0xFFFF) {
return Err(InflateError::InvalidStoredLength);
}
if out_pos + len > output.len() {
return Err(InflateError::OutputOverflow);
}
let mut remaining = len;
while remaining > 0 && bits.bits_remaining() >= 8 {
output[out_pos] = bits.take(8) as u8;
out_pos += 1;
remaining -= 1;
}
if remaining > 0 {
let ptr = bits.input_ptr();
let end = bits.input_end();
let avail = unsafe { end.offset_from(ptr) } as usize;
if avail < remaining {
return Err(InflateError::UnexpectedEof);
}
unsafe {
core::ptr::copy_nonoverlapping(ptr, output.as_mut_ptr().add(out_pos), remaining);
}
for i in 0..remaining {
if bits.bits_remaining() < 8 {
unsafe { bits.refill() };
}
if bits.bits_remaining() < 8 {
let p = bits.input_ptr();
if p >= bits.input_end() {
return Err(InflateError::UnexpectedEof);
}
output[out_pos + i] = unsafe { *p };
return Err(InflateError::UnexpectedEof);
}
output[out_pos + i] = bits.take(8) as u8;
}
out_pos += remaining;
}
Ok(out_pos)
}
static CODELEN_ORDER: [usize; 19] = [
16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15,
];
fn decode_dynamic_header(
bits: &mut BitReader,
tables: &mut DecompressTables,
) -> Result<(), InflateError> {
unsafe { bits.refill() };
if bits.bits_remaining() < 14 {
return Err(InflateError::UnexpectedEof);
}
let hlit = bits.take(5) as usize + 257; let hdist = bits.take(5) as usize + 1; let hclen = bits.take(4) as usize + 4;
if hlit > 286 || hdist > 32 {
return Err(InflateError::InvalidCodeLengths);
}
let mut codelen_lens = [0u8; 19];
for i in 0..hclen {
if bits.bits_remaining() < 3 {
unsafe { bits.refill() };
}
codelen_lens[CODELEN_ORDER[i]] = bits.take(3) as u8;
}
tables::build_decode_table(
&codelen_lens,
&mut tables.precode,
tables::PRECODE_TABLEBITS,
tables::TableKind::Precode,
)?;
let total = hlit + hdist;
let mut lens_buf = [0u8; 286 + 32]; let lens = &mut lens_buf[..total];
let mut i = 0;
while i < total {
if bits.bits_remaining() < 15 {
unsafe { bits.refill() };
}
let idx = (bits.raw_buf() as u32) & ((1u32 << tables::PRECODE_TABLEBITS) - 1);
let entry = tables.precode[idx as usize];
let code_len = entry & 0xF;
bits.consume(code_len);
let sym = (entry >> 16) & 0xFF;
match sym as usize {
0..=15 => {
lens[i] = sym as u8;
i += 1;
}
16 => {
if bits.bits_remaining() < 2 {
unsafe { bits.refill() };
}
let repeat = bits.take(2) as usize + 3;
if i == 0 || i + repeat > total {
return Err(InflateError::InvalidCodeLengths);
}
let prev = lens[i - 1];
for _ in 0..repeat {
lens[i] = prev;
i += 1;
}
}
17 => {
if bits.bits_remaining() < 3 {
unsafe { bits.refill() };
}
let repeat = bits.take(3) as usize + 3;
if i + repeat > total {
return Err(InflateError::InvalidCodeLengths);
}
for _ in 0..repeat {
lens[i] = 0;
i += 1;
}
}
18 => {
if bits.bits_remaining() < 7 {
unsafe { bits.refill() };
}
let repeat = bits.take(7) as usize + 11;
if i + repeat > total {
return Err(InflateError::InvalidCodeLengths);
}
for _ in 0..repeat {
lens[i] = 0;
i += 1;
}
}
_ => return Err(InflateError::InvalidCodeLengths),
}
}
let litlen_lens = &lens[..hlit];
let dist_lens = &lens[hlit..];
tables::build_decode_table(
litlen_lens,
&mut tables.litlen,
tables::LITLEN_TABLEBITS,
tables::TableKind::Litlen,
)?;
tables::build_decode_table(
dist_lens,
&mut tables.dist,
tables::DIST_TABLEBITS,
tables::TableKind::Dist,
)?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn inflate_empty_stored() {
let data = [
0b00000001u8, 0x00, 0x00, 0xFF, 0xFF, ];
let mut out = vec![0u8; OVERWRITE_HEADROOM];
let written = inflate_into(&data, &mut out).expect("stored block");
assert_eq!(written, 0);
}
#[test]
fn inflate_stored_hello() {
let mut data = vec![0b00000001u8]; let len: u16 = 5;
data.extend_from_slice(&len.to_le_bytes());
data.extend_from_slice(&(!len).to_le_bytes());
data.extend_from_slice(b"Hello");
let mut out = vec![0u8; 5 + OVERWRITE_HEADROOM];
let written = inflate_into(&data, &mut out).expect("stored hello");
assert_eq!(written, 5);
assert_eq!(&out[..5], b"Hello");
}
#[test]
fn inflate_fixed_roundtrip() {
let original = b"The quick brown fox jumps over the lazy dog. \
The quick brown fox jumps over the lazy dog.";
let compressed = miniz_oxide::deflate::compress_to_vec(original, 1);
let mut out = vec![0u8; original.len() + OVERWRITE_HEADROOM];
let written = inflate_into(&compressed, &mut out).expect("inflate fixed");
assert_eq!(written, original.len());
assert_eq!(&out[..written], original.as_slice());
}
#[test]
fn inflate_dynamic_roundtrip() {
let original = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ".repeat(100);
let compressed = miniz_oxide::deflate::compress_to_vec(&original, 6);
let mut out = vec![0u8; original.len() + OVERWRITE_HEADROOM];
let written = inflate_into(&compressed, &mut out).expect("inflate dynamic");
assert_eq!(written, original.len());
assert_eq!(&out[..written], original.as_slice());
}
#[test]
fn inflate_to_vec_convenience() {
let original = b"Hello, World!".repeat(50);
let compressed = miniz_oxide::deflate::compress_to_vec(&original, 6);
let result = inflate_to_vec(&compressed, original.len()).expect("inflate_to_vec");
assert_eq!(result, original);
}
#[test]
fn inflate_large_data() {
let mut original = vec![0u8; 65536];
for (i, b) in original.iter_mut().enumerate() {
*b = ((i * 7 + 13) % 256) as u8;
}
let compressed = miniz_oxide::deflate::compress_to_vec(&original, 6);
let result = inflate_to_vec(&compressed, original.len()).expect("large inflate");
assert_eq!(result.len(), original.len());
assert_eq!(result, original);
}
#[test]
fn inflate_all_zeros() {
let original = vec![0u8; 32768];
let compressed = miniz_oxide::deflate::compress_to_vec(&original, 6);
let result = inflate_to_vec(&compressed, original.len()).expect("all zeros");
assert_eq!(result, original);
}
#[test]
fn inflate_burst_refill_regression() {
let mut original = Vec::new();
let mut s: u64 = 18 | 1;
let mut rng = || {
s ^= s << 13;
s ^= s >> 7;
s ^= s << 17;
s
};
let tags = [
"name", "name:en", "name:de", "name:ru", "name:zh", "name:ar",
"name:ja", "alt_name",
];
while original.len() < 200_000 {
original.extend_from_slice(b" <tag k=\"");
original.extend_from_slice(tags[(rng() as usize) % tags.len()].as_bytes());
original.extend_from_slice(b"\" v=\"");
let words = (rng() % 4 + 1) as usize;
for _ in 0..words {
let len = (rng() % 8 + 2) as usize;
for _ in 0..len {
let cp = match rng() % 5 {
0 => 0x41 + (rng() % 26),
1 => 0x400 + (rng() % 0x60),
2 => 0x4E00 + (rng() % 0x500),
3 => 0x600 + (rng() % 0x50),
_ => 0x3040 + (rng() % 0x90),
};
if let Some(c) = char::from_u32(cp as u32) {
let mut b = [0u8; 4];
original.extend_from_slice(c.encode_utf8(&mut b).as_bytes());
}
}
original.push(b' ');
}
original.extend_from_slice(b"\"/>\n");
}
let compressed = miniz_oxide::deflate::compress_to_vec(&original, 1);
let result = inflate_to_vec(&compressed, original.len()).expect("burst regression");
assert_eq!(result.len(), original.len(), "length must match exactly");
assert_eq!(result, original, "byte-exact decode (no burst corruption)");
}
#[test]
fn inflate_short_repeats() {
let mut original = Vec::with_capacity(4096);
for _ in 0..512 {
original.extend_from_slice(b"ABCABCABC");
}
let compressed = miniz_oxide::deflate::compress_to_vec(&original, 6);
let result = inflate_to_vec(&compressed, original.len()).expect("short repeats");
assert_eq!(result, original);
}
#[test]
fn inflate_vs_miniz_many_sizes() {
let patterns: Vec<Vec<u8>> = vec![
{
let mut v = vec![0xCA, 0xFE, 0xBA, 0xBE, 0x00, 0x00, 0x00, 0x34];
for i in 0..2000 {
v.push((i * 37 + 13) as u8);
}
v
},
b"package org.json;\nimport java.util.*;\n".repeat(200),
{
let mut v = Vec::with_capacity(16384);
for i in 0..4096 {
if i % 10 < 3 {
v.extend_from_slice(&[0u8; 4]);
} else {
v.push((i * 7 + 3) as u8);
}
}
v
},
];
for (idx, original) in patterns.iter().enumerate() {
for level in [1, 6, 9] {
let compressed = miniz_oxide::deflate::compress_to_vec(original, level);
let miniz_out = miniz_oxide::inflate::decompress_to_vec(&compressed).unwrap();
let our_out = inflate_to_vec(&compressed, original.len())
.unwrap_or_else(|e| panic!("pattern {idx} level {level}: {e:?}"));
assert_eq!(our_out, miniz_out,
"pattern {idx} level {level}: output mismatch (lens {} vs {})",
our_out.len(), miniz_out.len());
}
}
}
#[test]
fn inflate_real_jar_entry() {
let comp_path = "/tmp/xml_class_compressed.bin";
let exp_path = "/tmp/xml_class_expected.bin";
if !std::path::Path::new(comp_path).exists() { return; }
let compressed = std::fs::read(comp_path).unwrap();
let expected = std::fs::read(exp_path).unwrap();
let mut out = vec![0u8; expected.len() + OVERWRITE_HEADROOM];
match inflate_into(&compressed, &mut out) {
Ok(written) => {
out.truncate(written);
if out != expected {
let pos = out.iter().zip(expected.iter())
.position(|(a, b)| a != b)
.unwrap_or(out.len().min(expected.len()));
panic!("MISMATCH at byte {} (got 0x{:02x} vs expected 0x{:02x}, lens {} vs {})",
pos,
if pos < out.len() { out[pos] } else { 0 },
if pos < expected.len() { expected[pos] } else { 0 },
out.len(), expected.len());
}
}
Err(e) => panic!("inflate error: {e:?}"),
}
}
}