use std::{cell::RefCell, io::Read};
use bytes::Bytes;
use lz4rip::frame::FrameDecoder;
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 {
lz4rip::block::get_maximum_output_size(largest_block) - largest_block
};
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);
for block in data.chunks(BLOCK_SIZE) {
if block.len() < MIN_COMPRESSIBLE_BLOCK {
let size = u32::try_from(block.len()).expect("a short block's length fits u32")
| BLOCK_UNCOMPRESSED_BIT;
out.extend_from_slice(&size.to_le_bytes());
out.extend_from_slice(block);
continue;
}
let header = out.len();
out.extend_from_slice(&0u32.to_le_bytes());
let start = out.len();
out.resize(
start + lz4rip::block::get_maximum_output_size(block.len()),
0,
);
let compressed = if block.len() >= usize::from(u16::MAX) {
thread_local! {
static COMPRESSOR: RefCell<lz4rip::block::CompressorRef> =
RefCell::new(lz4rip::block::CompressorRef::new());
}
COMPRESSOR.with(|compressor| {
compressor
.borrow_mut()
.compress_into(block, &mut out[start..])
})
} else {
lz4rip::block::compress_into(block, &mut out[start..])
};
let n =
compressed.map_err(|e| CompressionError::InvalidData(format!("lz4 compress: {e}")))?;
let size = if n < block.len() {
out.truncate(start + n);
u32::try_from(n).expect("a 64 KiB block's compressed length fits u32")
} else {
out.truncate(start + block.len());
out[start..].copy_from_slice(block);
u32::try_from(block.len()).expect("a 64 KiB block's length fits u32")
| BLOCK_UNCOMPRESSED_BIT
};
out[header..start].copy_from_slice(&size.to_le_bytes());
}
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 {
lzzzz::lz4::max_compressed_size(largest_block) - largest_block
};
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);
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))
}
const LIBLZ4_HC_CLEVEL_MAX: i32 = 12;
fn write_hc_block(out: &mut Vec<u8>, block: &[u8], level: i32) -> Result<(), CompressionError> {
if block.len() < MIN_COMPRESSIBLE_BLOCK {
let size = u32::try_from(block.len()).expect("a short block's length fits u32")
| BLOCK_UNCOMPRESSED_BIT;
out.extend_from_slice(&size.to_le_bytes());
out.extend_from_slice(block);
return Ok(());
}
let level = level.min(LIBLZ4_HC_CLEVEL_MAX);
let header = out.len();
out.extend_from_slice(&0u32.to_le_bytes());
let start = out.len();
out.resize(start + lzzzz::lz4::max_compressed_size(block.len()), 0);
let comp_len = lzzzz::lz4_hc::compress(block, &mut out[start..], level)
.map_err(|e| CompressionError::InvalidData(format!("lz4 hc compress: {e}")))?;
let size = if comp_len < block.len() {
out.truncate(start + comp_len);
u32::try_from(comp_len).expect("a 64 KiB block's compressed length fits u32")
} else {
out.truncate(start + block.len());
out[start..].copy_from_slice(block);
u32::try_from(block.len()).expect("a 64 KiB block's length fits u32")
| BLOCK_UNCOMPRESSED_BIT
};
out[header..start].copy_from_slice(&size.to_le_bytes());
Ok(())
}
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()]);
}
}