use crate::hfile::error::{HFileError, Result};
use flate2::read::GzDecoder;
use std::io::Read;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum CompressionCodec {
Lzo = 0,
Gzip = 1,
#[default]
None = 2,
Snappy = 3,
Lz4 = 4,
Bzip2 = 5,
Zstd = 6,
}
impl CompressionCodec {
pub fn from_id(id: u32) -> Result<Self> {
match id {
0 => Ok(CompressionCodec::Lzo),
1 => Ok(CompressionCodec::Gzip),
2 => Ok(CompressionCodec::None),
3 => Ok(CompressionCodec::Snappy),
4 => Ok(CompressionCodec::Lz4),
5 => Ok(CompressionCodec::Bzip2),
6 => Ok(CompressionCodec::Zstd),
_ => Err(HFileError::UnsupportedCompression(id)),
}
}
pub fn decompress(&self, compressed_data: &[u8], uncompressed_size: usize) -> Result<Vec<u8>> {
match self {
CompressionCodec::None => Ok(compressed_data.to_vec()),
CompressionCodec::Gzip => {
let mut decoder = GzDecoder::new(compressed_data);
let mut decompressed = Vec::with_capacity(uncompressed_size);
decoder.read_to_end(&mut decompressed).map_err(|e| {
HFileError::DecompressionError(format!("GZIP decompression failed: {e}"))
})?;
Ok(decompressed)
}
CompressionCodec::Lzo => Err(HFileError::DecompressionError(
"LZO compression not yet supported".to_string(),
)),
CompressionCodec::Snappy => Err(HFileError::DecompressionError(
"Snappy compression not yet supported".to_string(),
)),
CompressionCodec::Lz4 => Err(HFileError::DecompressionError(
"LZ4 compression not yet supported".to_string(),
)),
CompressionCodec::Bzip2 => Err(HFileError::DecompressionError(
"BZIP2 compression not yet supported".to_string(),
)),
CompressionCodec::Zstd => Err(HFileError::DecompressionError(
"ZSTD compression not yet supported".to_string(),
)),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use flate2::Compression;
use flate2::write::GzEncoder;
use std::io::Write;
#[test]
fn test_from_id_maps_known_codec_ids() -> Result<()> {
assert_eq!(CompressionCodec::from_id(0)?, CompressionCodec::Lzo);
assert_eq!(CompressionCodec::from_id(1)?, CompressionCodec::Gzip);
assert_eq!(CompressionCodec::from_id(2)?, CompressionCodec::None);
assert_eq!(CompressionCodec::from_id(3)?, CompressionCodec::Snappy);
assert_eq!(CompressionCodec::from_id(4)?, CompressionCodec::Lz4);
assert_eq!(CompressionCodec::from_id(5)?, CompressionCodec::Bzip2);
assert_eq!(CompressionCodec::from_id(6)?, CompressionCodec::Zstd);
Ok(())
}
#[test]
fn test_from_id_rejects_unknown_codec_id() {
let err = CompressionCodec::from_id(42).unwrap_err();
assert!(matches!(err, HFileError::UnsupportedCompression(42)));
}
#[test]
fn test_decompress_none_returns_input_bytes() -> Result<()> {
let input = b"uncompressed hfile block";
let output = CompressionCodec::None.decompress(input, input.len())?;
assert_eq!(output, input);
Ok(())
}
#[test]
fn test_decompress_gzip_decodes_payload() -> Result<()> {
let input = b"gzip-compressed hfile block";
let mut encoder = GzEncoder::new(Vec::new(), Compression::default());
encoder.write_all(input)?;
let compressed = encoder.finish()?;
let output = CompressionCodec::Gzip.decompress(&compressed, input.len())?;
assert_eq!(output, input);
Ok(())
}
#[test]
fn test_decompress_gzip_rejects_invalid_payload() {
let err = CompressionCodec::Gzip
.decompress(b"not gzip data", 0)
.unwrap_err();
assert!(matches!(err, HFileError::DecompressionError(message) if message.contains("GZIP")));
}
#[test]
fn test_decompress_unsupported_codecs_return_errors() {
for codec in [
CompressionCodec::Lzo,
CompressionCodec::Snappy,
CompressionCodec::Lz4,
CompressionCodec::Bzip2,
CompressionCodec::Zstd,
] {
let err = codec.decompress(b"compressed", 0).unwrap_err();
assert!(
matches!(err, HFileError::DecompressionError(message) if message.contains("not yet supported"))
);
}
}
}