use core::mem;
use super::dispatch::{compress_generic, set_external_dict};
use super::lz4mid::fill_htable;
use super::search::insert;
use super::types::{
clear_tables, get_clevel_params, init_internal, HcCCtxInternal, HcStrategy,
LZ4HC_CLEVEL_DEFAULT, LZ4HC_CLEVEL_MAX, LZ4HC_HASHSIZE,
};
use crate::block::compress::compress_bound;
use crate::block::types::LimitedOutputDirective;
pub struct Lz4StreamHc {
pub(crate) ctx: HcCCtxInternal,
}
unsafe impl Send for Lz4StreamHc {}
impl Lz4StreamHc {
pub fn create() -> Option<Box<Self>> {
let mut stream = Box::new(Lz4StreamHc {
ctx: HcCCtxInternal::new(),
});
set_compression_level(&mut stream, LZ4HC_CLEVEL_DEFAULT);
Some(stream)
}
}
#[inline]
pub fn sizeof_state_hc() -> usize {
mem::size_of::<HcCCtxInternal>()
}
pub fn init_stream_hc(state: &mut Lz4StreamHc) {
let ctx = &mut state.ctx;
clear_tables(ctx);
ctx.end = core::ptr::null();
ctx.prefix_start = core::ptr::null();
ctx.dict_start = core::ptr::null();
ctx.dict_limit = 0;
ctx.low_limit = 0;
ctx.next_to_update = 0;
ctx.compression_level = 0;
ctx.favor_dec_speed = 0;
ctx.dirty = 0;
ctx.dict_ctx = core::ptr::null();
set_compression_level(state, LZ4HC_CLEVEL_DEFAULT);
}
pub unsafe fn compress_hc_ext_state_fast_reset(
state: &mut Lz4StreamHc,
src: *const u8,
dst: *mut u8,
src_size: i32,
dst_capacity: i32,
compression_level: i32,
) -> i32 {
reset_stream_hc_fast(state, compression_level);
init_internal(&mut state.ctx, src);
let mut src_size_mut = src_size;
let limit = if dst_capacity < compress_bound(src_size) {
LimitedOutputDirective::LimitedOutput
} else {
LimitedOutputDirective::NotLimited
};
compress_generic(
&mut state.ctx,
src,
dst,
&mut src_size_mut,
dst_capacity,
compression_level,
limit,
)
}
pub unsafe fn compress_hc_ext_state(
state: &mut Lz4StreamHc,
src: *const u8,
dst: *mut u8,
src_size: i32,
dst_capacity: i32,
compression_level: i32,
) -> i32 {
init_stream_hc(state);
compress_hc_ext_state_fast_reset(state, src, dst, src_size, dst_capacity, compression_level)
}
pub unsafe fn compress_hc(
src: *const u8,
dst: *mut u8,
src_size: i32,
dst_capacity: i32,
compression_level: i32,
) -> i32 {
let Some(mut state) = Lz4StreamHc::create() else {
return 0;
};
compress_hc_ext_state(
&mut state,
src,
dst,
src_size,
dst_capacity,
compression_level,
)
}
pub unsafe fn compress_hc_dest_size(
state: &mut Lz4StreamHc,
src: *const u8,
dst: *mut u8,
src_size_ptr: &mut i32,
target_dst_size: i32,
c_level: i32,
) -> i32 {
init_stream_hc(state);
init_internal(&mut state.ctx, src);
set_compression_level(state, c_level);
compress_generic(
&mut state.ctx,
src,
dst,
src_size_ptr,
target_dst_size,
c_level,
LimitedOutputDirective::FillOutput,
)
}
pub fn reset_stream_hc(state: &mut Lz4StreamHc, compression_level: i32) {
init_stream_hc(state);
set_compression_level(state, compression_level);
}
pub fn reset_stream_hc_fast(state: &mut Lz4StreamHc, compression_level: i32) {
if state.ctx.dirty != 0 {
init_stream_hc(state);
} else {
let ctx = &mut state.ctx;
let prefix_len: u32 = if ctx.end.is_null() || ctx.prefix_start.is_null() {
0
} else {
(unsafe { ctx.end.offset_from(ctx.prefix_start) }) as u32
};
ctx.dict_limit = ctx.dict_limit.wrapping_add(prefix_len);
ctx.prefix_start = core::ptr::null();
ctx.end = core::ptr::null();
ctx.dict_ctx = core::ptr::null();
}
set_compression_level(state, compression_level);
}
pub fn set_compression_level(state: &mut Lz4StreamHc, mut compression_level: i32) {
if compression_level < 1 {
compression_level = LZ4HC_CLEVEL_DEFAULT;
}
if compression_level > LZ4HC_CLEVEL_MAX {
compression_level = LZ4HC_CLEVEL_MAX;
}
state.ctx.compression_level = compression_level as i16;
}
pub fn favor_decompression_speed(state: &mut Lz4StreamHc, favor: bool) {
state.ctx.favor_dec_speed = if favor { 1 } else { 0 };
}
pub unsafe fn load_dict_hc(state: &mut Lz4StreamHc, dictionary: *const u8, dict_size: i32) -> i32 {
debug_assert!(dict_size >= 0);
let (dict, dict_size) = if dict_size as usize > 64 * 1024 {
let trim = dict_size as usize - 64 * 1024;
(dictionary.add(trim), 64_i32 * 1024)
} else {
(dictionary, dict_size)
};
let c_level = state.ctx.compression_level as i32;
init_stream_hc(state);
set_compression_level(state, c_level);
let cp = get_clevel_params(c_level);
let ctx = &mut state.ctx;
init_internal(ctx, dict);
ctx.end = dict.add(dict_size as usize);
if cp.strat == HcStrategy::Lz4Mid {
fill_htable(ctx, dict, dict_size as usize);
} else if dict_size as usize >= LZ4HC_HASHSIZE {
insert(ctx, ctx.end.sub(3));
}
dict_size
}
pub unsafe fn attach_hc_dictionary(
working_stream: &mut Lz4StreamHc,
dictionary_stream: Option<*const Lz4StreamHc>,
) {
working_stream.ctx.dict_ctx = match dictionary_stream {
Some(ptr) => &(*ptr).ctx as *const HcCCtxInternal,
None => core::ptr::null(),
};
}
unsafe fn compress_hc_continue_generic(
state: &mut Lz4StreamHc,
src: *const u8,
dst: *mut u8,
src_size_ptr: &mut i32,
dst_capacity: i32,
limit: LimitedOutputDirective,
) -> i32 {
if state.ctx.prefix_start.is_null() {
init_internal(&mut state.ctx, src);
}
let overflow_reload: Option<(*const u8, i32)> = {
let ctx = &state.ctx;
let prefix_len: usize = if ctx.end.is_null() || ctx.prefix_start.is_null() {
0
} else {
ctx.end.offset_from(ctx.prefix_start) as usize
};
const GB_2: usize = 2 * 1024 * 1024 * 1024;
if prefix_len.saturating_add(ctx.dict_limit as usize) > GB_2 {
let dict_size = prefix_len.min(64 * 1024) as i32;
let dict_ptr = if dict_size > 0 {
ctx.end.sub(dict_size as usize)
} else {
ctx.end
};
Some((dict_ptr, dict_size))
} else {
None
}
};
if let Some((dict_ptr, dict_size)) = overflow_reload {
load_dict_hc(state, dict_ptr, dict_size);
}
if !state.ctx.end.is_null() && src != state.ctx.end {
set_external_dict(&mut state.ctx, src);
}
let overlap_update: Option<(u32, u32, *const u8, *const u8)> = {
let ctx = &state.ctx;
if !ctx.dict_start.is_null() && ctx.dict_limit > ctx.low_limit {
let source_end = src.add(*src_size_ptr as usize);
let dict_begin = ctx.dict_start;
let dict_size_bytes = (ctx.dict_limit - ctx.low_limit) as usize;
let dict_end = ctx.dict_start.add(dict_size_bytes);
if source_end > dict_begin && src < dict_end {
let eff_source_end = if source_end > dict_end {
dict_end
} else {
source_end
};
let advance = eff_source_end.offset_from(ctx.dict_start) as u32;
let low_limit_new = ctx.low_limit.wrapping_add(advance);
let dict_limit = ctx.dict_limit;
let dict_start_new = ctx.dict_start.add(advance as usize);
let prefix_start = ctx.prefix_start;
Some((low_limit_new, dict_limit, dict_start_new, prefix_start))
} else {
None
}
} else {
None
}
};
if let Some((low_limit_new, dict_limit, dict_start_new, prefix_start)) = overlap_update {
let ctx = &mut state.ctx;
ctx.low_limit = low_limit_new;
ctx.dict_start = dict_start_new;
if dict_limit - ctx.low_limit < LZ4HC_HASHSIZE as u32 {
ctx.low_limit = dict_limit;
ctx.dict_start = prefix_start;
}
}
let c_level = state.ctx.compression_level as i32;
compress_generic(
&mut state.ctx,
src,
dst,
src_size_ptr,
dst_capacity,
c_level,
limit,
)
}
pub unsafe fn compress_hc_continue(
state: &mut Lz4StreamHc,
src: *const u8,
dst: *mut u8,
src_size: i32,
dst_capacity: i32,
) -> i32 {
let mut src_size_mut = src_size;
let limit = if dst_capacity < compress_bound(src_size) {
LimitedOutputDirective::LimitedOutput
} else {
LimitedOutputDirective::NotLimited
};
compress_hc_continue_generic(state, src, dst, &mut src_size_mut, dst_capacity, limit)
}
pub unsafe fn compress_hc_continue_dest_size(
state: &mut Lz4StreamHc,
src: *const u8,
dst: *mut u8,
src_size_ptr: &mut i32,
target_dst_size: i32,
) -> i32 {
compress_hc_continue_generic(
state,
src,
dst,
src_size_ptr,
target_dst_size,
LimitedOutputDirective::FillOutput,
)
}
pub unsafe fn save_dict_hc(state: &mut Lz4StreamHc, safe_buffer: *mut u8, dict_size: i32) -> i32 {
let ctx = &state.ctx;
let prefix_size = (ctx.end as usize).wrapping_sub(ctx.prefix_start as usize) as i32;
debug_assert!(prefix_size >= 0);
let mut dict_size = dict_size;
if dict_size > 64 * 1024 {
dict_size = 64 * 1024;
}
if dict_size < 4 {
dict_size = 0;
}
if dict_size > prefix_size {
dict_size = prefix_size;
}
debug_assert!(!safe_buffer.is_null() || dict_size == 0);
if dict_size > 0 {
core::ptr::copy(
ctx.end.sub(dict_size as usize),
safe_buffer,
dict_size as usize,
);
}
let end_index =
(ctx.end as usize).wrapping_sub(ctx.prefix_start as usize) as u32 + ctx.dict_limit;
let ctx = &mut state.ctx;
ctx.end = if safe_buffer.is_null() {
core::ptr::null()
} else {
(safe_buffer as *const u8).add(dict_size as usize)
};
ctx.prefix_start = safe_buffer as *const u8;
ctx.dict_limit = end_index.wrapping_sub(dict_size as u32);
ctx.low_limit = end_index.wrapping_sub(dict_size as u32);
ctx.dict_start = ctx.prefix_start;
if ctx.next_to_update < ctx.dict_limit {
ctx.next_to_update = ctx.dict_limit;
}
dict_size
}