use crate::block::compress::compress_fast_ext_state_fast_reset;
use crate::block::stream::Lz4Stream;
use crate::frame::cdict::Lz4FCDict;
use crate::frame::header::{
lz4f_compress_bound_internal, lz4f_compress_frame_bound, lz4f_get_block_size,
lz4f_header_checksum, lz4f_optimal_bsid, write_le32, write_le64,
};
use crate::frame::types::{
BlockChecksum, BlockCompressMode, BlockMode, BlockSizeId, ContentChecksum, CtxType, Lz4FCCtx,
Lz4FError, Preferences, BF_SIZE, BH_SIZE, MAX_FH_SIZE,
};
use crate::hc::api::{
attach_hc_dictionary, compress_hc_continue, compress_hc_ext_state_fast_reset,
favor_decompression_speed, init_stream_hc, load_dict_hc, reset_stream_hc_fast, save_dict_hc,
set_compression_level as hc_set_compression_level, Lz4StreamHc,
};
use crate::hc::types::LZ4HC_CLEVEL_MIN;
use crate::xxhash::{xxh32_oneshot, Xxh32State};
pub const LZ4F_MAGIC_NUMBER: u32 = 0x184D_2204;
pub const LZ4F_VERSION: u32 = 100;
const PTR_BYTES: usize = core::mem::size_of::<*mut ()>();
const KB64: usize = 64 * 1024;
#[derive(Debug, Clone, Copy, Default)]
pub struct CompressOptions {
pub stable_src: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum CompressMode {
FastIndependent,
FastLinked,
HcIndependent,
HcLinked,
Uncompressed,
}
#[inline]
fn select_compress_mode(
block_mode: BlockMode,
level: i32,
compress_mode: BlockCompressMode,
) -> CompressMode {
if compress_mode == BlockCompressMode::Uncompressed {
return CompressMode::Uncompressed;
}
if level < LZ4HC_CLEVEL_MIN {
if block_mode == BlockMode::Independent {
CompressMode::FastIndependent
} else {
CompressMode::FastLinked
}
} else if block_mode == BlockMode::Independent {
CompressMode::HcIndependent
} else {
CompressMode::HcLinked
}
}
fn read_inner_ptr(cctx: &Lz4FCCtx) -> usize {
match cctx.lz4_ctx.as_ref() {
Some(v) if v.len() >= PTR_BYTES => usize::from_ne_bytes(v[..PTR_BYTES].try_into().unwrap()),
_ => 0,
}
}
fn write_inner_ptr(cctx: &mut Lz4FCCtx, ptr: usize) {
let bytes = ptr.to_ne_bytes();
match cctx.lz4_ctx.as_mut() {
Some(v) if v.len() >= PTR_BYTES => {
v[..PTR_BYTES].copy_from_slice(&bytes);
}
_ => {
cctx.lz4_ctx = Some(bytes.to_vec());
}
}
}
unsafe fn free_inner_ctx(cctx: &mut Lz4FCCtx) {
let ptr = read_inner_ptr(cctx);
if ptr != 0 {
match cctx.lz4_ctx_alloc {
1 => drop(Box::from_raw(ptr as *mut Lz4Stream)),
2 => drop(Box::from_raw(ptr as *mut Lz4StreamHc)),
_ => {}
}
}
cctx.lz4_ctx = None;
cctx.lz4_ctx_alloc = 0;
cctx.lz4_ctx_type = CtxType::None;
}
#[inline]
unsafe fn fast_ctx_ptr(cctx: &Lz4FCCtx) -> *mut Lz4Stream {
read_inner_ptr(cctx) as *mut Lz4Stream
}
#[inline]
unsafe fn hc_ctx_ptr(cctx: &Lz4FCCtx) -> *mut Lz4StreamHc {
read_inner_ptr(cctx) as *mut Lz4StreamHc
}
#[inline]
unsafe fn cdict_ref(cctx: &Lz4FCCtx) -> *const Lz4FCDict {
cctx.cdict_ptr as *const Lz4FCDict
}
unsafe fn lz4f_init_stream(
ctx_ptr: usize, cdict: *const Lz4FCDict,
level: i32,
block_mode: BlockMode,
) {
if level < LZ4HC_CLEVEL_MIN {
let stream = &mut *(ctx_ptr as *mut Lz4Stream);
if !cdict.is_null() || block_mode == BlockMode::Linked {
stream.reset_fast();
if !cdict.is_null() {
let dict_stream: *const Lz4Stream = &*(*cdict).fast_ctx;
stream.attach_dictionary(Some(dict_stream));
}
}
} else {
let stream = &mut *(ctx_ptr as *mut Lz4StreamHc);
reset_stream_hc_fast(stream, level);
if !cdict.is_null() {
let hc_dict: *const Lz4StreamHc = &*(*cdict).hc_ctx;
attach_hc_dictionary(stream, Some(hc_dict));
}
}
}
unsafe fn local_save_dict(cctx: &mut Lz4FCCtx) -> i32 {
let buf_len = cctx.tmp_buf.len().min(KB64) as i32;
let buf_ptr = cctx.tmp_buf.as_mut_ptr();
if cctx.prefs.compression_level < LZ4HC_CLEVEL_MIN {
let stream = &mut *fast_ctx_ptr(cctx);
let slice = core::slice::from_raw_parts_mut(buf_ptr, buf_len as usize);
stream.save_dict(slice)
} else {
let stream = &mut *hc_ctx_ptr(cctx);
save_dict_hc(stream, buf_ptr, buf_len)
}
}
unsafe fn lz4f_make_block(
dst: &mut [u8],
src: &[u8],
mode: CompressMode,
cctx: &mut Lz4FCCtx,
crc_flag: bool,
) -> usize {
let src_size = src.len();
let level = cctx.prefs.compression_level;
let ctx_ptr = read_inner_ptr(cctx);
let cdict = cdict_ref(cctx);
let compress_dst: *mut u8 = dst.as_mut_ptr().add(BH_SIZE);
let compress_cap: i32 = if src_size > 0 {
(src_size - 1) as i32
} else {
0
};
let c_size: usize = match mode {
CompressMode::Uncompressed => 0,
CompressMode::FastIndependent => {
lz4f_init_stream(ctx_ptr, cdict, level, BlockMode::Independent);
let stream = &mut *(ctx_ptr as *mut Lz4Stream);
let result: i32 = if !cdict.is_null() {
let dst_slice =
core::slice::from_raw_parts_mut(compress_dst, src_size.saturating_sub(1));
stream.compress_fast_continue(src, dst_slice, accel(level))
} else {
match compress_fast_ext_state_fast_reset(
&mut stream.internal,
src.as_ptr(),
src_size as i32,
compress_dst,
compress_cap,
accel(level),
) {
Ok(n) => n as i32,
Err(_) => 0,
}
};
result.max(0) as usize
}
CompressMode::FastLinked => {
let stream = &mut *(ctx_ptr as *mut Lz4Stream);
let dst_slice =
core::slice::from_raw_parts_mut(compress_dst, src_size.saturating_sub(1));
stream
.compress_fast_continue(src, dst_slice, accel(level))
.max(0) as usize
}
CompressMode::HcIndependent => {
lz4f_init_stream(ctx_ptr, cdict, level, BlockMode::Independent);
let stream = &mut *(ctx_ptr as *mut Lz4StreamHc);
let result: i32 = if !cdict.is_null() {
compress_hc_continue(
stream,
src.as_ptr(),
compress_dst,
src_size as i32,
compress_cap,
)
} else {
compress_hc_ext_state_fast_reset(
stream,
src.as_ptr(),
compress_dst,
src_size as i32,
compress_cap,
level,
)
};
result.max(0) as usize
}
CompressMode::HcLinked => {
let stream = &mut *(ctx_ptr as *mut Lz4StreamHc);
compress_hc_continue(
stream,
src.as_ptr(),
compress_dst,
src_size as i32,
compress_cap,
)
.max(0) as usize
}
};
let final_c_size: usize;
if c_size == 0 || c_size >= src_size {
final_c_size = src_size;
write_le32(
dst,
0,
final_c_size as u32 | crate::frame::types::LZ4F_BLOCKUNCOMPRESSED_FLAG,
);
dst[BH_SIZE..BH_SIZE + src_size].copy_from_slice(src);
} else {
final_c_size = c_size;
write_le32(dst, 0, final_c_size as u32);
}
if crc_flag {
let crc = xxh32_oneshot(&dst[BH_SIZE..BH_SIZE + final_c_size], 0);
write_le32(dst, BH_SIZE + final_c_size, crc);
}
BH_SIZE + final_c_size + if crc_flag { BF_SIZE } else { 0 }
}
#[inline]
fn accel(level: i32) -> i32 {
if level < 0 {
-level + 1
} else {
1
}
}
impl Lz4FCCtx {
pub fn new(version: u32) -> Box<Self> {
Box::new(Lz4FCCtx {
cmem: Default::default(),
prefs: Preferences::default(),
version,
c_stage: 0,
max_block_size: 0,
max_buffer_size: 0,
tmp_buf: Vec::new(),
tmp_in_offset: 0,
tmp_in_size: 0,
total_in_size: 0,
xxh: Xxh32State::new(0),
lz4_ctx: None,
lz4_ctx_alloc: 0,
lz4_ctx_type: CtxType::None,
block_compress_mode: BlockCompressMode::Compressed,
cdict_ptr: 0,
})
}
}
impl Drop for Lz4FCCtx {
fn drop(&mut self) {
unsafe { free_inner_ctx(self) }
}
}
pub fn lz4f_create_compression_context(version: u32) -> Result<Box<Lz4FCCtx>, Lz4FError> {
if version != LZ4F_VERSION {
return Err(Lz4FError::AllocationFailed);
}
Ok(Lz4FCCtx::new(version))
}
#[inline]
pub fn lz4f_free_compression_context(_cctx: Box<Lz4FCCtx>) {
}
pub fn lz4f_compress_begin_internal(
cctx: &mut Lz4FCCtx,
dst: &mut [u8],
dict_buffer: Option<&[u8]>,
cdict: Option<*const Lz4FCDict>,
prefs: Option<&Preferences>,
) -> Result<usize, Lz4FError> {
if dst.len() < MAX_FH_SIZE {
return Err(Lz4FError::DstMaxSizeTooSmall);
}
let prefs_val = prefs.copied().unwrap_or_default();
cctx.prefs = prefs_val;
let ctx_type_id: u16 = if cctx.prefs.compression_level < LZ4HC_CLEVEL_MIN {
1
} else {
2
};
if cctx.lz4_ctx_alloc < ctx_type_id {
unsafe { free_inner_ctx(cctx) };
let raw_ptr: usize = if ctx_type_id == 1 {
let stream = Lz4Stream::new();
Box::into_raw(stream) as usize
} else {
let stream = Lz4StreamHc::create().ok_or(Lz4FError::AllocationFailed)?;
Box::into_raw(stream) as usize
};
write_inner_ptr(cctx, raw_ptr);
cctx.lz4_ctx_alloc = ctx_type_id;
cctx.lz4_ctx_type = if ctx_type_id == 1 {
CtxType::Fast
} else {
CtxType::Hc
};
} else if cctx.lz4_ctx_type as u16 != ctx_type_id {
let ptr = read_inner_ptr(cctx);
if ctx_type_id == 1 {
unsafe {
let stream = &mut *(ptr as *mut Lz4Stream);
stream.reset();
}
} else {
unsafe {
let stream = &mut *(ptr as *mut Lz4StreamHc);
init_stream_hc(stream);
set_hc_level(stream, cctx.prefs.compression_level);
}
}
cctx.lz4_ctx_type = if ctx_type_id == 1 {
CtxType::Fast
} else {
CtxType::Hc
};
}
if cctx.prefs.frame_info.block_size_id == BlockSizeId::Default {
cctx.prefs.frame_info.block_size_id = BlockSizeId::Max64Kb;
}
cctx.max_block_size = lz4f_get_block_size(cctx.prefs.frame_info.block_size_id).unwrap_or(KB64);
let required_buff_size: usize = if prefs_val.auto_flush {
if cctx.prefs.frame_info.block_mode == BlockMode::Linked {
KB64
} else {
0
}
} else {
cctx.max_block_size
+ if cctx.prefs.frame_info.block_mode == BlockMode::Linked {
128 * 1024
} else {
0
}
};
if cctx.max_buffer_size < required_buff_size {
cctx.tmp_buf = vec![0u8; required_buff_size];
cctx.max_buffer_size = required_buff_size;
}
cctx.tmp_in_offset = 0;
cctx.tmp_in_size = 0;
cctx.xxh = Xxh32State::new(0);
let cdict_raw: *const Lz4FCDict = cdict.unwrap_or(core::ptr::null());
cctx.cdict_ptr = cdict_raw as usize;
let ctx_ptr = read_inner_ptr(cctx);
if cctx.prefs.frame_info.block_mode == BlockMode::Linked {
unsafe {
lz4f_init_stream(
ctx_ptr,
cdict_raw,
cctx.prefs.compression_level,
BlockMode::Linked,
);
}
}
if cctx.prefs.compression_level >= LZ4HC_CLEVEL_MIN {
unsafe {
let stream = &mut *(ctx_ptr as *mut Lz4StreamHc);
favor_decompression_speed(stream, prefs_val.favor_dec_speed);
}
}
if let Some(dict) = dict_buffer {
if !dict.is_empty() {
if dict.len() > i32::MAX as usize {
return Err(Lz4FError::ParameterInvalid);
}
unsafe {
if ctx_type_id == 1 {
let stream = &mut *(ctx_ptr as *mut Lz4Stream);
stream.load_dict(dict);
} else {
let stream = &mut *(ctx_ptr as *mut Lz4StreamHc);
load_dict_hc(stream, dict.as_ptr(), dict.len() as i32);
}
}
}
}
let mut pos: usize = 0;
write_le32(dst, pos, LZ4F_MAGIC_NUMBER);
pos += 4;
let header_start = pos;
let fi = &cctx.prefs.frame_info;
let flg: u8 = (1u8 << 6) | ((fi.block_mode as u8 & 1) << 5)
| ((fi.block_checksum_flag as u8 & 1) << 4)
| (if fi.content_size > 0 { 1u8 } else { 0 } << 3)
| ((fi.content_checksum_flag as u8 & 1) << 2)
| (if fi.dict_id > 0 { 1u8 } else { 0 });
dst[pos] = flg;
pos += 1;
let bd: u8 = (fi.block_size_id as u8 & 7) << 4;
dst[pos] = bd;
pos += 1;
if fi.content_size > 0 {
write_le64(dst, pos, fi.content_size);
pos += 8;
cctx.total_in_size = 0;
}
if fi.dict_id > 0 {
write_le32(dst, pos, fi.dict_id);
pos += 4;
}
let hc = lz4f_header_checksum(&dst[header_start..pos]);
dst[pos] = hc;
pos += 1;
cctx.c_stage = 1; Ok(pos)
}
pub fn lz4f_compress_begin(
cctx: &mut Lz4FCCtx,
dst: &mut [u8],
prefs: Option<&Preferences>,
) -> Result<usize, Lz4FError> {
lz4f_compress_begin_internal(cctx, dst, None, None, prefs)
}
pub fn lz4f_compress_begin_using_dict(
cctx: &mut Lz4FCCtx,
dst: &mut [u8],
dict: &[u8],
prefs: Option<&Preferences>,
) -> Result<usize, Lz4FError> {
lz4f_compress_begin_internal(cctx, dst, Some(dict), None, prefs)
}
pub unsafe fn lz4f_compress_begin_using_cdict(
cctx: &mut Lz4FCCtx,
dst: &mut [u8],
cdict: *const Lz4FCDict,
prefs: Option<&Preferences>,
) -> Result<usize, Lz4FError> {
let cdict_opt = if cdict.is_null() { None } else { Some(cdict) };
lz4f_compress_begin_internal(cctx, dst, None, cdict_opt, prefs)
}
pub fn lz4f_compress_bound(src_size: usize, prefs: Option<&Preferences>) -> usize {
let default_prefs = Preferences::default();
let prefs = prefs.unwrap_or(&default_prefs);
let already_buffered = if prefs.auto_flush {
0
} else {
usize::MAX };
lz4f_compress_bound_internal(src_size, prefs, already_buffered)
}
pub fn lz4f_compress_update_impl(
cctx: &mut Lz4FCCtx,
dst: &mut [u8],
src: &[u8],
opts: Option<&CompressOptions>,
block_compression: BlockCompressMode,
) -> Result<usize, Lz4FError> {
if cctx.c_stage != 1 {
return Err(Lz4FError::CompressionStateUninitialized);
}
if dst.len() < lz4f_compress_bound_internal(src.len(), &cctx.prefs, cctx.tmp_in_size) {
return Err(Lz4FError::DstMaxSizeTooSmall);
}
if block_compression == BlockCompressMode::Uncompressed && dst.len() < src.len() {
return Err(Lz4FError::DstMaxSizeTooSmall);
}
let opts = opts.copied().unwrap_or_default();
let block_size = cctx.max_block_size;
let mut dst_pos: usize = 0;
if cctx.block_compress_mode != block_compression {
let flush_size = lz4f_flush_impl(cctx, dst, None)?;
dst_pos += flush_size;
cctx.block_compress_mode = block_compression;
}
let compress_mode = select_compress_mode(
cctx.prefs.frame_info.block_mode,
cctx.prefs.compression_level,
block_compression,
);
let crc_flag = cctx.prefs.frame_info.block_checksum_flag == BlockChecksum::Enabled;
let block_mode = cctx.prefs.frame_info.block_mode;
let mut src_pos: usize = 0;
let mut last_block_status = 0u8;
if cctx.tmp_in_size > 0 {
let size_to_copy = block_size - cctx.tmp_in_size;
if size_to_copy > src.len() - src_pos {
let append = &src[src_pos..];
let dst_off = cctx.tmp_in_offset + cctx.tmp_in_size;
cctx.tmp_buf[dst_off..dst_off + append.len()].copy_from_slice(append);
src_pos = src.len();
cctx.tmp_in_size += append.len();
} else {
last_block_status = 1; let fill_slice = &src[src_pos..src_pos + size_to_copy];
let tmp_dst = cctx.tmp_in_offset + cctx.tmp_in_size;
cctx.tmp_buf[tmp_dst..tmp_dst + size_to_copy].copy_from_slice(fill_slice);
src_pos += size_to_copy;
let tmp_in_off = cctx.tmp_in_offset;
let written = unsafe {
let src_slice =
core::slice::from_raw_parts(cctx.tmp_buf.as_ptr().add(tmp_in_off), block_size);
lz4f_make_block(
&mut dst[dst_pos..],
src_slice,
compress_mode,
cctx,
crc_flag,
)
};
dst_pos += written;
if block_mode == BlockMode::Linked {
cctx.tmp_in_offset += block_size;
}
cctx.tmp_in_size = 0;
}
}
while src.len() - src_pos >= block_size {
last_block_status = 2; let block = &src[src_pos..src_pos + block_size];
let written =
unsafe { lz4f_make_block(&mut dst[dst_pos..], block, compress_mode, cctx, crc_flag) };
dst_pos += written;
src_pos += block_size;
}
if cctx.prefs.auto_flush && src_pos < src.len() {
last_block_status = 2; let rem = &src[src_pos..];
let written =
unsafe { lz4f_make_block(&mut dst[dst_pos..], rem, compress_mode, cctx, crc_flag) };
dst_pos += written;
src_pos = src.len();
}
if block_mode == BlockMode::Linked && last_block_status == 2 {
if opts.stable_src {
cctx.tmp_in_offset = 0;
} else {
let real_dict_size = unsafe { local_save_dict(cctx) };
debug_assert!(real_dict_size >= 0 && real_dict_size as usize <= KB64);
cctx.tmp_in_offset = real_dict_size as usize;
}
}
if !cctx.prefs.auto_flush {
let tmp_in_end = cctx.tmp_in_offset + block_size;
if tmp_in_end > cctx.max_buffer_size {
let real_dict_size = unsafe { local_save_dict(cctx) };
cctx.tmp_in_offset = real_dict_size as usize;
debug_assert!(cctx.tmp_in_offset + block_size <= cctx.max_buffer_size);
}
}
if src_pos < src.len() {
let rem = &src[src_pos..];
let dst_off = cctx.tmp_in_offset + cctx.tmp_in_size;
cctx.tmp_buf[dst_off..dst_off + rem.len()].copy_from_slice(rem);
cctx.tmp_in_size += rem.len();
}
if cctx.prefs.frame_info.content_checksum_flag == ContentChecksum::Enabled {
cctx.xxh.update(src);
}
cctx.total_in_size += src.len() as u64;
Ok(dst_pos)
}
pub fn lz4f_compress_update(
cctx: &mut Lz4FCCtx,
dst: &mut [u8],
src: &[u8],
opts: Option<&CompressOptions>,
) -> Result<usize, Lz4FError> {
lz4f_compress_update_impl(cctx, dst, src, opts, BlockCompressMode::Compressed)
}
pub fn lz4f_uncompressed_update(
cctx: &mut Lz4FCCtx,
dst: &mut [u8],
src: &[u8],
opts: Option<&CompressOptions>,
) -> Result<usize, Lz4FError> {
lz4f_compress_update_impl(cctx, dst, src, opts, BlockCompressMode::Uncompressed)
}
fn lz4f_flush_impl(
cctx: &mut Lz4FCCtx,
dst: &mut [u8],
_opts: Option<&CompressOptions>,
) -> Result<usize, Lz4FError> {
if cctx.tmp_in_size == 0 {
return Ok(0); }
if cctx.c_stage != 1 {
return Err(Lz4FError::CompressionStateUninitialized);
}
let min_dst = cctx.tmp_in_size + BH_SIZE + BF_SIZE;
if dst.len() < min_dst {
return Err(Lz4FError::DstMaxSizeTooSmall);
}
let compress_mode = select_compress_mode(
cctx.prefs.frame_info.block_mode,
cctx.prefs.compression_level,
cctx.block_compress_mode,
);
let crc_flag = cctx.prefs.frame_info.block_checksum_flag == BlockChecksum::Enabled;
let tmp_in_off = cctx.tmp_in_offset;
let tmp_in_sz = cctx.tmp_in_size;
let written = unsafe {
let src_slice =
core::slice::from_raw_parts(cctx.tmp_buf.as_ptr().add(tmp_in_off), tmp_in_sz);
lz4f_make_block(dst, src_slice, compress_mode, cctx, crc_flag)
};
if cctx.prefs.frame_info.block_mode == BlockMode::Linked {
cctx.tmp_in_offset += cctx.tmp_in_size;
}
cctx.tmp_in_size = 0;
if cctx.tmp_in_offset + cctx.max_block_size > cctx.max_buffer_size {
let real_dict_size = unsafe { local_save_dict(cctx) };
cctx.tmp_in_offset = real_dict_size as usize;
}
Ok(written)
}
pub fn lz4f_flush(
cctx: &mut Lz4FCCtx,
dst: &mut [u8],
opts: Option<&CompressOptions>,
) -> Result<usize, Lz4FError> {
lz4f_flush_impl(cctx, dst, opts)
}
pub fn lz4f_compress_end(
cctx: &mut Lz4FCCtx,
dst: &mut [u8],
opts: Option<&CompressOptions>,
) -> Result<usize, Lz4FError> {
let flush_size = lz4f_flush_impl(cctx, dst, opts)?;
let mut pos = flush_size;
if dst.len() - pos < 4 {
return Err(Lz4FError::DstMaxSizeTooSmall);
}
write_le32(dst, pos, 0);
pos += 4;
if cctx.prefs.frame_info.content_checksum_flag == ContentChecksum::Enabled {
if dst.len() - pos < 4 {
return Err(Lz4FError::DstMaxSizeTooSmall);
}
let xxh = cctx.xxh.digest();
write_le32(dst, pos, xxh);
pos += 4;
}
cctx.c_stage = 0;
if cctx.prefs.frame_info.content_size != 0
&& cctx.prefs.frame_info.content_size != cctx.total_in_size
{
return Err(Lz4FError::FrameSizeWrong);
}
Ok(pos)
}
pub fn lz4f_compress_frame_using_cdict(
cctx: &mut Lz4FCCtx,
dst: &mut [u8],
src: &[u8],
cdict: *const Lz4FCDict,
prefs: Option<&Preferences>,
) -> Result<usize, Lz4FError> {
let mut local_prefs = prefs.copied().unwrap_or_default();
if local_prefs.frame_info.content_size != 0 {
local_prefs.frame_info.content_size = src.len() as u64;
}
local_prefs.frame_info.block_size_id =
lz4f_optimal_bsid(local_prefs.frame_info.block_size_id, src.len());
local_prefs.auto_flush = true;
if src.len() <= lz4f_get_block_size(local_prefs.frame_info.block_size_id).unwrap_or(KB64) {
local_prefs.frame_info.block_mode = BlockMode::Independent;
}
let frame_bound = lz4f_compress_frame_bound(src.len(), Some(&local_prefs));
if dst.len() < frame_bound {
return Err(Lz4FError::DstMaxSizeTooSmall);
}
let opts = CompressOptions { stable_src: true };
let cdict_opt = if cdict.is_null() { None } else { Some(cdict) };
let header_size = lz4f_compress_begin_internal(cctx, dst, None, cdict_opt, Some(&local_prefs))?;
let mut pos = header_size;
let c_size = lz4f_compress_update(cctx, &mut dst[pos..], src, Some(&opts))?;
pos += c_size;
let tail_size = lz4f_compress_end(cctx, &mut dst[pos..], Some(&opts))?;
pos += tail_size;
Ok(pos)
}
pub fn lz4f_compress_frame(
dst: &mut [u8],
src: &[u8],
prefs: Option<&Preferences>,
) -> Result<usize, Lz4FError> {
let mut cctx = Lz4FCCtx::new(LZ4F_VERSION);
lz4f_compress_frame_using_cdict(&mut cctx, dst, src, core::ptr::null(), prefs)
}
fn set_hc_level(stream: &mut Lz4StreamHc, level: i32) {
hc_set_compression_level(stream, level);
}
#[cfg(test)]
mod tests {
use super::*;
use crate::frame::types::{BlockSizeId, FrameInfo};
#[test]
fn compress_frame_magic_number() {
let src = b"hello world";
let frame_bound = lz4f_compress_frame_bound(src.len(), None);
let mut dst = vec![0u8; frame_bound];
let written = lz4f_compress_frame(&mut dst, src, None).expect("compress_frame failed");
assert!(written >= 4, "must write at least magic number");
let magic = u32::from_le_bytes(dst[..4].try_into().unwrap());
assert_eq!(magic, LZ4F_MAGIC_NUMBER, "magic number must be 0x184D2204");
}
#[test]
fn magic_constant() {
assert_eq!(LZ4F_MAGIC_NUMBER, 0x184D_2204u32);
}
#[test]
fn compress_frame_nonempty() {
let src: Vec<u8> = (0u8..=255).cycle().take(4096).collect();
let frame_bound = lz4f_compress_frame_bound(src.len(), None);
let mut dst = vec![0u8; frame_bound];
let written = lz4f_compress_frame(&mut dst, &src, None).expect("compress_frame");
assert!(
written > 4,
"compressed output must contain more than magic"
);
}
#[test]
fn compress_frame_empty_src() {
let frame_bound = lz4f_compress_frame_bound(0, None);
let mut dst = vec![0u8; frame_bound];
let written = lz4f_compress_frame(&mut dst, &[], None).expect("empty compress_frame");
assert!(written >= 11);
let magic = u32::from_le_bytes(dst[..4].try_into().unwrap());
assert_eq!(magic, LZ4F_MAGIC_NUMBER);
}
#[test]
fn compress_frame_with_content_checksum() {
let src = b"content checksum test payload aaaa bbbb cccc dddd";
let prefs = Preferences {
frame_info: FrameInfo {
content_checksum_flag: ContentChecksum::Enabled,
..Default::default()
},
..Default::default()
};
let frame_bound = lz4f_compress_frame_bound(src.len(), Some(&prefs));
let mut dst = vec![0u8; frame_bound];
let written =
lz4f_compress_frame(&mut dst, src, Some(&prefs)).expect("checksum compress_frame");
assert!(written > 4);
let prefs_no_csum = Preferences::default();
let frame_bound2 = lz4f_compress_frame_bound(src.len(), Some(&prefs_no_csum));
let mut dst2 = vec![0u8; frame_bound2];
let written2 = lz4f_compress_frame(&mut dst2, src, Some(&prefs_no_csum))
.expect("no-checksum compress_frame");
assert_eq!(written, written2 + 4);
}
#[test]
fn streaming_compress_valid_frame() {
let src: Vec<u8> = b"the quick brown fox jumps over the lazy dog"
.iter()
.cycle()
.take(8192)
.copied()
.collect();
let prefs = Preferences {
frame_info: FrameInfo {
block_size_id: BlockSizeId::Max64Kb,
content_checksum_flag: ContentChecksum::Enabled,
..Default::default()
},
auto_flush: true,
..Default::default()
};
let mut cctx = Lz4FCCtx::new(LZ4F_VERSION);
let frame_bound = lz4f_compress_frame_bound(src.len(), Some(&prefs));
let mut out = vec![0u8; frame_bound];
let mut pos = 0;
pos += lz4f_compress_begin(&mut cctx, &mut out[pos..], Some(&prefs)).expect("begin");
for chunk in src.chunks(1024) {
let opts = CompressOptions { stable_src: false };
pos += lz4f_compress_update(&mut cctx, &mut out[pos..], chunk, Some(&opts))
.expect("update");
}
pos += lz4f_compress_end(&mut cctx, &mut out[pos..], None).expect("end");
assert!(pos > 0, "streaming must produce output");
let magic = u32::from_le_bytes(out[..4].try_into().unwrap());
assert_eq!(
magic, LZ4F_MAGIC_NUMBER,
"streaming frame must start with magic"
);
}
#[test]
fn streaming_stable_src_matches_one_shot() {
let src: Vec<u8> = b"aaaaaaaabbbbbbbbccccccccdddddddd"
.iter()
.cycle()
.take(256)
.copied()
.collect();
let prefs = Preferences {
frame_info: FrameInfo {
block_mode: BlockMode::Independent,
content_checksum_flag: ContentChecksum::Enabled,
..Default::default()
},
auto_flush: true,
..Default::default()
};
let frame_bound = lz4f_compress_frame_bound(src.len(), Some(&prefs));
let mut one_shot = vec![0u8; frame_bound];
let one_shot_len =
lz4f_compress_frame(&mut one_shot, &src, Some(&prefs)).expect("one-shot");
let mut cctx = Lz4FCCtx::new(LZ4F_VERSION);
let mut streaming = vec![0u8; frame_bound];
let mut pos = 0;
let opts = CompressOptions { stable_src: true };
pos += lz4f_compress_begin(&mut cctx, &mut streaming[pos..], Some(&prefs)).expect("begin");
pos += lz4f_compress_update(&mut cctx, &mut streaming[pos..], &src, Some(&opts))
.expect("update");
pos += lz4f_compress_end(&mut cctx, &mut streaming[pos..], Some(&opts)).expect("end");
assert_eq!(pos, one_shot_len);
assert_eq!(&streaming[..pos], &one_shot[..one_shot_len]);
}
#[test]
fn create_ctx_wrong_version() {
assert!(lz4f_create_compression_context(99).is_err());
}
#[test]
fn create_ctx_correct_version() {
let ctx = lz4f_create_compression_context(LZ4F_VERSION);
assert!(ctx.is_ok());
}
#[test]
fn free_ctx_no_panic() {
let ctx = lz4f_create_compression_context(LZ4F_VERSION).unwrap();
lz4f_free_compression_context(ctx);
}
#[test]
fn select_compress_mode_uncompressed() {
assert_eq!(
select_compress_mode(BlockMode::Linked, 0, BlockCompressMode::Uncompressed),
CompressMode::Uncompressed
);
}
#[test]
fn select_compress_mode_fast_independent() {
assert_eq!(
select_compress_mode(BlockMode::Independent, 0, BlockCompressMode::Compressed),
CompressMode::FastIndependent
);
}
#[test]
fn select_compress_mode_fast_linked() {
assert_eq!(
select_compress_mode(BlockMode::Linked, 0, BlockCompressMode::Compressed),
CompressMode::FastLinked
);
}
#[test]
fn select_compress_mode_hc_independent() {
assert_eq!(
select_compress_mode(
BlockMode::Independent,
LZ4HC_CLEVEL_MIN,
BlockCompressMode::Compressed
),
CompressMode::HcIndependent
);
}
#[test]
fn select_compress_mode_hc_linked() {
assert_eq!(
select_compress_mode(
BlockMode::Linked,
LZ4HC_CLEVEL_MIN,
BlockCompressMode::Compressed
),
CompressMode::HcLinked
);
}
}