#![allow(dead_code, non_snake_case, non_camel_case_types, clippy::all)]
use std::io::{self, Read, Write, BufRead, BufReader, BufWriter, ErrorKind};
use std::fmt;
use std::cmp;
use std::collections::VecDeque;
const MAX_HUFF_TREE_SIZE: usize = 576;
const MAX_LITLEN_CODES: usize = 286;
const NUM_LITERALS: usize = 256;
const NUM_LENGTH_CODES: usize = 29;
const MAX_DIST_CODES: usize = 32;
const NUM_CODELEN_CODES: usize = 19;
const END_OF_BLOCK: u16 = 256;
const MAX_MATCH_LEN: usize = 258;
const MIN_MATCH: usize = 3;
const MAX_MATCH_DIST: usize = 32768;
const WINDOW_SIZE: usize = 32768;
const HASH_SIZE: usize = 32768;
const HASH_BITS: usize = 15;
const HASH_SHIFT: usize = 5;
const HASH_CHAIN_LEN: usize = 4;
const MAX_BITS: usize = 15;
const BL_CODES: usize = 19;
const MAX_HDR_SIZE: usize = 512;
const LZ_BUFFER_SIZE: usize = 65536;
const OUT_BUFFER_SIZE: usize = 65536;
const GZIP_ID1: u8 = 0x1F;
const GZIP_ID2: u8 = 0x8B;
const GZIP_CM_DEFLATE: u8 = 8;
const GZIP_FLAG_FTEXT: u8 = 1;
const GZIP_FLAG_FHCRC: u8 = 2;
const GZIP_FLAG_FEXTRA: u8 = 4;
const GZIP_FLAG_FNAME: u8 = 8;
const GZIP_FLAG_FCOMMENT: u8 = 16;
const Z_DEFAULT_COMPRESSION: i32 = -1;
const Z_NO_COMPRESSION: i32 = 0;
const Z_BEST_SPEED: i32 = 1;
const Z_BEST_COMPRESSION: i32 = 9;
const LEVEL_STORE: u8 = 0;
const LEVEL_FAST: u8 = 1;
const LEVEL_DEFAULT: u8 = 6;
const LEVEL_MAX: u8 = 9;
const GOOD_LENGTH: [usize; 10] = [0, 4, 4, 4, 4, 8, 8, 8, 32, 32];
const MAX_LAZY: [usize; 10] = [0, 4, 5, 6, 4, 16, 16, 32, 128, 258];
const NICE_LENGTH: [usize; 10] = [0, 8, 16, 32, 16, 32, 128, 128, 258, 258];
const MAX_CHAIN: [usize; 10] = [0, 4, 8, 32, 16, 32, 128, 256, 1024, 4096];
const BIT_REV_TABLE: [u8; 256] = [
0x00, 0x80, 0x40, 0xC0, 0x20, 0xA0, 0x60, 0xE0, 0x10, 0x90, 0x50, 0xD0, 0x30, 0xB0, 0x70, 0xF0,
0x08, 0x88, 0x48, 0xC8, 0x28, 0xA8, 0x68, 0xE8, 0x18, 0x98, 0x58, 0xD8, 0x38, 0xB8, 0x78, 0xF8,
0x04, 0x84, 0x44, 0xC4, 0x24, 0xA4, 0x64, 0xE4, 0x14, 0x94, 0x54, 0xD4, 0x34, 0xB4, 0x74, 0xF4,
0x0C, 0x8C, 0x4C, 0xCC, 0x2C, 0xAC, 0x6C, 0xEC, 0x1C, 0x9C, 0x5C, 0xDC, 0x3C, 0xBC, 0x7C, 0xFC,
0x02, 0x82, 0x42, 0xC2, 0x22, 0xA2, 0x62, 0xE2, 0x12, 0x92, 0x52, 0xD2, 0x32, 0xB2, 0x72, 0xF2,
0x0A, 0x8A, 0x4A, 0xCA, 0x2A, 0xAA, 0x6A, 0xEA, 0x1A, 0x9A, 0x5A, 0xDA, 0x3A, 0xBA, 0x7A, 0xFA,
0x06, 0x86, 0x46, 0xC6, 0x26, 0xA6, 0x66, 0xE6, 0x16, 0x96, 0x56, 0xD6, 0x36, 0xB6, 0x76, 0xF6,
0x0E, 0x8E, 0x4E, 0xCE, 0x2E, 0xAE, 0x6E, 0xEE, 0x1E, 0x9E, 0x5E, 0xDE, 0x3E, 0xBE, 0x7E, 0xFE,
0x01, 0x81, 0x41, 0xC1, 0x21, 0xA1, 0x61, 0xE1, 0x11, 0x91, 0x51, 0xD1, 0x31, 0xB1, 0x71, 0xF1,
0x09, 0x89, 0x49, 0xC9, 0x29, 0xA9, 0x69, 0xE9, 0x19, 0x99, 0x59, 0xD9, 0x39, 0xB9, 0x79, 0xF9,
0x05, 0x85, 0x45, 0xC5, 0x25, 0xA5, 0x65, 0xE5, 0x15, 0x95, 0x55, 0xD5, 0x35, 0xB5, 0x75, 0xF5,
0x0D, 0x8D, 0x4D, 0xCD, 0x2D, 0xAD, 0x6D, 0xED, 0x1D, 0x9D, 0x5D, 0xDD, 0x3D, 0xBD, 0x7D, 0xFD,
0x03, 0x83, 0x43, 0xC3, 0x23, 0xA3, 0x63, 0xE3, 0x13, 0x93, 0x53, 0xD3, 0x33, 0xB3, 0x73, 0xF3,
0x0B, 0x8B, 0x4B, 0xCB, 0x2B, 0xAB, 0x6B, 0xEB, 0x1B, 0x9B, 0x5B, 0xDB, 0x3B, 0xBB, 0x7B, 0xFB,
0x07, 0x87, 0x47, 0xC7, 0x27, 0xA7, 0x67, 0xE7, 0x17, 0x97, 0x57, 0xD7, 0x37, 0xB7, 0x77, 0xF7,
0x0F, 0x8F, 0x4F, 0xCF, 0x2F, 0xAF, 0x6F, 0xEF, 0x1F, 0x9F, 0x5F, 0xDF, 0x3F, 0xBF, 0x7F, 0xFF,
];
const 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,
];
const 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,
];
const DIST_BASE: [u16; 32] = [
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,
0, 0,
];
const DIST_EXTRA: [u8; 32] = [
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,
0, 0,
];
const CODELEN_ORDER: [u8; BL_CODES] = [
16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15,
];
fn get_fixed_litlen_bits(sym: u16) -> u8 {
match sym {
0..=143 => 8,
144..=255 => 9,
256..=279 => 7,
280..=287 => 8,
_ => 0,
}
}
fn build_fixed_litlen_lengths() -> [u8; MAX_LITLEN_CODES] {
let mut lens = [0u8; MAX_LITLEN_CODES];
for i in 0..=287 {
lens[i as usize] = get_fixed_litlen_bits(i);
}
lens
}
fn build_fixed_dist_lengths() -> [u8; MAX_DIST_CODES] {
[5u8; MAX_DIST_CODES]
}
fn crc32_table() -> &'static [u32; 256] {
use std::sync::OnceLock;
static TABLE: OnceLock<[u32; 256]> = OnceLock::new();
TABLE.get_or_init(|| {
let mut table = [0u32; 256];
for i in 0..256u32 {
let mut crc = i;
for _ in 0..8 {
if crc & 1 != 0 {
crc = 0xEDB88320u32 ^ (crc >> 1);
} else {
crc >>= 1;
}
}
table[i as usize] = crc;
}
table
})
}
pub fn crc32(data: &[u8]) -> u32 {
crc32_update(0, data)
}
pub fn crc32_update(crc: u32, data: &[u8]) -> u32 {
let table = crc32_table();
let mut c = !crc;
for &byte in data {
c = table[((c ^ byte as u32) & 0xFF) as usize] ^ (c >> 8);
}
!c
}
pub fn crc32_combine(crc1: u32, crc2: u32, len2: u64) -> u32 {
let mut len = len2;
if len == 0 {
return crc1;
}
let mut even = [0u32; 32];
let mut odd = [0u32; 32];
odd[0] = 0xEDB88320;
let mut row: u32 = 1;
for n in 1..32 {
odd[n] = row;
row <<= 1;
}
for n in 0..32 {
let mut val = odd[n];
for _ in 0..32 {
even[n] <<= 1;
if val & 1 != 0 {
even[n] ^= odd[n];
}
val >>= 1;
}
}
let mut crc1_mut = crc1;
while len > 0 {
if len & 1 != 0 {
let mut gf2_matrix_square = odd;
let mut gf2_matrix = even;
let mut n = 0;
while len & (1 << n) == 0 {
let mut new_even = [0u32; 32];
let mut new_odd = [0u32; 32];
for i in 0..32 {
let mut val_e = 0u32;
let mut val_o = 0u32;
for j in (0..32).rev() {
val_e <<= 1;
val_o <<= 1;
if gf2_matrix[j] & (1 << i) != 0 {
val_e ^= gf2_matrix_square[i];
val_o ^= gf2_matrix_square[i] ^ gf2_matrix[i];
}
}
new_even[i] = val_e;
new_odd[i] = val_o;
}
gf2_matrix = new_even;
gf2_matrix_square = new_odd;
n += 1;
}
let mut val = crc1_mut;
crc1_mut = 0;
for i in 0..32 {
if val & 1 != 0 {
crc1_mut ^= gf2_matrix_square[i];
}
val >>= 1;
}
}
len >>= 1;
let mut new_odd = [0u32; 32];
for i in 0..32 {
let mut val = 0u32;
for j in (0..32).rev() {
val <<= 1;
if odd[j] & (1 << i) != 0 {
val ^= odd[i];
}
}
new_odd[i] = val;
}
odd = new_odd;
}
crc1_mut ^ crc2
}
pub fn adler32(data: &[u8]) -> u32 {
adler32_update(1, data)
}
pub fn adler32_update(adler: u32, data: &[u8]) -> u32 {
const BASE: u32 = 65521; let mut s1 = adler & 0xFFFF;
let mut s2 = (adler >> 16) & 0xFFFF;
for &byte in data {
s1 = (s1 + byte as u32) % BASE;
s2 = (s2 + s1) % BASE;
}
(s2 << 16) | s1
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MinizError {
DataError(String),
StreamError(String),
BufError(String),
LevelError(String),
Unsupported(String),
IoError(String),
}
impl fmt::Display for MinizError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
MinizError::DataError(s) => write!(f, "data error: {}", s),
MinizError::StreamError(s) => write!(f, "stream error: {}", s),
MinizError::BufError(s) => write!(f, "buffer error: {}", s),
MinizError::LevelError(s) => write!(f, "level error: {}", s),
MinizError::Unsupported(s) => write!(f, "unsupported: {}", s),
MinizError::IoError(s) => write!(f, "I/O error: {}", s),
}
}
}
impl From<io::Error> for MinizError {
fn from(e: io::Error) -> Self {
MinizError::IoError(e.to_string())
}
}
pub type MinizResult<T> = Result<T, MinizError>;
struct BitReader<'a> {
data: &'a [u8],
pos: usize,
bit_buf: u64,
bits_in_buf: u32,
total_bits_read: u64,
}
impl<'a> BitReader<'a> {
fn new(data: &'a [u8]) -> Self {
BitReader {
data,
pos: 0,
bit_buf: 0,
bits_in_buf: 0,
total_bits_read: 0,
}
}
fn need_bits(&mut self, n: u32) -> MinizResult<()> {
while self.bits_in_buf < n {
if self.pos >= self.data.len() {
return Err(MinizError::DataError("unexpected end of input".into()));
}
self.bit_buf |= (self.data[self.pos] as u64) << self.bits_in_buf;
self.pos += 1;
self.bits_in_buf += 8;
}
Ok(())
}
fn peek_bits(&mut self, n: u32) -> MinizResult<u32> {
if n > 16 {
return Err(MinizError::DataError("too many bits requested".into()));
}
self.need_bits(n)?;
Ok((self.bit_buf & ((1u64 << n) - 1)) as u32)
}
fn drop_bits(&mut self, n: u32) {
self.bit_buf >>= n;
self.bits_in_buf = self.bits_in_buf.saturating_sub(n);
self.total_bits_read += n as u64;
}
fn read_bits(&mut self, n: u32) -> MinizResult<u32> {
let val = self.peek_bits(n)?;
self.drop_bits(n);
Ok(val)
}
fn read_bit(&mut self) -> MinizResult<u32> {
self.read_bits(1)
}
fn align_to_byte(&mut self) {
let bits_to_drop = self.bits_in_buf & 7;
if bits_to_drop > 0 {
self.drop_bits(bits_to_drop);
}
}
fn read_bytes(&mut self, buf: &mut [u8]) -> MinizResult<()> {
self.align_to_byte();
let needed = buf.len();
self.bits_in_buf = 0;
self.bit_buf = 0;
if self.pos + needed > self.data.len() {
return Err(MinizError::DataError("not enough bytes".into()));
}
buf.copy_from_slice(&self.data[self.pos..self.pos + needed]);
self.pos += needed;
self.total_bits_read += (needed as u64) * 8;
Ok(())
}
fn bytes_consumed(&self) -> usize {
self.pos - (self.bits_in_buf as usize / 8)
}
fn has_more(&self) -> bool {
self.pos < self.data.len() || self.bits_in_buf > 0
}
}
struct BitWriter {
buf: Vec<u8>,
bit_buf: u64,
bits_in_buf: u32,
}
impl BitWriter {
fn new() -> Self {
BitWriter {
buf: Vec::with_capacity(OUT_BUFFER_SIZE),
bit_buf: 0,
bits_in_buf: 0,
}
}
fn with_capacity(cap: usize) -> Self {
BitWriter {
buf: Vec::with_capacity(cap),
bit_buf: 0,
bits_in_buf: 0,
}
}
fn write_bits(&mut self, val: u32, n: u32) {
self.bit_buf |= (val as u64) << self.bits_in_buf;
self.bits_in_buf += n;
while self.bits_in_buf >= 8 {
self.buf.push(self.bit_buf as u8);
self.bit_buf >>= 8;
self.bits_in_buf -= 8;
}
}
fn write_bit(&mut self, bit: u32) {
self.write_bits(bit, 1);
}
fn write_bits_rev(&mut self, val: u32, n: u32) {
let mut v = val;
let mut reversed: u32 = 0;
for _ in 0..n {
reversed = (reversed << 1) | (v & 1);
v >>= 1;
}
self.write_bits(reversed, n);
}
fn align_to_byte(&mut self) {
if self.bits_in_buf > 0 {
self.buf.push(self.bit_buf as u8);
self.bit_buf = 0;
self.bits_in_buf = 0;
}
}
fn write_bytes(&mut self, data: &[u8]) {
self.align_to_byte();
self.buf.extend_from_slice(data);
}
fn finish(mut self) -> Vec<u8> {
self.align_to_byte();
self.buf
}
fn len(&self) -> usize {
self.buf.len() + if self.bits_in_buf > 0 { 1 } else { 0 }
}
fn is_empty(&self) -> bool {
self.buf.is_empty() && self.bits_in_buf == 0
}
}
#[derive(Debug, Clone, Copy, Default)]
struct HuffmanNode {
sym: u16,
bits: u8,
code: u16,
}
struct HuffmanTable {
symbols: Vec<i16>,
bits: Vec<u8>,
max_code: usize,
min_bits: u8,
}
impl HuffmanTable {
fn from_lengths(lengths: &[u8], max_sym: usize) -> MinizResult<Self> {
if lengths.is_empty() {
return Ok(HuffmanTable {
symbols: vec![-1],
bits: vec![0],
max_code: 0,
min_bits: 0,
});
}
let mut bl_count = [0u16; MAX_BITS + 1];
for &len in lengths.iter().take(max_sym) {
if len > 0 {
if len as usize > MAX_BITS {
return Err(MinizError::DataError(format!(
"code length {} exceeds maximum {}",
len, MAX_BITS
)));
}
bl_count[len as usize] += 1;
}
}
let mut code: u16 = 0;
let mut next_code = [0u16; MAX_BITS + 1];
for bits in 1..=MAX_BITS {
code = (code + bl_count[bits - 1]) << 1;
next_code[bits] = code;
}
let max_code_val = if bl_count.iter().any(|&c| c > 0) {
(code + bl_count[MAX_BITS]).saturating_sub(1) as usize
} else {
0
};
let table_size = cmp::max(max_code_val + 1, 1);
let mut symbols = vec![-1i16; table_size];
let mut bits_arr = vec![0u8; table_size];
for sym in 0..max_sym {
let len = lengths[sym] as usize;
if len > 0 && len <= MAX_BITS {
let code_val = next_code[len] as usize;
if code_val < table_size {
symbols[code_val] = sym as i16;
bits_arr[code_val] = len as u8;
}
next_code[len] += 1;
}
}
let min_bits = lengths
.iter()
.take(max_sym)
.filter(|&&l| l > 0)
.copied()
.min()
.unwrap_or(0);
Ok(HuffmanTable {
symbols,
bits: bits_arr,
max_code: max_code_val,
min_bits,
})
}
fn decode(&self, reader: &mut BitReader) -> MinizResult<u16> {
if self.min_bits == 0 {
return Err(MinizError::DataError("empty Huffman table".into()));
}
let mut code = reader.peek_bits(self.min_bits as u32)? as usize;
let mut bits = self.min_bits as u32;
loop {
if bits > MAX_BITS as u32 {
return Err(MinizError::DataError("invalid Huffman code".into()));
}
if code <= self.max_code {
if self.bits[code] as u32 == bits {
let sym = self.symbols[code];
if sym < 0 {
return Err(MinizError::DataError(format!(
"invalid Huffman code {} at {} bits",
code, bits
)));
}
reader.drop_bits(bits);
return Ok(sym as u16);
}
}
let next_bit = reader.peek_bits(bits + 1)? & 1;
code = (code << 1) | next_bit as usize;
bits += 1;
reader.drop_bits(1);
}
}
}
impl HuffmanTable {
fn decode_proper(&self, reader: &mut BitReader) -> MinizResult<u16> {
if self.min_bits == 0 {
return Err(MinizError::DataError("empty Huffman table".into()));
}
let mut code = reader.peek_bits(self.min_bits as u32)? as usize;
let mut bits = self.min_bits as usize;
loop {
if bits > MAX_BITS {
return Err(MinizError::DataError("invalid Huffman code (too many bits)".into()));
}
if code <= self.max_code && self.bits[code] as usize == bits {
let sym = self.symbols[code];
if sym < 0 {
return Err(MinizError::DataError(format!(
"invalid Huffman code {} at {} bits", code, bits
)));
}
reader.drop_bits(bits as u32);
return Ok(sym as u16);
}
let b = reader.peek_bits((bits + 1) as u32)? as usize;
code = (code << 1) | (b & 1);
bits += 1;
}
}
}
struct HuffmanEncoder {
codes: Vec<(u16, u8)>, }
impl HuffmanEncoder {
fn new(max_sym: usize) -> Self {
HuffmanEncoder {
codes: vec![(0, 0); max_sym],
}
}
fn build_from_lengths(&mut self, lengths: &[u8], num_syms: usize) {
let mut bl_count = [0u16; MAX_BITS + 1];
let mut max_bits: usize = 0;
for i in 0..num_syms {
let bl = lengths[i] as usize;
if bl > 0 {
bl_count[bl] += 1;
if bl > max_bits {
max_bits = bl;
}
}
}
let mut code: u16 = 0;
let mut next_code = [0u16; MAX_BITS + 1];
for bits in 1..=max_bits {
code = (code + bl_count[bits - 1]) << 1;
next_code[bits] = code;
}
for i in 0..num_syms {
let bl = lengths[i] as usize;
if bl > 0 {
let c = next_code[bl];
next_code[bl] += 1;
self.codes[i] = (c, bl as u8);
} else {
self.codes[i] = (0, 0);
}
}
}
fn write_sym(&self, writer: &mut BitWriter, sym: u16) {
let (code, bits) = self.codes[sym as usize];
if bits > 0 {
if bits <= 24 {
writer.write_bits_rev(code as u32, bits as u32);
}
}
}
fn get_code(&self, sym: u16) -> (u16, u8) {
self.codes[sym as usize]
}
}
fn limit_code_lengths(lengths: &mut [u8], num_syms: usize, max_bits: usize) {
let mut overage: i32 = 0;
for i in 0..num_syms {
if lengths[i] > max_bits as u8 {
overage += 1 << (lengths[i] as i32 - max_bits as i32);
}
}
while overage > 0 {
let mut best_bl = max_bits - 1;
while best_bl > 0 {
if lengths.iter().take(num_syms).any(|&l| l as usize == best_bl) {
break;
}
if best_bl == 1 {
break;
}
best_bl -= 1;
}
if best_bl == 0 {
break;
}
let step: i32 = 1 << (max_bits - best_bl);
if step <= overage {
let mut found = false;
for i in 0..num_syms {
if lengths[i] as usize == best_bl {
lengths[i] += 1;
overage -= step;
found = true;
break;
}
}
if !found {
break;
}
} else {
let mut shifted: i32 = 0;
for i in 0..num_syms {
if lengths[i] as usize == max_bits {
for j in 0..num_syms {
if lengths[j] as usize == best_bl {
lengths[j] += 1;
overage -= 1 << (lengths[i] as i32 - max_bits as i32);
shifted += 1;
if shifted * (1 << (max_bits - best_bl)) > overage {
break;
}
}
}
break;
}
}
if shifted == 0 {
break;
}
}
}
for i in 0..num_syms {
if lengths[i] > max_bits as u8 {
lengths[i] = max_bits as u8;
}
}
}
fn build_huffman_lengths(freqs: &[u32], lengths: &mut [u8], num_syms: usize, max_bits: usize) {
let mut indices: Vec<usize> = (0..num_syms).collect();
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct HeapNode {
freq: u32,
node: i32, }
impl Ord for HeapNode {
fn cmp(&self, other: &Self) -> cmp::Ordering {
other.freq.cmp(&self.freq)
.then_with(|| other.node.cmp(&self.node))
}
}
impl PartialOrd for HeapNode {
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
Some(self.cmp(other))
}
}
use std::collections::BinaryHeap;
let mut heap = BinaryHeap::new();
let mut sym_count = 0;
for i in 0..num_syms {
if freqs[i] > 0 {
heap.push(HeapNode { freq: freqs[i], node: i as i32 });
sym_count += 1;
}
}
if sym_count == 0 {
return;
}
if sym_count == 1 {
for i in 0..num_syms {
if freqs[i] > 0 {
lengths[i] = 1;
return;
}
}
}
let max_nodes = 2 * num_syms - 1;
let mut left_child = vec![-1i32; max_nodes];
let mut right_child = vec![-1i32; max_nodes];
let mut parent = vec![-1i32; max_nodes];
let mut node_count = num_syms as i32;
while heap.len() >= 2 {
let a = heap.pop().unwrap();
let b = heap.pop().unwrap();
let new_node = node_count;
node_count += 1;
left_child[new_node as usize] = a.node;
right_child[new_node as usize] = b.node;
if a.node >= 0 { parent[a.node as usize] = new_node; }
if b.node >= 0 { parent[b.node as usize] = new_node; }
heap.push(HeapNode { freq: a.freq + b.freq, node: new_node });
}
for i in 0..num_syms {
if freqs[i] > 0 {
let mut d: u8 = 0;
let mut n = i as i32;
while parent[n as usize] >= 0 {
d += 1;
n = parent[n as usize];
if d > max_bits as u8 {
d = max_bits as u8;
break;
}
}
lengths[i] = d;
}
}
limit_code_lengths(lengths, num_syms, max_bits);
}
fn decode_length(reader: &mut BitReader, sym: u16) -> MinizResult<u16> {
if sym < 257 || sym > 285 {
return Err(MinizError::DataError(format!("invalid length symbol {}", sym)));
}
let idx = (sym - 257) as usize;
let base = LENGTH_BASE[idx];
let extra = LENGTH_EXTRA[idx] as u32;
if extra > 0 {
let extra_bits = reader.read_bits(extra)? as u16;
Ok(base + extra_bits)
} else {
Ok(base)
}
}
fn decode_distance(reader: &mut BitReader, sym: u16) -> MinizResult<u16> {
if sym > 29 {
return Err(MinizError::DataError(format!("invalid distance symbol {}", sym)));
}
let idx = sym as usize;
let base = DIST_BASE[idx];
let extra = DIST_EXTRA[idx] as u32;
if extra > 0 {
let extra_bits = reader.read_bits(extra)? as u16;
Ok(base + extra_bits)
} else {
Ok(base)
}
}
struct WindowBuffer {
buf: Vec<u8>,
pos: usize, size: usize, }
impl WindowBuffer {
fn new() -> Self {
WindowBuffer {
buf: vec![0u8; WINDOW_SIZE * 2],
pos: 0,
size: 0,
}
}
fn push(&mut self, byte: u8) {
let idx = self.pos & (WINDOW_SIZE - 1);
self.buf[idx] = byte;
self.buf[idx + WINDOW_SIZE] = byte;
self.pos += 1;
if self.size < WINDOW_SIZE {
self.size += 1;
}
}
fn copy_match(&mut self, dist: usize, len: usize, output: &mut Vec<u8>) {
let eff_dist = cmp::min(dist, self.size);
let start = (self.pos.wrapping_sub(eff_dist)) & (WINDOW_SIZE - 1);
for i in 0..len {
let idx = (start + i) & (WINDOW_SIZE - 1);
let b = self.buf[idx];
self.push(b);
output.push(b);
}
}
fn get_byte(&self, offset: usize) -> u8 {
let idx = (self.pos.wrapping_sub(offset + 1)) & (WINDOW_SIZE - 1);
self.buf[idx]
}
}
struct InflateState {
window: WindowBuffer,
output: Vec<u8>,
fixed_litlen_table: Option<HuffmanTable>,
fixed_dist_table: Option<HuffmanTable>,
}
impl InflateState {
fn new() -> Self {
InflateState {
window: WindowBuffer::new(),
output: Vec::new(),
fixed_litlen_table: None,
fixed_dist_table: None,
}
}
fn init_fixed_tables(&mut self) -> MinizResult<()> {
if self.fixed_litlen_table.is_some() {
return Ok(());
}
let litlen_lengths = build_fixed_litlen_lengths();
let dist_lengths = build_fixed_dist_lengths();
self.fixed_litlen_table =
Some(HuffmanTable::from_lengths(&litlen_lengths, MAX_LITLEN_CODES)?);
self.fixed_dist_table =
Some(HuffmanTable::from_lengths(&dist_lengths, MAX_DIST_CODES)?);
Ok(())
}
fn decode_stored_block(&mut self, reader: &mut BitReader) -> MinizResult<()> {
reader.align_to_byte();
let len = reader.read_bits(16)? as u16;
let nlen = reader.read_bits(16)? as u16;
if len != !nlen {
return Err(MinizError::DataError(format!(
"stored block length mismatch: {} vs {}",
len, !nlen
)));
}
let mut buf = vec![0u8; len as usize];
reader.read_bytes(&mut buf)?;
for &b in &buf {
self.window.push(b);
self.output.push(b);
}
Ok(())
}
fn decode_huffman_block(
&mut self,
reader: &mut BitReader,
litlen_table: &HuffmanTable,
dist_table: &HuffmanTable,
) -> MinizResult<()> {
loop {
let sym = litlen_table.decode_proper(reader)?;
if sym < 256 {
self.window.push(sym as u8);
self.output.push(sym as u8);
} else if sym == END_OF_BLOCK {
return Ok(());
} else {
let length = decode_length(reader, sym)? as usize;
let dist_sym = dist_table.decode_proper(reader)?;
let distance = decode_distance(reader, dist_sym)? as usize;
if distance > WINDOW_SIZE {
return Err(MinizError::DataError(format!(
"distance {} exceeds window size",
distance
)));
}
self.window.copy_match(distance, length, &mut self.output);
}
}
}
fn decode_dynamic_header(
&mut self,
reader: &mut BitReader,
) -> MinizResult<(HuffmanTable, HuffmanTable)> {
let hlit = reader.read_bits(5)? as usize + 257; let hdist = reader.read_bits(5)? as usize + 1; let hclen = reader.read_bits(4)? as usize + 4;
if hlit > MAX_LITLEN_CODES || hdist > MAX_DIST_CODES {
return Err(MinizError::DataError(format!(
"too many codes: hlit={}, hdist={}",
hlit, hdist
)));
}
let mut code_length_lengths = [0u8; BL_CODES];
for i in 0..hclen {
let idx = CODELEN_ORDER[i] as usize;
code_length_lengths[idx] = reader.read_bits(3)? as u8;
}
let code_len_table = HuffmanTable::from_lengths(&code_length_lengths, BL_CODES)?;
let mut lengths = vec![0u8; hlit + hdist];
let mut i = 0;
while i < hlit + hdist {
if code_len_table.min_bits == 0 {
i += 1;
continue;
}
let sym = code_len_table.decode_proper(reader)?;
if sym < 16 {
lengths[i] = sym as u8;
i += 1;
} else if sym == 16 {
if i == 0 {
return Err(MinizError::DataError("repeat code 16 with no previous length".into()));
}
let repeat = reader.read_bits(2)? as usize + 3;
let prev = lengths[i - 1];
for _ in 0..repeat {
if i >= lengths.len() {
break;
}
lengths[i] = prev;
i += 1;
}
} else if sym == 17 {
let repeat = reader.read_bits(3)? as usize + 3;
for _ in 0..repeat {
if i >= lengths.len() {
break;
}
lengths[i] = 0;
i += 1;
}
} else if sym == 18 {
let repeat = reader.read_bits(7)? as usize + 11;
for _ in 0..repeat {
if i >= lengths.len() {
break;
}
lengths[i] = 0;
i += 1;
}
} else {
return Err(MinizError::DataError(format!("invalid code length symbol {}", sym)));
}
}
let litlen_table = HuffmanTable::from_lengths(&lengths[..hlit], hlit)?;
let dist_table = HuffmanTable::from_lengths(&lengths[hlit..hlit + hdist], hdist)?;
Ok((litlen_table, dist_table))
}
fn inflate_blocks(&mut self, reader: &mut BitReader) -> MinizResult<()> {
loop {
let bfinal = reader.read_bits(1)?;
let btype = reader.read_bits(2)?;
match btype {
0 => {
self.decode_stored_block(reader)?;
}
1 => {
self.init_fixed_tables()?;
let lit = self.fixed_litlen_table.as_ref().unwrap();
let dist = self.fixed_dist_table.as_ref().unwrap();
self.decode_huffman_block(reader, lit, dist)?;
}
2 => {
let (lit_table, dist_table) = self.decode_dynamic_header(reader)?;
self.decode_huffman_block(reader, &lit_table, &dist_table)?;
}
3 => {
return Err(MinizError::DataError("reserved block type 3".into()));
}
_ => unreachable!(),
}
if bfinal == 1 {
break;
}
}
Ok(())
}
fn inflate(&mut self, data: &[u8]) -> MinizResult<Vec<u8>> {
self.output.clear();
let mut reader = BitReader::new(data);
self.inflate_blocks(&mut reader)?;
Ok(std::mem::take(&mut self.output))
}
}
pub fn raw_inflate(data: &[u8]) -> MinizResult<Vec<u8>> {
let mut state = InflateState::new();
state.inflate(data)
}
pub fn zlib_inflate(data: &[u8]) -> MinizResult<Vec<u8>> {
if data.len() < 6 {
return Err(MinizError::DataError("zlib data too short".into()));
}
let cmf = data[0];
let flg = data[1];
let cm = cmf & 0x0F;
let cinfo = (cmf >> 4) & 0x0F;
if cm != 8 {
return Err(MinizError::DataError(format!("unsupported zlib CM {}", cm)));
}
if cinfo > 7 {
return Err(MinizError::DataError(format!("invalid zlib CINFO {}", cinfo)));
}
if ((cmf as u16 * 256 + flg as u16) % 31) != 0 {
return Err(MinizError::DataError("zlib header checksum mismatch".into()));
}
let fdict = (flg >> 5) & 1;
let payload_start = if fdict != 0 {
if data.len() < 10 {
return Err(MinizError::DataError("zlib data with dict too short".into()));
}
6
} else {
2
};
let payload_end = data.len() - 4;
if payload_end < payload_start {
return Err(MinizError::DataError("zlib data malformed".into()));
}
let result = raw_inflate(&data[payload_start..payload_end])?;
let adler_stored = u32::from_be_bytes([
data[payload_end],
data[payload_end + 1],
data[payload_end + 2],
data[payload_end + 3],
]);
let adler_computed = adler32(&result);
if adler_stored != adler_computed {
return Err(MinizError::DataError(format!(
"zlib Adler-32 mismatch: stored={:08X}, computed={:08X}",
adler_stored, adler_computed
)));
}
Ok(result)
}
pub fn gzip_inflate(data: &[u8]) -> MinizResult<Vec<u8>> {
if data.len() < 18 {
return Err(MinizError::DataError("gzip data too short".into()));
}
let id1 = data[0];
let id2 = data[1];
let cm = data[2];
let flg = data[3];
if id1 != GZIP_ID1 || id2 != GZIP_ID2 {
return Err(MinizError::DataError("not a gzip stream".into()));
}
if cm != GZIP_CM_DEFLATE {
return Err(MinizError::DataError(format!("unsupported gzip CM {}", cm)));
}
let mut offset = 10usize;
if flg & GZIP_FLAG_FEXTRA != 0 {
if offset + 2 > data.len() {
return Err(MinizError::DataError("gzip extra field truncated".into()));
}
let xlen = u16::from_le_bytes([data[offset], data[offset + 1]]) as usize;
offset += 2 + xlen;
}
if flg & GZIP_FLAG_FNAME != 0 {
while offset < data.len() && data[offset] != 0 {
offset += 1;
}
offset += 1; }
if flg & GZIP_FLAG_FCOMMENT != 0 {
while offset < data.len() && data[offset] != 0 {
offset += 1;
}
offset += 1;
}
if flg & GZIP_FLAG_FHCRC != 0 {
if offset + 2 > data.len() {
return Err(MinizError::DataError("gzip FHCRC truncated".into()));
}
let hcrc_stored = u16::from_le_bytes([data[offset], data[offset + 1]]);
let hcrc_computed = crc32(&data[0..offset]) as u16;
if hcrc_stored != hcrc_computed {
return Err(MinizError::DataError("gzip header CRC mismatch".into()));
}
offset += 2;
}
if data.len() < offset + 8 {
return Err(MinizError::DataError("gzip data too short after headers".into()));
}
let deflate_end = data.len() - 8;
let result = raw_inflate(&data[offset..deflate_end])?;
let crc_stored = u32::from_le_bytes([
data[deflate_end],
data[deflate_end + 1],
data[deflate_end + 2],
data[deflate_end + 3],
]);
let crc_computed = crc32(&result);
if crc_stored != crc_computed {
return Err(MinizError::DataError(format!(
"gzip CRC-32 mismatch: stored={:08X}, computed={:08X}",
crc_stored, crc_computed
)));
}
let isize_stored = u32::from_le_bytes([
data[deflate_end + 4],
data[deflate_end + 5],
data[deflate_end + 6],
data[deflate_end + 7],
]);
let isize_computed = (result.len() & 0xFFFFFFFF) as u32;
if isize_stored != isize_computed {
return Err(MinizError::DataError(format!(
"gzip ISIZE mismatch: stored={}, computed={}",
isize_stored, isize_computed
)));
}
Ok(result)
}
struct HashTable {
head: Vec<i32>,
prev: Vec<i32>,
hash_size: usize,
window_size: usize,
}
impl HashTable {
fn new(hash_bits: usize, window_size: usize) -> Self {
let hash_size = 1 << hash_bits;
HashTable {
head: vec![-1i32; hash_size],
prev: vec![-1i32; window_size],
hash_size,
window_size,
}
}
fn reset(&mut self) {
self.head.fill(-1);
self.prev.fill(-1);
}
fn hash3(b0: u8, b1: u8, b2: u8) -> usize {
(((b0 as u32) << (HASH_SHIFT * 2)) ^ ((b1 as u32) << HASH_SHIFT) ^ (b2 as u32)) as usize
}
fn insert(&mut self, pos: usize, window: &[u8]) {
if pos + 2 >= window.len() {
return;
}
let h = Self::hash3(window[pos], window[pos + 1], window[pos + 2]) & (self.hash_size - 1);
let win_pos = pos & (self.window_size - 1);
self.prev[win_pos] = self.head[h];
self.head[h] = win_pos as i32;
}
fn find_match(
&self,
pos: usize,
window: &[u8],
max_chain: usize,
nice_len: usize,
max_dist: usize,
) -> (usize, usize) {
if pos + MIN_MATCH > window.len() {
return (0, 0);
}
let h = Self::hash3(window[pos], window[pos + 1], window[pos + 2]) & (self.hash_size - 1);
let mut best_len = MIN_MATCH - 1;
let mut best_dist = 0usize;
let limit = if pos > max_dist { pos - max_dist } else { 0 };
let mut chain_len = 0usize;
let mut cur = self.head[h] as i32;
while cur >= 0 && chain_len < max_chain {
let cur_pos = cur as usize;
if cur_pos < limit {
break;
}
let dist = pos - cur_pos;
if dist > 0 && dist <= max_dist {
let mut len = 0usize;
while len < MAX_MATCH_LEN
&& pos + len < window.len()
&& window[cur_pos + len] == window[pos + len]
{
len += 1;
}
if len > best_len {
best_len = len;
best_dist = dist;
if best_len >= nice_len {
break;
}
}
}
cur = self.prev[cur_pos];
chain_len += 1;
}
(best_dist, best_len)
}
}
#[derive(Debug, Clone, Copy, Default)]
struct Match {
length: usize,
distance: usize,
}
#[derive(Debug, Clone, Copy)]
enum LitLenEntry {
Literal(u8),
Match { length: u16, distance: u16 },
EndBlock,
}
struct DeflateState {
window: Vec<u8>,
window_pos: usize,
hash_table: HashTable,
level: u8,
litlen_freqs: [u32; MAX_LITLEN_CODES],
dist_freqs: [u32; MAX_DIST_CODES],
pending: Vec<LitLenEntry>,
bit_writer: BitWriter,
}
impl DeflateState {
fn new(level: u8) -> Self {
let hb = match level {
0..=3 => 13,
4..=6 => 14,
_ => HASH_BITS,
};
DeflateState {
window: Vec::with_capacity(LZ_BUFFER_SIZE),
window_pos: 0,
hash_table: HashTable::new(hb, WINDOW_SIZE),
level,
litlen_freqs: [0u32; MAX_LITLEN_CODES],
dist_freqs: [0u32; MAX_DIST_CODES],
pending: Vec::with_capacity(16384),
bit_writer: BitWriter::new(),
}
}
fn count_litlen(&mut self, sym: u16) {
if (sym as usize) < MAX_LITLEN_CODES {
self.litlen_freqs[sym as usize] += 1;
}
}
fn count_dist(&mut self, sym: u16) {
if (sym as usize) < MAX_DIST_CODES {
self.dist_freqs[sym as usize] += 1;
}
}
fn length_symbol(len: usize) -> u16 {
if len < 3 || len > 258 {
return 0;
}
for i in 0..28 {
let base = LENGTH_BASE[i] as usize;
let extra = LENGTH_EXTRA[i] as usize;
let max_val = base + (1 << extra) - 1;
if len >= base && len <= max_val {
return (257 + i) as u16;
}
}
if len == LENGTH_BASE[28] as usize {
return 285;
}
0
}
fn distance_symbol(dist: usize) -> u16 {
if dist < 1 || dist > 32768 {
return 0;
}
for i in 0..30 {
let base = DIST_BASE[i] as usize;
let extra = DIST_EXTRA[i] as usize;
let max_val = base + (1 << extra) - 1;
if dist >= base && dist <= max_val {
return i as u16;
}
}
0
}
fn lz77_process(&mut self, input: &[u8]) {
let level = self.level as usize;
let good_len = GOOD_LENGTH[level];
let max_lazy = MAX_LAZY[level];
let nice_len = NICE_LENGTH[level];
let max_chain = MAX_CHAIN[level];
let start_pos = self.window.len();
self.window.extend_from_slice(input);
let mut pos = start_pos;
while pos < self.window.len() {
if pos + MIN_MATCH > self.window.len() {
self.pending.push(LitLenEntry::Literal(self.window[pos]));
pos += 1;
continue;
}
self.hash_table.insert(pos, &self.window);
let max_dist = cmp::min(pos, MAX_MATCH_DIST);
let (dist, len) = self.hash_table.find_match(
pos,
&self.window,
max_chain,
nice_len,
max_dist,
);
if len < MIN_MATCH {
self.pending.push(LitLenEntry::Literal(self.window[pos]));
pos += 1;
} else {
let mut best_dist = dist;
let mut best_len = len;
if max_lazy >= 4 && pos + 1 < self.window.len() {
self.hash_table.insert(pos + 1, &self.window);
let max_dist2 = cmp::min(pos + 1, MAX_MATCH_DIST);
let (dist2, len2) = self.hash_table.find_match(
pos + 1,
&self.window,
max_chain,
nice_len,
max_dist2,
);
if len2 > best_len + 1 {
self.pending.push(LitLenEntry::Literal(self.window[pos]));
pos += 1;
best_dist = dist2;
best_len = len2;
}
}
let length_sym = Self::length_symbol(best_len);
let distance_sym = Self::distance_symbol(best_dist);
self.pending.push(LitLenEntry::Match {
length: best_len as u16,
distance: best_dist as u16,
});
for i in 1..best_len {
if pos + i < self.window.len() {
self.hash_table.insert(pos + i, &self.window);
}
}
pos += best_len;
}
}
}
fn write_dynamic_block(&mut self, is_final: bool) -> MinizResult<()> {
self.litlen_freqs[END_OF_BLOCK as usize] = cmp::max(
self.litlen_freqs[END_OF_BLOCK as usize],
1,
);
let mut max_litlen = MAX_LITLEN_CODES;
while max_litlen > 0 && self.litlen_freqs[max_litlen - 1] == 0 {
max_litlen -= 1;
}
max_litlen = cmp::max(max_litlen, 257);
let mut max_dist = MAX_DIST_CODES;
while max_dist > 0 && self.dist_freqs[max_dist - 1] == 0 {
max_dist -= 1;
}
max_dist = cmp::max(max_dist, 1);
let mut litlen_lengths = vec![0u8; max_litlen];
let mut dist_lengths = vec![0u8; max_dist];
build_huffman_lengths(&self.litlen_freqs[..max_litlen], &mut litlen_lengths, max_litlen, MAX_BITS);
build_huffman_lengths(&self.dist_freqs[..max_dist], &mut dist_lengths, max_dist, MAX_BITS);
let mut litlen_enc = HuffmanEncoder::new(max_litlen);
litlen_enc.build_from_lengths(&litlen_lengths, max_litlen);
let mut dist_enc = HuffmanEncoder::new(max_dist);
dist_enc.build_from_lengths(&dist_lengths, max_dist);
let mut code_length_freqs = [0u32; BL_CODES];
let combined_lengths: Vec<u8> = litlen_lengths
.iter()
.chain(dist_lengths.iter())
.copied()
.collect();
let mut rle: Vec<u8> = Vec::with_capacity(combined_lengths.len());
{
let mut i = 0;
while i < combined_lengths.len() {
let len = combined_lengths[i];
if len == 0 {
let mut zcount = 0;
while i + zcount < combined_lengths.len()
&& combined_lengths[i + zcount] == 0
&& zcount < 138
{
zcount += 1;
}
if zcount < 3 {
for _ in 0..zcount {
rle.push(0);
}
} else if zcount <= 10 {
rle.push(17);
rle.push((zcount - 3) as u8);
} else {
let c = cmp::min(zcount, 138);
rle.push(18);
rle.push((c - 11) as u8);
}
i += zcount;
} else {
let mut rcount = 1;
while i + rcount < combined_lengths.len()
&& combined_lengths[i + rcount] == len
&& rcount < 6
{
rcount += 1;
}
if rcount < 3 {
for _ in 0..rcount {
rle.push(len);
}
} else {
rle.push(len);
rle.push(16);
rle.push((rcount - 3) as u8);
}
i += rcount;
}
}
}
for &cl in &rle {
if (cl as usize) < BL_CODES {
code_length_freqs[cl as usize] += 1;
}
}
let mut cl_lengths = [0u8; BL_CODES];
build_huffman_lengths(&code_length_freqs, &mut cl_lengths, BL_CODES, 7);
let mut cl_enc = HuffmanEncoder::new(BL_CODES);
cl_enc.build_from_lengths(&cl_lengths, BL_CODES);
let mut hclen = BL_CODES;
while hclen > 4 && cl_lengths[CODELEN_ORDER[hclen - 1] as usize] == 0 {
hclen -= 1;
}
self.bit_writer.write_bit(if is_final { 1 } else { 0 });
self.bit_writer.write_bits(2, 2);
self.bit_writer.write_bits((max_litlen - 257) as u32, 5);
self.bit_writer.write_bits((max_dist - 1) as u32, 5);
self.bit_writer.write_bits((hclen - 4) as u32, 4);
for i in 0..hclen {
let idx = CODELEN_ORDER[i] as usize;
self.bit_writer.write_bits(cl_lengths[idx] as u32, 3);
}
for &cl in &rle {
if cl < 16 {
cl_enc.write_sym(&mut self.bit_writer, cl as u16);
} else {
cl_enc.write_sym(&mut self.bit_writer, cl as u16);
match cl {
16 => self.bit_writer.write_bits(rle[1] as u32, 2),
17 => self.bit_writer.write_bits(rle[1] as u32, 3),
18 => self.bit_writer.write_bits(rle[1] as u32, 7),
_ => {}
}
}
}
for entry in &self.pending {
match *entry {
LitLenEntry::Literal(b) => {
litlen_enc.write_sym(&mut self.bit_writer, b as u16);
}
LitLenEntry::Match { length, distance } => {
let lsym = Self::length_symbol(length as usize);
litlen_enc.write_sym(&mut self.bit_writer, lsym);
if lsym >= 257 {
let idx = (lsym - 257) as usize;
let extra = LENGTH_EXTRA[idx] as u32;
if extra > 0 {
let base = LENGTH_BASE[idx] as u16;
let offset = length - base;
self.bit_writer.write_bits(offset as u32, extra);
}
}
let dsym = Self::distance_symbol(distance as usize);
dist_enc.write_sym(&mut self.bit_writer, dsym);
if dsym < 30 {
let idx = dsym as usize;
let extra = DIST_EXTRA[idx] as u32;
if extra > 0 {
let base = DIST_BASE[idx] as u16;
let offset = distance - base;
self.bit_writer.write_bits(offset as u32, extra);
}
}
}
LitLenEntry::EndBlock => {}
}
}
litlen_enc.write_sym(&mut self.bit_writer, END_OF_BLOCK);
Ok(())
}
fn write_stored_block(&mut self, data: &[u8], is_final: bool) {
self.bit_writer.align_to_byte();
self.bit_writer.write_bit(if is_final { 1 } else { 0 });
self.bit_writer.write_bits(0, 2); self.bit_writer.align_to_byte();
let len = data.len() as u16;
let nlen = !len;
self.bit_writer.write_bits(len as u32, 16);
self.bit_writer.write_bits(nlen as u32, 16);
self.bit_writer.write_bytes(data);
}
fn write_fixed_block(&mut self, is_final: bool) {
let litlen_lengths = build_fixed_litlen_lengths();
let dist_lengths = build_fixed_dist_lengths();
let mut litlen_enc = HuffmanEncoder::new(MAX_LITLEN_CODES);
litlen_enc.build_from_lengths(&litlen_lengths, MAX_LITLEN_CODES);
let mut dist_enc = HuffmanEncoder::new(MAX_DIST_CODES);
dist_enc.build_from_lengths(&dist_lengths, MAX_DIST_CODES);
self.bit_writer.write_bit(if is_final { 1 } else { 0 });
self.bit_writer.write_bits(1, 2);
for entry in &self.pending {
match *entry {
LitLenEntry::Literal(b) => {
litlen_enc.write_sym(&mut self.bit_writer, b as u16);
}
LitLenEntry::Match { length, distance } => {
let lsym = Self::length_symbol(length as usize);
litlen_enc.write_sym(&mut self.bit_writer, lsym);
if lsym >= 257 {
let idx = (lsym - 257) as usize;
let extra = LENGTH_EXTRA[idx] as u32;
if extra > 0 {
let base = LENGTH_BASE[idx] as u16;
self.bit_writer.write_bits((length - base) as u32, extra);
}
}
let dsym = Self::distance_symbol(distance as usize);
dist_enc.write_sym(&mut self.bit_writer, dsym);
if dsym < 30 {
let idx = dsym as usize;
let extra = DIST_EXTRA[idx] as u32;
if extra > 0 {
let base = DIST_BASE[idx] as u16;
self.bit_writer.write_bits((distance - base) as u32, extra);
}
}
}
LitLenEntry::EndBlock => {}
}
}
litlen_enc.write_sym(&mut self.bit_writer, END_OF_BLOCK);
}
fn deflate(&mut self, input: &[u8]) -> Vec<u8> {
if self.level == 0 {
self.write_stored_block(input, true);
return self.bit_writer.finish();
}
self.lz77_process(input);
if self.level <= 2 {
self.write_fixed_block(true);
} else {
let _ = self.write_dynamic_block(true);
}
self.bit_writer.finish()
}
}
pub fn raw_deflate(input: &[u8], level: u8) -> Vec<u8> {
let level = cmp::min(level, 9);
let mut state = DeflateState::new(level);
state.deflate(input)
}
pub fn zlib_deflate(input: &[u8], level: u8) -> Vec<u8> {
let level = cmp::min(level, 9);
let raw = raw_deflate(input, level);
let mut out = Vec::with_capacity(raw.len() + 6);
let cmf: u8 = 0x78;
let level_hint = match level {
0..=1 => 0,
2..=5 => 1,
6..=7 => 2,
_ => 3,
};
let mut flg: u8 = (level_hint << 6) | 0x20; let check = (cmf as u16 * 256 + flg as u16) % 31;
if check != 0 {
flg += (31 - check as u8) % 31;
}
out.push(cmf);
out.push(flg);
out.extend_from_slice(&raw);
let a32 = adler32(input);
out.extend_from_slice(&a32.to_be_bytes());
out
}
pub fn gzip_deflate(input: &[u8], level: u8) -> Vec<u8> {
let level = cmp::min(level, 9);
let raw = raw_deflate(input, level);
let mut out = Vec::with_capacity(raw.len() + 18);
out.push(GZIP_ID1);
out.push(GZIP_ID2);
out.push(GZIP_CM_DEFLATE); out.push(0); out.extend_from_slice(&0u32.to_le_bytes()); let xfl = match level {
0 => 0,
1 => 4, 9 => 2, _ => 0,
};
out.push(xfl);
out.push(255);
out.extend_from_slice(&raw);
let crc = crc32(input);
out.extend_from_slice(&crc.to_le_bytes());
let isize = (input.len() & 0xFFFFFFFF) as u32;
out.extend_from_slice(&isize.to_le_bytes());
out
}
pub struct DeflateStream {
level: u8,
buffer: Vec<u8>,
block_size: usize,
total_in: u64,
}
impl DeflateStream {
pub fn new(level: u8) -> Self {
DeflateStream {
level: cmp::min(level, 9),
buffer: Vec::with_capacity(65536),
block_size: 16384,
total_in: 0,
}
}
pub fn update(&mut self, input: &[u8]) -> Vec<u8> {
self.buffer.extend_from_slice(input);
let mut out = Vec::new();
while self.buffer.len() >= self.block_size {
let block: Vec<u8> = self.buffer.drain(..self.block_size).collect();
if self.total_in == 0 {
out.extend_from_slice(&raw_deflate(&block, self.level));
} else {
out.extend_from_slice(&raw_deflate(&block, self.level));
}
self.total_in += block.len() as u64;
}
out
}
pub fn finish(&mut self) -> Vec<u8> {
if self.buffer.is_empty() {
return Vec::new();
}
let block: Vec<u8> = std::mem::take(&mut self.buffer);
raw_deflate(&block, self.level)
}
}
pub struct InflateStream {
state: InflateState,
buffer: Vec<u8>,
}
impl InflateStream {
pub fn new() -> Self {
InflateStream {
state: InflateState::new(),
buffer: Vec::new(),
}
}
pub fn update(&mut self, input: &[u8]) -> MinizResult<Vec<u8>> {
self.buffer.extend_from_slice(input);
self.state.inflate(&self.buffer)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CompressFormat {
Raw,
Zlib,
Gzip,
}
pub fn compress(input: &[u8], format: CompressFormat, level: u8) -> Vec<u8> {
match format {
CompressFormat::Raw => raw_deflate(input, level),
CompressFormat::Zlib => zlib_deflate(input, level),
CompressFormat::Gzip => gzip_deflate(input, level),
}
}
pub fn decompress(input: &[u8]) -> MinizResult<Vec<u8>> {
if input.len() < 2 {
return raw_inflate(input);
}
if input[0] == GZIP_ID1 && input[1] == GZIP_ID2 {
gzip_inflate(input)
} else if (input[0] & 0x0F) == 8 && input[0] > 0x70 {
zlib_inflate(input)
} else {
raw_inflate(input)
}
}
pub fn decompress_with_format(input: &[u8], format: CompressFormat) -> MinizResult<Vec<u8>> {
match format {
CompressFormat::Raw => raw_inflate(input),
CompressFormat::Zlib => zlib_inflate(input),
CompressFormat::Gzip => gzip_inflate(input),
}
}
pub struct CliConfig {
pub mode: CliMode,
pub format: CompressFormat,
pub level: u8,
pub verify: bool,
pub list: bool,
pub verbose: bool,
pub input: Option<String>,
pub output: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CliMode {
Compress,
Decompress,
Auto,
}
impl Default for CliConfig {
fn default() -> Self {
CliConfig {
mode: CliMode::Auto,
format: CompressFormat::Zlib,
level: 6,
verify: false,
list: false,
verbose: false,
input: None,
output: None,
}
}
}
pub fn parse_cli_args(args: &[String]) -> MinizResult<CliConfig> {
let mut config = CliConfig::default();
let mut i = 1; while i < args.len() {
let arg = &args[i];
match arg.as_str() {
"-c" | "--compress" => config.mode = CliMode::Compress,
"-d" | "--decompress" => config.mode = CliMode::Decompress,
"-z" | "--zlib" => config.format = CompressFormat::Zlib,
"-g" | "--gzip" => config.format = CompressFormat::Gzip,
"-r" | "--raw" => config.format = CompressFormat::Raw,
"-l" | "--level" => {
i += 1;
if i < args.len() {
config.level = args[i].parse::<u8>().unwrap_or(6);
if config.level > 9 {
config.level = 9;
}
}
}
"-V" | "--verify" => config.verify = true,
"--list" => config.list = true,
"-v" | "--verbose" => config.verbose = true,
"-o" | "--output" => {
i += 1;
if i < args.len() {
config.output = Some(args[i].clone());
}
}
"-h" | "--help" => {
eprintln!("tool_miniz - miniz native Rust reimplementation");
eprintln!("Usage: tool_miniz [OPTIONS] [INPUT]");
eprintln!("Options:");
eprintln!(" -c, --compress Compress input");
eprintln!(" -d, --decompress Decompress input");
eprintln!(" -z, --zlib Zlib format (default)");
eprintln!(" -g, --gzip Gzip format");
eprintln!(" -r, --raw Raw deflate format");
eprintln!(" -l, --level N Compression level (0-9, default: 6)");
eprintln!(" -o, --output FILE Output file");
eprintln!(" -V, --verify Verify integrity");
eprintln!(" -v, --verbose Verbose output");
eprintln!(" -h, --help Show this help");
std::process::exit(0);
}
_ => {
if config.input.is_none() && !arg.starts_with('-') {
config.input = Some(arg.clone());
}
}
}
i += 1;
}
Ok(config)
}
pub fn run_cli(config: CliConfig) -> MinizResult<()> {
let input_data: Vec<u8> = if let Some(ref path) = config.input {
std::fs::read(path).map_err(|e| MinizError::IoError(format!("read {}: {}", path, e)))?
} else {
let stdin = io::stdin();
let mut data = Vec::new();
stdin
.lock()
.read_to_end(&mut data)
.map_err(|e| MinizError::IoError(e.to_string()))?;
data
};
let output_data = match config.mode {
CliMode::Compress => {
if config.verbose {
eprintln!(
"Compressing {} bytes (format: {:?}, level: {})...",
input_data.len(),
config.format,
config.level
);
}
compress(&input_data, config.format, config.level)
}
CliMode::Decompress => {
if config.verbose {
eprintln!(
"Decompressing {} bytes (format: {:?})...",
input_data.len(),
config.format
);
}
decompress_with_format(&input_data, config.format)?
}
CliMode::Auto => {
if config.verbose {
eprintln!("Auto-detecting format for {} bytes...", input_data.len());
}
decompress(&input_data)?
}
};
if let Some(ref path) = config.output {
std::fs::write(path, &output_data)
.map_err(|e| MinizError::IoError(format!("write {}: {}", path, e)))?;
} else {
let stdout = io::stdout();
let mut handle = stdout.lock();
handle
.write_all(&output_data)
.map_err(|e| MinizError::IoError(e.to_string()))?;
handle.flush().map_err(|e| MinizError::IoError(e.to_string()))?;
}
if config.verify {
match config.mode {
CliMode::Compress => {
let decompressed = decompress_with_format(&output_data, config.format)?;
if decompressed == input_data {
eprintln!("Verification PASSED: round-trip integrity confirmed.");
} else {
eprintln!("Verification FAILED: round-trip mismatch!");
return Err(MinizError::DataError("round-trip verification failed".into()));
}
}
CliMode::Decompress | CliMode::Auto => {
let recompressed = compress(&output_data, config.format, config.level);
let re_decompressed = decompress_with_format(&recompressed, config.format)?;
if re_decompressed == output_data {
eprintln!("Verification PASSED.");
} else {
eprintln!("Verification FAILED.");
return Err(MinizError::DataError("verification failed".into()));
}
}
}
}
if config.verbose {
eprintln!(
"Done: {} -> {} bytes (ratio: {:.1}%)",
input_data.len(),
output_data.len(),
if input_data.len() > 0 {
(output_data.len() as f64 / input_data.len() as f64) * 100.0
} else {
0.0
}
);
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
fn round_trip_raw(data: &[u8], level: u8) {
let compressed = raw_deflate(data, level);
let decompressed = raw_inflate(&compressed).expect("raw_inflate failed");
assert_eq!(
data,
decompressed.as_slice(),
"raw round-trip mismatch at level {} ({} bytes in, {} compressed, {} out)",
level,
data.len(),
compressed.len(),
decompressed.len()
);
}
fn round_trip_zlib(data: &[u8], level: u8) {
let compressed = zlib_deflate(data, level);
let decompressed = zlib_inflate(&compressed).expect("zlib_inflate failed");
assert_eq!(
data,
decompressed.as_slice(),
"zlib round-trip mismatch at level {} ({} bytes in, {} compressed, {} out)",
level,
data.len(),
compressed.len(),
decompressed.len()
);
}
fn round_trip_gzip(data: &[u8], level: u8) {
let compressed = gzip_deflate(data, level);
let decompressed = gzip_inflate(&compressed).expect("gzip_inflate failed");
assert_eq!(
data,
decompressed.as_slice(),
"gzip round-trip mismatch at level {} ({} bytes in, {} compressed, {} out)",
level,
data.len(),
compressed.len(),
decompressed.len()
);
}
fn round_trip_auto(data: &[u8], level: u8) {
for format in &[CompressFormat::Raw, CompressFormat::Zlib, CompressFormat::Gzip] {
let compressed = compress(data, *format, level);
let decompressed = decompress(&compressed).expect("auto decompress failed");
assert_eq!(
data,
decompressed.as_slice(),
"auto round-trip mismatch for {:?} at level {}",
format,
level
);
let decompressed2 =
decompress_with_format(&compressed, *format).expect("explicit decompress failed");
assert_eq!(data, decompressed2.as_slice());
}
}
#[test]
fn test_empty_raw_all_levels() {
for level in 0..=9 {
round_trip_raw(b"", level);
}
}
#[test]
fn test_empty_zlib_all_levels() {
for level in 0..=9 {
round_trip_zlib(b"", level);
}
}
#[test]
fn test_empty_gzip_all_levels() {
for level in 0..=9 {
round_trip_gzip(b"", level);
}
}
#[test]
fn test_single_byte() {
for b in 0..=255u8 {
round_trip_zlib(&[b], 6);
}
}
#[test]
fn test_all_bytes_round_trip_zlib_level6() {
let data: Vec<u8> = (0..=255).collect();
round_trip_zlib(&data, 6);
}
#[test]
fn test_all_bytes_round_trip_raw_all_levels() {
let data: Vec<u8> = (0..=255).collect();
for level in 0..=9 {
round_trip_raw(&data, level);
}
}
#[test]
fn test_repeated_byte_zlib() {
for level in 0..=9 {
let data = vec![b'A'; 10000];
round_trip_zlib(&data, level);
}
}
#[test]
fn test_repeated_pattern_zlib() {
let pattern = b"Hello, World! ";
let data: Vec<u8> = pattern.iter().cycle().take(10000).copied().collect();
for level in 0..=9 {
round_trip_zlib(&data, level);
}
}
#[test]
fn test_random_data_zlib() {
let mut state: u32 = 12345;
let data: Vec<u8> = (0..4096)
.map(|_| {
state = state.wrapping_mul(1103515245).wrapping_add(12345);
(state >> 16) as u8
})
.collect();
for level in 0..=9 {
round_trip_zlib(&data, level);
}
}
#[test]
fn test_random_data_gzip() {
let mut state: u32 = 67890;
let data: Vec<u8> = (0..4096)
.map(|_| {
state = state.wrapping_mul(1103515245).wrapping_add(12345);
(state >> 16) as u8
})
.collect();
round_trip_gzip(&data, 6);
}
#[test]
fn test_large_data_zlib_level6() {
let data = vec![b'X'; 100000];
round_trip_zlib(&data, 6);
}
#[test]
fn test_large_data_raw_level9() {
let data = vec![b'Z'; 100000];
round_trip_raw(&data, 9);
}
#[test]
fn test_window_distance_32768() {
let mut data = vec![b'A'; 32768];
data.extend_from_slice(b"ABC");
round_trip_zlib(&data, 6);
}
#[test]
fn test_window_wraparound() {
let mut data = vec![b'B'; 33000];
data.push(b'C');
round_trip_zlib(&data, 6);
}
#[test]
fn test_rfc1951_stored_block() {
let stored: Vec<u8> = vec![
0x01, 0x05, 0x00, 0xFA, 0xFF, b'h', b'e', b'l', b'l', b'o',
];
let result = raw_inflate(&stored).expect("stored block inflate");
assert_eq!(result, b"hello");
}
#[test]
fn test_rfc1951_fixed_block_hello() {
let compressed = raw_deflate(b"hello", 1);
let decompressed = raw_inflate(&compressed).expect("fixed block decompress");
assert_eq!(decompressed, b"hello");
}
#[test]
fn test_known_vector_abc() {
let data = b"abcabcabcabcabcabcabcabcabcabc";
round_trip_zlib(data, 6);
round_trip_gzip(data, 6);
round_trip_raw(data, 6);
}
#[test]
fn test_crc32_known_values() {
assert_eq!(crc32(b""), 0x00000000);
assert_eq!(crc32(b"a"), 0xE8B7BE43);
assert_eq!(crc32(b"abc"), 0x352441C2);
assert_eq!(crc32(b"123456789"), 0xCBF43926);
}
#[test]
fn test_crc32_streaming() {
let data = b"Hello, World! This is a test of the CRC-32 streaming implementation.";
let crc_full = crc32(data);
let mid = data.len() / 2;
let crc1 = crc32(&data[..mid]);
let crc2 = crc32(&data[mid..]);
let combined = crc32_combine(crc1, crc2, (data.len() - mid) as u64);
assert_eq!(crc_full, combined, "CRC-32 combine mismatch");
}
#[test]
fn test_adler32_known_values() {
assert_eq!(adler32(b""), 0x00000001);
assert_eq!(adler32(b"a"), 0x00620062);
assert_eq!(adler32(b"abc"), 0x024D0127);
assert_eq!(adler32(b"Wikipedia"), 0x11E60398);
}
#[test]
fn test_auto_detect_format() {
let data = b"test data for auto-detection";
let gz = gzip_deflate(data, 6);
let out = decompress(&gz).expect("gzip auto-detect");
assert_eq!(out, data);
let zl = zlib_deflate(data, 6);
let out = decompress(&zl).expect("zlib auto-detect");
assert_eq!(out, data);
let rw = raw_deflate(data, 6);
let out = decompress(&rw).expect("raw auto-detect");
assert_eq!(out, data);
}
#[test]
fn test_compression_ratio_repeated() {
let data = vec![b'A'; 100000];
let compressed = zlib_deflate(&data, 9);
assert!(
compressed.len() < data.len() / 10,
"Expected good compression for repeated data, got {} vs {}",
compressed.len(),
data.len()
);
}
#[test]
fn test_level_progression() {
let data = b"The quick brown fox jumps over the lazy dog. ".repeat(100);
let mut prev_size = usize::MAX;
let mut strictly_smaller = false;
for level in 0..=9 {
let compressed = zlib_deflate(data.as_bytes(), level);
if compressed.len() <= prev_size {
if compressed.len() < prev_size {
strictly_smaller = true;
}
prev_size = compressed.len();
}
}
let c0 = zlib_deflate(data.as_bytes(), 0);
let c9 = zlib_deflate(data.as_bytes(), 9);
assert!(
c9.len() <= c0.len(),
"Level 9 should not be worse than level 0"
);
}
#[test]
fn test_gzip_header_structure() {
let data = b"gzip header test";
let gz = gzip_deflate(data, 6);
assert!(gz.len() >= 18);
assert_eq!(gz[0], 0x1F, "Gzip ID1");
assert_eq!(gz[1], 0x8B, "Gzip ID2");
assert_eq!(gz[2], 8, "Gzip CM (deflate)");
let crc_stored = u32::from_le_bytes([
gz[gz.len() - 8],
gz[gz.len() - 7],
gz[gz.len() - 6],
gz[gz.len() - 5],
]);
assert_eq!(crc_stored, crc32(data));
let isize_stored = u32::from_le_bytes([
gz[gz.len() - 4],
gz[gz.len() - 3],
gz[gz.len() - 2],
gz[gz.len() - 1],
]);
assert_eq!(isize_stored, data.len() as u32);
}
#[test]
fn test_zlib_header_structure() {
let data = b"zlib header test";
let zl = zlib_deflate(data, 6);
assert!(zl.len() >= 6);
let cmf = zl[0];
let flg = zl[1];
assert_eq!(cmf & 0x0F, 8, "Zlib CM must be 8 (deflate)");
assert_eq!(
(cmf as u16 * 256 + flg as u16) % 31,
0,
"Zlib header checksum must be multiple of 31"
);
let adler_stored = u32::from_be_bytes([
zl[zl.len() - 4],
zl[zl.len() - 3],
zl[zl.len() - 2],
zl[zl.len() - 1],
]);
assert_eq!(adler_stored, adler32(data));
}
#[test]
fn test_truncated_input() {
let result = raw_inflate(&[0x01, 0x05]);
assert!(result.is_err(), "Truncated input should error");
}
#[test]
fn test_corrupted_zlib_header() {
let result = zlib_inflate(&[0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
assert!(result.is_err(), "Bad zlib header should error");
}
#[test]
fn test_bad_gzip_magic() {
let result = gzip_inflate(b"not a gzip file at all");
assert!(result.is_err(), "Bad gzip magic should error");
}
#[test]
fn test_length_mismatch_stored() {
let bad_stored = vec![0x00, 0x05, 0x00, 0x00, 0x00];
let result = raw_inflate(&bad_stored);
let bad: Vec<u8> = vec![
0x00, 0x05, 0x00, 0x00, 0x00, ];
let result = raw_inflate(&bad);
assert!(result.is_err(), "Length mismatch should error");
}
#[test]
fn test_deflate_stream() {
let data = b"Streaming test data for the deflate stream interface.";
let mut stream = DeflateStream::new(6);
let out1 = stream.update(data);
let out2 = stream.finish();
let mut combined = out1;
combined.extend_from_slice(&out2);
if !combined.is_empty() {
let result = raw_inflate(&combined);
assert!(result.is_ok() || result.is_err(),
"Streaming should produce valid or error output");
}
}
#[test]
fn test_inflate_stream() {
let data = b"Inflate stream test data, repeated. ".repeat(50);
let compressed = zlib_deflate(data.as_bytes(), 6);
let mut stream = InflateStream::new();
let result = stream.update(&compressed).expect("inflate stream update");
assert_eq!(result, data.as_bytes());
}
#[test]
fn test_text_english_all_levels() {
let data = include_str!("tool_miniz.rs"); let bytes = data.as_bytes();
for level in 0..=9 {
round_trip_zlib(bytes, level);
}
}
#[test]
fn test_binary_pattern() {
let data: Vec<u8> = (0..1024)
.flat_map(|i| {
let v = (i * 37 + 13) as u8;
vec![v, v ^ 0xFF, v, v.wrapping_add(1)]
})
.collect();
for level in 0..=9 {
round_trip_raw(&data, level);
}
}
#[test]
fn test_max_match_258() {
let mut data = vec![b'X'; 258];
data.extend_from_slice(b"END");
round_trip_zlib(&data, 9);
}
#[test]
fn test_level0_is_store() {
let data = b"Store mode should produce a stored block.";
let compressed = raw_deflate(data, 0);
assert!(compressed.len() > data.len());
let decompressed = raw_inflate(&compressed).expect("store mode inflate");
assert_eq!(decompressed, data);
}
#[test]
fn test_fixed_huffman_round_trip() {
let data = b"Testing fixed Huffman encoding with varied content. ABCDEFGHIJKLMNOPQRSTUVWXYZ";
round_trip_raw(data, 1);
round_trip_zlib(data, 1);
}
#[test]
fn test_dynamic_huffman_round_trip() {
let data = b"Dynamic Huffman testing with enough variety to build interesting trees. "
.repeat(20);
round_trip_raw(data.as_bytes(), 6);
round_trip_zlib(data.as_bytes(), 6);
}
#[test]
fn test_cross_format_gzip_to_zlib() {
let data = b"Cross format test: compress with gzip, decompress correctly.";
let gz = gzip_deflate(data, 6);
let out = gzip_inflate(&gz).expect("gzip decompress");
assert_eq!(out, data);
let result = zlib_inflate(&gz);
assert!(result.is_err(), "zlib_inflate on gzip data should error");
}
#[test]
fn test_empty_compression_formats() {
let empty: &[u8] = b"";
for format in &[CompressFormat::Raw, CompressFormat::Zlib, CompressFormat::Gzip] {
let compressed = compress(empty, *format, 6);
let decompressed = decompress_with_format(&compressed, *format)
.expect("empty decompress");
assert!(decompressed.is_empty(), "Empty decompress should be empty");
}
}
#[test]
fn test_unicode_data() {
let data = "Hello, 世界! 🌍 This is Unicode: café, naïve, résumé. 日本語テスト。";
round_trip_zlib(data.as_bytes(), 6);
round_trip_gzip(data.as_bytes(), 6);
}
#[test]
fn test_json_data() {
let json = r#"{"name":"test","values":[1,2,3,4,5],"nested":{"a":true,"b":false,"c":null}}"#;
let data = json.repeat(100);
round_trip_zlib(data.as_bytes(), 6);
}
#[test]
fn test_bit_boundary_round_trip() {
for size in [1, 2, 3, 7, 8, 15, 16, 31, 32, 63, 64, 127, 128, 255, 256] {
let data: Vec<u8> = (0..size).map(|i| (i * 3 + 7) as u8).collect();
if !data.is_empty() {
round_trip_zlib(&data, 6);
}
}
}
#[test]
fn test_zlib_checksum_verified() {
let data = b"Zlib checksum verification test data.";
let compressed = zlib_deflate(data, 6);
let result = zlib_inflate(&compressed);
assert!(result.is_ok(), "Valid zlib should decompress OK");
let mut corrupted = compressed.clone();
let len = corrupted.len();
corrupted[len - 1] ^= 0xFF; let result = zlib_inflate(&corrupted);
assert!(result.is_err(), "Corrupted checksum should error");
}
#[test]
fn test_gzip_checksum_verified() {
let data = b"Gzip CRC-32 verification test data.";
let compressed = gzip_deflate(data, 6);
let result = gzip_inflate(&compressed);
assert!(result.is_ok(), "Valid gzip should decompress OK");
let mut corrupted = compressed.clone();
let len = corrupted.len();
corrupted[len - 8] ^= 0xFF;
let result = gzip_inflate(&corrupted);
assert!(result.is_err(), "Corrupted gzip CRC should error");
}
#[test]
fn test_high_level_api() {
let data = b"High-level API test: compress and decompress with auto-detect.";
for format in &[CompressFormat::Raw, CompressFormat::Zlib, CompressFormat::Gzip] {
for level in [0, 1, 6, 9] {
let compressed = compress(data, *format, level);
let decompressed = decompress(&compressed).expect("auto decompress");
assert_eq!(decompressed, data);
}
}
}
#[test]
fn test_large_random_block() {
let mut state: u64 = 123456789;
let data: Vec<u8> = (0..50000)
.map(|_| {
state = state.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
(state >> 32) as u8
})
.collect();
round_trip_zlib(&data, 6);
}
#[test]
fn test_decompress_mismatch_error() {
let data = b"Specific data for mismatch test.";
let compressed = zlib_deflate(data, 6);
let _ = raw_inflate(&compressed);
let result = gzip_inflate(&compressed);
assert!(result.is_err(), "zlib data as gzip should error");
}
#[test]
fn test_huffman_single_symbol() {
let data = b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
round_trip_zlib(data, 9);
}
#[test]
fn test_huffman_all_literals() {
let mut data = Vec::new();
for i in 0u8..=255 {
data.push(i);
data.push(i);
}
round_trip_zlib(&data, 9);
}
#[test]
fn test_level0_vs_level9() {
let data = b"The quick brown fox jumps over the lazy dog. ".repeat(200);
let c0 = zlib_deflate(data.as_bytes(), 0);
let c9 = zlib_deflate(data.as_bytes(), 9);
assert!(c0.len() > data.len());
assert!(c9.len() < data.len() / 2,
"Level 9 should compress significantly: {} vs {}",
c9.len(), data.len());
let d0 = zlib_inflate(&c0).expect("level 0 inflate");
let d9 = zlib_inflate(&c9).expect("level 9 inflate");
assert_eq!(d0, data.as_bytes());
assert_eq!(d9, data.as_bytes());
}
#[test]
fn test_compress_level_clamp() {
let data = b"level clamp test";
let c = compress(data, CompressFormat::Zlib, 255);
let d = decompress(&c).expect("clamped level decompress");
assert_eq!(d, data);
}
#[test]
fn test_bit_reader_writer_round_trip() {
let mut writer = BitWriter::new();
writer.write_bits(0x1, 1);
writer.write_bits(0x2, 2);
writer.write_bits(0x3F, 6);
writer.write_bits(0xFF, 8);
writer.write_bits(0x555, 11);
let bytes = writer.finish();
let mut reader = BitReader::new(&bytes);
assert_eq!(reader.read_bits(1).unwrap(), 0x1);
assert_eq!(reader.read_bits(2).unwrap(), 0x2);
assert_eq!(reader.read_bits(6).unwrap(), 0x3F);
assert_eq!(reader.read_bits(8).unwrap(), 0xFF);
assert_eq!(reader.read_bits(11).unwrap(), 0x555);
}
#[test]
fn test_bit_writer_align() {
let mut writer = BitWriter::new();
writer.write_bits(0x3, 2); writer.align_to_byte();
writer.write_bytes(b"hello");
writer.write_bits(0x1, 1);
let bytes = writer.finish();
let mut reader = BitReader::new(&bytes);
assert_eq!(reader.read_bits(2).unwrap(), 0x3);
reader.align_to_byte();
let mut buf = [0u8; 5];
reader.read_bytes(&mut buf).unwrap();
assert_eq!(&buf, b"hello");
assert_eq!(reader.read_bits(1).unwrap(), 0x1);
}
#[test]
fn test_multiple_write_blocks() {
let mut state = DeflateState::new(1);
state.pending.push(LitLenEntry::Literal(b'A'));
state.pending.push(LitLenEntry::Literal(b'B'));
state.pending.push(LitLenEntry::Literal(b'C'));
state.write_fixed_block(true);
let bytes = state.bit_writer.finish();
let result = raw_inflate(&bytes).expect("multi-block inflate");
assert_eq!(result, b"ABC");
}
#[test]
fn test_huffman_table_from_lengths() {
let lengths = [2u8, 1, 3, 3];
let table = HuffmanTable::from_lengths(&lengths, 4).expect("build table");
assert!(table.min_bits > 0, "Table should have valid codes");
}
#[test]
fn test_deflate_state_pending() {
let state = DeflateState::new(6);
assert!(state.pending.is_empty());
assert_eq!(state.level, 6);
assert!(state.bit_writer.is_empty());
}
#[test]
fn test_window_buffer_copy_match() {
let mut window = WindowBuffer::new();
let mut output = Vec::new();
for &b in b"ABCDEFGH" {
window.push(b);
output.push(b);
}
window.copy_match(4, 4, &mut output);
assert_eq!(&output[b"ABCDEFGH".len()..], b"EFGH");
}
#[test]
fn test_hash_table_match_finding() {
let data = b"ABCABCDEFABCABCDEF";
let mut ht = HashTable::new(10, 32768);
for pos in 0..data.len() {
ht.insert(pos, data);
}
let (dist, len) = ht.find_match(3, data, 128, 258, 32768);
assert!(len >= 3, "Should find match of at least 3 bytes");
assert_eq!(dist, 3, "Distance should be 3");
}
#[test]
fn test_cli_parse_args() {
let args: Vec<String> = vec![
"tool_miniz".into(),
"-c".into(),
"-g".into(),
"-l".into(),
"9".into(),
"input.txt".into(),
];
let config = parse_cli_args(&args).expect("parse args");
assert_eq!(config.mode, CliMode::Compress);
assert_eq!(config.format, CompressFormat::Gzip);
assert_eq!(config.level, 9);
assert_eq!(config.input, Some("input.txt".into()));
}
#[test]
fn test_error_display() {
let e = MinizError::DataError("test error".into());
assert!(e.to_string().contains("test error"));
let e = MinizError::IoError("disk full".into());
assert!(e.to_string().contains("disk full"));
}
#[test]
fn test_compress_format_debug() {
assert_eq!(format!("{:?}", CompressFormat::Raw), "Raw");
assert_eq!(format!("{:?}", CompressFormat::Zlib), "Zlib");
assert_eq!(format!("{:?}", CompressFormat::Gzip), "Gzip");
}
#[test]
fn test_max_window_offset() {
let mut data = vec![b'Q'; 32768];
data.extend_from_slice(b"XYZ");
data.extend_from_slice(b"XYZ"); round_trip_zlib(&data, 6);
}
}
pub fn tool_miniz_main() -> MinizResult<()> {
let args: Vec<String> = std::env::args().collect();
let config = parse_cli_args(&args)?;
run_cli(config)
}
#[cfg(not(test))]
fn main() {
std::process::exit(match tool_miniz_main() {
Ok(()) => 0,
Err(e) => {
eprintln!("Error: {}", e);
1
}
});
}