use std::{cell::RefCell, io::Read};
use bytes::Bytes;
use lz4rip::{
block::{CompressorRef, compress_into, get_maximum_output_size},
frame::FrameDecoder,
};
use lzzzz::{lz4::max_compressed_size, lz4_hc};
use crate::CompressionError;
const DEFAULT_LEVEL: i32 = 9;
const BLOCK_SIZE: usize = 64 * 1024;
const MIN_COMPRESSIBLE_BLOCK: usize = 13;
const BLOCK_UNCOMPRESSED_BIT: u32 = 0x8000_0000;
const FRAME_HEADER: &[u8] = &[0x04, 0x22, 0x4D, 0x18, 0x60, 0x40, 0x82];
pub fn compress(data: &[u8]) -> Result<Bytes, CompressionError> {
let largest_block = data.len().min(BLOCK_SIZE);
let slack = if largest_block < MIN_COMPRESSIBLE_BLOCK {
0
} else {
get_maximum_output_size(largest_block) - largest_block
};
let mut out = frame_buffer(data, slack);
for block in data.chunks(BLOCK_SIZE) {
write_block(
&mut out,
block,
get_maximum_output_size(block.len()),
|scratch| {
let compressed = if block.len() >= usize::from(u16::MAX) {
thread_local! {
static COMPRESSOR: RefCell<CompressorRef> =
RefCell::new(CompressorRef::new());
}
COMPRESSOR
.with(|compressor| compressor.borrow_mut().compress_into(block, scratch))
} else {
compress_into(block, scratch)
};
compressed.map_err(|e| CompressionError::InvalidData(format!("lz4 compress: {e}")))
},
)?;
}
out.extend_from_slice(&0u32.to_le_bytes());
Ok(Bytes::from(out))
}
pub fn compress_with_level(data: &[u8], level: i32) -> Result<Bytes, CompressionError> {
if level == DEFAULT_LEVEL {
return compress(data);
}
let largest_block = data.len().min(BLOCK_SIZE);
let slack = if largest_block < MIN_COMPRESSIBLE_BLOCK {
0
} else {
max_compressed_size(largest_block) - largest_block
};
let mut out = frame_buffer(data, slack);
for block in data.chunks(BLOCK_SIZE) {
write_hc_block(&mut out, block, level)?;
}
out.extend_from_slice(&0u32.to_le_bytes()); Ok(Bytes::from(out))
}
fn frame_buffer(data: &[u8], slack: usize) -> Vec<u8> {
let capacity = data
.len()
.saturating_add(data.len().div_ceil(BLOCK_SIZE).saturating_mul(4))
.saturating_add(FRAME_HEADER.len() + 4 + slack);
let mut out = Vec::with_capacity(capacity);
out.extend_from_slice(FRAME_HEADER);
out
}
const LIBLZ4_HC_CLEVEL_MAX: i32 = 12;
fn write_hc_block(out: &mut Vec<u8>, block: &[u8], level: i32) -> Result<(), CompressionError> {
let level = level.min(LIBLZ4_HC_CLEVEL_MAX);
write_block(out, block, max_compressed_size(block.len()), |scratch| {
lz4_hc::compress(block, scratch, level)
.map_err(|e| CompressionError::InvalidData(format!("lz4 hc compress: {e}")))
})
}
fn write_block(
out: &mut Vec<u8>,
block: &[u8],
max_compressed: usize,
compress: impl FnOnce(&mut [u8]) -> Result<usize, CompressionError>,
) -> Result<(), CompressionError> {
if block.len() < MIN_COMPRESSIBLE_BLOCK {
let size = block_size(block.len())? | BLOCK_UNCOMPRESSED_BIT;
out.extend_from_slice(&size.to_le_bytes());
out.extend_from_slice(block);
return Ok(());
}
let header = out.len();
out.resize(header + 4 + max_compressed, 0);
let (size_slot, scratch) = out
.split_at_mut_checked(header)
.and_then(|(_, tail)| tail.split_first_chunk_mut::<4>())
.ok_or_else(|| scratch_error(block.len()))?;
let n = compress(scratch)?;
let (size, stored) = if n < block.len() {
(block_size(n)?, n)
} else {
scratch
.get_mut(..block.len())
.ok_or_else(|| scratch_error(block.len()))?
.copy_from_slice(block);
(
block_size(block.len())? | BLOCK_UNCOMPRESSED_BIT,
block.len(),
)
};
*size_slot = size.to_le_bytes();
out.truncate(header + 4 + stored);
Ok(())
}
fn block_size(len: usize) -> Result<u32, CompressionError> {
u32::try_from(len)
.map_err(|_| CompressionError::InvalidData(format!("lz4 block of {len} bytes exceeds u32")))
}
fn scratch_error(len: usize) -> CompressionError {
CompressionError::InvalidData(format!(
"lz4 scratch space too short for a {len}-byte block"
))
}
pub fn decompress(data: &[u8], max_output: usize) -> Result<Bytes, CompressionError> {
if data.is_empty() {
return Err(CompressionError::InvalidData("empty lz4 payload".into()));
}
let decoder = FrameDecoder::new(data);
let mut limited = decoder.take((max_output as u64).saturating_add(1));
let mut out = Vec::with_capacity(data.len().saturating_mul(2).min(max_output));
limited
.read_to_end(&mut out)
.map_err(|e| CompressionError::InvalidData(format!("lz4 decode: {e}")))?;
if out.len() > max_output {
return Err(CompressionError::TooLarge { limit: max_output });
}
Ok(Bytes::from(out))
}
#[cfg(test)]
mod tests {
use super::*;
const HELLO: &[u8] = b"hello kafka, this is a moderately repetitive payload to compress";
const BIG_CAP: usize = 256 * 1024 * 1024;
#[test]
fn roundtrip() {
let z = compress(HELLO).unwrap();
let back = decompress(&z, BIG_CAP).unwrap();
assert2::assert!(back.as_ref() == HELLO);
}
#[test]
fn decompress_empty_rejected() {
assert2::assert!(matches!(
decompress(b"", BIG_CAP),
Err(CompressionError::InvalidData(_))
));
}
#[test]
fn decompress_garbage_rejected() {
assert2::assert!(matches!(
decompress(b"this is not lz4", BIG_CAP),
Err(CompressionError::InvalidData(_))
));
}
#[test]
fn larger_payload_roundtrips() {
let big = vec![0xABu8; 128 * 1024]; let z = compress(&big).unwrap();
let back = decompress(&z, BIG_CAP).unwrap();
assert2::assert!(back.as_ref() == big.as_slice());
}
#[test]
fn decompression_bomb_rejected() {
let bomb = vec![0u8; 64 * 1024 * 1024];
let z = compress(&bomb).unwrap();
assert2::assert!(matches!(
decompress(&z, 1024),
Err(CompressionError::TooLarge { limit: 1024 })
));
let back = decompress(&z, BIG_CAP).unwrap();
assert2::assert!(back.as_ref() == bomb.as_slice());
}
#[test]
fn decompress_at_exact_cap_succeeds() {
let z = compress(HELLO).unwrap();
let back = decompress(&z, HELLO.len()).unwrap();
assert2::assert!(back.as_ref() == HELLO);
assert2::assert!(matches!(
decompress(&z, HELLO.len() - 1),
Err(CompressionError::TooLarge { limit }) if limit == HELLO.len() - 1
));
}
#[test]
fn frame_uses_64kib_independent_blocks() {
let big = vec![0xCDu8; 128 * 1024];
let z = compress(&big).unwrap();
let flg = z[4];
let bd = z[5];
assert2::assert!(&z[..7] == &[0x04, 0x22, 0x4D, 0x18, 0x60, 0x40, 0x82]);
assert2::assert!((bd >> 4) & 0x7 == 4);
assert2::assert!((flg >> 5) & 1 == 1);
assert2::assert!((flg >> 4) & 1 == 0);
assert2::assert!((flg >> 2) & 1 == 0);
}
fn compressible_payload() -> Vec<u8> {
let unit = b"the quick brown fox jumps over the lazy dog, again and again; ";
unit.iter().copied().cycle().take(3 * BLOCK_SIZE).collect()
}
#[test]
fn compress_with_level_uses_hc_except_at_the_default() {
let payload = compressible_payload();
let fast = compress(&payload).unwrap();
for (level, expect_fast_bytes) in [(9, true), (1, false), (17, false)] {
let out = compress_with_level(&payload, level).unwrap();
assert2::assert!(
(out.as_ref() == fast.as_ref()) == expect_fast_bytes,
"level {level}"
);
if !expect_fast_bytes {
assert2::assert!(out.len() < fast.len(), "level {level}");
}
let back = decompress(&out, BIG_CAP).unwrap();
assert2::assert!(back.as_ref() == payload.as_slice(), "level {level}");
}
}
#[test]
fn levels_above_liblz4_max_clamp_to_liblz4_max() {
let payload = compressible_payload();
let at_max = compress_with_level(&payload, LIBLZ4_HC_CLEVEL_MAX).unwrap();
for level in [13, 17] {
let out = compress_with_level(&payload, level).unwrap();
assert2::assert!(out.as_ref() == at_max.as_ref(), "level {level}");
let back = decompress(&out, BIG_CAP).unwrap();
assert2::assert!(back.as_ref() == payload.as_slice(), "level {level}");
}
}
#[test]
fn incompressible_block_falls_back_to_stored() {
let incompressible: Vec<u8> = (0..=255u8).collect();
let out = compress_with_level(&incompressible, 1).unwrap();
let block_size = u32::from_le_bytes(out[FRAME_HEADER.len()..][..4].try_into().unwrap());
assert2::assert!(block_size & BLOCK_UNCOMPRESSED_BIT != 0);
assert2::assert!((block_size & !BLOCK_UNCOMPRESSED_BIT) as usize == incompressible.len());
let back = decompress(&out, BIG_CAP).unwrap();
assert2::assert!(back.as_ref() == incompressible.as_slice());
}
#[test]
fn compress_with_level_hc_frame_uses_the_same_header_as_the_fast_frame() {
let payload = compressible_payload();
let fast = compress(&payload).unwrap();
let hc = compress_with_level(&payload, 1).unwrap();
assert2::assert!(fast[..FRAME_HEADER.len()] == hc[..FRAME_HEADER.len()]);
}
}