pub mod bzip2;
pub mod deflate;
pub mod lzma;
use crate::error::Error;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CompressionMethod {
Deflate,
Bzip2,
Lzma,
None,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CompressionMode {
Solid,
NonSolid,
}
pub fn read_length_prefix(data: &[u8]) -> Result<(bool, u32), Error> {
if data.len() < 4 {
return Err(Error::TooShort {
expected: 4,
actual: data.len(),
context: "length prefix",
});
}
let raw = crate::util::read_u32_le(data, 0);
let is_compressed = raw & 0x8000_0000 != 0;
let size = raw & 0x7FFF_FFFF;
Ok((is_compressed, size))
}
pub fn detect_compression(data: &[u8]) -> CompressionMethod {
if data.is_empty() {
return CompressionMethod::None;
}
if data[0] == 0x5D && data.len() >= 5 {
return CompressionMethod::Lzma;
}
if data[0] == 0x31 && data.len() >= 4 {
return CompressionMethod::Bzip2;
}
CompressionMethod::Deflate
}
pub fn decompress_block(
data: &[u8],
method: CompressionMethod,
max_output: usize,
expected_size: Option<usize>,
) -> Result<Vec<u8>, Error> {
match method {
CompressionMethod::Deflate => deflate::decompress_deflate(data, max_output),
CompressionMethod::Bzip2 => bzip2::decompress_bzip2(data, max_output),
CompressionMethod::Lzma => lzma::decompress_lzma(data, max_output, expected_size),
CompressionMethod::None => Ok(data.to_vec()),
}
}
pub fn decompress_header(
data: &[u8],
expected_size: usize,
) -> Result<(Vec<u8>, CompressionMethod, CompressionMode, usize), Error> {
let (is_compressed, size) = read_length_prefix(data)?;
if !is_compressed && (4 + size as usize) <= data.len() {
let size = size as usize;
return Ok((
data[4..4 + size].to_vec(),
CompressionMethod::None,
CompressionMode::NonSolid,
4 + size,
));
}
let compressed_size = size as usize;
let non_solid_viable = is_compressed && data.len() >= 4 + compressed_size;
let compressed_data = if non_solid_viable {
&data[4..4 + compressed_size]
} else {
&[] as &[u8]
};
let non_solid_consumed = 4 + compressed_size;
let method = detect_compression(compressed_data);
if let Ok(decompressed) = decompress_block(compressed_data, method, expected_size, None) {
if !decompressed.is_empty() {
return Ok((
decompressed,
method,
CompressionMode::NonSolid,
non_solid_consumed,
));
}
}
let methods = [
CompressionMethod::Lzma,
CompressionMethod::Deflate,
CompressionMethod::Bzip2,
];
for &m in &methods {
if m == method {
continue;
}
if let Ok(decompressed) = decompress_block(compressed_data, m, expected_size, None) {
if !decompressed.is_empty() {
return Ok((
decompressed,
m,
CompressionMode::NonSolid,
non_solid_consumed,
));
}
}
}
let solid_expected = expected_size + 4; let solid_method = detect_compression(data);
if let Ok(decompressed) =
decompress_block(data, solid_method, solid_expected, Some(solid_expected))
{
let stripped = strip_solid_prefix(decompressed)?;
return Ok((stripped, solid_method, CompressionMode::Solid, 0));
}
for &m in &methods {
if m == solid_method {
continue;
}
if let Ok(decompressed) = decompress_block(data, m, solid_expected, Some(solid_expected)) {
let stripped = strip_solid_prefix(decompressed)?;
return Ok((stripped, m, CompressionMode::Solid, 0));
}
}
Err(Error::UnsupportedCompression)
}
fn strip_solid_prefix(data: Vec<u8>) -> Result<Vec<u8>, Error> {
if data.len() < 4 {
return Err(Error::TooShort {
expected: 4,
actual: data.len(),
context: "solid stream length prefix",
});
}
let prefix = crate::util::read_u32_le(&data, 0) as usize;
if prefix == data.len() - 4 {
Ok(data[4..].to_vec())
} else {
Ok(data)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn read_length_prefix_compressed() {
let val = 0x8000_0000u32 | 1000;
let data = val.to_le_bytes();
let (is_compressed, size) = read_length_prefix(&data).unwrap();
assert!(is_compressed);
assert_eq!(size, 1000);
}
#[test]
fn read_length_prefix_uncompressed() {
let val = 2048u32;
let data = val.to_le_bytes();
let (is_compressed, size) = read_length_prefix(&data).unwrap();
assert!(!is_compressed);
assert_eq!(size, 2048);
}
#[test]
fn read_length_prefix_too_short() {
let data = [0u8; 3];
assert!(read_length_prefix(&data).is_err());
}
#[test]
fn detect_compression_lzma() {
let data = [0x5D, 0x00, 0x00, 0x01, 0x00, 0xFF];
assert_eq!(detect_compression(&data), CompressionMethod::Lzma);
}
#[test]
fn detect_compression_bzip2() {
let data = [0x31, 0x41, 0x59, 0x26];
assert_eq!(detect_compression(&data), CompressionMethod::Bzip2);
}
#[test]
fn detect_compression_deflate_fallback() {
let data = [0x78, 0x9C, 0x01, 0x02];
assert_eq!(detect_compression(&data), CompressionMethod::Deflate);
}
#[test]
fn detect_compression_empty() {
assert_eq!(detect_compression(&[]), CompressionMethod::None);
}
#[test]
fn decompress_header_uncompressed() {
let payload = b"hello world test data";
let size = payload.len() as u32;
let mut data = Vec::new();
data.extend_from_slice(&size.to_le_bytes());
data.extend_from_slice(payload);
let (decompressed, method, mode, consumed) =
decompress_header(&data, payload.len()).unwrap();
assert_eq!(&decompressed, payload);
assert_eq!(method, CompressionMethod::None);
assert_eq!(mode, CompressionMode::NonSolid);
assert_eq!(consumed, 4 + payload.len());
}
}