blob: b60c3772b96b3cdfa5ac558dc501d2d2774215ca [file] [log] [blame]
//! This module contains functionality for compression.
pub mod core;
mod buffer;
use self::core::*;
/// How much processing the compressor should do to compress the data.
/// `NoCompression` and `Bestspeed` have special meanings, the other levels determine the number
/// of checks for matches in the hash chains and whether to use lazy or greedy parsing.
#[repr(i32)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum CompressionLevel {
/// Don't do any compression, only output uncompressed blocks.
NoCompression = 0,
/// Fast compression. Uses a special compression routine that is optimized for speed.
BestSpeed = 1,
/// Slow/high compression. Do a lot of checks to try to find good matches.
BestCompression = 9,
/// Even more checks, can be very slow.
UberCompression = 10,
/// Default compromise between speed and compression.
DefaultLevel = 6,
/// Use the default compression level.
DefaultCompression = -1,
}
// Missing safe rust analogue (this and mem-to-mem are quite similar)
/*
fn tdefl_compress(
d: Option<&mut CompressorOxide>,
in_buf: *const c_void,
in_size: Option<&mut usize>,
out_buf: *mut c_void,
out_size: Option<&mut usize>,
flush: TDEFLFlush,
) -> TDEFLStatus {
let res = match d {
None => {
in_size.map(|size| *size = 0);
out_size.map(|size| *size = 0);
(TDEFLStatus::BadParam, 0, 0)
},
Some(compressor) => {
let callback_res = CallbackOxide::new(
compressor.callback_func.clone(),
in_buf,
in_size,
out_buf,
out_size,
);
if let Ok(mut callback) = callback_res {
let res = compress(compressor, &mut callback, flush);
callback.update_size(Some(res.1), Some(res.2));
res
} else {
(TDEFLStatus::BadParam, 0, 0)
}
}
};
res.0
}*/
// Missing safe rust analogue
/*
fn tdefl_init(
d: Option<&mut CompressorOxide>,
put_buf_func: PutBufFuncPtr,
put_buf_user: *mut c_void,
flags: c_int,
) -> TDEFLStatus {
if let Some(d) = d {
*d = CompressorOxide::new(
put_buf_func.map(|func|
CallbackFunc { put_buf_func: func, put_buf_user: put_buf_user }
),
flags as u32,
);
TDEFLStatus::Okay
} else {
TDEFLStatus::BadParam
}
}*/
// Missing safe rust analogue (though maybe best served by flate2 front-end instead)
/*
fn tdefl_compress_mem_to_output(
buf: *const c_void,
buf_len: usize,
put_buf_func: PutBufFuncPtr,
put_buf_user: *mut c_void,
flags: c_int,
) -> bool*/
// Missing safe Rust analogue
/*
fn tdefl_compress_mem_to_mem(
out_buf: *mut c_void,
out_buf_len: usize,
src_buf: *const c_void,
src_buf_len: usize,
flags: c_int,
) -> usize*/
/// Compress the input data to a vector, using the specified compression level (0-10).
pub fn compress_to_vec(input: &[u8], level: u8) -> Vec<u8> {
compress_to_vec_inner(input, level, 0, 0)
}
/// Compress the input data to a vector, using the specified compression level (0-10), and with a
/// zlib wrapper.
pub fn compress_to_vec_zlib(input: &[u8], level: u8) -> Vec<u8> {
compress_to_vec_inner(input, level, 1, 0)
}
/// Simple function to compress data to a vec.
fn compress_to_vec_inner(input: &[u8], level: u8, window_bits: i32, strategy: i32) -> Vec<u8> {
// The comp flags function sets the zlib flag if the window_bits parameter is > 0.
let flags = create_comp_flags_from_zip_params(level.into(), window_bits, strategy);
let mut compressor = CompressorOxide::new(flags);
let mut output = Vec::with_capacity(input.len() / 2);
// # Unsafe
// We trust compress to not read the uninitialized bytes.
unsafe {
let cap = output.capacity();
output.set_len(cap);
}
let mut in_pos = 0;
let mut out_pos = 0;
loop {
let (status, bytes_in, bytes_out) =
compress(
&mut compressor,
&input[in_pos..],
&mut output[out_pos..],
TDEFLFlush::Finish,
);
out_pos += bytes_out;
in_pos += bytes_in;
match status {
TDEFLStatus::Done => {
output.truncate(out_pos);
break;
}
TDEFLStatus::Okay => {
// We need more space, so extend the vector.
if output.len().saturating_sub(out_pos) < 30 {
let current_len = output.len();
output.reserve(current_len);
// # Unsafe
// We trust compress to not read the uninitialized bytes.
unsafe {
let cap = output.capacity();
output.set_len(cap);
}
}
}
// Not supposed to happen unless there is a bug.
_ => panic!("Bug! Unexpectedly failed to compress!"),
}
}
output
}
#[cfg(test)]
mod test {
use super::{compress_to_vec, compress_to_vec_inner, CompressionStrategy};
use inflate::decompress_to_vec;
/// Test deflate example.
///
/// Check if the encoder produces the same code as the example given by Mark Adler here:
/// https://stackoverflow.com/questions/17398931/deflate-encoding-with-static-huffman-codes/17415203
#[test]
fn compress_small() {
let test_data = b"Deflate late";
let check = [0x73, 0x49, 0x4d, 0xcb, 0x49, 0x2c, 0x49, 0x55, 0x00, 0x11, 0x00];
let res = compress_to_vec(test_data, 1);
assert_eq!(&check[..], res.as_slice());
let res = compress_to_vec(test_data, 9);
assert_eq!(&check[..], res.as_slice());
}
#[test]
fn compress_huff_only() {
let test_data = b"Deflate late";
let res = compress_to_vec_inner(test_data, 1, 0, CompressionStrategy::HuffmanOnly as i32);
let d = decompress_to_vec(res.as_slice()).expect("Failed to decompress!");
assert_eq!(test_data, d.as_slice());
}
/// Test that a raw block compresses fine.
#[test]
fn compress_raw() {
let text = b"Hello, zlib!";
let encoded = {
let len = text.len();
let notlen = !len;
let mut encoded =
vec![1, len as u8, (len >> 8) as u8, notlen as u8, (notlen >> 8) as u8];
encoded.extend_from_slice(&text[..]);
encoded
};
let res = compress_to_vec(text, 0);
assert_eq!(encoded, res.as_slice());
}
#[test]
fn short() {
let test_data = [10, 10, 10, 10, 10, 55];
let c = compress_to_vec(&test_data, 9);
let d = decompress_to_vec(c.as_slice()).expect("Failed to decompress!");
assert_eq!(&test_data, d.as_slice());
// Check that a static block is used here, rather than a raw block
// , so the data is actually compressed.
// (The optimal compressed length would be 5, but neither miniz nor zlib manages that either
// as neither checks matches against the byte at index 0.)
assert!(c.len() <= 6);
}
}