use std::io;
use crate::block::{compress_bound, compress_fast, Lz4Stream};
use crate::hc::{
attach_hc_dictionary, compress_hc, compress_hc_continue, load_dict_hc, reset_stream_hc_fast,
Lz4StreamHc,
};
const LZ4HC_CLEVEL_MIN: i32 = 2;
pub trait CompressionStrategy: Send + Sync {
fn compress_block(&mut self, src: &[u8], dst: &mut Vec<u8>) -> io::Result<usize>;
}
#[inline]
fn ensure_dst_capacity(src_len: usize, dst: &mut Vec<u8>) {
let bound = compress_bound(src_len as i32) as usize;
if dst.len() < bound {
dst.resize(bound, 0u8);
}
}
pub struct NoStreamFast {
acceleration: i32,
}
impl NoStreamFast {
pub fn new(c_level: i32) -> Self {
let acceleration = if c_level < 0 { -c_level + 1 } else { 1 };
NoStreamFast { acceleration }
}
}
unsafe impl Send for NoStreamFast {}
unsafe impl Sync for NoStreamFast {}
impl CompressionStrategy for NoStreamFast {
fn compress_block(&mut self, src: &[u8], dst: &mut Vec<u8>) -> io::Result<usize> {
ensure_dst_capacity(src.len(), dst);
compress_fast(src, dst, self.acceleration)
.map_err(|e| io::Error::other(format!("compress_fast failed: {e:?}")))
}
}
pub struct NoStreamHC {
c_level: i32,
}
impl NoStreamHC {
pub fn new(c_level: i32) -> Self {
NoStreamHC { c_level }
}
}
unsafe impl Send for NoStreamHC {}
unsafe impl Sync for NoStreamHC {}
impl CompressionStrategy for NoStreamHC {
fn compress_block(&mut self, src: &[u8], dst: &mut Vec<u8>) -> io::Result<usize> {
ensure_dst_capacity(src.len(), dst);
let written = unsafe {
compress_hc(
src.as_ptr(),
dst.as_mut_ptr(),
src.len() as i32,
dst.len() as i32,
self.c_level,
)
};
if written == 0 {
Err(io::Error::other("compress_hc returned 0"))
} else {
Ok(written as usize)
}
}
}
pub struct StreamFast {
c_level: i32,
stream: Box<Lz4Stream>,
dict_stream: Box<Lz4Stream>,
_dict: Vec<u8>,
}
impl StreamFast {
pub fn new(c_level: i32, dict: &[u8]) -> io::Result<Self> {
let stream = Lz4Stream::new();
let mut dict_stream = Lz4Stream::new();
let dict_copy: Vec<u8> = dict.to_vec();
if !dict_copy.is_empty() {
dict_stream.load_dict_slow(&dict_copy);
}
Ok(StreamFast {
c_level,
stream,
dict_stream,
_dict: dict_copy,
})
}
}
unsafe impl Send for StreamFast {}
unsafe impl Sync for StreamFast {}
impl CompressionStrategy for StreamFast {
fn compress_block(&mut self, src: &[u8], dst: &mut Vec<u8>) -> io::Result<usize> {
ensure_dst_capacity(src.len(), dst);
let acceleration = if self.c_level < 0 {
-self.c_level + 1
} else {
1
};
self.stream.reset_fast();
let dict_ptr = if self._dict.is_empty() {
None
} else {
Some(&*self.dict_stream as *const Lz4Stream)
};
unsafe {
self.stream.attach_dictionary(dict_ptr);
}
let written = self.stream.compress_fast_continue(src, dst, acceleration);
if written == 0 {
Err(io::Error::other("compress_fast_continue returned 0"))
} else {
Ok(written as usize)
}
}
}
pub struct StreamHC {
c_level: i32,
stream_hc: Box<Lz4StreamHc>,
dict_stream_hc: Box<Lz4StreamHc>,
_dict: Vec<u8>,
}
impl StreamHC {
pub fn new(c_level: i32, dict: &[u8]) -> io::Result<Self> {
let stream_hc =
Lz4StreamHc::create().ok_or_else(|| io::Error::other("Lz4StreamHc::create failed"))?;
let mut dict_stream_hc = Lz4StreamHc::create()
.ok_or_else(|| io::Error::other("Lz4StreamHc::create (dict) failed"))?;
let dict_copy: Vec<u8> = dict.to_vec();
reset_stream_hc_fast(&mut dict_stream_hc, c_level);
if !dict_copy.is_empty() {
unsafe {
load_dict_hc(
&mut dict_stream_hc,
dict_copy.as_ptr(),
dict_copy.len() as i32,
);
}
}
Ok(StreamHC {
c_level,
stream_hc,
dict_stream_hc,
_dict: dict_copy,
})
}
}
unsafe impl Send for StreamHC {}
unsafe impl Sync for StreamHC {}
impl CompressionStrategy for StreamHC {
fn compress_block(&mut self, src: &[u8], dst: &mut Vec<u8>) -> io::Result<usize> {
ensure_dst_capacity(src.len(), dst);
reset_stream_hc_fast(&mut self.stream_hc, self.c_level);
let dict_ptr = if self._dict.is_empty() {
None
} else {
Some(&*self.dict_stream_hc as *const Lz4StreamHc)
};
unsafe {
attach_hc_dictionary(&mut self.stream_hc, dict_ptr);
}
let written = unsafe {
compress_hc_continue(
&mut self.stream_hc,
src.as_ptr(),
dst.as_mut_ptr(),
src.len() as i32,
dst.len() as i32,
)
};
if written == 0 {
Err(io::Error::other("compress_hc_continue returned 0"))
} else {
Ok(written as usize)
}
}
}
pub fn build_compression_parameters(
c_level: i32,
_src_size: usize,
_block_size: usize,
) -> Box<dyn CompressionStrategy> {
if c_level < LZ4HC_CLEVEL_MIN {
Box::new(NoStreamFast::new(c_level))
} else {
Box::new(NoStreamHC::new(c_level))
}
}
pub fn build_compression_parameters_with_dict(
c_level: i32,
dict: &[u8],
) -> io::Result<Box<dyn CompressionStrategy>> {
if c_level < LZ4HC_CLEVEL_MIN {
Ok(Box::new(StreamFast::new(c_level, dict)?))
} else {
Ok(Box::new(StreamHC::new(c_level, dict)?))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::block::decompress_safe;
const SAMPLE: &[u8] = b"hello world hello world hello world hello world \
this is a test of lz4 block compression round-trip!";
fn lz4_decompress(compressed: &[u8], original_len: usize) -> Vec<u8> {
let mut out = vec![0u8; original_len];
let n = decompress_safe(compressed, &mut out).expect("decompress_safe failed");
assert_eq!(n, original_len);
out
}
#[test]
fn no_stream_fast_roundtrip() {
let mut strategy = NoStreamFast::new(1);
let mut dst = Vec::new();
let n = strategy.compress_block(SAMPLE, &mut dst).unwrap();
let recovered = lz4_decompress(&dst[..n], SAMPLE.len());
assert_eq!(recovered.as_slice(), SAMPLE);
}
#[test]
fn no_stream_fast_negative_level_roundtrip() {
let mut strategy = NoStreamFast::new(-5);
let mut dst = Vec::new();
let n = strategy.compress_block(SAMPLE, &mut dst).unwrap();
let recovered = lz4_decompress(&dst[..n], SAMPLE.len());
assert_eq!(recovered.as_slice(), SAMPLE);
}
#[test]
fn no_stream_hc_roundtrip() {
let mut strategy = NoStreamHC::new(9);
let mut dst = Vec::new();
let n = strategy.compress_block(SAMPLE, &mut dst).unwrap();
let recovered = lz4_decompress(&dst[..n], SAMPLE.len());
assert_eq!(recovered.as_slice(), SAMPLE);
}
#[test]
fn no_stream_hc_min_level_roundtrip() {
let mut strategy = NoStreamHC::new(LZ4HC_CLEVEL_MIN);
let mut dst = Vec::new();
let n = strategy.compress_block(SAMPLE, &mut dst).unwrap();
let recovered = lz4_decompress(&dst[..n], SAMPLE.len());
assert_eq!(recovered.as_slice(), SAMPLE);
}
#[test]
fn stream_fast_no_dict_roundtrip() {
let mut strategy = StreamFast::new(1, b"").unwrap();
let mut dst = Vec::new();
let n = strategy.compress_block(SAMPLE, &mut dst).unwrap();
let recovered = lz4_decompress(&dst[..n], SAMPLE.len());
assert_eq!(recovered.as_slice(), SAMPLE);
}
#[test]
fn stream_fast_with_dict_roundtrip() {
let dict = b"hello world ";
let mut strategy = StreamFast::new(1, dict).unwrap();
let mut dst = Vec::new();
let n = strategy.compress_block(SAMPLE, &mut dst).unwrap();
assert!(n > 0);
}
#[test]
fn stream_hc_no_dict_roundtrip() {
let mut strategy = StreamHC::new(9, b"").unwrap();
let mut dst = Vec::new();
let n = strategy.compress_block(SAMPLE, &mut dst).unwrap();
let recovered = lz4_decompress(&dst[..n], SAMPLE.len());
assert_eq!(recovered.as_slice(), SAMPLE);
}
#[test]
fn stream_hc_with_dict_roundtrip() {
let dict = b"hello world ";
let mut strategy = StreamHC::new(9, dict).unwrap();
let mut dst = Vec::new();
let n = strategy.compress_block(SAMPLE, &mut dst).unwrap();
assert!(n > 0);
}
#[test]
fn build_compression_parameters_selects_fast() {
let mut s = build_compression_parameters(1, 65536, 65536);
let mut dst = Vec::new();
let n = s.compress_block(SAMPLE, &mut dst).unwrap();
let recovered = lz4_decompress(&dst[..n], SAMPLE.len());
assert_eq!(recovered.as_slice(), SAMPLE);
}
#[test]
fn build_compression_parameters_selects_hc() {
let mut s = build_compression_parameters(9, 65536, 65536);
let mut dst = Vec::new();
let n = s.compress_block(SAMPLE, &mut dst).unwrap();
let recovered = lz4_decompress(&dst[..n], SAMPLE.len());
assert_eq!(recovered.as_slice(), SAMPLE);
}
#[test]
fn build_with_dict_selects_stream_fast() {
let dict = b"hello world ";
let mut s = build_compression_parameters_with_dict(1, dict).unwrap();
let mut dst = Vec::new();
s.compress_block(SAMPLE, &mut dst).unwrap();
}
#[test]
fn build_with_dict_selects_stream_hc() {
let dict = b"hello world ";
let mut s = build_compression_parameters_with_dict(9, dict).unwrap();
let mut dst = Vec::new();
s.compress_block(SAMPLE, &mut dst).unwrap();
}
}