#![allow(dead_code, unused_imports, unused_variables, unused_mut)]
use std::cmp;
use std::fmt;
use std::fs;
use std::io::{self, BufReader, BufWriter, Read, Write};
use std::path::Path;
use std::time::Instant;
pub const LZ4_VERSION: u32 = 1_09_04;
pub const LZ4_MAX_INPUT_SIZE: usize = 0x7E00_0000;
const LZ4_HASH_LOG: usize = 12;
const LZ4_HASHTABLESIZE: usize = 1 << LZ4_HASH_LOG;
const LZ4_HASH_MASK: u32 = (LZ4_HASHTABLESIZE as u32) - 1;
const LZ4_HC_HASH_LOG: usize = 15;
const LZ4_HC_HASHTABLESIZE: usize = 1 << LZ4_HC_HASH_LOG;
const LZ4_HC_HASH_MASK: u32 = (LZ4_HC_HASHTABLESIZE as u32) - 1;
const LZ4_HASH_MULTIPLIER: u32 = 2_654_435_761;
pub const MINMATCH: usize = 4;
const MFLIMIT: usize = 12;
const LASTLITERALS: usize = 5;
pub const LZ4_DISTANCE_MAX: usize = 65535;
pub const LZ4_MAX_MATCH_LEN: usize = 0xFFFF + MINMATCH;
const LZ4F_MAGIC: u32 = 0x184D_2204;
const LZ4F_HEADER_SIZE_MIN: usize = 3;
const LZ4F_HEADER_SIZE_MAX: usize = 15;
const LZ4F_BLOCK_CHECKSUM_SIZE: usize = 4;
const LZ4F_CONTENT_CHECKSUM_SIZE: usize = 4;
const LZ4F_END_MARK: u32 = 0x0000_0000;
pub const LZ4F_MAX_BLOCK_SIZE: usize = 4 * 1024 * 1024;
pub const LZ4F_FLG_VERSION: u8 = 0b1100_0000;
pub const LZ4F_FLG_VERSION_01: u8 = 0b0100_0000;
pub const LZ4F_FLG_BLOCK_INDEPENDENCE: u8 = 0b0010_0000;
pub const LZ4F_FLG_BLOCK_CHECKSUM: u8 = 0b0001_0000;
pub const LZ4F_FLG_CONTENT_SIZE: u8 = 0b0000_1000;
pub const LZ4F_FLG_CONTENT_CHECKSUM: u8 = 0b0000_0100;
pub const LZ4F_FLG_DICT_ID: u8 = 0b0000_0010;
pub const LZ4F_BLOCK_MAXSIZE_64KB: u8 = 4;
pub const LZ4F_BLOCK_MAXSIZE_256KB: u8 = 5;
pub const LZ4F_BLOCK_MAXSIZE_1MB: u8 = 6;
pub const LZ4F_BLOCK_MAXSIZE_4MB: u8 = 7;
fn block_maxsize_from_bd(bd: u8) -> usize {
match bd & 0x07 {
4 => 64 * 1024,
5 => 256 * 1024,
6 => 1024 * 1024,
7 => 4 * 1024 * 1024,
_ => 64 * 1024,
}
}
fn bd_from_block_maxsize(bs: usize) -> u8 {
if bs <= 64 * 1024 {
LZ4F_BLOCK_MAXSIZE_64KB
} else if bs <= 256 * 1024 {
LZ4F_BLOCK_MAXSIZE_256KB
} else if bs <= 1024 * 1024 {
LZ4F_BLOCK_MAXSIZE_1MB
} else {
LZ4F_BLOCK_MAXSIZE_4MB
}
}
const LZ4F_DEFAULT_BLOCK_SIZE: usize = 4 * 1024 * 1024;
const XXH32_PRIME1: u32 = 2_654_435_761;
const XXH32_PRIME2: u32 = 2_246_822_519;
const XXH32_PRIME3: u32 = 3_266_489_917;
const XXH32_PRIME4: u32 = 668_265_263;
const XXH32_PRIME5: u32 = 374_761_393;
#[inline(always)]
fn xxh_rotl32(x: u32, r: u32) -> u32 {
(x << r) | (x >> (32 - r))
}
#[inline(always)]
fn xxh32_round(acc: u32, lane: u32) -> u32 {
xxh_rotl32(acc.wrapping_add(lane.wrapping_mul(XXH32_PRIME2)), 13).wrapping_mul(XXH32_PRIME1)
}
#[inline(always)]
fn xxh32_avalanche(mut h: u32) -> u32 {
h ^= h >> 15;
h = h.wrapping_mul(XXH32_PRIME2);
h ^= h >> 13;
h = h.wrapping_mul(XXH32_PRIME3);
h ^= h >> 16;
h
}
#[inline(always)]
fn read_u32_le(buf: &[u8], off: usize) -> u32 {
u32::from_le_bytes([buf[off], buf[off + 1], buf[off + 2], buf[off + 3]])
}
pub fn xxh32(data: &[u8], seed: u32) -> u32 {
let len = data.len();
let mut h32: u32;
if len >= 16 {
let limit = len - 16;
let mut v1 = seed.wrapping_add(XXH32_PRIME1).wrapping_add(XXH32_PRIME2);
let mut v2 = seed.wrapping_add(XXH32_PRIME2);
let mut v3 = seed;
let mut v4 = seed.wrapping_sub(XXH32_PRIME1);
let mut p = 0usize;
while p <= limit {
v1 = xxh32_round(v1, read_u32_le(data, p));
p += 4;
v2 = xxh32_round(v2, read_u32_le(data, p));
p += 4;
v3 = xxh32_round(v3, read_u32_le(data, p));
p += 4;
v4 = xxh32_round(v4, read_u32_le(data, p));
p += 4;
}
h32 = xxh_rotl32(v1, 1)
.wrapping_add(xxh_rotl32(v2, 7))
.wrapping_add(xxh_rotl32(v3, 12))
.wrapping_add(xxh_rotl32(v4, 18));
} else {
h32 = seed.wrapping_add(XXH32_PRIME5);
}
h32 = h32.wrapping_add(len as u32);
let mut p = (len / 16) * 16;
while p + 4 <= len {
h32 = h32
.wrapping_add(read_u32_le(data, p).wrapping_mul(XXH32_PRIME3))
.wrapping_mul(XXH32_PRIME4);
h32 = xxh_rotl32(h32, 17);
p += 4;
}
while p < len {
h32 = h32.wrapping_add((data[p] as u32).wrapping_mul(XXH32_PRIME5));
h32 = xxh_rotl32(h32, 11).wrapping_mul(XXH32_PRIME1);
p += 1;
}
xxh32_avalanche(h32)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Lz4Error {
InputTooLarge,
OutputTooSmall,
OutputTooLarge,
CorruptedBlock,
CorruptedFrame,
HeaderChecksumError,
ContentChecksumError,
BlockChecksumError,
UnsupportedFeature,
DictionaryTooLarge,
IoError(io::ErrorKind),
InvalidParameter(&'static str),
}
impl fmt::Display for Lz4Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InputTooLarge => write!(f, "input is too large for LZ4 block compressor"),
Self::OutputTooSmall => write!(f, "output buffer is too small"),
Self::OutputTooLarge => write!(f, "decompressed data exceeds declared maximum size"),
Self::CorruptedBlock => write!(f, "corrupted or invalid LZ4 block"),
Self::CorruptedFrame => write!(f, "corrupted or invalid LZ4 frame"),
Self::HeaderChecksumError => write!(f, "frame header checksum mismatch"),
Self::ContentChecksumError => write!(f, "content checksum mismatch"),
Self::BlockChecksumError => write!(f, "block checksum mismatch"),
Self::UnsupportedFeature => write!(f, "unsupported frame-format feature"),
Self::DictionaryTooLarge => write!(f, "dictionary is too large"),
Self::IoError(kind) => write!(f, "I/O error: {:?}", kind),
Self::InvalidParameter(msg) => write!(f, "invalid parameter: {}", msg),
}
}
}
impl From<io::Error> for Lz4Error {
fn from(e: io::Error) -> Self {
Self::IoError(e.kind())
}
}
pub type Lz4Result<T> = Result<T, Lz4Error>;
#[inline(always)]
fn write_u16_le(dst: &mut [u8], offset: usize, val: u16) {
dst[offset] = val as u8;
dst[offset + 1] = (val >> 8) as u8;
}
#[inline(always)]
fn write_u32_le(dst: &mut [u8], offset: usize, val: u32) {
dst[offset] = val as u8;
dst[offset + 1] = (val >> 8) as u8;
dst[offset + 2] = (val >> 16) as u8;
dst[offset + 3] = (val >> 24) as u8;
}
#[inline(always)]
fn write_u64_le(dst: &mut [u8], offset: usize, val: u64) {
dst[offset] = val as u8;
dst[offset + 1] = (val >> 8) as u8;
dst[offset + 2] = (val >> 16) as u8;
dst[offset + 3] = (val >> 24) as u8;
dst[offset + 4] = (val >> 32) as u8;
dst[offset + 5] = (val >> 40) as u8;
dst[offset + 6] = (val >> 48) as u8;
dst[offset + 7] = (val >> 56) as u8;
}
#[inline(always)]
fn read_u16_le_at(src: &[u8], offset: usize) -> u16 {
u16::from_le_bytes([src[offset], src[offset + 1]])
}
#[inline(always)]
fn read_u32_le_at(src: &[u8], offset: usize) -> u32 {
u32::from_le_bytes([src[offset], src[offset + 1], src[offset + 2], src[offset + 3]])
}
#[inline(always)]
fn read_u64_le_at(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(always)]
fn lz4_hash(v: u32, log: u32) -> u32 {
(v.wrapping_mul(LZ4_HASH_MULTIPLIER)) >> (32 - log)
}
#[inline(always)]
fn lz4_read32(src: &[u8], p: usize) -> u32 {
u32::from_le_bytes([src[p], src[p + 1], src[p + 2], src[p + 3]])
}
#[inline(always)]
fn lz4_count_match(src: &[u8], ref_pos: usize, cur_pos: usize) -> usize {
let src_len = src.len();
let mut r = ref_pos;
let mut c = cur_pos;
let limit = src_len - LASTLITERALS;
let mut count = MINMATCH;
while c + count < limit && src[r + count] == src[c + count] {
count += 1;
}
while c + count < src_len && src[r + count] == src[c + count] {
count += 1;
}
count
}
fn lz4_encode_length(
dst: &mut [u8],
op: &mut usize,
len: u32,
token_nibble: &mut u8,
) -> Lz4Result<()> {
if len < 15 {
*token_nibble = len as u8;
return Ok(());
}
*token_nibble = 15;
let mut remaining = len - 15;
while remaining >= 255 {
dst[*op] = 255;
*op += 1;
remaining -= 255;
}
dst[*op] = remaining as u8;
*op += 1;
Ok(())
}
#[inline]
fn lz4_decode_length(src: &[u8], ip: &mut usize, base: u32) -> Lz4Result<u32> {
if base < 15 {
return Ok(base);
}
let mut length = base;
loop {
if *ip >= src.len() {
return Err(Lz4Error::CorruptedBlock);
}
let b = src[*ip] as u32;
*ip += 1;
length += b;
if b < 255 {
break;
}
}
Ok(length)
}
pub fn lz4_compress_fast(
src: &[u8],
dst: &mut [u8],
acceleration: u32,
) -> Lz4Result<usize> {
let src_size = src.len();
if src_size > LZ4_MAX_INPUT_SIZE {
return Err(Lz4Error::InputTooLarge);
}
let accel = acceleration.max(1);
let mut hash_table = vec![0u32; LZ4_HASHTABLESIZE];
let mut ip = 0usize; let mut anchor = 0usize; let mut op = 0usize;
if src_size < MFLIMIT {
return encode_last_literals(src, dst, &mut op, anchor, src_size);
}
let mut forward_h = lz4_hash(lz4_read32(src, ip), LZ4_HASH_LOG as u32);
let mut forward_ip = ip;
let mut step = 0u32;
let mut search_match_nb = accel << 6;
loop {
let mut find_match_attempts = search_match_nb;
loop {
forward_ip += step as usize;
step = search_match_nb >> 6;
if forward_ip > src_size - MFLIMIT {
let lit_len = src_size - anchor;
return encode_last_literals_with_pending(
src,
dst,
&mut op,
anchor,
src_size,
&hash_table,
);
}
forward_h = lz4_hash(lz4_read32(src, forward_ip), LZ4_HASH_LOG as u32);
let entry = hash_table[forward_h as usize];
let mut reference = if entry == 0 { 0 } else { (entry - 1) as usize };
hash_table[forward_h as usize] = (forward_ip + 1) as u32;
if reference != 0
&& forward_ip - reference <= LZ4_DISTANCE_MAX
&& lz4_read32(src, reference) == lz4_read32(src, forward_ip)
{
break; }
find_match_attempts = find_match_attempts.saturating_sub(1);
if find_match_attempts == 0 {
reference = 0; step = accel;
if forward_ip > src_size - MFLIMIT {
return encode_last_literals_with_pending(
src, dst, &mut op, anchor, src_size, &hash_table,
);
}
}
}
let mut match_len = lz4_count_match(src, reference, forward_ip);
let match_len = cmp::min(match_len, src_size - forward_ip);
let lit_len = forward_ip - anchor;
let token = &mut 0u8;
let token_pos = op;
op += 1;
let mut token_high = 0u8;
lz4_encode_length(dst, &mut op, lit_len as u32, &mut token_high)?;
*token = token_high << 4;
dst[op..op + lit_len].copy_from_slice(&src[anchor..forward_ip]);
op += lit_len;
anchor = forward_ip + match_len;
let match_len_enc = (match_len - MINMATCH) as u32;
let mut token_low = 0u8;
lz4_encode_length(dst, &mut op, match_len_enc, &mut token_low)?;
dst[token_pos] = *token | token_low;
let offset = (forward_ip - reference) as u16;
write_u16_le(dst, op, offset);
op += 2;
ip = forward_ip + match_len;
forward_ip = ip;
anchor = ip;
step = 0;
search_match_nb = accel << 6;
if ip > src_size - MFLIMIT {
break;
}
let fill_start = if ip > anchor.wrapping_sub(1).saturating_sub(2) {
ip.saturating_sub(2)
} else {
ip
};
}
encode_last_literals(src, dst, &mut op, anchor, src_size)
}
fn encode_last_literals_with_pending(
src: &[u8],
dst: &mut [u8],
op: &mut usize,
anchor: usize,
src_size: usize,
_hash_table: &[u32],
) -> Lz4Result<usize> {
encode_last_literals(src, dst, op, anchor, src_size)
}
fn encode_last_literals(
src: &[u8],
dst: &mut [u8],
op: &mut usize,
anchor: usize,
src_size: usize,
) -> Lz4Result<usize> {
let lit_len = src_size - anchor;
if lit_len == 0 {
return Ok(*op);
}
if lit_len >= 15 {
let token_pos = *op;
*op += 1;
let mut token_high = 0u8;
lz4_encode_length(dst, op, lit_len as u32, &mut token_high)?;
dst[token_pos] = token_high << 4; dst[*op..*op + lit_len].copy_from_slice(&src[anchor..src_size]);
*op += lit_len;
} else {
dst[*op] = (lit_len as u8) << 4;
*op += 1;
dst[*op..*op + lit_len].copy_from_slice(&src[anchor..src_size]);
*op += lit_len;
}
Ok(*op)
}
pub fn lz4_compress_default(src: &[u8], dst: &mut [u8]) -> Lz4Result<usize> {
lz4_compress_fast(src, dst, 1)
}
pub fn lz4_compress_bound(input_size: usize) -> usize {
if input_size > LZ4_MAX_INPUT_SIZE {
return 0;
}
let mut bound = input_size + (input_size / 255) + 16;
if bound < input_size {
return 0;
}
bound
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct HcLevel {
pub level: u32,
pub search_depth: u32,
pub lazy: bool,
pub optimal_parse: bool,
}
impl HcLevel {
pub fn new(level: u32) -> Self {
let lvl = level.clamp(1, 12);
let (depth, lazy, opt) = match lvl {
1 => (1, false, false),
2 => (4, false, false),
3 => (16, false, false),
4 => (32, false, false),
5 => (64, true, false),
6 => (128, true, false),
7 => (256, true, false),
8 => (512, true, false),
9 => (1024, true, false),
10 => (2048, true, true),
11 => (4096, true, true),
12 => (8192, true, true),
_ => unreachable!(),
};
Self {
level: lvl,
search_depth: depth,
lazy,
optimal_parse: opt,
}
}
}
struct Lz4HcCtx {
hash_table: Vec<u32>,
chain_table: Vec<u32>,
max_depth: u32,
src_size: usize,
opt_cost: Vec<u32>,
opt_match: Vec<(u32, u32)>,
opt_lit: Vec<bool>,
}
impl Lz4HcCtx {
fn new(src_size: usize, max_depth: u32) -> Self {
let ht_size = LZ4_HC_HASHTABLESIZE;
let chain_size = src_size;
Self {
hash_table: vec![0u32; ht_size],
chain_table: vec![0u32; chain_size],
max_depth,
src_size,
opt_cost: vec![u32::MAX; src_size + 1],
opt_match: vec![(0, 0); src_size + 1],
opt_lit: vec![false; src_size + 1],
}
}
fn reset(&mut self) {
self.hash_table.fill(0);
self.chain_table[..self.src_size].fill(0);
}
fn insert(&mut self, src: &[u8], pos: usize) {
if pos + MINMATCH > self.src_size {
return;
}
let h = lz4_hash(lz4_read32(src, pos), LZ4_HC_HASH_LOG as u32) as usize;
self.chain_table[pos] = self.hash_table[h];
self.hash_table[h] = (pos + 1) as u32; }
fn insert_range(&mut self, src: &[u8], start: usize, end: usize) {
for pos in start..cmp::min(end, self.src_size.saturating_sub(MINMATCH)) {
self.insert(src, pos);
}
}
fn find_best_match(
&self,
src: &[u8],
ip: usize,
min_len: usize,
target_len: usize,
) -> (usize, usize) {
if ip + MINMATCH > self.src_size {
return (0, 0);
}
let h = lz4_hash(lz4_read32(src, ip), LZ4_HC_HASH_LOG as u32) as usize;
let mut match_idx = self.hash_table[h];
let mut depth = self.max_depth;
let mut best_len = min_len.saturating_sub(1);
let mut best_pos = 0usize;
while match_idx != 0 && depth > 0 {
let ref_pos = (match_idx - 1) as usize;
if ip - ref_pos > LZ4_DISTANCE_MAX {
break;
}
if lz4_read32(src, ref_pos) == lz4_read32(src, ip) {
let ml = lz4_count_match(src, ref_pos, ip);
let ml = cmp::min(ml, self.src_size - ip);
if ml > best_len {
best_len = ml;
best_pos = ref_pos;
if ml >= target_len {
break;
}
}
}
match_idx = self.chain_table[ref_pos];
depth -= 1;
}
if best_len >= min_len {
(best_pos, best_len)
} else {
(0, 0)
}
}
fn sequence_cost(lit_len: usize, match_len: usize) -> u32 {
let mut cost = 1u32;
if lit_len >= 15 {
let extra = lit_len - 15;
cost += 1 + (extra / 255) as u32;
}
cost += lit_len as u32;
cost += 2;
let ml_enc = match_len - MINMATCH;
if ml_enc >= 15 {
let extra = ml_enc - 15;
cost += 1 + (extra / 255) as u32;
}
cost
}
fn literals_cost(n: usize) -> u32 {
if n == 0 {
return 0;
}
let mut cost = 1u32; if n >= 15 {
let extra = n - 15;
cost += 1 + (extra / 255) as u32;
}
cost += n as u32; cost
}
fn optimal_parse(&mut self, src: &[u8], start: usize) {
let n = self.src_size;
self.opt_cost[start] = 0;
for pos in start..n {
let cur_cost = self.opt_cost[pos];
if cur_cost == u32::MAX {
continue; }
let next_cost = cur_cost + 1; if pos + 1 <= n && cur_cost + 1 < self.opt_cost[pos + 1] {
self.opt_cost[pos + 1] = cur_cost + 1;
self.opt_lit[pos] = true;
}
if pos + MINMATCH > n {
continue;
}
let (match_pos, match_len) = self.find_best_match(src, pos, MINMATCH, LZ4_MAX_MATCH_LEN);
if match_len >= MINMATCH && match_pos != 0 {
let new_pos = pos + match_len;
if new_pos <= n {
let seq_cost = cur_cost + Self::sequence_cost(0, match_len);
if seq_cost < self.opt_cost[new_pos] {
self.opt_cost[new_pos] = seq_cost;
self.opt_match[pos] = (match_pos as u32, match_len as u32);
self.opt_lit[pos] = false;
}
}
}
}
}
}
pub fn lz4_compress_hc(src: &[u8], dst: &mut [u8], level: u32) -> Lz4Result<usize> {
let hc_level = HcLevel::new(level);
let src_size = src.len();
if src_size > LZ4_MAX_INPUT_SIZE {
return Err(Lz4Error::InputTooLarge);
}
let mut ctx = Lz4HcCtx::new(src_size, hc_level.search_depth);
if hc_level.optimal_parse && src_size <= 65536 {
return lz4_compress_hc_optimal(src, dst, &mut ctx);
}
let mut ip = 0usize;
let mut anchor = 0usize;
let mut op = 0usize;
if src_size < MFLIMIT {
return encode_last_literals(src, dst, &mut op, anchor, src_size);
}
while ip <= src_size - MFLIMIT {
ctx.insert(src, ip);
let (match_pos, mut match_len) = ctx.find_best_match(src, ip, MINMATCH, LZ4_MAX_MATCH_LEN);
if match_len >= MINMATCH && match_pos != 0 {
if hc_level.lazy && ip + 1 <= src_size - MFLIMIT {
ctx.insert(src, ip + 1);
let (lazy_pos, lazy_len) =
ctx.find_best_match(src, ip + 1, match_len + 1, LZ4_MAX_MATCH_LEN);
if lazy_len > match_len + 1 {
ip += 1;
continue;
}
}
let lit_len = ip - anchor;
match_len = cmp::min(match_len, src_size - ip);
let token_pos = op;
op += 1;
let mut token_high = 0u8;
lz4_encode_length(dst, &mut op, lit_len as u32, &mut token_high)?;
let mut token = token_high << 4;
dst[op..op + lit_len].copy_from_slice(&src[anchor..ip]);
op += lit_len;
let match_len_enc = (match_len - MINMATCH) as u32;
let mut token_low = 0u8;
lz4_encode_length(dst, &mut op, match_len_enc, &mut token_low)?;
dst[token_pos] = token | token_low;
let offset = (ip - match_pos) as u16;
write_u16_le(dst, op, offset);
op += 2;
let match_end = ip + match_len;
ctx.insert_range(src, ip + 1, match_end);
ip = match_end;
anchor = ip;
} else {
ip += 1;
}
}
encode_last_literals(src, dst, &mut op, anchor, src_size)
}
fn lz4_compress_hc_optimal(
src: &[u8],
dst: &mut [u8],
ctx: &mut Lz4HcCtx,
) -> Lz4Result<usize> {
let src_size = src.len();
let n = src_size;
for pos in 0..n.saturating_sub(MINMATCH) {
ctx.insert(src, pos);
}
ctx.optimal_parse(src, 0);
let mut pos = 0usize;
let mut op = 0usize;
while pos < n {
if ctx.opt_match[pos].1 >= MINMATCH as u32 && !ctx.opt_lit[pos] {
let match_pos = ctx.opt_match[pos].0 as usize;
let match_len = ctx.opt_match[pos].1 as usize;
let match_len = cmp::min(match_len, n - pos);
let lit_len = pos;
break;
} else {
pos += 1;
}
}
let mut ip = 0usize;
let mut anchor = 0usize;
while ip < n {
if ip < n
&& !ctx.opt_lit[ip]
&& ctx.opt_match[ip].1 >= MINMATCH as u32
{
let match_pos = ctx.opt_match[ip].0 as usize;
let match_len = cmp::min(ctx.opt_match[ip].1 as usize, n - ip);
let lit_len = ip - anchor;
let token_pos = op;
op += 1;
let mut token_high = 0u8;
lz4_encode_length(dst, &mut op, lit_len as u32, &mut token_high)?;
let mut token = token_high << 4;
dst[op..op + lit_len].copy_from_slice(&src[anchor..ip]);
op += lit_len;
let match_len_enc = (match_len - MINMATCH) as u32;
let mut token_low = 0u8;
lz4_encode_length(dst, &mut op, match_len_enc, &mut token_low)?;
dst[token_pos] = token | token_low;
let offset = (ip - match_pos) as u16;
write_u16_le(dst, op, offset);
op += 2;
ip += match_len;
anchor = ip;
} else {
ip += 1;
}
}
encode_last_literals(src, dst, &mut op, anchor, n)
}
pub fn lz4_decompress_safe(
src: &[u8],
dst: &mut [u8],
max_output: usize,
) -> Lz4Result<usize> {
let src_len = src.len();
if src_len == 0 {
return Err(Lz4Error::CorruptedBlock);
}
let mut ip = 0usize; let mut op = 0usize;
let output_limit = cmp::min(dst.len(), max_output);
let safe_output_limit = output_limit.saturating_sub(LASTLITERALS);
loop {
if ip >= src_len {
return Err(Lz4Error::CorruptedBlock);
}
let token = src[ip];
ip += 1;
let mut lit_len = lz4_decode_length(src, &mut ip, (token >> 4) as u32)? as usize;
if op + lit_len > output_limit || ip + lit_len > src_len {
return Err(Lz4Error::CorruptedBlock);
}
dst[op..op + lit_len].copy_from_slice(&src[ip..ip + lit_len]);
op += lit_len;
ip += lit_len;
if ip >= src_len {
break;
}
if ip + 2 > src_len {
return Err(Lz4Error::CorruptedBlock);
}
let offset = read_u16_le_at(src, ip) as usize;
ip += 2;
if offset == 0 {
return Err(Lz4Error::CorruptedBlock);
}
if offset > op {
return Err(Lz4Error::CorruptedBlock);
}
let match_pos = op - offset;
let mut match_len =
lz4_decode_length(src, &mut ip, (token & 0x0F) as u32)? as usize + MINMATCH;
if op + match_len > output_limit {
return Err(Lz4Error::CorruptedBlock);
}
if offset >= match_len {
dst.copy_within(match_pos..match_pos + match_len, op);
op += match_len;
} else {
let mut copied = 0;
while copied < match_len {
dst[op + copied] = dst[match_pos + copied];
copied += 1;
}
op += match_len;
}
}
Ok(op)
}
#[derive(Debug, Clone)]
pub struct Lz4FramePreferences {
pub block_size: usize,
pub block_mode: Lz4BlockMode,
pub compression_level: u32,
pub content_checksum: bool,
pub block_checksum: bool,
pub auto_flush: bool,
}
impl Default for Lz4FramePreferences {
fn default() -> Self {
Self {
block_size: LZ4F_DEFAULT_BLOCK_SIZE,
block_mode: Lz4BlockMode::BlockIndependent,
compression_level: 0,
content_checksum: true,
block_checksum: false,
auto_flush: false,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Lz4BlockMode {
BlockIndependent,
BlockLinked,
}
#[derive(Debug, Clone)]
pub struct Lz4FrameInfo {
pub block_size: usize,
pub block_mode: Lz4BlockMode,
pub content_checksum: bool,
pub block_checksum: bool,
pub content_size: Option<u64>,
pub dictionary_id: Option<u32>,
}
pub fn lz4f_compress_frame(
src: &[u8],
dst: &mut [u8],
prefs: &Lz4FramePreferences,
) -> Lz4Result<usize> {
let mut op = 0usize;
let src_size = src.len();
write_u32_le(dst, op, LZ4F_MAGIC);
op += 4;
let mut flg = LZ4F_FLG_VERSION_01;
match prefs.block_mode {
Lz4BlockMode::BlockIndependent => flg |= LZ4F_FLG_BLOCK_INDEPENDENCE,
Lz4BlockMode::BlockLinked => {} }
if prefs.block_checksum {
flg |= LZ4F_FLG_BLOCK_CHECKSUM;
}
if prefs.content_checksum {
flg |= LZ4F_FLG_CONTENT_CHECKSUM;
}
flg |= LZ4F_FLG_CONTENT_SIZE;
let bd = bd_from_block_maxsize(prefs.block_size);
let content_size_bytes = src_size as u64;
let header_data = [flg, bd];
let mut hc_input = Vec::with_capacity(15);
hc_input.extend_from_slice(&header_data);
hc_input.extend_from_slice(&content_size_bytes.to_le_bytes());
let hc = ((xxh32(&hc_input, 0) >> 8) & 0xFF) as u8;
dst[op] = flg;
op += 1;
dst[op] = bd;
op += 1;
write_u64_le(dst, op, content_size_bytes);
op += 8;
dst[op] = hc;
op += 1;
let block_max = block_maxsize_from_bd(bd);
let mut src_offset = 0usize;
let mut content_xxh: u32 = 0;
if prefs.content_checksum {
content_xxh = xxh32(src, 0);
}
while src_offset < src_size {
let chunk_size = cmp::min(block_max, src_size - src_offset);
let chunk = &src[src_offset..src_offset + chunk_size];
let max_csize = lz4_compress_bound(chunk_size);
if max_csize == 0 {
return Err(Lz4Error::InputTooLarge);
}
let mut cbuf = vec![0u8; max_csize];
let csize = if prefs.compression_level >= 2 {
lz4_compress_hc(chunk, &mut cbuf, prefs.compression_level)?
} else {
lz4_compress_fast(chunk, &mut cbuf, 1)?
};
let (block_size_field, block_data, block_data_len) = if csize < chunk_size {
(csize as u32, &cbuf[..csize], csize)
} else {
let uflag = 0x8000_0000u32;
(uflag | (chunk_size as u32), chunk, chunk_size)
};
write_u32_le(dst, op, block_size_field);
op += 4;
dst[op..op + block_data_len].copy_from_slice(block_data);
op += block_data_len;
if prefs.block_checksum {
let bcs = xxh32(block_data, 0);
write_u32_le(dst, op, bcs);
op += 4;
}
src_offset += chunk_size;
}
write_u32_le(dst, op, LZ4F_END_MARK);
op += 4;
if prefs.content_checksum {
write_u32_le(dst, op, content_xxh);
op += 4;
}
Ok(op)
}
pub fn lz4f_get_frame_info(src: &[u8]) -> Lz4Result<(Lz4FrameInfo, usize)> {
if src.len() < 7 {
return Err(Lz4Error::CorruptedFrame);
}
let magic = read_u32_le_at(src, 0);
if magic != LZ4F_MAGIC {
return Err(Lz4Error::CorruptedFrame);
}
let flg = src[4];
let bd = src[5];
if flg & LZ4F_FLG_VERSION != LZ4F_FLG_VERSION_01 {
return Err(Lz4Error::UnsupportedFeature);
}
let block_size = block_maxsize_from_bd(bd);
let block_mode = if flg & LZ4F_FLG_BLOCK_INDEPENDENCE != 0 {
Lz4BlockMode::BlockIndependent
} else {
Lz4BlockMode::BlockLinked
};
let content_checksum = flg & LZ4F_FLG_CONTENT_CHECKSUM != 0;
let block_checksum = flg & LZ4F_FLG_BLOCK_CHECKSUM != 0;
let has_content_size = flg & LZ4F_FLG_CONTENT_SIZE != 0;
let has_dict_id = flg & LZ4F_FLG_DICT_ID != 0;
let mut pos = 6usize;
let mut content_size: Option<u64> = None;
let mut dictionary_id: Option<u32> = None;
if has_content_size {
if src.len() < pos + 8 {
return Err(Lz4Error::CorruptedFrame);
}
content_size = Some(read_u64_le_at(src, pos));
pos += 8;
}
if has_dict_id {
if src.len() < pos + 4 {
return Err(Lz4Error::CorruptedFrame);
}
dictionary_id = Some(read_u32_le_at(src, pos));
pos += 4;
}
if src.len() < pos + 1 {
return Err(Lz4Error::CorruptedFrame);
}
let stored_hc = src[pos];
pos += 1;
let header_end = pos;
let header_bytes = &src[4..header_end - 1]; let computed_hc = ((xxh32(header_bytes, 0) >> 8) & 0xFF) as u8;
if computed_hc != stored_hc {
return Err(Lz4Error::HeaderChecksumError);
}
let info = Lz4FrameInfo {
block_size,
block_mode,
content_checksum,
block_checksum,
content_size,
dictionary_id,
};
Ok((info, header_end + 4)) }
pub fn lz4f_decompress_frame(src: &[u8], dst: &mut [u8]) -> Lz4Result<usize> {
let (info, header_len) = lz4f_get_frame_info(src)?;
let mut ip = header_len;
let mut op = 0usize;
let src_len = src.len();
let max_output = dst.len();
let expected_size = info.content_size.map(|s| s as usize);
let mut dbuf = vec![0u8; info.block_size + MINMATCH];
while ip + 4 <= src_len {
let block_size_field = read_u32_le_at(src, ip);
ip += 4;
if block_size_field == LZ4F_END_MARK {
break;
}
let is_uncompressed = (block_size_field & 0x8000_0000) != 0;
let block_data_size = (block_size_field & 0x7FFF_FFFF) as usize;
if ip + block_data_size > src_len {
return Err(Lz4Error::CorruptedFrame);
}
let block_bytes = &src[ip..ip + block_data_size];
ip += block_data_size;
if is_uncompressed {
if op + block_data_size > max_output {
return Err(Lz4Error::OutputTooLarge);
}
dst[op..op + block_data_size].copy_from_slice(block_bytes);
op += block_data_size;
} else {
let decompressed = lz4_decompress_safe(block_bytes, &mut dbuf, info.block_size)?;
if op + decompressed > max_output {
return Err(Lz4Error::OutputTooLarge);
}
dst[op..op + decompressed].copy_from_slice(&dbuf[..decompressed]);
op += decompressed;
}
if info.block_checksum {
if ip + 4 > src_len {
return Err(Lz4Error::CorruptedFrame);
}
let stored = read_u32_le_at(src, ip);
ip += 4;
let computed = xxh32(
&dst[op.saturating_sub(block_data_size.max(0))..op],
0,
);
if stored != computed {
return Err(Lz4Error::BlockChecksumError);
}
}
}
if info.content_checksum {
if ip + 4 > src_len {
return Err(Lz4Error::CorruptedFrame);
}
let stored_cc = read_u32_le_at(src, ip);
let computed_cc = xxh32(&dst[..op], 0);
if stored_cc != computed_cc {
return Err(Lz4Error::ContentChecksumError);
}
}
if let Some(expected) = expected_size {
if op != expected {
return Err(Lz4Error::CorruptedFrame);
}
}
Ok(op)
}
pub struct Lz4fCompressionContext {
prefs: Lz4FramePreferences,
buffer: Vec<u8>,
total_in: u64,
content_xxh: u32,
header_written: bool,
finished: bool,
dict: Vec<u8>,
}
impl Lz4fCompressionContext {
pub fn new(prefs: Lz4FramePreferences) -> Self {
Self {
prefs,
buffer: Vec::new(),
total_in: 0,
content_xxh: 0,
header_written: false,
finished: false,
dict: Vec::new(),
}
}
pub fn begin(&mut self, dst: &mut [u8]) -> Lz4Result<usize> {
if self.header_written {
return Err(Lz4Error::InvalidParameter("frame already started"));
}
let mut op = 0usize;
write_u32_le(dst, op, LZ4F_MAGIC);
op += 4;
let mut flg = LZ4F_FLG_VERSION_01;
match self.prefs.block_mode {
Lz4BlockMode::BlockIndependent => flg |= LZ4F_FLG_BLOCK_INDEPENDENCE,
Lz4BlockMode::BlockLinked => {}
}
if self.prefs.block_checksum {
flg |= LZ4F_FLG_BLOCK_CHECKSUM;
}
if self.prefs.content_checksum {
flg |= LZ4F_FLG_CONTENT_CHECKSUM;
}
let bd = bd_from_block_maxsize(self.prefs.block_size);
let header_bytes = [flg, bd];
let hc = ((xxh32(&header_bytes, 0) >> 8) & 0xFF) as u8;
dst[op] = flg;
op += 1;
dst[op] = bd;
op += 1;
dst[op] = hc;
op += 1;
self.header_written = true;
self.content_xxh = 0;
Ok(op)
}
pub fn update(
&mut self,
src: &[u8],
dst: &mut [u8],
) -> Lz4Result<usize> {
if !self.header_written {
return Err(Lz4Error::InvalidParameter("frame not started"));
}
if self.finished {
return Err(Lz4Error::InvalidParameter("frame already finished"));
}
let mut op = 0usize;
let mut src_offset = 0usize;
let block_max = self.prefs.block_size;
if !self.buffer.is_empty() {
let needed = block_max - self.buffer.len();
let take = cmp::min(needed, src.len());
self.buffer.extend_from_slice(&src[..take]);
src_offset = take;
self.total_in += take as u64;
if self.prefs.content_checksum {
self.content_xxh = xxh32(&src[..take], 0); }
if self.buffer.len() >= block_max || src_offset >= src.len() {
op += self.flush_block(&self.buffer.clone(), &mut dst[op..])?;
self.buffer.clear();
}
}
while src_offset < src.len() {
let remaining = src.len() - src_offset;
if remaining + self.buffer.len() < block_max && !self.prefs.auto_flush {
self.buffer.extend_from_slice(&src[src_offset..]);
self.total_in += remaining as u64;
break;
}
let take = cmp::min(block_max - self.buffer.len(), src.len() - src_offset);
let chunk = &src[src_offset..src_offset + take];
src_offset += take;
self.total_in += take as u64;
let mut block_data = Vec::new();
block_data.extend_from_slice(&self.buffer);
block_data.extend_from_slice(chunk);
self.buffer.clear();
op += self.flush_block(&block_data, &mut dst[op..])?;
}
Ok(op)
}
pub fn end(&mut self, dst: &mut [u8]) -> Lz4Result<usize> {
if !self.header_written {
return Err(Lz4Error::InvalidParameter("frame not started"));
}
if self.finished {
return Ok(0);
}
let mut op = 0usize;
if !self.buffer.is_empty() {
op += self.flush_block(&self.buffer.clone(), &mut dst[op..])?;
self.buffer.clear();
}
write_u32_le(dst, op, LZ4F_END_MARK);
op += 4;
if self.prefs.content_checksum {
write_u32_le(dst, op, self.content_xxh);
op += 4;
}
self.finished = true;
Ok(op)
}
fn flush_block(&self, data: &[u8], dst: &mut [u8]) -> Lz4Result<usize> {
let mut op = 0usize;
let data_len = data.len();
if data_len == 0 {
return Ok(0);
}
let max_csize = lz4_compress_bound(data_len);
let mut cbuf = vec![0u8; max_csize];
let csize = if self.prefs.compression_level >= 2 {
lz4_compress_hc(data, &mut cbuf, self.prefs.compression_level)?
} else {
lz4_compress_fast(data, &mut cbuf, 1)?
};
let (block_field, block_bytes) = if csize < data_len {
(csize as u32, &cbuf[..csize])
} else {
(0x8000_0000 | (data_len as u32), data)
};
write_u32_le(dst, op, block_field);
op += 4;
let blen = block_bytes.len();
dst[op..op + blen].copy_from_slice(block_bytes);
op += blen;
if self.prefs.block_checksum {
let bcs = xxh32(block_bytes, 0);
write_u32_le(dst, op, bcs);
op += 4;
}
Ok(op)
}
}
pub struct Lz4fDecompressionContext {
info: Option<Lz4FrameInfo>,
input_buffer: Vec<u8>,
content_xxh: u32,
total_out: u64,
pending_output: Vec<u8>,
finished: bool,
dict: Vec<u8>,
}
impl Lz4fDecompressionContext {
pub fn new() -> Self {
Self {
info: None,
input_buffer: Vec::new(),
content_xxh: 0,
total_out: 0,
pending_output: Vec::new(),
finished: false,
dict: Vec::new(),
}
}
pub fn decompress(
&mut self,
src: &[u8],
dst: &mut [u8],
) -> Lz4Result<(usize, usize)> {
if self.finished {
return Ok((0, 0));
}
self.input_buffer.extend_from_slice(src);
let mut consumed = 0usize;
let mut written = 0usize;
if !self.pending_output.is_empty() {
let take = cmp::min(self.pending_output.len(), dst.len() - written);
dst[written..written + take].copy_from_slice(&self.pending_output[..take]);
written += take;
self.pending_output.drain(..take);
if !self.pending_output.is_empty() {
return Ok((written, consumed));
}
}
if self.info.is_none() {
if self.input_buffer.len() < 7 {
consumed = src.len();
return Ok((written, consumed));
}
let (info, header_len) = lz4f_get_frame_info(&self.input_buffer)?;
self.info = Some(info);
self.input_buffer.drain(..header_len);
consumed = header_len.saturating_sub(src.len().saturating_sub(self.input_buffer.len() + header_len));
}
let info = self.info.as_ref().unwrap().clone();
loop {
if self.input_buffer.len() < 4 {
break;
}
let block_field = read_u32_le_at(&self.input_buffer, 0);
if block_field == LZ4F_END_MARK {
self.input_buffer.drain(..4);
if info.content_checksum {
if self.input_buffer.len() < 4 {
break;
}
let stored_cc = read_u32_le_at(&self.input_buffer, 0);
self.input_buffer.drain(..4);
}
self.finished = true;
break;
}
let is_uncompressed = (block_field & 0x8000_0000) != 0;
let block_data_size = (block_field & 0x7FFF_FFFF) as usize;
if self.input_buffer.len() < 4 + block_data_size {
break; }
let block_bytes = &self.input_buffer[4..4 + block_data_size];
let mut block_drain = 4 + block_data_size;
let mut expected_bcs: Option<u32> = None;
if info.block_checksum {
if self.input_buffer.len() < block_drain + 4 {
break;
}
expected_bcs = Some(read_u32_le_at(&self.input_buffer, block_drain));
block_drain += 4;
}
let decompressed: Vec<u8> = if is_uncompressed {
block_bytes.to_vec()
} else {
let mut dbuf = vec![0u8; info.block_size + MINMATCH];
let dlen = lz4_decompress_safe(block_bytes, &mut dbuf, info.block_size)?;
dbuf[..dlen].to_vec()
};
if let Some(stored) = expected_bcs {
let computed = xxh32(&decompressed, 0);
if stored != computed {
return Err(Lz4Error::BlockChecksumError);
}
}
self.input_buffer.drain(..block_drain);
let take = cmp::min(decompressed.len(), dst.len() - written);
dst[written..written + take].copy_from_slice(&decompressed[..take]);
written += take;
self.total_out += take as u64;
if take < decompressed.len() {
self.pending_output
.extend_from_slice(&decompressed[take..]);
}
if info.block_mode == Lz4BlockMode::BlockLinked {
self.dict = decompressed;
if self.dict.len() > LZ4_DISTANCE_MAX {
let excess = self.dict.len() - LZ4_DISTANCE_MAX;
self.dict.drain(..excess);
}
}
if written >= dst.len() {
break; }
}
consumed = src.len().saturating_sub(self.input_buffer.len().saturating_sub(
if self.info.is_none() {
0
} else {
0
},
));
Ok((written, src.len()))
}
pub fn is_finished(&self) -> bool {
self.finished
}
pub fn frame_info(&self) -> Option<&Lz4FrameInfo> {
self.info.as_ref()
}
}
impl Default for Lz4fDecompressionContext {
fn default() -> Self {
Self::new()
}
}
pub const LZ4_MAX_DICT_SIZE: usize = 65536;
pub fn lz4_load_dict(
dict: &[u8],
hash_table: &mut [u32],
ht_size_log: usize,
) -> Lz4Result<()> {
if dict.len() > LZ4_MAX_DICT_SIZE {
return Err(Lz4Error::DictionaryTooLarge);
}
if dict.len() < MINMATCH {
return Ok(());
}
let ht_mask = ((1u32 << ht_size_log) - 1) as u32;
for i in 0..dict.len().saturating_sub(MINMATCH - 1) {
let h = lz4_hash(lz4_read32(dict, i), ht_size_log as u32);
hash_table[(h & ht_mask) as usize] = (i + 1) as u32;
}
Ok(())
}
pub fn lz4_hc_load_dict(
dict: &[u8],
ctx: &mut Lz4HcCtx,
) -> Lz4Result<()> {
if dict.len() > LZ4_MAX_DICT_SIZE {
return Err(Lz4Error::DictionaryTooLarge);
}
for pos in 0..dict.len().saturating_sub(MINMATCH - 1) {
let h = lz4_hash(lz4_read32(dict, pos), LZ4_HC_HASH_LOG as u32) as usize;
ctx.chain_table[pos] = ctx.hash_table[h];
ctx.hash_table[h] = (pos + 1) as u32;
}
Ok(())
}
pub fn lz4_compress_fast_using_dict(
src: &[u8],
dst: &mut [u8],
dict: &[u8],
acceleration: u32,
) -> Lz4Result<usize> {
let mut hash_table = vec![0u32; LZ4_HASHTABLESIZE];
lz4_load_dict(dict, &mut hash_table, LZ4_HASH_LOG)?;
lz4_compress_fast_with_table(src, dst, &hash_table, acceleration)
}
pub fn lz4_compress_hc_using_dict(
src: &[u8],
dst: &mut [u8],
dict: &[u8],
level: u32,
) -> Lz4Result<usize> {
let hc_level = HcLevel::new(level);
let src_size = src.len();
if src_size > LZ4_MAX_INPUT_SIZE {
return Err(Lz4Error::InputTooLarge);
}
let mut ctx = Lz4HcCtx::new(src_size + dict.len(), hc_level.search_depth);
lz4_hc_load_dict(dict, &mut ctx)?;
lz4_compress_hc_with_ctx(src, dst, &mut ctx, dict.len())
}
fn lz4_compress_fast_with_table(
src: &[u8],
dst: &mut [u8],
hash_table: &[u32],
acceleration: u32,
) -> Lz4Result<usize> {
let src_size = src.len();
if src_size > LZ4_MAX_INPUT_SIZE {
return Err(Lz4Error::InputTooLarge);
}
let accel = acceleration.max(1);
let mut ht = hash_table.to_vec(); let mut ip = 0usize;
let mut anchor = 0usize;
let mut op = 0usize;
if src_size < MFLIMIT {
return encode_last_literals(src, dst, &mut op, anchor, src_size);
}
let mut forward_h = lz4_hash(lz4_read32(src, ip), LZ4_HASH_LOG as u32);
let mut forward_ip = ip;
let mut step = 0u32;
let mut search_match_nb = accel << 6;
loop {
let mut find_match_attempts = search_match_nb;
loop {
forward_ip += step as usize;
step = search_match_nb >> 6;
if forward_ip > src_size - MFLIMIT {
return encode_last_literals_with_hash(src, dst, &mut op, anchor, src_size, &ht);
}
forward_h = lz4_hash(lz4_read32(src, forward_ip), LZ4_HASH_LOG as u32);
let entry = ht[forward_h as usize];
let reference = if entry == 0 { 0 } else { (entry - 1) as usize };
ht[forward_h as usize] = (forward_ip + 1) as u32;
if reference != 0
&& forward_ip.wrapping_sub(reference) <= LZ4_DISTANCE_MAX
&& lz4_read32(src, reference) == lz4_read32(src, forward_ip)
{
break;
}
find_match_attempts = find_match_attempts.saturating_sub(1);
if find_match_attempts == 0 {
step = accel;
if forward_ip > src_size - MFLIMIT {
return encode_last_literals_with_hash(src, dst, &mut op, anchor, src_size, &ht);
}
}
}
let mut match_len = lz4_count_match(src, reference, forward_ip);
match_len = cmp::min(match_len, src_size - forward_ip);
let lit_len = forward_ip - anchor;
let token_pos = op;
op += 1;
let mut token_high = 0u8;
lz4_encode_length(dst, &mut op, lit_len as u32, &mut token_high)?;
let mut token = token_high << 4;
dst[op..op + lit_len].copy_from_slice(&src[anchor..forward_ip]);
op += lit_len;
anchor = forward_ip + match_len;
let match_len_enc = (match_len - MINMATCH) as u32;
let mut token_low = 0u8;
lz4_encode_length(dst, &mut op, match_len_enc, &mut token_low)?;
dst[token_pos] = token | token_low;
let offset = (forward_ip - reference) as u16;
write_u16_le(dst, op, offset);
op += 2;
ip = forward_ip + match_len;
forward_ip = ip;
anchor = ip;
step = 0;
search_match_nb = accel << 6;
if ip > src_size - MFLIMIT {
break;
}
}
encode_last_literals(src, dst, &mut op, anchor, src_size)
}
fn encode_last_literals_with_hash(
src: &[u8],
dst: &mut [u8],
op: &mut usize,
anchor: usize,
src_size: usize,
_hash_table: &[u32],
) -> Lz4Result<usize> {
encode_last_literals(src, dst, op, anchor, src_size)
}
fn lz4_compress_hc_with_ctx(
src: &[u8],
dst: &mut [u8],
ctx: &mut Lz4HcCtx,
dict_offset: usize,
) -> Lz4Result<usize> {
let src_size = src.len();
let mut ip = 0usize;
let mut anchor = 0usize;
let mut op = 0usize;
if src_size < MFLIMIT {
return encode_last_literals(src, dst, &mut op, anchor, src_size);
}
while ip <= src_size - MFLIMIT {
let dict_ip = dict_offset + ip;
ctx.insert(src, ip);
let (match_pos, mut match_len) =
ctx.find_best_match(src, ip, MINMATCH, LZ4_MAX_MATCH_LEN);
if match_len >= MINMATCH && match_pos != 0 {
let lit_len = ip - anchor;
match_len = cmp::min(match_len, src_size - ip);
let token_pos = op;
op += 1;
let mut token_high = 0u8;
lz4_encode_length(dst, &mut op, lit_len as u32, &mut token_high)?;
let mut token = token_high << 4;
dst[op..op + lit_len].copy_from_slice(&src[anchor..ip]);
op += lit_len;
let match_len_enc = (match_len - MINMATCH) as u32;
let mut token_low = 0u8;
lz4_encode_length(dst, &mut op, match_len_enc, &mut token_low)?;
dst[token_pos] = token | token_low;
let real_offset = if match_pos < ip {
ip - match_pos
} else {
ip + dict_offset - match_pos
};
let offset_u16 = cmp::min(real_offset, LZ4_DISTANCE_MAX) as u16;
write_u16_le(dst, op, offset_u16);
op += 2;
let match_end = ip + match_len;
ctx.insert_range(src, ip + 1, match_end);
ip = match_end;
anchor = ip;
} else {
ip += 1;
}
}
encode_last_literals(src, dst, &mut op, anchor, src_size)
}
#[derive(Debug, Clone)]
struct CliConfig {
mode: CliMode,
input: Option<String>,
output: Option<String>,
level: u32,
force: bool,
block_size: usize,
no_content_checksum: bool,
frame_format: bool,
benchmark_iterations: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum CliMode {
Compress,
Decompress,
Benchmark,
Verify,
Help,
}
impl Default for CliConfig {
fn default() -> Self {
Self {
mode: CliMode::Compress,
input: None,
output: None,
level: 1,
force: false,
block_size: LZ4F_DEFAULT_BLOCK_SIZE,
no_content_checksum: false,
frame_format: true,
benchmark_iterations: 5,
}
}
}
fn parse_args(args: &[String]) -> CliConfig {
let mut config = CliConfig::default();
let mut i = 0usize;
while i < args.len() {
let arg = &args[i];
match arg.as_str() {
"-d" | "--decompress" => config.mode = CliMode::Decompress,
"-b" | "--benchmark" => config.mode = CliMode::Benchmark,
"-t" | "--test" | "--verify" => config.mode = CliMode::Verify,
"-h" | "--help" => config.mode = CliMode::Help,
"-f" | "--force" => config.force = true,
"-1" => config.level = 1,
"-2" => config.level = 2,
"-3" => config.level = 3,
"-4" => config.level = 4,
"-5" => config.level = 5,
"-6" => config.level = 6,
"-7" => config.level = 7,
"-8" => config.level = 8,
"-9" => config.level = 9,
"-10" => config.level = 10,
"-11" => config.level = 11,
"-12" => config.level = 12,
"--no-frame-crc" => config.no_content_checksum = true,
"--no-frame" => config.frame_format = false,
a if a.starts_with("-B") => {
if let Ok(bs) = a[2..].parse::<usize>() {
config.block_size = bs;
}
}
a if a.starts_with("-i") => {
if let Ok(iter) = a[2..].parse::<usize>() {
config.benchmark_iterations = iter;
}
}
a if !a.starts_with('-') => {
if config.input.is_none() {
config.input = Some(a.clone());
} else if config.output.is_none() {
config.output = Some(a.clone());
}
}
_ => {
}
}
i += 1;
}
config
}
fn print_help(prog: &str) {
println!("LZ4 — Clean-room native Rust reimplementation v{}.{}.{}",
LZ4_VERSION / 10000,
(LZ4_VERSION / 100) % 100,
LZ4_VERSION % 100,
);
println!();
println!("Usage: {} [options] [input] [output]", prog);
println!();
println!("Modes:");
println!(" (default) Compress input → output");
println!(" -d Decompress input → output");
println!(" -b Benchmark (compress & decompress, report speed)");
println!(" -t Verify compressed file integrity");
println!();
println!("Options:");
println!(" -1..-12 Compression level (1=fast, 12=best)");
println!(" -f Force overwrite of output file");
println!(" -B<size> Block size in bytes (default: 4M)");
println!(" --no-frame-crc Disable content checksum in frame");
println!(" --no-frame Use raw block format (no frame header)");
println!(" -h, --help Show this help message");
}
pub fn run_cli(args: &[String]) -> Lz4Result<()> {
let config = parse_args(args);
match config.mode {
CliMode::Help => {
let prog = args.first().map(|s| s.as_str()).unwrap_or("lz4");
print_help(prog);
Ok(())
}
CliMode::Compress => {
let input = config
.input
.as_deref()
.ok_or(Lz4Error::InvalidParameter("no input file"))?;
let output = config.output.as_deref().unwrap_or_else(|| {
""
});
let output = if output.is_empty() {
&(input.to_owned() + ".lz4")
} else {
output
};
if Path::new(output).exists() && !config.force {
eprintln!("Error: output file '{}' already exists. Use -f to overwrite.", output);
return Err(Lz4Error::InvalidParameter("output file exists"));
}
compress_file(input, output, &config)
}
CliMode::Decompress => {
let input = config
.input
.as_deref()
.ok_or(Lz4Error::InvalidParameter("no input file"))?;
let output = if let Some(out) = &config.output {
out.clone()
} else {
if input.ends_with(".lz4") {
input[..input.len() - 4].to_owned()
} else {
input.to_owned() + ".out"
}
};
if Path::new(&output).exists() && !config.force {
eprintln!("Error: output file '{}' already exists. Use -f to overwrite.", output);
return Err(Lz4Error::InvalidParameter("output file exists"));
}
decompress_file(input, &output, &config)
}
CliMode::Benchmark => {
let input = config
.input
.as_deref()
.ok_or(Lz4Error::InvalidParameter("no input file for benchmark"))?;
run_benchmark(input, &config)
}
CliMode::Verify => {
let input = config
.input
.as_deref()
.ok_or(Lz4Error::InvalidParameter("no input file to verify"))?;
verify_file(input, &config)
}
}
}
fn compress_file(input_path: &str, output_path: &str, config: &CliConfig) -> Lz4Result<()> {
let src = fs::read(input_path)?;
if src.is_empty() {
eprintln!("Warning: empty input file.");
}
let input_size = src.len();
if config.frame_format {
let prefs = Lz4FramePreferences {
block_size: config.block_size,
compression_level: config.level,
content_checksum: !config.no_content_checksum,
..Default::default()
};
let bound = lz4f_frame_bound(input_size);
let mut dst = vec![0u8; bound];
let csize = lz4f_compress_frame(&src, &mut dst, &prefs)?;
fs::write(output_path, &dst[..csize])?;
let ratio = if input_size > 0 {
(csize as f64 / input_size as f64) * 100.0
} else {
0.0
};
println!(
"Compressed: {} → {} ({:.1}%) level {}",
format_size(input_size),
format_size(csize),
ratio,
config.level,
);
} else {
let max_csize = lz4_compress_bound(input_size);
let mut dst = vec![0u8; max_csize];
let csize = if config.level >= 2 {
lz4_compress_hc(&src, &mut dst, config.level)?
} else {
lz4_compress_fast(&src, &mut dst, 1)?
};
fs::write(output_path, &dst[..csize])?;
let ratio = if input_size > 0 {
(csize as f64 / input_size as f64) * 100.0
} else {
0.0
};
println!(
"Compressed: {} → {} ({:.1}%) level {}",
format_size(input_size),
format_size(csize),
ratio,
config.level,
);
}
Ok(())
}
fn decompress_file(input_path: &str, output_path: &str, config: &CliConfig) -> Lz4Result<()> {
let src = fs::read(input_path)?;
let decompressed = if let Ok((info, _)) = lz4f_get_frame_info(&src) {
let mut dst = vec![0u8; info.content_size.unwrap_or(8 * 1024 * 1024) as usize + 1024];
let dsize = lz4f_decompress_frame(&src, &mut dst)?;
dst[..dsize].to_vec()
} else {
let max_out = src.len() * 255; let mut dst = vec![0u8; max_out];
let dsize = lz4_decompress_safe(&src, &mut dst, max_out)?;
dst[..dsize].to_vec()
};
fs::write(output_path, &decompressed)?;
println!(
"Decompressed: {} → {}",
format_size(src.len()),
format_size(decompressed.len()),
);
Ok(())
}
fn run_benchmark(input_path: &str, config: &CliConfig) -> Lz4Result<()> {
let src = fs::read(input_path)?;
let input_size = src.len();
println!("Benchmark: {} ({} iterations)", format_size(input_size), config.benchmark_iterations);
let max_csize = lz4_compress_bound(input_size);
let mut cbuf = vec![0u8; max_csize];
let compress_start = Instant::now();
let mut csize = 0usize;
for _ in 0..config.benchmark_iterations {
if config.level >= 2 {
csize = lz4_compress_hc(&src, &mut cbuf, config.level)?;
} else {
csize = lz4_compress_fast(&src, &mut cbuf, 1)?;
}
}
let compress_elapsed = compress_start.elapsed();
let compress_mbps = (input_size * config.benchmark_iterations) as f64
/ compress_elapsed.as_secs_f64()
/ 1_048_576.0;
println!(
" Compression: {:.2} MB/s ({} → {}, {:.1}%)",
compress_mbps,
format_size(input_size),
format_size(csize),
if input_size > 0 { (csize as f64 / input_size as f64) * 100.0 } else { 0.0 },
);
let mut dbuf = vec![0u8; input_size + 1024];
let decompress_start = Instant::now();
let mut dsize = 0usize;
for _ in 0..config.benchmark_iterations {
dsize = lz4_decompress_safe(&cbuf[..csize], &mut dbuf, input_size)?;
}
let decompress_elapsed = decompress_start.elapsed();
let decompress_mbps = (dsize * config.benchmark_iterations) as f64
/ decompress_elapsed.as_secs_f64()
/ 1_048_576.0;
println!(
" Decompression: {:.2} MB/s",
decompress_mbps,
);
if &dbuf[..dsize] != &src[..] {
eprintln!(" ERROR: decompressed data does not match original!");
return Err(Lz4Error::CorruptedBlock);
}
println!(" Verification: OK");
Ok(())
}
fn verify_file(input_path: &str, config: &CliConfig) -> Lz4Result<()> {
let src = fs::read(input_path)?;
println!("Verifying: {} ({})", input_path, format_size(src.len()));
let decompressed = if let Ok((info, _)) = lz4f_get_frame_info(&src) {
let mut dst = vec![0u8; info.content_size.unwrap_or(8 * 1024 * 1024) as usize + 1024];
let dsize = lz4f_decompress_frame(&src, &mut dst)?;
dst[..dsize].to_vec()
} else {
let max_out = src.len() * 255;
let mut dst = vec![0u8; max_out];
let dsize = lz4_decompress_safe(&src, &mut dst, max_out)?;
dst[..dsize].to_vec()
};
println!(
" Decompressed: {} (checksum OK)",
format_size(decompressed.len()),
);
Ok(())
}
fn format_size(bytes: usize) -> String {
if bytes >= 1_073_741_824 {
format!("{:.2} GiB", bytes as f64 / 1_073_741_824.0)
} else if bytes >= 1_048_576 {
format!("{:.2} MiB", bytes as f64 / 1_048_576.0)
} else if bytes >= 1024 {
format!("{:.2} KiB", bytes as f64 / 1024.0)
} else {
format!("{} B", bytes)
}
}
fn lz4f_frame_bound(input_size: usize) -> usize {
let block_bound = lz4_compress_bound(input_size);
let overhead = 4 + LZ4F_HEADER_SIZE_MAX + 4 + 4;
let block_overhead = (input_size / 65536 + 1) * 4; block_bound + overhead + block_overhead + 1024
}
#[cfg(test)]
mod tests {
use super::*;
fn roundtrip_fast(data: &[u8]) {
let max_csize = lz4_compress_bound(data.len());
let mut cbuf = vec![0u8; max_csize + 64];
let csize = lz4_compress_fast(data, &mut cbuf, 1).expect("fast compress failed");
let mut dbuf = vec![0u8; data.len() + 64];
let dsize =
lz4_decompress_safe(&cbuf[..csize], &mut dbuf, data.len() + 64).expect("decompress failed");
assert_eq!(dsize, data.len(), "decompressed size mismatch");
assert_eq!(&dbuf[..dsize], data, "round-trip data mismatch (fast)");
}
fn roundtrip_hc(data: &[u8], level: u32) {
let max_csize = lz4_compress_bound(data.len());
let mut cbuf = vec![0u8; max_csize + 64];
let csize = lz4_compress_hc(data, &mut cbuf, level).expect("HC compress failed");
let mut dbuf = vec![0u8; data.len() + 64];
let dsize =
lz4_decompress_safe(&cbuf[..csize], &mut dbuf, data.len() + 64).expect("decompress failed");
assert_eq!(dsize, data.len(), "decompressed size mismatch");
assert_eq!(&dbuf[..dsize], data, "round-trip data mismatch (HC level {})", level);
}
fn roundtrip_frame(data: &[u8], prefs: &Lz4FramePreferences) {
let bound = lz4f_frame_bound(data.len());
let mut cbuf = vec![0u8; bound + 256];
let csize = lz4f_compress_frame(data, &mut cbuf, prefs).expect("frame compress failed");
let mut dbuf = vec![0u8; data.len() + 1024];
let dsize = if prefs.compression_level >= 2 {
let (info, _) = lz4f_get_frame_info(&cbuf[..csize]).expect("parse frame info");
if let Some(expected) = info.content_size {
let mut dbuf2 = vec![0u8; expected as usize + 1024];
let d = lz4f_decompress_frame(&cbuf[..csize], &mut dbuf2).expect("frame decompress");
d
} else {
lz4f_decompress_frame(&cbuf[..csize], &mut dbuf).expect("frame decompress")
}
} else {
lz4f_decompress_frame(&cbuf[..csize], &mut dbuf).expect("frame decompress")
};
assert_eq!(dsize, data.len(), "frame decompressed size mismatch");
assert_eq!(&dbuf[..dsize], data, "frame round-trip data mismatch");
}
#[test]
fn test_roundtrip_empty() {
let data = b"";
let max_csize = lz4_compress_bound(0);
let mut cbuf = vec![0u8; max_csize + 64];
let csize = lz4_compress_fast(data, &mut cbuf, 1).expect("compress empty");
assert_eq!(csize, 1, "empty block should produce 1 byte (token=0)");
let mut dbuf = vec![0u8; 64];
let dsize = lz4_decompress_safe(&cbuf[..csize], &mut dbuf, 64).expect("decompress empty");
assert_eq!(dsize, 0);
}
#[test]
fn test_roundtrip_single_byte() {
roundtrip_fast(&[0x42]);
roundtrip_hc(&[0x42], 1);
roundtrip_hc(&[0x42], 9);
}
#[test]
fn test_roundtrip_all_zeros() {
let data = vec![0u8; 4096];
roundtrip_fast(&data);
for lvl in &[1, 4, 9, 12] {
roundtrip_hc(&data, *lvl);
}
}
#[test]
fn test_roundtrip_all_ff() {
let data = vec![0xFFu8; 4096];
roundtrip_fast(&data);
roundtrip_hc(&data, 6);
}
#[test]
fn test_roundtrip_randomish() {
let mut data = Vec::with_capacity(1024);
let mut seed = 12345u32;
for _ in 0..1024 {
seed = seed.wrapping_mul(1103515245).wrapping_add(12345);
data.push((seed >> 16) as u8);
}
roundtrip_fast(&data);
roundtrip_hc(&data, 3);
roundtrip_hc(&data, 9);
}
#[test]
fn test_roundtrip_repeating_abcd() {
let pattern = b"ABCD";
let data: Vec<u8> = pattern.iter().cycle().take(4096).copied().collect();
roundtrip_fast(&data);
roundtrip_hc(&data, 1);
roundtrip_hc(&data, 12);
}
#[test]
fn test_long_literals() {
let data: Vec<u8> = (0..65536u32).map(|i| (i & 0xFF) as u8 ^ ((i >> 8) as u8)).collect();
roundtrip_fast(&data);
}
#[test]
fn test_boundary_mflimit() {
let data: Vec<u8> = (0..12u8).collect();
roundtrip_fast(&data);
}
#[test]
fn test_boundary_just_above_mflimit() {
let data: Vec<u8> = (0..13u8).collect();
roundtrip_fast(&data);
roundtrip_hc(&data, 1);
}
#[test]
fn test_boundary_64k() {
let data = vec![0xABu8; 65536];
roundtrip_fast(&data);
roundtrip_hc(&data, 7);
}
#[test]
fn test_overlapping_match() {
let data = vec![b'A'; 256];
roundtrip_fast(&data);
roundtrip_hc(&data, 5);
}
#[test]
fn test_offset_one_match() {
let pattern = b"ab";
let data: Vec<u8> = pattern.iter().cycle().take(1024).copied().collect();
roundtrip_fast(&data);
roundtrip_hc(&data, 8);
}
#[test]
fn test_max_distance_match() {
let mut data = vec![0u8; 65535 + 256];
data[0] = 0xDE;
data[1] = 0xAD;
data[2] = 0xBE;
data[3] = 0xEF;
data[65535] = 0xDE;
data[65535 + 1] = 0xAD;
data[65535 + 2] = 0xBE;
data[65535 + 3] = 0xEF;
roundtrip_fast(&data);
}
#[test]
fn test_hc_all_levels() {
let data: Vec<u8> = (0..256u32)
.flat_map(|i| {
let pat = [i as u8, (i ^ 0x55) as u8, (i >> 1) as u8, 0xAA];
pat.to_vec()
})
.cycle()
.take(8192)
.collect();
for lvl in 1..=12 {
roundtrip_hc(&data, lvl);
}
}
#[test]
fn test_decompress_corrupted() {
let mut data = vec![0u8; 64];
data[0] = 0xFF;
let result = lz4_decompress_safe(&data, &mut vec![0u8; 256], 256);
assert!(result.is_err(), "should reject corrupted block");
}
#[test]
fn test_decompress_truncated() {
let data = vec![0x10, 0x41, 0x42]; let result = lz4_decompress_safe(&data, &mut vec![0u8; 256], 256);
assert!(result.is_err(), "should reject truncated block");
}
#[test]
fn test_frame_roundtrip_independent() {
let data = (0..50000u32).map(|i| (i & 0xFF) as u8).collect::<Vec<_>>();
let prefs = Lz4FramePreferences {
block_size: 65536,
compression_level: 1,
content_checksum: true,
..Default::default()
};
roundtrip_frame(&data, &prefs);
}
#[test]
fn test_frame_roundtrip_hc() {
let data = vec![0x55u8; 100000];
let prefs = Lz4FramePreferences {
block_size: 65536,
compression_level: 9,
content_checksum: true,
block_checksum: true,
..Default::default()
};
roundtrip_frame(&data, &prefs);
}
#[test]
fn test_frame_large_block() {
let data = vec![b'X'; 200000];
let prefs = Lz4FramePreferences {
block_size: 1024 * 1024,
compression_level: 1,
content_checksum: true,
..Default::default()
};
roundtrip_frame(&data, &prefs);
}
#[test]
fn test_frame_no_content_checksum() {
let data = b"Hello, LZ4 Frame!".repeat(100);
let prefs = Lz4FramePreferences {
block_size: 65536,
compression_level: 1,
content_checksum: false,
..Default::default()
};
roundtrip_frame(data.as_bytes(), &prefs);
}
#[test]
fn test_frame_header_parsing() {
let data = b"Hello, Frame Test!".repeat(100);
let prefs = Lz4FramePreferences::default();
let bound = lz4f_frame_bound(data.len());
let mut cbuf = vec![0u8; bound];
let csize = lz4f_compress_frame(&data, &mut cbuf, &prefs).unwrap();
let (info, consumed) = lz4f_get_frame_info(&cbuf[..csize]).unwrap();
assert!(consumed > 7);
assert_eq!(info.content_size, Some(data.len() as u64));
assert_eq!(info.block_mode, Lz4BlockMode::BlockIndependent);
}
#[test]
fn test_streaming_roundtrip() {
let data = (0..200000u32).map(|i| (i.wrapping_mul(17) & 0xFF) as u8).collect::<Vec<_>>();
let prefs = Lz4FramePreferences {
block_size: 65536,
compression_level: 1,
content_checksum: true,
..Default::default()
};
let mut ctx = Lz4fCompressionContext::new(prefs.clone());
let mut header_buf = vec![0u8; LZ4F_HEADER_SIZE_MAX + 4];
let hlen = ctx.begin(&mut header_buf).unwrap();
let mut compressed = header_buf[..hlen].to_vec();
let chunk_size = 33333;
let mut offset = 0;
while offset < data.len() {
let end = cmp::min(offset + chunk_size, data.len());
let chunk = &data[offset..end];
let mut cbuf = vec![0u8; lz4_compress_bound(chunk.len()) + 256];
let w = ctx.update(chunk, &mut cbuf).unwrap();
compressed.extend_from_slice(&cbuf[..w]);
offset = end;
}
let mut end_buf = vec![0u8; 256];
let elen = ctx.end(&mut end_buf).unwrap();
compressed.extend_from_slice(&end_buf[..elen]);
let mut dbuf = vec![0u8; data.len() + 1024];
let dsize = lz4f_decompress_frame(&compressed, &mut dbuf).unwrap();
assert_eq!(dsize, data.len());
assert_eq!(&dbuf[..dsize], &data[..]);
}
#[test]
fn test_streaming_decompress_context() {
let data = vec![0xDEu8; 50000];
let prefs = Lz4FramePreferences::default();
let bound = lz4f_frame_bound(data.len());
let mut cbuf = vec![0u8; bound];
let csize = lz4f_compress_frame(&data, &mut cbuf, &prefs).unwrap();
let compressed = &cbuf[..csize];
let mut ctx = Lz4fDecompressionContext::new();
let mut output = Vec::new();
let mut in_offset = 0;
let mut out_buf = vec![0u8; 16384];
while in_offset < compressed.len() {
let chunk_size = cmp::min(7777, compressed.len() - in_offset);
let (written, consumed) = ctx
.decompress(&compressed[in_offset..in_offset + chunk_size], &mut out_buf)
.unwrap();
output.extend_from_slice(&out_buf[..written]);
in_offset += chunk_size; if ctx.is_finished() {
break;
}
}
assert_eq!(output.len(), data.len());
assert_eq!(&output, &data);
}
#[test]
fn test_xxh32_known_vectors() {
assert_eq!(xxh32(b"", 0), 0x02CC_5D05);
assert_eq!(xxh32(b"a", 0), 0x550D_7456);
assert_eq!(xxh32(b"abc", 0), 0x32D1_53FF);
assert_eq!(xxh32(b"message digest", 0), 0x7C94_8382);
assert_eq!(
xxh32(b"abcdefghijklmnopqrstuvwxyz", 0),
0x50A0_7E09
);
assert_eq!(xxh32(b"abc", 12345), 0xBF67_4AC1);
}
#[test]
fn test_compress_bound() {
assert!(lz4_compress_bound(0) >= 16);
assert!(lz4_compress_bound(1024) >= 1024);
assert!(lz4_compress_bound(LZ4_MAX_INPUT_SIZE) > LZ4_MAX_INPUT_SIZE);
assert_eq!(lz4_compress_bound(LZ4_MAX_INPUT_SIZE + 1), 0);
}
#[test]
fn test_dictionary_compress() {
let dict = b"ABCDEFGH";
let src = b"ABCDABCDABCDABCD";
let max_csize = lz4_compress_bound(src.len());
let mut cbuf = vec![0u8; max_csize];
let csize = lz4_compress_fast_using_dict(src, &mut cbuf, dict, 1).unwrap();
let mut dbuf = vec![0u8; src.len() + 64];
let dsize = lz4_decompress_safe(&cbuf[..csize], &mut dbuf, src.len() + 64).unwrap();
assert_eq!(dsize, src.len());
assert_eq!(&dbuf[..dsize], src);
}
#[test]
fn test_dictionary_hc_compress() {
let dict = b"The quick brown fox";
let src = b"The quick brown fox jumps over The quick brown fox";
let max_csize = lz4_compress_bound(src.len());
let mut cbuf = vec![0u8; max_csize];
let csize = lz4_compress_hc_using_dict(src, &mut cbuf, dict, 6).unwrap();
let mut dbuf = vec![0u8; src.len() + 64];
let dsize = lz4_decompress_safe(&cbuf[..csize], &mut dbuf, src.len() + 64).unwrap();
assert_eq!(dsize, src.len());
assert_eq!(&dbuf[..dsize], src);
}
#[test]
fn test_hc_level_params() {
let l1 = HcLevel::new(1);
assert_eq!(l1.search_depth, 1);
assert!(!l1.lazy);
assert!(!l1.optimal_parse);
let l9 = HcLevel::new(9);
assert_eq!(l9.search_depth, 1024);
assert!(l9.lazy);
assert!(!l9.optimal_parse);
let l12 = HcLevel::new(12);
assert_eq!(l12.search_depth, 8192);
assert!(l12.lazy);
assert!(l12.optimal_parse);
let l0 = HcLevel::new(0);
assert_eq!(l0.level, 1);
let l99 = HcLevel::new(99);
assert_eq!(l99.level, 12);
}
#[test]
fn test_acceleration() {
let data = vec![b'Z'; 10000];
for accel in &[1, 2, 4, 10, 100] {
let max_csize = lz4_compress_bound(data.len());
let mut cbuf = vec![0u8; max_csize];
let csize = lz4_compress_fast(&data, &mut cbuf, *accel).unwrap();
let mut dbuf = vec![0u8; data.len() + 64];
let dsize = lz4_decompress_safe(&cbuf[..csize], &mut dbuf, data.len() + 64).unwrap();
assert_eq!(dsize, data.len(), "accel={} roundtrip failed", accel);
}
}
#[test]
fn test_long_literal_encoding() {
let data: Vec<u8> = (0..1024usize).map(|i| (i.wrapping_mul(127) & 0xFF) as u8).collect();
roundtrip_fast(&data);
roundtrip_hc(&data, 1);
}
#[test]
fn test_frame_content_checksum_verified() {
let data = b"checksum test data".repeat(500);
let prefs = Lz4FramePreferences {
content_checksum: true,
..Default::default()
};
let bound = lz4f_frame_bound(data.len());
let mut cbuf = vec![0u8; bound];
let csize = lz4f_compress_frame(data.as_ref(), &mut cbuf, &prefs).unwrap();
let mut dbuf = vec![0u8; data.len() + 64];
let dsize = lz4f_decompress_frame(&cbuf[..csize], &mut dbuf).unwrap();
assert_eq!(dsize, data.len());
assert_eq!(&dbuf[..dsize], data.as_ref());
let mut corrupted = cbuf[..csize].to_vec();
let len = corrupted.len();
corrupted[len - 1] ^= 0xFF;
let result = lz4f_decompress_frame(&corrupted, &mut dbuf);
assert!(result.is_err(), "should detect corrupted content checksum");
}
#[test]
fn test_frame_block_checksum() {
let data = vec![0x42u8; 131072]; let prefs = Lz4FramePreferences {
block_size: 65536,
block_checksum: true,
content_checksum: true,
..Default::default()
};
roundtrip_frame(&data, &prefs);
}
#[test]
fn test_frame_linked_blocks() {
let data = vec![b'Q'; 100000];
let prefs = Lz4FramePreferences {
block_size: 32768,
block_mode: Lz4BlockMode::BlockLinked,
compression_level: 1,
content_checksum: true,
..Default::default()
};
roundtrip_frame(&data, &prefs);
}
#[test]
fn test_frame_various_block_sizes() {
let data = vec![0xA5u8; 500000];
for &bs in &[65536, 262144, 1048576, 4194304] {
let prefs = Lz4FramePreferences {
block_size: bs,
compression_level: 1,
content_checksum: true,
..Default::default()
};
roundtrip_frame(&data, &prefs);
}
}
#[test]
fn test_error_display() {
let e = Lz4Error::CorruptedBlock;
let s = format!("{}", e);
assert!(s.contains("corrupted"));
let e = Lz4Error::HeaderChecksumError;
let s = format!("{}", e);
assert!(s.contains("checksum"));
let e = Lz4Error::InvalidParameter("test param");
let s = format!("{}", e);
assert!(s.contains("test param"));
}
#[test]
fn test_format_size() {
assert_eq!(format_size(0), "0 B");
assert_eq!(format_size(512), "512 B");
assert!(format_size(2048).contains("KiB"));
assert!(format_size(5_000_000).contains("MiB"));
assert!(format_size(2_000_000_000).contains("GiB"));
}
#[test]
fn test_bd_mapping() {
assert_eq!(bd_from_block_maxsize(16384), LZ4F_BLOCK_MAXSIZE_64KB);
assert_eq!(bd_from_block_maxsize(65535), LZ4F_BLOCK_MAXSIZE_64KB);
assert_eq!(bd_from_block_maxsize(65536), LZ4F_BLOCK_MAXSIZE_64KB);
assert_eq!(bd_from_block_maxsize(65537), LZ4F_BLOCK_MAXSIZE_256KB);
assert_eq!(bd_from_block_maxsize(262144), LZ4F_BLOCK_MAXSIZE_256KB);
assert_eq!(bd_from_block_maxsize(1048576), LZ4F_BLOCK_MAXSIZE_1MB);
assert_eq!(bd_from_block_maxsize(4194304), LZ4F_BLOCK_MAXSIZE_4MB);
assert_eq!(bd_from_block_maxsize(9999999), LZ4F_BLOCK_MAXSIZE_4MB);
}
#[test]
fn test_block_maxsize_from_bd() {
assert_eq!(block_maxsize_from_bd(4), 65536);
assert_eq!(block_maxsize_from_bd(5), 262144);
assert_eq!(block_maxsize_from_bd(6), 1048576);
assert_eq!(block_maxsize_from_bd(7), 4194304);
assert_eq!(block_maxsize_from_bd(0), 65536); }
#[test]
fn test_hc_better_than_fast() {
let data = b"The quick brown fox jumps over the lazy dog. ".repeat(500);
let max_csize = lz4_compress_bound(data.len());
let mut cbuf_fast = vec![0u8; max_csize];
let csize_fast = lz4_compress_fast(data.as_ref(), &mut cbuf_fast, 1).unwrap();
let mut cbuf_hc = vec![0u8; max_csize];
let csize_hc = lz4_compress_hc(data.as_ref(), &mut cbuf_hc, 12).unwrap();
assert!(
csize_hc <= csize_fast + 32,
"HC level 12 should not be significantly worse than fast; fast={}, hc={}",
csize_fast,
csize_hc,
);
}
#[test]
fn test_known_compressed_block() {
let block = [0x10, b'H'];
let mut dbuf = [0u8; 64];
let dsize = lz4_decompress_safe(&block, &mut dbuf, 64).unwrap();
assert_eq!(dsize, 1);
assert_eq!(dbuf[0], b'H');
}
}