use std::io::{self, Read, Write};
use crate::block::compress::compress_bound;
use crate::block::decompress_api::decompress_safe;
use rayon::prelude::*;
use crate::io::decompress_resources::DecompressResources;
use crate::io::prefs::{Prefs, LEGACY_BLOCKSIZE};
const NB_BUFFSETS: usize = 4;
const LEGACY_BLOCK_HEADER_SIZE: usize = 4;
fn lz4_compress_bound() -> usize {
compress_bound(LEGACY_BLOCKSIZE as i32) as usize
}
fn read_exact_or_eof<R: Read>(src: &mut R, buf: &mut [u8]) -> io::Result<bool> {
let n = src.read(&mut buf[..1])?;
if n == 0 {
return Ok(false); }
src.read_exact(&mut buf[1..])?;
Ok(true)
}
pub fn decode_legacy_stream<R: Read, W: Write>(
src: &mut R,
dst: &mut W,
prefs: &Prefs,
_resources: &DecompressResources,
) -> io::Result<(u64, Option<u32>)> {
if prefs.nb_workers > 1 {
decode_legacy_mt(src, dst, prefs)
} else {
decode_legacy_st(src, dst)
}
}
fn decode_legacy_st<R: Read, W: Write>(src: &mut R, dst: &mut W) -> io::Result<(u64, Option<u32>)> {
let compress_bound = lz4_compress_bound();
let mut header = [0u8; LEGACY_BLOCK_HEADER_SIZE];
let mut in_buf = vec![0u8; compress_bound];
let mut stream_size: u64 = 0;
let mut next_magic: Option<u32> = None;
loop {
if !read_exact_or_eof(src, &mut header)? {
break;
}
let block_size = u32::from_le_bytes(header);
if block_size as usize > compress_bound {
next_magic = Some(block_size);
break;
}
let block_len = block_size as usize;
src.read_exact(&mut in_buf[..block_len])?;
let mut dec_buf = vec![0u8; LEGACY_BLOCKSIZE];
let dec_n = decompress_safe(&in_buf[..block_len], &mut dec_buf).map_err(|e| {
io::Error::new(
io::ErrorKind::InvalidData,
format!("Decoding Failed! Corrupted input detected!: {e:?}"),
)
})?;
stream_size += dec_n as u64;
dst.write_all(&dec_buf[..dec_n])?;
}
Ok((stream_size, next_magic))
}
fn decode_legacy_mt<R: Read, W: Write>(
src: &mut R,
dst: &mut W,
_prefs: &Prefs,
) -> io::Result<(u64, Option<u32>)> {
let compress_bound = lz4_compress_bound();
let mut stream_size: u64 = 0;
let mut next_magic: Option<u32> = None;
loop {
let mut batch: Vec<Vec<u8>> = Vec::with_capacity(NB_BUFFSETS);
let mut batch_done = false;
for _ in 0..NB_BUFFSETS {
let mut header = [0u8; LEGACY_BLOCK_HEADER_SIZE];
if !read_exact_or_eof(src, &mut header)? {
batch_done = true;
break;
}
let block_size = u32::from_le_bytes(header);
if block_size as usize > compress_bound {
next_magic = Some(block_size);
batch_done = true;
break;
}
let mut block = vec![0u8; block_size as usize];
src.read_exact(&mut block)?;
batch.push(block);
}
if batch.is_empty() {
break;
}
let results: Vec<io::Result<Vec<u8>>> = batch
.par_iter()
.map(|block| {
let mut dec_buf = vec![0u8; LEGACY_BLOCKSIZE];
let n = decompress_safe(block, &mut dec_buf).map_err(|e| {
io::Error::new(
io::ErrorKind::InvalidData,
format!("Decoding Failed! Corrupted input detected!: {e:?}"),
)
})?;
dec_buf.truncate(n);
Ok(dec_buf)
})
.collect();
for result in results {
let decompressed = result?;
stream_size += decompressed.len() as u64;
dst.write_all(&decompressed)?;
}
if batch_done {
break;
}
}
Ok((stream_size, next_magic))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::io::prefs::Prefs;
fn make_resources() -> DecompressResources {
DecompressResources::new(&Prefs::default()).expect("resources")
}
fn make_legacy_stream(data: &[u8]) -> Vec<u8> {
const LZ4IO_LEGACY_MAGICNUMBER: u32 = 0x184C2102;
let mut stream = Vec::new();
stream.extend_from_slice(&LZ4IO_LEGACY_MAGICNUMBER.to_le_bytes());
for chunk in data.chunks(LEGACY_BLOCKSIZE) {
let compressed = crate::block::compress_block_to_vec(chunk);
let block_size = compressed.len() as u32;
stream.extend_from_slice(&block_size.to_le_bytes());
stream.extend_from_slice(&compressed);
}
stream
}
fn legacy_payload(stream: &[u8]) -> &[u8] {
&stream[4..] }
#[test]
fn st_decompress_small() {
let original = b"Hello, legacy LZ4 world!";
let stream = make_legacy_stream(original);
let payload = legacy_payload(&stream);
let prefs = Prefs::default(); let res = make_resources();
let mut out = Vec::new();
let (size, magic) =
decode_legacy_stream(&mut std::io::Cursor::new(payload), &mut out, &prefs, &res)
.expect("decode should succeed");
assert_eq!(out, original);
assert_eq!(size, original.len() as u64);
assert!(magic.is_none(), "no chained frame");
}
#[test]
fn st_decompress_multi_block() {
let block1 = vec![0x41u8; 64]; let block2 = vec![0x42u8; 32]; let mut payload = Vec::new();
for chunk in [block1.as_slice(), block2.as_slice()] {
let compressed = crate::block::compress_block_to_vec(chunk);
payload.extend_from_slice(&(compressed.len() as u32).to_le_bytes());
payload.extend_from_slice(&compressed);
}
let prefs = Prefs::default();
let res = make_resources();
let mut out = Vec::new();
let (size, magic) =
decode_legacy_stream(&mut std::io::Cursor::new(&payload), &mut out, &prefs, &res)
.expect("decode should succeed");
let mut expected = block1.clone();
expected.extend_from_slice(&block2);
assert_eq!(out, expected);
assert_eq!(size, expected.len() as u64);
assert!(magic.is_none());
}
#[test]
fn st_clean_eof_returns_none_magic() {
let prefs = Prefs::default();
let res = make_resources();
let mut out = Vec::new();
let (size, magic) =
decode_legacy_stream(&mut std::io::Cursor::new(b""), &mut out, &prefs, &res)
.expect("empty stream should succeed");
assert_eq!(size, 0);
assert!(magic.is_none());
}
#[test]
fn st_next_magic_returned() {
let next_magic_value: u32 = 0x184D2204; let mut payload = Vec::new();
let data = b"test data for magic detection";
let compressed = crate::block::compress_block_to_vec(data);
payload.extend_from_slice(&(compressed.len() as u32).to_le_bytes());
payload.extend_from_slice(&compressed);
payload.extend_from_slice(&next_magic_value.to_le_bytes());
let prefs = Prefs::default();
let res = make_resources();
let mut out = Vec::new();
let (size, magic) =
decode_legacy_stream(&mut std::io::Cursor::new(&payload), &mut out, &prefs, &res)
.expect("decode should succeed");
assert_eq!(out, data.as_ref());
assert_eq!(size, data.len() as u64);
assert_eq!(magic, Some(next_magic_value));
}
#[test]
fn mt_decompress_small() {
let original = b"Hello, MT legacy LZ4!";
let stream = make_legacy_stream(original);
let payload = legacy_payload(&stream);
let mut prefs = Prefs::default();
prefs.nb_workers = 2; let res = make_resources();
let mut out = Vec::new();
let (size, magic) =
decode_legacy_stream(&mut std::io::Cursor::new(payload), &mut out, &prefs, &res)
.expect("MT decode should succeed");
assert_eq!(out, original);
assert_eq!(size, original.len() as u64);
assert!(magic.is_none());
}
#[test]
fn mt_and_st_produce_same_output() {
let data: Vec<u8> = (0u8..=255).cycle().take(1024).collect();
let stream = make_legacy_stream(&data);
let payload = legacy_payload(&stream);
let res = make_resources();
let mut prefs_st = Prefs::default();
prefs_st.nb_workers = 0;
let mut prefs_mt = Prefs::default();
prefs_mt.nb_workers = 4;
let mut out_st = Vec::new();
let (sz_st, mag_st) = decode_legacy_stream(
&mut std::io::Cursor::new(payload),
&mut out_st,
&prefs_st,
&res,
)
.unwrap();
let mut out_mt = Vec::new();
let (sz_mt, mag_mt) = decode_legacy_stream(
&mut std::io::Cursor::new(payload),
&mut out_mt,
&prefs_mt,
&res,
)
.unwrap();
assert_eq!(out_st, out_mt);
assert_eq!(sz_st, sz_mt);
assert_eq!(mag_st, mag_mt);
}
#[test]
fn corrupted_input_returns_error() {
let mut payload = Vec::new();
payload.extend_from_slice(&10u32.to_le_bytes()); payload.extend_from_slice(&[0xFF; 10]);
let prefs = Prefs::default();
let res = make_resources();
let mut out = Vec::new();
let result =
decode_legacy_stream(&mut std::io::Cursor::new(&payload), &mut out, &prefs, &res);
assert!(result.is_err(), "corrupted input should return an error");
}
}