#![allow(unsafe_code)]
use libz_sys as z;
use crate::error::{Error, Result};
pub const LEVEL: i32 = 1;
pub const WINDOW_BITS: i32 = 15;
pub const MEM_LEVEL: i32 = 9;
pub const DEFAULT_LEVEL: i32 = 6;
pub const DEFAULT_MEM_LEVEL: i32 = 8;
pub fn compress(data: &[u8]) -> Result<Vec<u8>> {
compress_with(data, LEVEL, WINDOW_BITS, MEM_LEVEL)
}
pub fn compress_default(data: &[u8]) -> Result<Vec<u8>> {
compress_with(data, DEFAULT_LEVEL, WINDOW_BITS, DEFAULT_MEM_LEVEL)
}
pub fn compress_with(data: &[u8], level: i32, window_bits: i32, mem_level: i32) -> Result<Vec<u8>> {
let input_length = z::uInt::try_from(data.len()).map_err(|_| Error::format("Block too large to compress"))?;
unsafe {
let mut stream = z::z_stream {
next_in: std::ptr::null_mut(),
avail_in: 0,
total_in: 0,
next_out: std::ptr::null_mut(),
avail_out: 0,
total_out: 0,
msg: std::ptr::null_mut(),
state: std::ptr::null_mut(),
zalloc,
zfree,
opaque: std::ptr::null_mut(),
data_type: 0,
adler: 0,
reserved: 0,
};
let code = z::deflateInit2_(
&mut stream,
level,
z::Z_DEFLATED,
window_bits,
mem_level,
z::Z_DEFAULT_STRATEGY,
z::zlibVersion(),
std::mem::size_of::<z::z_stream>() as i32,
);
if code != z::Z_OK {
return Err(Error::format(format!("deflateInit2 failed ({code})")));
}
let bound = z::deflateBound(&mut stream, input_length as z::uLong) as usize;
let mut output = vec![0u8; bound];
stream.next_in = data.as_ptr() as *mut u8;
stream.avail_in = input_length;
stream.next_out = output.as_mut_ptr();
stream.avail_out = bound as z::uInt;
let code = z::deflate(&mut stream, z::Z_FINISH);
let written = stream.total_out as usize;
z::deflateEnd(&mut stream);
if code != z::Z_STREAM_END {
return Err(Error::format(format!("deflate failed ({code})")));
}
output.truncate(written);
Ok(output)
}
}
unsafe extern "C" fn zalloc(_opaque: z::voidpf, items: z::uInt, size: z::uInt) -> z::voidpf {
unsafe { libc::calloc(items as usize, size as usize) }
}
unsafe extern "C" fn zfree(_opaque: z::voidpf, address: z::voidpf) {
unsafe { libc::free(address) }
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Read;
#[test]
fn compressed_data_inflates_back() {
let data: Vec<u8> = (0..70_000u32).map(|i| (i % 251) as u8).collect();
let packed = compress(&data).unwrap();
assert_eq!(&packed[..2], &[0x78, 0x01]);
let mut unpacked = Vec::new();
flate2::read::ZlibDecoder::new(packed.as_slice()).read_to_end(&mut unpacked).unwrap();
assert_eq!(unpacked, data);
}
}