use super::liblz4::*;
use libc::c_char;
use std::io::{Error, ErrorKind, Result};
pub enum CompressionMode {
HIGHCOMPRESSION(i32),
FAST(i32),
DEFAULT,
}
pub fn compress(src: &[u8], mode: Option<CompressionMode>, prepend_size: bool) -> Result<Vec<u8>> {
let compress_bound: i32 = unsafe { LZ4_compressBound(src.len() as i32) };
if src.len() > (i32::max_value() as usize) || compress_bound <= 0 {
return Err(Error::new(
ErrorKind::InvalidInput,
"Compression input too long.",
));
}
let mut compressed: Vec<u8> = vec![
0;
(if prepend_size {
compress_bound + 4
} else {
compress_bound
}) as usize
];
let dec_size;
{
let dst_buf: &mut [u8];
if prepend_size {
let size = src.len() as u32;
compressed[0] = size as u8;
compressed[1] = (size >> 8) as u8;
compressed[2] = (size >> 16) as u8;
compressed[3] = (size >> 24) as u8;
dst_buf = &mut compressed[4..];
} else {
dst_buf = &mut compressed;
}
dec_size = match mode {
Some(CompressionMode::HIGHCOMPRESSION(level)) => unsafe {
LZ4_compress_HC(
src.as_ptr() as *const c_char,
dst_buf.as_mut_ptr() as *mut c_char,
src.len() as i32,
compress_bound,
level,
)
},
Some(CompressionMode::FAST(accel)) => unsafe {
LZ4_compress_fast(
src.as_ptr() as *const c_char,
dst_buf.as_mut_ptr() as *mut c_char,
src.len() as i32,
compress_bound,
accel,
)
},
_ => unsafe {
LZ4_compress_default(
src.as_ptr() as *const c_char,
dst_buf.as_mut_ptr() as *mut c_char,
src.len() as i32,
compress_bound,
)
},
};
}
if dec_size <= 0 {
return Err(Error::new(ErrorKind::Other, "Compression failed"));
}
compressed.truncate(if prepend_size { dec_size + 4 } else { dec_size } as usize);
Ok(compressed)
}
pub fn decompress(mut src: &[u8], uncompressed_size: Option<i32>) -> Result<Vec<u8>> {
let size;
if let Some(s) = uncompressed_size {
size = s;
} else {
if src.len() < 4 {
return Err(Error::new(
ErrorKind::InvalidInput,
"Source buffer must at least contain size prefix.",
));
}
size =
(src[0] as i32) | (src[1] as i32) << 8 | (src[2] as i32) << 16 | (src[3] as i32) << 24;
src = &src[4..];
}
if size <= 0 {
return Err(Error::new(
ErrorKind::InvalidInput,
if uncompressed_size.is_some() {
"Size parameter must not be negative."
} else {
"Parsed size prefix in buffer must not be negative."
},
));
}
if unsafe { LZ4_compressBound(size) } <= 0 {
return Err(Error::new(
ErrorKind::InvalidInput,
"Given size parameter is too big",
));
}
let mut decompressed = vec![0u8; size as usize];
let dec_bytes = unsafe {
LZ4_decompress_safe(
src.as_ptr() as *const c_char,
decompressed.as_mut_ptr() as *mut c_char,
src.len() as i32,
size,
)
};
if dec_bytes < 0 {
return Err(Error::new(
ErrorKind::InvalidData,
"Decompression failed. Input invalid or too long?",
));
}
decompressed.truncate(dec_bytes as usize);
Ok(decompressed)
}
#[cfg(test)]
mod test {
use block::{compress, decompress, CompressionMode};
#[test]
fn test_compression_without_prefix() {
let size = 65536;
let mut to_compress = Vec::with_capacity(size);
for i in 0..size {
to_compress.push(i as u8);
}
let mut v: Vec<Vec<u8>> = vec![];
for i in 1..100 {
v.push(compress(&to_compress, Some(CompressionMode::FAST(i)), false).unwrap());
}
for i in 1..12 {
v.push(
compress(
&to_compress,
Some(CompressionMode::HIGHCOMPRESSION(i)),
false,
).unwrap(),
);
}
v.push(compress(&to_compress, None, false).unwrap());
for val in v {
assert_eq!(
decompress(&val, Some(to_compress.len() as i32)).unwrap(),
to_compress
);
}
}
#[test]
fn test_compression_with_prefix() {
let size = 65536;
let mut to_compress = Vec::with_capacity(size);
for i in 0..size {
to_compress.push(i as u8);
}
let mut v: Vec<Vec<u8>> = vec![];
for i in 1..100 {
v.push(compress(&to_compress, Some(CompressionMode::FAST(i)), true).unwrap());
}
for i in 1..12 {
v.push(
compress(
&to_compress,
Some(CompressionMode::HIGHCOMPRESSION(i)),
true,
).unwrap(),
);
}
v.push(compress(&to_compress, None, true).unwrap());
for val in v {
assert_eq!(decompress(&val, None).unwrap(), to_compress);
}
}
#[test]
fn test_decompression_with_prefix() {
let compressed: [u8; 250] = [
0, 188, 0, 0, 255, 32, 116, 104, 105, 115, 32, 105, 115, 32, 97, 32, 116, 101, 115,
116, 32, 115, 116, 114, 105, 110, 103, 32, 99, 111, 109, 112, 114, 101, 115, 115, 101,
100, 32, 98, 121, 32, 112, 121, 116, 104, 111, 110, 45, 108, 122, 52, 32, 47, 0, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
117, 80, 45, 108, 122, 52, 32,
];
let mut reference: String = String::new();
for _ in 0..1024 {
reference += "this is a test string compressed by python-lz4 ";
}
assert_eq!(decompress(&compressed, None).unwrap(), reference.as_bytes())
}
}