use core::ptr;
use super::compress::{compress_generic, LZ4_ACCELERATION_DEFAULT, LZ4_ACCELERATION_MAX};
use super::types::{
get_index_on_hash, hash_position, prepare_table, put_index_on_hash, DictDirective,
DictIssueDirective, LimitedOutputDirective, StreamStateInternal, TableType, KB,
};
const HASH_UNIT: usize = core::mem::size_of::<usize>();
#[derive(Clone, Copy, PartialEq, Eq)]
enum LoadDictMode {
Fast,
Slow,
}
#[derive(Default)]
pub struct Lz4Stream {
pub(crate) internal: StreamStateInternal,
}
unsafe impl Send for Lz4Stream {}
impl Lz4Stream {
pub fn new() -> Box<Self> {
Box::new(Self {
internal: StreamStateInternal::new(),
})
}
pub fn reset(&mut self) {
self.internal = StreamStateInternal::new();
}
pub fn reset_fast(&mut self) {
unsafe {
prepare_table(&mut self.internal, 0, TableType::ByU32);
}
}
fn load_dict_internal(&mut self, dictionary: &[u8], mode: LoadDictMode) -> i32 {
let dict_size = dictionary.len();
let dict_ptr = dictionary.as_ptr();
let dict_end: *const u8 = unsafe { dict_ptr.add(dict_size) };
self.reset();
self.internal.current_offset = self.internal.current_offset.wrapping_add(64 * KB as u32);
if dict_size < HASH_UNIT {
return 0;
}
let p_start: *const u8 = if dict_size > 64 * KB {
unsafe { dict_end.sub(64 * KB) }
} else {
dict_ptr
};
self.internal.dictionary = p_start;
self.internal.dict_size = (dict_size.min(64 * KB)) as u32;
self.internal.table_type = TableType::ByU32 as u32;
let mut p = p_start;
let mut idx32 = self.internal.current_offset - self.internal.dict_size;
unsafe {
while p.add(HASH_UNIT) <= dict_end {
let h = hash_position(p, TableType::ByU32);
put_index_on_hash(
idx32,
h,
self.internal.hash_table.as_mut_ptr(),
TableType::ByU32,
);
p = p.add(3);
idx32 = idx32.wrapping_add(3);
}
if mode == LoadDictMode::Slow {
p = p_start;
idx32 = self.internal.current_offset - self.internal.dict_size;
let limit = self.internal.current_offset.wrapping_sub(64 * KB as u32);
while p.add(HASH_UNIT) <= dict_end {
let h = hash_position(p, TableType::ByU32);
if get_index_on_hash(h, self.internal.hash_table.as_ptr(), TableType::ByU32)
<= limit
{
put_index_on_hash(
idx32,
h,
self.internal.hash_table.as_mut_ptr(),
TableType::ByU32,
);
}
p = p.add(1);
idx32 = idx32.wrapping_add(1);
}
}
}
self.internal.dict_size as i32
}
pub fn load_dict(&mut self, dictionary: &[u8]) -> i32 {
self.load_dict_internal(dictionary, LoadDictMode::Fast)
}
pub fn load_dict_slow(&mut self, dictionary: &[u8]) -> i32 {
self.load_dict_internal(dictionary, LoadDictMode::Slow)
}
pub unsafe fn attach_dictionary(&mut self, dict_stream: Option<*const Lz4Stream>) {
let dict_ctx: *const StreamStateInternal = match dict_stream {
None => ptr::null(),
Some(p) => {
debug_assert!(!p.is_null());
&(*p).internal
}
};
if !dict_ctx.is_null() {
if self.internal.current_offset == 0 {
self.internal.current_offset = 64 * KB as u32;
}
if (*dict_ctx).dict_size == 0 {
self.internal.dict_ctx = ptr::null();
return;
}
}
self.internal.dict_ctx = dict_ctx;
}
pub fn renorm_dict(&mut self, next_size: i32) {
debug_assert!(next_size >= 0);
if self.internal.current_offset.wrapping_add(next_size as u32) > 0x8000_0000 {
let delta = self.internal.current_offset.wrapping_sub(64 * KB as u32);
let dict_end: *const u8 = unsafe {
self.internal
.dictionary
.add(self.internal.dict_size as usize)
};
for entry in self.internal.hash_table.iter_mut() {
if *entry < delta {
*entry = 0;
} else {
*entry = entry.wrapping_sub(delta);
}
}
self.internal.current_offset = 64 * KB as u32;
if self.internal.dict_size > 64 * KB as u32 {
self.internal.dict_size = 64 * KB as u32;
}
self.internal.dictionary = unsafe { dict_end.sub(self.internal.dict_size as usize) };
}
}
pub fn compress_fast_continue(&mut self, src: &[u8], dst: &mut [u8], acceleration: i32) -> i32 {
let table_type = TableType::ByU32;
let input_size = src.len() as i32;
let max_output_size = dst.len() as i32;
let source_ptr = src.as_ptr();
let dest_ptr = dst.as_mut_ptr();
let dict_end: *const u8 = if self.internal.dict_size != 0 {
unsafe {
self.internal
.dictionary
.add(self.internal.dict_size as usize)
}
} else {
ptr::null()
};
self.renorm_dict(input_size);
let acceleration = acceleration
.max(LZ4_ACCELERATION_DEFAULT)
.min(LZ4_ACCELERATION_MAX);
let dict_end: *const u8 = if self.internal.dict_size < 4
&& dict_end != source_ptr && input_size > 0
&& self.internal.dict_ctx.is_null()
{
self.internal.dict_size = 0;
self.internal.dictionary = source_ptr;
source_ptr
} else {
dict_end
};
if !dict_end.is_null() {
let source_end = unsafe { source_ptr.add(src.len()) };
if source_end > self.internal.dictionary && source_end < dict_end {
let remaining = (dict_end as usize) - (source_end as usize);
let mut new_dict_size = remaining.min(64 * KB) as u32;
if new_dict_size < 4 {
new_dict_size = 0;
}
self.internal.dict_size = new_dict_size;
self.internal.dictionary =
unsafe { dict_end.sub(self.internal.dict_size as usize) };
}
}
if dict_end == source_ptr {
let result = unsafe {
if self.internal.dict_size < (64 * KB as u32)
&& self.internal.dict_size < self.internal.current_offset
{
compress_generic(
&mut self.internal,
source_ptr,
dest_ptr,
input_size,
ptr::null_mut(),
max_output_size,
LimitedOutputDirective::LimitedOutput,
table_type,
DictDirective::WithPrefix64k,
DictIssueDirective::DictSmall,
acceleration,
)
} else {
compress_generic(
&mut self.internal,
source_ptr,
dest_ptr,
input_size,
ptr::null_mut(),
max_output_size,
LimitedOutputDirective::LimitedOutput,
table_type,
DictDirective::WithPrefix64k,
DictIssueDirective::NoDictIssue,
acceleration,
)
}
};
return match result {
Ok(n) => n as i32,
Err(_) => 0,
};
}
let result = unsafe {
if !self.internal.dict_ctx.is_null() {
if input_size > 4 * KB as i32 {
let dict_ctx_ptr = self.internal.dict_ctx;
ptr::copy_nonoverlapping(
dict_ctx_ptr,
&mut self.internal as *mut StreamStateInternal,
1,
);
compress_generic(
&mut self.internal,
source_ptr,
dest_ptr,
input_size,
ptr::null_mut(),
max_output_size,
LimitedOutputDirective::LimitedOutput,
table_type,
DictDirective::UsingExtDict,
DictIssueDirective::NoDictIssue,
acceleration,
)
} else {
compress_generic(
&mut self.internal,
source_ptr,
dest_ptr,
input_size,
ptr::null_mut(),
max_output_size,
LimitedOutputDirective::LimitedOutput,
table_type,
DictDirective::UsingDictCtx,
DictIssueDirective::NoDictIssue,
acceleration,
)
}
} else if self.internal.dict_size < 64 * KB as u32
&& self.internal.dict_size < self.internal.current_offset
{
compress_generic(
&mut self.internal,
source_ptr,
dest_ptr,
input_size,
ptr::null_mut(),
max_output_size,
LimitedOutputDirective::LimitedOutput,
table_type,
DictDirective::UsingExtDict,
DictIssueDirective::DictSmall,
acceleration,
)
} else {
compress_generic(
&mut self.internal,
source_ptr,
dest_ptr,
input_size,
ptr::null_mut(),
max_output_size,
LimitedOutputDirective::LimitedOutput,
table_type,
DictDirective::UsingExtDict,
DictIssueDirective::NoDictIssue,
acceleration,
)
}
};
self.internal.dictionary = source_ptr;
self.internal.dict_size = input_size as u32;
match result {
Ok(n) => n as i32,
Err(_) => 0,
}
}
pub unsafe fn compress_force_ext_dict(
&mut self,
src: *const u8,
dst: *mut u8,
src_size: i32,
dst_capacity: i32,
) -> i32 {
self.renorm_dict(src_size);
let result = if self.internal.dict_size < 64 * KB as u32
&& self.internal.dict_size < self.internal.current_offset
{
compress_generic(
&mut self.internal,
src,
dst,
src_size,
ptr::null_mut(),
dst_capacity,
LimitedOutputDirective::NotLimited,
TableType::ByU32,
DictDirective::UsingExtDict,
DictIssueDirective::DictSmall,
1,
)
} else {
compress_generic(
&mut self.internal,
src,
dst,
src_size,
ptr::null_mut(),
dst_capacity,
LimitedOutputDirective::NotLimited,
TableType::ByU32,
DictDirective::UsingExtDict,
DictIssueDirective::NoDictIssue,
1,
)
};
self.internal.dictionary = src;
self.internal.dict_size = src_size as u32;
match result {
Ok(n) => n as i32,
Err(_) => 0,
}
}
pub fn save_dict(&mut self, safe_buffer: &mut [u8]) -> i32 {
let mut dict_size = (safe_buffer.len().min(64 * KB)) as u32;
if dict_size > self.internal.dict_size {
dict_size = self.internal.dict_size;
}
if dict_size > 0 {
let previous_dict_end: *const u8 = unsafe {
self.internal
.dictionary
.add(self.internal.dict_size as usize)
};
unsafe {
ptr::copy(
previous_dict_end.sub(dict_size as usize),
safe_buffer.as_mut_ptr(),
dict_size as usize,
);
}
}
self.internal.dictionary = safe_buffer.as_ptr();
self.internal.dict_size = dict_size;
dict_size as i32
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::block::types::KB;
#[test]
fn renorm_dict_triggers_when_overflow_would_occur() {
let mut stream = Lz4Stream::new();
stream.internal.current_offset = 0x7FFF_FFF0;
stream.internal.hash_table[0] = 0x7FFF_0000;
stream.internal.hash_table[1] = 0x0000_0001;
stream.renorm_dict(32);
assert_eq!(
stream.internal.current_offset,
64 * KB as u32,
"renorm_dict must reset current_offset to 64 KB"
);
}
#[test]
fn renorm_dict_with_large_dict_clips_to_64kb() {
let mut stream = Lz4Stream::new();
stream.internal.current_offset = 0x7FFF_FFF0;
stream.internal.dict_size = 128 * KB as u32; stream.renorm_dict(32);
assert_eq!(
stream.internal.dict_size,
64 * KB as u32,
"renorm_dict must clip dict_size to 64 KB"
);
}
#[test]
fn renorm_dict_noop_when_below_boundary() {
let mut stream = Lz4Stream::new();
stream.internal.current_offset = 1024;
stream.renorm_dict(1024);
assert_eq!(stream.internal.current_offset, 1024);
}
#[test]
fn compress_fast_continue_large_block_with_attached_dict_ctx() {
let dict_data: Vec<u8> = (0u8..=255).cycle().take(64 * KB).collect();
let mut dict_stream = Lz4Stream::new();
dict_stream.load_dict(&dict_data);
let mut working_stream = Lz4Stream::new();
unsafe {
working_stream.attach_dictionary(Some(&*dict_stream as *const Lz4Stream));
}
let src: Vec<u8> = (0u8..=127).cycle().take(8 * KB).collect();
let bound = {
let b = crate::block::compress::compress_bound(src.len() as i32);
b.max(0) as usize
};
let mut dst = vec![0u8; bound.max(src.len() + 64)];
let n = working_stream.compress_fast_continue(&src, &mut dst, 1);
assert!(
n >= 0,
"compress_fast_continue with large dict_ctx must not panic"
);
}
#[test]
fn compress_fast_continue_source_end_overlaps_dict_tail_clips_dict() {
let unified_buf: Vec<u8> = (0u8..=255).cycle().take(1024).collect();
let mut stream = Lz4Stream::new();
stream.internal.dictionary = unsafe { unified_buf.as_ptr().add(8) };
stream.internal.dict_size = 100;
stream.internal.current_offset = 64 * KB as u32;
let source = &unified_buf[50..100];
let mut dst = vec![0u8; 512];
let _ = stream.compress_fast_continue(source, &mut dst, 1);
}
}