use crate::archive::format::CompressionMethod;
use crate::error::{EngramError, Result};
use std::io::{Read, Write};
pub const FRAME_SIZE: usize = 65536;
pub const MIN_FRAME_COMPRESSION_SIZE: usize = 52_428_800;
pub fn compress_frames(data: &[u8], method: CompressionMethod) -> Result<Vec<u8>> {
if data.len() < MIN_FRAME_COMPRESSION_SIZE {
return Err(EngramError::InvalidFormat(
"File too small for frame compression".to_string(),
));
}
let frame_count = data.len().div_ceil(FRAME_SIZE);
let mut output = Vec::new();
output.write_all(&(frame_count as u32).to_le_bytes())?;
for frame_idx in 0..frame_count {
let start = frame_idx * FRAME_SIZE;
let end = std::cmp::min(start + FRAME_SIZE, data.len());
let frame_data = &data[start..end];
let compressed_frame = match method {
CompressionMethod::Lz4 => compress_lz4_frame(frame_data)?,
CompressionMethod::Zstd => compress_zstd_frame(frame_data)?,
CompressionMethod::None => {
return Err(EngramError::InvalidFormat(
"Frame compression requires LZ4 or Zstd".to_string(),
));
}
};
output.write_all(&(compressed_frame.len() as u32).to_le_bytes())?;
output.write_all(&compressed_frame)?;
}
Ok(output)
}
pub fn decompress_frames(
data: &[u8],
method: CompressionMethod,
expected_size: u64,
) -> Result<Vec<u8>> {
let mut cursor = std::io::Cursor::new(data);
let mut output = Vec::with_capacity(expected_size as usize);
let mut frame_count_bytes = [0u8; 4];
cursor.read_exact(&mut frame_count_bytes)?;
let frame_count = u32::from_le_bytes(frame_count_bytes);
for _ in 0..frame_count {
let mut frame_size_bytes = [0u8; 4];
cursor.read_exact(&mut frame_size_bytes)?;
let frame_size = u32::from_le_bytes(frame_size_bytes) as usize;
let mut frame_data = vec![0u8; frame_size];
cursor.read_exact(&mut frame_data)?;
let decompressed_frame = match method {
CompressionMethod::Lz4 => decompress_lz4_frame(&frame_data)?,
CompressionMethod::Zstd => decompress_zstd_frame(&frame_data)?,
CompressionMethod::None => {
return Err(EngramError::InvalidFormat(
"Frame compression requires LZ4 or Zstd".to_string(),
));
}
};
output.extend_from_slice(&decompressed_frame);
}
if output.len() != expected_size as usize {
return Err(EngramError::DecompressionFailed(format!(
"Frame decompression size mismatch: expected {}, got {}",
expected_size,
output.len()
)));
}
Ok(output)
}
fn compress_lz4_frame(data: &[u8]) -> Result<Vec<u8>> {
Ok(lz4_flex::compress_prepend_size(data))
}
fn compress_zstd_frame(data: &[u8]) -> Result<Vec<u8>> {
zstd::encode_all(data, 6).map_err(|e| {
EngramError::CompressionFailed(format!("Zstd frame compression failed: {}", e))
})
}
fn decompress_lz4_frame(data: &[u8]) -> Result<Vec<u8>> {
lz4_flex::decompress_size_prepended(data).map_err(|e| {
EngramError::DecompressionFailed(format!("LZ4 frame decompression failed: {}", e))
})
}
fn decompress_zstd_frame(data: &[u8]) -> Result<Vec<u8>> {
zstd::decode_all(data).map_err(|e| {
EngramError::DecompressionFailed(format!("Zstd frame decompression failed: {}", e))
})
}
pub fn should_use_frames(size: usize) -> bool {
size >= MIN_FRAME_COMPRESSION_SIZE
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_frame_compression_lz4() {
let size = 60 * 1024 * 1024;
let data: Vec<u8> = (0..size).map(|i| (i % 256) as u8).collect();
let compressed = compress_frames(&data, CompressionMethod::Lz4).unwrap();
assert!(compressed.len() < data.len());
let decompressed =
decompress_frames(&compressed, CompressionMethod::Lz4, data.len() as u64).unwrap();
assert_eq!(decompressed.len(), data.len());
assert_eq!(decompressed, data);
}
#[test]
fn test_frame_compression_zstd() {
let size = 60 * 1024 * 1024;
let data: Vec<u8> = vec![42u8; size];
let compressed = compress_frames(&data, CompressionMethod::Zstd).unwrap();
assert!(compressed.len() < size / 100);
let decompressed =
decompress_frames(&compressed, CompressionMethod::Zstd, data.len() as u64).unwrap();
assert_eq!(decompressed.len(), data.len());
assert_eq!(decompressed, data);
}
#[test]
fn test_should_use_frames() {
assert!(!should_use_frames(10 * 1024 * 1024)); assert!(!should_use_frames(40 * 1024 * 1024)); assert!(should_use_frames(60 * 1024 * 1024)); assert!(should_use_frames(100 * 1024 * 1024)); }
#[test]
fn test_frame_size_calculation() {
let size: usize = 60 * 1024 * 1024;
let frame_count = size.div_ceil(FRAME_SIZE);
assert_eq!(frame_count, 960);
}
#[test]
fn test_small_file_error() {
let small_data = vec![0u8; 1024]; let result = compress_frames(&small_data, CompressionMethod::Lz4);
assert!(result.is_err());
}
}