use core::cmp;
use std::io::{self, BufRead, Error, ErrorKind, Read};
use byteorder::{LittleEndian, ReadBytesExt};
use crate::lz4_jblock_decompress::lz4_jblock_decompress;
use crate::xxhash32::XXHash32;
const LZ4_BLOCK_STARTING_BUF_SIZE: usize = 64 * 1024;
const DEFAULT_SEED: u32 = 0x9747b28c;
pub struct Lz4JBlockReader<R> {
inner: R,
buf_compressed: Vec<u8>,
buf_decompressed: Vec<u8>,
pos: usize,
cap: usize,
stop_after_empty_block: bool,
saw_empty_block: bool,
check_checksum: bool,
}
impl<R: Read> Lz4JBlockReader<R> {
pub fn new(
reader: R,
stop_after_empty_block: bool,
check_checksum: bool,
) -> Lz4JBlockReader<R> {
Lz4JBlockReader {
inner: reader,
buf_compressed: vec![0; LZ4_BLOCK_STARTING_BUF_SIZE],
buf_decompressed: vec![0; LZ4_BLOCK_STARTING_BUF_SIZE],
pos: 0,
cap: 0,
stop_after_empty_block,
saw_empty_block: false,
check_checksum,
}
}
fn fill_buf_decompressed(&mut self) -> io::Result<()> {
assert_eq!(self.pos, self.cap);
if self.stop_after_empty_block && self.saw_empty_block {
self.pos = 0;
self.cap = 0;
return Ok(());
}
let magic_z = match self.inner.read_u8() {
Ok(b) => b,
Err(err) => {
return if err.kind() == ErrorKind::UnexpectedEof {
self.pos = 0;
self.cap = 0;
Ok(())
} else {
Err(err)
}
}
};
if magic_z != b'L' {
return Err(Error::new(ErrorKind::InvalidData, "wrong lz4 magic"));
}
let mut magic_leftovers = [0 as u8; 7];
self.inner.read_exact(&mut magic_leftovers)?;
if &magic_leftovers != b"Z4Block" {
return Err(Error::new(ErrorKind::InvalidData, "wrong lz4 magic"));
}
let token = self.inner.read_u8()?;
let compression_method = token & 0xF0;
let compression_level = 10 + (token & 0x0F);
let max_decompressed_buf_len = (1 as usize) << compression_level;
let chunk_length = self.inner.read_u32::<LittleEndian>()? as usize;
let original_length = self.inner.read_u32::<LittleEndian>()? as usize;
let original_checksum = self.inner.read_u32::<LittleEndian>()?;
if chunk_length == 0 {
if original_length != 0 {
return Err(Error::new(
ErrorKind::InvalidData,
"lz4 compressed chunk is empty, but decompressed one is not",
));
}
if original_checksum != 0 {
return Err(Error::new(
ErrorKind::InvalidData,
"lz4 block is empty, but checksum is not 0",
));
}
self.saw_empty_block = true;
return if self.stop_after_empty_block {
self.pos = 0;
self.cap = 0;
Ok(())
} else {
self.fill_buf_decompressed()
};
}
if original_length > max_decompressed_buf_len {
return Err(Error::new(
ErrorKind::InvalidData,
"lz4 decompressed buf length mismatch",
));
}
if self.buf_decompressed.capacity() < original_length {
self.buf_decompressed.resize(max_decompressed_buf_len, 0);
}
match compression_method {
0x10 => {
if original_length != chunk_length {
return Err(Error::new(
ErrorKind::InvalidData,
"lz4 uncompressed chunk length not equal original length",
));
}
let buf_decompressed_capped = &mut self.buf_decompressed[..chunk_length];
self.inner.read_exact(buf_decompressed_capped)?;
self.pos = 0;
self.cap = chunk_length;
}
0x20 => {
if self.buf_compressed.capacity() < chunk_length {
self.buf_compressed
.resize(max_decompressed_buf_len.max(chunk_length), 0);
}
let buf_compressed_capped = &mut self.buf_compressed[..chunk_length];
self.inner.read_exact(buf_compressed_capped)?;
let buf_decompressed_capped = &mut self.buf_decompressed[..original_length];
let buf_compressed_capped = buf_compressed_capped.as_ref();
let decompressed_length =
lz4_jblock_decompress(buf_compressed_capped, buf_decompressed_capped)?;
assert_eq!(original_length, decompressed_length);
self.pos = 0;
self.cap = decompressed_length;
}
_ => {
return Err(Error::new(
ErrorKind::InvalidData,
"unknown lz4 compression method",
));
}
};
if self.check_checksum {
let mut xxhash32 = XXHash32::new(DEFAULT_SEED);
xxhash32.update(&self.buf_decompressed[..self.cap]);
let computed_checksum = xxhash32.digest() & 0x0FFFFFFFu32;
if original_checksum != computed_checksum {
return Err(Error::new(ErrorKind::InvalidData, "lz4 checksum mismatch"));
}
}
Ok(())
}
}
impl<R: Read> Read for Lz4JBlockReader<R> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let bytes_read = {
let mut rem = self.fill_buf()?;
rem.read(buf)?
};
self.consume(bytes_read);
Ok(bytes_read)
}
}
impl<R: Read> BufRead for Lz4JBlockReader<R> {
fn fill_buf(&mut self) -> io::Result<&[u8]> {
if self.pos >= self.cap {
self.fill_buf_decompressed()?;
}
Ok(&self.buf_decompressed[self.pos..self.cap])
}
fn consume(&mut self, amt: usize) {
self.pos = cmp::min(self.pos + amt, self.cap);
}
}