use crate::frame::types::{
BlockChecksum, BlockSizeId, ContentChecksum, Preferences, BF_SIZE, BH_SIZE, MAX_FH_SIZE,
};
use crate::xxhash::xxh32_oneshot;
pub const LZ4HC_CLEVEL_MAX: i32 = 12;
#[inline]
pub fn read_le32(src: &[u8], offset: usize) -> u32 {
u32::from_le_bytes([
src[offset],
src[offset + 1],
src[offset + 2],
src[offset + 3],
])
}
#[inline]
pub fn write_le32(dst: &mut [u8], offset: usize, value: u32) {
dst[offset..offset + 4].copy_from_slice(&value.to_le_bytes());
}
#[inline]
pub fn read_le64(src: &[u8], offset: usize) -> u64 {
u64::from_le_bytes([
src[offset],
src[offset + 1],
src[offset + 2],
src[offset + 3],
src[offset + 4],
src[offset + 5],
src[offset + 6],
src[offset + 7],
])
}
#[inline]
pub fn write_le64(dst: &mut [u8], offset: usize, value: u64) {
dst[offset..offset + 8].copy_from_slice(&value.to_le_bytes());
}
#[inline]
pub fn lz4f_compression_level_max() -> i32 {
LZ4HC_CLEVEL_MAX
}
pub fn lz4f_get_block_size(block_size_id: BlockSizeId) -> Option<usize> {
const BLOCK_SIZES: [usize; 4] = [
64 * 1024, 256 * 1024, 1024 * 1024, 4 * 1024 * 1024, ];
let id = match block_size_id {
BlockSizeId::Default => BlockSizeId::Max64Kb,
other => other,
};
let idx = match id {
BlockSizeId::Max64Kb => 0,
BlockSizeId::Max256Kb => 1,
BlockSizeId::Max1Mb => 2,
BlockSizeId::Max4Mb => 3,
BlockSizeId::Default => return None, };
Some(BLOCK_SIZES[idx])
}
pub fn lz4f_optimal_bsid(requested_bsid: BlockSizeId, src_size: usize) -> BlockSizeId {
let mut proposed = BlockSizeId::Max64Kb;
let mut max_block_size: usize = 64 * 1024;
while (requested_bsid as u32) > (proposed as u32) {
if src_size <= max_block_size {
return proposed;
}
proposed = match proposed {
BlockSizeId::Max64Kb => BlockSizeId::Max256Kb,
BlockSizeId::Max256Kb => BlockSizeId::Max1Mb,
BlockSizeId::Max1Mb => BlockSizeId::Max4Mb,
_ => break, };
max_block_size <<= 2; }
requested_bsid
}
#[inline]
pub fn lz4f_header_checksum(header: &[u8]) -> u8 {
let xxh = xxh32_oneshot(header, 0);
((xxh >> 8) & 0xFF) as u8
}
pub fn lz4f_compress_bound_internal(
src_size: usize,
prefs: &Preferences,
already_buffered: usize,
) -> usize {
let flush = prefs.auto_flush || src_size == 0;
let block_id = match prefs.frame_info.block_size_id {
BlockSizeId::Default => BlockSizeId::Max64Kb,
id => id,
};
let block_size = lz4f_get_block_size(block_id).unwrap_or(64 * 1024);
let max_buffered = block_size - 1;
let buffered_size = already_buffered.min(max_buffered); let max_src_size = src_size + buffered_size;
let nb_full_blocks = max_src_size / block_size;
let partial_block_size = max_src_size & (block_size - 1);
let last_block_size = if flush { partial_block_size } else { 0 };
let nb_blocks = nb_full_blocks + usize::from(last_block_size > 0);
let block_crc_size = if prefs.frame_info.block_checksum_flag == BlockChecksum::Enabled {
BF_SIZE
} else {
0
};
let frame_end = BH_SIZE
+ if prefs.frame_info.content_checksum_flag == ContentChecksum::Enabled {
BF_SIZE
} else {
0
};
((BH_SIZE + block_crc_size) * nb_blocks)
+ (block_size * nb_full_blocks)
+ last_block_size
+ frame_end
}
pub fn lz4f_compress_frame_bound(src_size: usize, prefs: Option<&Preferences>) -> usize {
let mut local_prefs = prefs.copied().unwrap_or_default();
local_prefs.auto_flush = true;
MAX_FH_SIZE + lz4f_compress_bound_internal(src_size, &local_prefs, 0)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::frame::types::{BlockMode, FrameInfo};
#[test]
fn le32_roundtrip() {
let mut buf = [0u8; 4];
write_le32(&mut buf, 0, 0xDEAD_BEEF);
assert_eq!(read_le32(&buf, 0), 0xDEAD_BEEF);
assert_eq!(buf, [0xEF, 0xBE, 0xAD, 0xDE]);
}
#[test]
fn le32_offset() {
let mut buf = [0u8; 8];
write_le32(&mut buf, 4, 0x0102_0304);
assert_eq!(read_le32(&buf, 4), 0x0102_0304);
assert_eq!(&buf[..4], &[0u8; 4]);
}
#[test]
fn le64_roundtrip() {
let mut buf = [0u8; 8];
write_le64(&mut buf, 0, 0x0102_0304_0506_0708u64);
assert_eq!(read_le64(&buf, 0), 0x0102_0304_0506_0708u64);
assert_eq!(buf, [0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01]);
}
#[test]
fn le64_max_value() {
let mut buf = [0u8; 8];
write_le64(&mut buf, 0, u64::MAX);
assert_eq!(read_le64(&buf, 0), u64::MAX);
}
#[test]
fn get_block_size_max64kb() {
assert_eq!(lz4f_get_block_size(BlockSizeId::Max64Kb), Some(65536));
}
#[test]
fn get_block_size_all_ids() {
assert_eq!(lz4f_get_block_size(BlockSizeId::Default), Some(65_536));
assert_eq!(lz4f_get_block_size(BlockSizeId::Max64Kb), Some(65_536));
assert_eq!(lz4f_get_block_size(BlockSizeId::Max256Kb), Some(262_144));
assert_eq!(lz4f_get_block_size(BlockSizeId::Max1Mb), Some(1_048_576));
assert_eq!(lz4f_get_block_size(BlockSizeId::Max4Mb), Some(4_194_304));
}
#[test]
fn optimal_bsid_src_fits_64kb() {
assert_eq!(
lz4f_optimal_bsid(BlockSizeId::Max4Mb, 1024),
BlockSizeId::Max64Kb,
);
}
#[test]
fn optimal_bsid_src_needs_256kb() {
assert_eq!(
lz4f_optimal_bsid(BlockSizeId::Max4Mb, 100_000),
BlockSizeId::Max256Kb,
);
}
#[test]
fn optimal_bsid_requested_limits_result() {
assert_eq!(
lz4f_optimal_bsid(BlockSizeId::Max64Kb, 100_000),
BlockSizeId::Max64Kb,
);
}
#[test]
fn optimal_bsid_exact_boundary() {
assert_eq!(
lz4f_optimal_bsid(BlockSizeId::Max4Mb, 64 * 1024),
BlockSizeId::Max64Kb,
);
assert_eq!(
lz4f_optimal_bsid(BlockSizeId::Max4Mb, 64 * 1024 + 1),
BlockSizeId::Max256Kb,
);
}
#[test]
fn header_checksum_is_deterministic() {
let header = [0x60u8]; assert_eq!(lz4f_header_checksum(&header), lz4f_header_checksum(&header));
}
#[test]
fn header_checksum_formula() {
let header = [0x60u8, 0x70u8];
let xxh = xxh32_oneshot(&header, 0);
assert_eq!(lz4f_header_checksum(&header), ((xxh >> 8) & 0xFF) as u8);
}
#[test]
fn header_checksum_empty() {
let xxh = xxh32_oneshot(&[], 0);
assert_eq!(lz4f_header_checksum(&[]), ((xxh >> 8) & 0xFF) as u8);
}
#[test]
fn compress_bound_internal_zero_src_no_buffered() {
let prefs = Preferences::default();
assert_eq!(lz4f_compress_bound_internal(0, &prefs, 0), 4);
}
#[test]
fn compress_bound_internal_one_full_block() {
let prefs = Preferences::default();
assert_eq!(lz4f_compress_bound_internal(65_536, &prefs, 0), 65_544);
}
#[test]
fn compress_bound_internal_with_checksums() {
let prefs = Preferences {
frame_info: FrameInfo {
block_size_id: BlockSizeId::Max64Kb,
block_mode: BlockMode::Linked,
content_checksum_flag: ContentChecksum::Enabled,
block_checksum_flag: BlockChecksum::Enabled,
..FrameInfo::default()
},
..Preferences::default()
};
assert_eq!(lz4f_compress_bound_internal(0, &prefs, 0), 8);
}
#[test]
fn compress_frame_bound_zero_null_prefs() {
assert_eq!(lz4f_compress_frame_bound(0, None), 23);
}
#[test]
fn compress_frame_bound_includes_header_size() {
let prefs = Preferences::default();
let internal = lz4f_compress_bound_internal(1024, &prefs, 0);
let prefs_flushed = Preferences {
auto_flush: true,
..prefs
};
let expected = MAX_FH_SIZE + lz4f_compress_bound_internal(1024, &prefs_flushed, 0);
assert_eq!(lz4f_compress_frame_bound(1024, Some(&prefs)), expected);
let _ = internal; }
#[test]
fn compression_level_max_is_12() {
assert_eq!(lz4f_compression_level_max(), 12);
}
}