use std::io::{self, Read, Write};
use super::{Decoder, Encoder, method};
const LZ5_MAGIC: u32 = 0x184D2205;
const MIN_MATCH: usize = 3;
const BLOCK_SIZES: [usize; 8] = [
0, 64 * 1024, 256 * 1024, 1024 * 1024, 4 * 1024 * 1024, 16 * 1024 * 1024, 64 * 1024 * 1024, 256 * 1024 * 1024, ];
pub struct Lz5Decoder<R: Read> {
inner: R,
buffer: Vec<u8>,
buffer_pos: usize,
read_pos: usize,
finished: bool,
header_parsed: bool,
block_independent: bool,
max_block_size: usize,
block_checksum: bool,
content_checksum: bool,
content_size: Option<u64>,
last_offset: usize,
}
impl<R: Read> std::fmt::Debug for Lz5Decoder<R> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Lz5Decoder")
.field("buffer_pos", &self.buffer_pos)
.field("read_pos", &self.read_pos)
.field("finished", &self.finished)
.field("max_block_size", &self.max_block_size)
.finish()
}
}
impl<R: Read + Send> Lz5Decoder<R> {
pub fn new(input: R) -> Self {
Self {
inner: input,
buffer: Vec::new(),
buffer_pos: 0,
read_pos: 0,
finished: false,
header_parsed: false,
block_independent: true,
max_block_size: 4 * 1024 * 1024, block_checksum: false,
content_checksum: false,
content_size: None,
last_offset: 0,
}
}
fn parse_header(&mut self) -> io::Result<()> {
let mut magic_bytes = [0u8; 4];
self.inner.read_exact(&mut magic_bytes)?;
let magic = u32::from_le_bytes(magic_bytes);
if magic != LZ5_MAGIC {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!(
"Invalid LZ5 magic number: 0x{:08X}, expected 0x{:08X}",
magic, LZ5_MAGIC
),
));
}
let mut flg = [0u8; 1];
self.inner.read_exact(&mut flg)?;
let flg = flg[0];
let version = (flg >> 6) & 0x03;
if version != 0x01 {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("Unsupported LZ5 version: {}", version),
));
}
self.block_independent = (flg & 0x20) != 0;
self.block_checksum = (flg & 0x10) != 0;
let has_content_size = (flg & 0x08) != 0;
self.content_checksum = (flg & 0x04) != 0;
let mut bd = [0u8; 1];
self.inner.read_exact(&mut bd)?;
let bd = bd[0];
let block_size_id = ((bd >> 4) & 0x07) as usize;
if block_size_id == 0 || block_size_id >= BLOCK_SIZES.len() {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("Invalid block size ID: {}", block_size_id),
));
}
self.max_block_size = BLOCK_SIZES[block_size_id];
if has_content_size {
let mut size_bytes = [0u8; 8];
self.inner.read_exact(&mut size_bytes)?;
self.content_size = Some(u64::from_le_bytes(size_bytes));
}
let mut _hc = [0u8; 1];
self.inner.read_exact(&mut _hc)?;
self.buffer.reserve(self.max_block_size);
self.header_parsed = true;
Ok(())
}
fn read_block(&mut self) -> io::Result<bool> {
let mut size_bytes = [0u8; 4];
self.inner.read_exact(&mut size_bytes)?;
let block_header = u32::from_le_bytes(size_bytes);
if block_header == 0 {
if self.content_checksum {
let mut checksum = [0u8; 4];
self.inner.read_exact(&mut checksum)?;
}
return Ok(false);
}
let is_uncompressed = (block_header & 0x80000000) != 0;
let block_size = (block_header & 0x7FFFFFFF) as usize;
if block_size > self.max_block_size {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!(
"Block size {} exceeds maximum {}",
block_size, self.max_block_size
),
));
}
let mut block_data = vec![0u8; block_size];
self.inner.read_exact(&mut block_data)?;
if self.block_checksum {
let mut _checksum = [0u8; 4];
self.inner.read_exact(&mut _checksum)?;
}
if self.block_independent {
self.buffer.clear();
self.buffer_pos = 0;
self.read_pos = 0;
}
if is_uncompressed {
self.buffer.extend_from_slice(&block_data);
self.buffer_pos = self.buffer.len();
} else {
self.decompress_block(&block_data)?;
}
Ok(true)
}
fn decompress_block(&mut self, data: &[u8]) -> io::Result<()> {
let mut pos = 0;
let len = data.len();
while pos < len {
let token = data[pos];
pos += 1;
let (literal_len, match_len, offset) = if (token & 0x80) != 0 {
if pos >= len {
return Err(io::Error::new(
io::ErrorKind::UnexpectedEof,
"Incomplete LZ5 block",
));
}
let offset_high = ((token >> 5) & 0x03) as usize;
let literal_len = ((token >> 3) & 0x03) as usize;
let match_len = (token & 0x07) as usize;
let offset_low = data[pos] as usize;
pos += 1;
let offset = (offset_high << 8) | offset_low;
(literal_len, match_len, Some(offset))
} else if (token & 0xC0) == 0x00 {
if pos + 1 >= len {
return Err(io::Error::new(
io::ErrorKind::UnexpectedEof,
"Incomplete LZ5 block",
));
}
let literal_len = ((token >> 3) & 0x07) as usize;
let match_len = (token & 0x07) as usize;
let offset = u16::from_le_bytes([data[pos], data[pos + 1]]) as usize;
pos += 2;
(literal_len, match_len, Some(offset))
} else if (token & 0xE0) == 0x40 {
if pos + 2 >= len {
return Err(io::Error::new(
io::ErrorKind::UnexpectedEof,
"Incomplete LZ5 block",
));
}
let literal_len = ((token >> 3) & 0x03) as usize;
let match_len = (token & 0x07) as usize;
let offset = data[pos] as usize
| ((data[pos + 1] as usize) << 8)
| ((data[pos + 2] as usize) << 16);
pos += 3;
(literal_len, match_len, Some(offset))
} else {
let literal_len = ((token >> 3) & 0x03) as usize;
let match_len = (token & 0x07) as usize;
(literal_len, match_len, None)
};
let max_literal = if (token & 0xC0) == 0x00 { 7 } else { 3 };
let literal_len = if literal_len == max_literal {
self.read_extended_length(data, &mut pos, literal_len)?
} else {
literal_len
};
if literal_len > 0 {
if pos + literal_len > len {
return Err(io::Error::new(
io::ErrorKind::UnexpectedEof,
"Incomplete literals",
));
}
self.buffer.extend_from_slice(&data[pos..pos + literal_len]);
pos += literal_len;
}
if pos >= len {
break;
}
let actual_offset = match offset {
Some(0) => {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"Invalid zero offset",
));
}
Some(o) => {
self.last_offset = o;
o
}
None => self.last_offset,
};
if actual_offset == 0 {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"No last offset available",
));
}
let match_len = if match_len == 7 {
self.read_extended_length(data, &mut pos, match_len)?
} else {
match_len
};
let match_len = match_len + MIN_MATCH;
if actual_offset > self.buffer.len() {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!(
"Match offset {} exceeds buffer size {}",
actual_offset,
self.buffer.len()
),
));
}
let start = self.buffer.len() - actual_offset;
for i in 0..match_len {
let byte = self.buffer[start + (i % actual_offset)];
self.buffer.push(byte);
}
}
self.buffer_pos = self.buffer.len();
Ok(())
}
fn read_extended_length(
&self,
data: &[u8],
pos: &mut usize,
initial: usize,
) -> io::Result<usize> {
let mut length = initial;
loop {
if *pos >= data.len() {
return Err(io::Error::new(
io::ErrorKind::UnexpectedEof,
"Incomplete extended length",
));
}
let byte = data[*pos];
*pos += 1;
length += byte as usize;
if byte != 255 {
break;
}
}
Ok(length)
}
}
impl<R: Read + Send> Read for Lz5Decoder<R> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
if self.finished {
return Ok(0);
}
if !self.header_parsed {
self.parse_header()?;
}
if self.read_pos < self.buffer_pos {
let available = self.buffer_pos - self.read_pos;
let to_copy = buf.len().min(available);
buf[..to_copy].copy_from_slice(&self.buffer[self.read_pos..self.read_pos + to_copy]);
self.read_pos += to_copy;
return Ok(to_copy);
}
loop {
let has_more = self.read_block()?;
if !has_more {
self.finished = true;
return Ok(0);
}
if self.read_pos < self.buffer_pos {
let available = self.buffer_pos - self.read_pos;
let to_copy = buf.len().min(available);
buf[..to_copy]
.copy_from_slice(&self.buffer[self.read_pos..self.read_pos + to_copy]);
self.read_pos += to_copy;
return Ok(to_copy);
}
}
}
}
impl<R: Read + Send> Decoder for Lz5Decoder<R> {
fn method_id(&self) -> &'static [u8] {
method::LZ5
}
}
#[derive(Debug, Clone)]
pub struct Lz5EncoderOptions {
pub level: i32,
pub block_size_id: u8,
pub block_independent: bool,
pub content_checksum: bool,
pub block_checksum: bool,
}
impl Default for Lz5EncoderOptions {
fn default() -> Self {
Self {
level: 6,
block_size_id: 4, block_independent: true,
content_checksum: false,
block_checksum: false,
}
}
}
impl Lz5EncoderOptions {
pub fn new() -> Self {
Self::default()
}
pub fn level(mut self, level: i32) -> Self {
self.level = level.clamp(1, 15);
self
}
pub fn block_size_id(mut self, id: u8) -> Self {
self.block_size_id = id.clamp(1, 7);
self
}
pub fn block_independent(mut self, independent: bool) -> Self {
self.block_independent = independent;
self
}
pub fn max_block_size(&self) -> usize {
BLOCK_SIZES[self.block_size_id as usize]
}
}
const HASH_TABLE_SIZE: usize = 1 << 20;
const HASH_MASK: usize = HASH_TABLE_SIZE - 1;
pub struct Lz5Encoder<W: Write> {
inner: W,
options: Lz5EncoderOptions,
buffer: Vec<u8>,
max_block_size: usize,
header_written: bool,
hash_table: Vec<u32>,
last_offset: usize,
}
impl<W: Write> std::fmt::Debug for Lz5Encoder<W> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Lz5Encoder")
.field("options", &self.options)
.field("buffer_len", &self.buffer.len())
.field("max_block_size", &self.max_block_size)
.finish_non_exhaustive()
}
}
impl<W: Write + Send> Lz5Encoder<W> {
pub fn new(output: W, options: Lz5EncoderOptions) -> Self {
let max_block_size = options.max_block_size();
Self {
inner: output,
options,
buffer: Vec::with_capacity(max_block_size),
max_block_size,
header_written: false,
hash_table: vec![0; HASH_TABLE_SIZE],
last_offset: 0,
}
}
fn write_header(&mut self) -> io::Result<()> {
self.inner.write_all(&LZ5_MAGIC.to_le_bytes())?;
let mut flg: u8 = 0x40; if self.options.block_independent {
flg |= 0x20;
}
if self.options.block_checksum {
flg |= 0x10;
}
if self.options.content_checksum {
flg |= 0x04;
}
self.inner.write_all(&[flg])?;
let bd: u8 = (self.options.block_size_id & 0x07) << 4;
self.inner.write_all(&[bd])?;
let hc = ((flg as u16 + bd as u16) % 256) as u8;
self.inner.write_all(&[hc])?;
self.header_written = true;
Ok(())
}
#[inline]
fn hash4(data: &[u8]) -> usize {
if data.len() < 4 {
return 0;
}
let v = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
((v.wrapping_mul(2654435761)) >> 12) as usize & HASH_MASK
}
fn find_match(&self, data: &[u8], pos: usize) -> Option<(usize, usize)> {
if pos + 4 > data.len() {
return None;
}
let hash = Self::hash4(&data[pos..]);
let match_pos = self.hash_table[hash] as usize;
if match_pos == 0 || match_pos > pos {
return None;
}
let offset = pos - match_pos;
if offset == 0 || offset > 0xFFFFFF {
return None;
}
let mut length = 0;
let max_len = (data.len() - pos).min(65535);
while length < max_len && data[match_pos + length] == data[pos + length] {
length += 1;
}
if length >= MIN_MATCH {
Some((offset, length))
} else {
None
}
}
fn compress_block(&mut self, data: &[u8]) -> io::Result<Vec<u8>> {
if data.is_empty() {
return Ok(Vec::new());
}
let mut output = Vec::with_capacity(data.len());
let mut pos = 0;
let mut lit_start = 0;
if self.options.block_independent {
self.hash_table.fill(0);
self.last_offset = 0;
}
while pos < data.len() {
let match_result = self.find_match(data, pos);
if let Some((match_offset, match_len)) = match_result {
let literals = &data[lit_start..pos];
self.encode_sequence(&mut output, literals, match_offset, match_len)?;
let match_end = pos + match_len;
for i in pos..match_end.min(data.len().saturating_sub(3)) {
if i + 4 <= data.len() {
let hash = Self::hash4(&data[i..]);
self.hash_table[hash] = i as u32;
}
}
self.last_offset = match_offset;
pos = match_end;
lit_start = pos;
} else {
if pos + 4 <= data.len() {
let hash = Self::hash4(&data[pos..]);
self.hash_table[hash] = pos as u32;
}
pos += 1;
}
}
if lit_start < data.len() {
let remaining = &data[lit_start..];
self.write_final_sequence(&mut output, remaining);
}
Ok(output)
}
fn write_final_sequence(&self, output: &mut Vec<u8>, literals: &[u8]) {
if literals.is_empty() {
return;
}
let lit_len = literals.len();
let ll = lit_len.min(7);
let mm = 0u8; let token = (ll as u8) << 3 | mm;
output.push(token);
output.extend_from_slice(&1u16.to_le_bytes());
if ll == 7 && lit_len > 7 {
self.write_extended_length(output, lit_len - 7);
}
output.extend_from_slice(literals);
}
fn encode_sequence(
&self,
output: &mut Vec<u8>,
literals: &[u8],
offset: usize,
match_len: usize,
) -> io::Result<()> {
let lit_len = literals.len();
let ml = match_len.saturating_sub(MIN_MATCH);
if offset <= 0x3FF {
let ll = lit_len.min(3);
let mm = ml.min(7);
let token = 0x80 | ((offset >> 8) as u8 & 0x03) << 5 | (ll as u8) << 3 | mm as u8;
output.push(token);
output.push(offset as u8);
if ll == 3 && lit_len > 3 {
self.write_extended_length(output, lit_len - 3);
}
output.extend_from_slice(literals);
if mm == 7 && ml > 7 {
self.write_extended_length(output, ml - 7);
}
} else if offset <= 0xFFFF {
let ll = lit_len.min(7);
let mm = ml.min(7);
let token = (ll as u8) << 3 | mm as u8;
output.push(token);
output.extend_from_slice(&(offset as u16).to_le_bytes());
if ll == 7 && lit_len > 7 {
self.write_extended_length(output, lit_len - 7);
}
output.extend_from_slice(literals);
if mm == 7 && ml > 7 {
self.write_extended_length(output, ml - 7);
}
} else {
let ll = lit_len.min(3);
let mm = ml.min(7);
let token = 0x40 | (ll as u8) << 3 | mm as u8;
output.push(token);
output.push(offset as u8);
output.push((offset >> 8) as u8);
output.push((offset >> 16) as u8);
if ll == 3 && lit_len > 3 {
self.write_extended_length(output, lit_len - 3);
}
output.extend_from_slice(literals);
if mm == 7 && ml > 7 {
self.write_extended_length(output, ml - 7);
}
}
Ok(())
}
fn write_extended_length(&self, output: &mut Vec<u8>, mut length: usize) {
while length >= 255 {
output.push(255);
length -= 255;
}
output.push(length as u8);
}
fn flush_block(&mut self) -> io::Result<()> {
if self.buffer.is_empty() {
return Ok(());
}
let data = std::mem::take(&mut self.buffer);
let compressed = self.compress_block(&data)?;
if compressed.len() >= data.len() {
let block_size = data.len() as u32 | 0x80000000;
self.inner.write_all(&block_size.to_le_bytes())?;
self.inner.write_all(&data)?;
} else {
let block_size = compressed.len() as u32;
self.inner.write_all(&block_size.to_le_bytes())?;
self.inner.write_all(&compressed)?;
}
Ok(())
}
pub fn try_finish(mut self) -> io::Result<W> {
if !self.header_written {
self.write_header()?;
}
self.flush_block()?;
self.inner.write_all(&0u32.to_le_bytes())?;
Ok(self.inner)
}
}
impl<W: Write + Send> Write for Lz5Encoder<W> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
if !self.header_written {
self.write_header()?;
}
self.buffer.extend_from_slice(buf);
while self.buffer.len() >= self.max_block_size {
let block_data: Vec<u8> = self.buffer.drain(..self.max_block_size).collect();
let compressed = self.compress_block(&block_data)?;
if compressed.len() >= block_data.len() {
let block_size = block_data.len() as u32 | 0x80000000;
self.inner.write_all(&block_size.to_le_bytes())?;
self.inner.write_all(&block_data)?;
} else {
let block_size = compressed.len() as u32;
self.inner.write_all(&block_size.to_le_bytes())?;
self.inner.write_all(&compressed)?;
}
}
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
self.inner.flush()
}
}
impl<W: Write + Send> Encoder for Lz5Encoder<W> {
fn method_id(&self) -> &'static [u8] {
method::LZ5
}
fn finish(mut self: Box<Self>) -> io::Result<()> {
if !self.header_written {
self.write_header()?;
}
self.flush_block()?;
self.inner.write_all(&0u32.to_le_bytes())?;
self.inner.flush()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Cursor;
#[test]
fn test_lz5_decoder_method_id() {
let data = vec![0u8; 16];
let decoder = Lz5Decoder::new(Cursor::new(data));
assert_eq!(decoder.method_id(), method::LZ5);
}
#[test]
fn test_lz5_encoder_method_id() {
let output = Vec::new();
let encoder = Lz5Encoder::new(output, Lz5EncoderOptions::default());
assert_eq!(encoder.method_id(), method::LZ5);
}
#[test]
fn test_lz5_encoder_options() {
let opts = Lz5EncoderOptions::new().level(9);
assert_eq!(opts.level, 9);
let opts_low = Lz5EncoderOptions::new().level(0);
assert_eq!(opts_low.level, 1);
let opts_high = Lz5EncoderOptions::new().level(20);
assert_eq!(opts_high.level, 15);
let opts_bs = Lz5EncoderOptions::new().block_size_id(3);
assert_eq!(opts_bs.max_block_size(), 1024 * 1024); }
#[test]
fn test_lz5_invalid_magic() {
let data = vec![0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
let mut decoder = Lz5Decoder::new(Cursor::new(data));
let mut buf = [0u8; 10];
let result = decoder.read(&mut buf);
assert!(result.is_err());
}
fn create_minimal_lz5_frame(uncompressed_data: &[u8]) -> Vec<u8> {
let mut frame = Vec::new();
frame.extend_from_slice(&LZ5_MAGIC.to_le_bytes());
frame.push(0x60);
frame.push(0x40);
frame.push(0x00);
let block_size = uncompressed_data.len() as u32 | 0x80000000;
frame.extend_from_slice(&block_size.to_le_bytes());
frame.extend_from_slice(uncompressed_data);
frame.extend_from_slice(&0u32.to_le_bytes());
frame
}
#[test]
fn test_lz5_uncompressed_block() {
let original = b"Hello, World! This is a test of uncompressed LZ5 data.";
let frame = create_minimal_lz5_frame(original);
let mut decoder = Lz5Decoder::new(Cursor::new(frame));
let mut output = Vec::new();
decoder
.read_to_end(&mut output)
.expect("Failed to decompress");
assert_eq!(output, original);
}
#[test]
fn test_lz5_encoder_roundtrip_small() {
let original = b"Hello, World!";
let mut compressed = Vec::new();
{
let mut encoder = Lz5Encoder::new(&mut compressed, Lz5EncoderOptions::default());
encoder.write_all(original).unwrap();
encoder.try_finish().unwrap();
}
let mut decoder = Lz5Decoder::new(Cursor::new(&compressed));
let mut decompressed = Vec::new();
decoder.read_to_end(&mut decompressed).unwrap();
assert_eq!(decompressed, original);
}
#[test]
fn test_lz5_encoder_roundtrip_repetitive() {
let original: Vec<u8> = b"ABCDEFGHIJKLMNOP".repeat(1000);
let mut compressed = Vec::new();
{
let mut encoder = Lz5Encoder::new(&mut compressed, Lz5EncoderOptions::default());
encoder.write_all(&original).unwrap();
encoder.try_finish().unwrap();
}
let mut decoder = Lz5Decoder::new(Cursor::new(&compressed));
let mut decompressed = Vec::new();
decoder.read_to_end(&mut decompressed).unwrap();
assert_eq!(decompressed, original);
}
#[test]
fn test_lz5_encoder_roundtrip_random() {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let mut original = vec![0u8; 1000];
for (i, byte) in original.iter_mut().enumerate() {
let mut hasher = DefaultHasher::new();
i.hash(&mut hasher);
*byte = hasher.finish() as u8;
}
let mut compressed = Vec::new();
{
let mut encoder = Lz5Encoder::new(&mut compressed, Lz5EncoderOptions::default());
encoder.write_all(&original).unwrap();
encoder.try_finish().unwrap();
}
let mut decoder = Lz5Decoder::new(Cursor::new(&compressed));
let mut decompressed = Vec::new();
decoder.read_to_end(&mut decompressed).unwrap();
assert_eq!(decompressed, original);
}
#[test]
fn test_lz5_encoder_empty_input() {
let mut compressed = Vec::new();
{
let encoder = Lz5Encoder::new(&mut compressed, Lz5EncoderOptions::default());
encoder.try_finish().unwrap();
}
assert!(!compressed.is_empty());
let mut decoder = Lz5Decoder::new(Cursor::new(&compressed));
let mut decompressed = Vec::new();
decoder.read_to_end(&mut decompressed).unwrap();
assert!(decompressed.is_empty());
}
#[test]
fn test_lz5_hash4() {
let data = b"test";
let hash = Lz5Encoder::<Vec<u8>>::hash4(data);
assert!(hash < HASH_TABLE_SIZE);
let data2 = b"abcd";
let hash2 = Lz5Encoder::<Vec<u8>>::hash4(data2);
assert!(hash2 < HASH_TABLE_SIZE);
}
}