use serde::{Deserialize, Serialize};
use std::io;
use thiserror::Error;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum CompressionAlgorithm {
None,
Fast,
Balanced,
Maximum,
}
impl Default for CompressionAlgorithm {
#[inline]
fn default() -> Self {
Self::Balanced
}
}
impl CompressionAlgorithm {
#[must_use]
#[inline]
pub const fn level(&self) -> i32 {
match self {
Self::None => 0,
Self::Fast => 1,
Self::Balanced => 6,
Self::Maximum => 9,
}
}
#[must_use]
#[inline]
pub const fn is_none(&self) -> bool {
matches!(self, Self::None)
}
}
#[derive(Debug, Error)]
pub enum CompressionError {
#[error("IO error: {0}")]
Io(#[from] io::Error),
#[error("Compression failed: {0}")]
CompressionFailed(String),
#[error("Decompression failed: {0}")]
DecompressionFailed(String),
#[error("Invalid compressed data")]
InvalidData,
}
#[derive(Debug, Clone)]
pub struct Compressor {
algorithm: CompressionAlgorithm,
stats: CompressionStats,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct CompressionStats {
pub bytes_in: u64,
pub bytes_out: u64,
pub compressions: u64,
pub decompressions: u64,
}
impl CompressionStats {
#[must_use]
#[inline]
pub fn compression_ratio(&self) -> f64 {
if self.bytes_in == 0 {
0.0
} else {
1.0 - (self.bytes_out as f64 / self.bytes_in as f64)
}
}
#[must_use]
#[inline]
pub const fn bytes_saved(&self) -> u64 {
self.bytes_in.saturating_sub(self.bytes_out)
}
#[must_use]
#[inline]
pub fn avg_ratio(&self) -> f64 {
if self.compressions == 0 {
0.0
} else {
self.compression_ratio()
}
}
}
impl Compressor {
#[must_use]
pub fn new(algorithm: CompressionAlgorithm) -> Self {
Self {
algorithm,
stats: CompressionStats::default(),
}
}
#[inline]
#[must_use]
pub const fn algorithm(&self) -> CompressionAlgorithm {
self.algorithm
}
#[inline]
#[must_use]
pub const fn stats(&self) -> &CompressionStats {
&self.stats
}
#[inline]
pub fn reset_stats(&mut self) {
self.stats = CompressionStats::default();
}
pub fn compress(&mut self, data: &[u8]) -> Result<Vec<u8>, CompressionError> {
if self.algorithm.is_none() || data.is_empty() {
return Ok(data.to_vec());
}
let original_len = data.len();
let compressed = match self.algorithm {
CompressionAlgorithm::None => data.to_vec(),
CompressionAlgorithm::Fast => {
compress_rle(data)
}
CompressionAlgorithm::Balanced | CompressionAlgorithm::Maximum => {
compress_deflate(data, self.algorithm.level())
.map_err(|e| CompressionError::CompressionFailed(e.to_string()))?
}
};
self.stats.bytes_in += original_len as u64;
self.stats.bytes_out += compressed.len() as u64;
self.stats.compressions += 1;
Ok(compressed)
}
pub fn decompress(&mut self, data: &[u8]) -> Result<Vec<u8>, CompressionError> {
if self.algorithm.is_none() || data.is_empty() {
return Ok(data.to_vec());
}
let decompressed = match self.algorithm {
CompressionAlgorithm::None => data.to_vec(),
CompressionAlgorithm::Fast => {
decompress_rle(data).map_err(|_| CompressionError::InvalidData)?
}
CompressionAlgorithm::Balanced | CompressionAlgorithm::Maximum => {
decompress_deflate(data)
.map_err(|e| CompressionError::DecompressionFailed(e.to_string()))?
}
};
self.stats.decompressions += 1;
Ok(decompressed)
}
pub fn compress_with_header(&mut self, data: &[u8]) -> Result<Vec<u8>, CompressionError> {
let compressed = self.compress(data)?;
let mut result = Vec::with_capacity(compressed.len() + 1);
result.push(self.algorithm as u8);
result.extend_from_slice(&compressed);
Ok(result)
}
pub fn decompress_with_header(&mut self, data: &[u8]) -> Result<Vec<u8>, CompressionError> {
if data.is_empty() {
return Err(CompressionError::InvalidData);
}
let _algorithm = data[0];
self.decompress(&data[1..])
}
}
fn compress_rle(data: &[u8]) -> Vec<u8> {
if data.is_empty() {
return Vec::new();
}
let mut result = Vec::with_capacity(data.len());
let mut i = 0;
while i < data.len() {
let byte = data[i];
let mut count = 1;
while i + count < data.len() && data[i + count] == byte && count < 255 {
count += 1;
}
if count >= 3 {
result.push(255); result.push(count as u8);
result.push(byte);
} else {
for _ in 0..count {
result.push(byte);
}
}
i += count;
}
result
}
fn decompress_rle(data: &[u8]) -> Result<Vec<u8>, CompressionError> {
let mut result = Vec::with_capacity(data.len() * 2);
let mut i = 0;
while i < data.len() {
if data[i] == 255 && i + 2 < data.len() {
let count = data[i + 1] as usize;
let byte = data[i + 2];
result.extend(std::iter::repeat_n(byte, count));
i += 3;
} else {
result.push(data[i]);
i += 1;
}
}
Ok(result)
}
fn compress_deflate(data: &[u8], level: i32) -> io::Result<Vec<u8>> {
let clamped = level.clamp(0, 9) as u8;
oxiarc_deflate::deflate(data, clamped).map_err(|e| io::Error::other(e.to_string()))
}
fn decompress_deflate(data: &[u8]) -> io::Result<Vec<u8>> {
oxiarc_deflate::inflate(data).map_err(|e| io::Error::other(e.to_string()))
}
#[must_use]
pub fn suggest_algorithm_for_content(content_type: &str) -> CompressionAlgorithm {
match content_type {
t if t.contains("jpeg") || t.contains("jpg") => CompressionAlgorithm::None,
t if t.contains("png") => CompressionAlgorithm::None,
t if t.contains("gif") => CompressionAlgorithm::None,
t if t.contains("mp4") || t.contains("webm") => CompressionAlgorithm::None,
t if t.contains("mp3") || t.contains("ogg") => CompressionAlgorithm::None,
t if t.contains("zip") || t.contains("gzip") => CompressionAlgorithm::None,
t if t.contains("text") || t.contains("json") || t.contains("xml") => {
CompressionAlgorithm::Maximum
}
t if t.contains("html") || t.contains("css") || t.contains("javascript") => {
CompressionAlgorithm::Balanced
}
_ => CompressionAlgorithm::Balanced,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_compress_decompress_none() {
let mut compressor = Compressor::new(CompressionAlgorithm::None);
let data = b"Hello, World!";
let compressed = compressor.compress(data).unwrap();
assert_eq!(compressed, data);
let decompressed = compressor.decompress(&compressed).unwrap();
assert_eq!(decompressed, data);
}
#[test]
fn test_compress_decompress_fast() {
let mut compressor = Compressor::new(CompressionAlgorithm::Fast);
let data = b"AAAAAAAAAA";
let compressed = compressor.compress(data).unwrap();
let decompressed = compressor.decompress(&compressed).unwrap();
assert_eq!(decompressed, data);
}
#[test]
fn test_compress_decompress_balanced() {
let mut compressor = Compressor::new(CompressionAlgorithm::Balanced);
let data = b"Hello, CHIE Protocol! ".repeat(100);
let compressed = compressor.compress(&data).unwrap();
assert!(compressed.len() < data.len());
let decompressed = compressor.decompress(&compressed).unwrap();
assert_eq!(decompressed, data);
}
#[test]
fn test_compress_decompress_maximum() {
let mut compressor = Compressor::new(CompressionAlgorithm::Maximum);
let data = b"Test data ".repeat(50);
let compressed = compressor.compress(&data).unwrap();
assert!(compressed.len() < data.len());
let decompressed = compressor.decompress(&compressed).unwrap();
assert_eq!(decompressed, data);
}
#[test]
fn test_compression_stats() {
let mut compressor = Compressor::new(CompressionAlgorithm::Balanced);
let data = b"Test ".repeat(100);
compressor.compress(&data).unwrap();
let stats = compressor.stats();
assert_eq!(stats.compressions, 1);
assert_eq!(stats.bytes_in, data.len() as u64);
assert!(stats.bytes_out < stats.bytes_in);
assert!(stats.compression_ratio() > 0.0);
}
#[test]
fn test_compress_with_header() {
let mut compressor = Compressor::new(CompressionAlgorithm::Balanced);
let data = b"Hello, World!";
let compressed = compressor.compress_with_header(data).unwrap();
assert_eq!(compressed[0], CompressionAlgorithm::Balanced as u8);
let decompressed = compressor.decompress_with_header(&compressed).unwrap();
assert_eq!(decompressed, data);
}
#[test]
fn test_suggest_algorithm_for_content() {
assert_eq!(
suggest_algorithm_for_content("image/jpeg"),
CompressionAlgorithm::None
);
assert_eq!(
suggest_algorithm_for_content("text/plain"),
CompressionAlgorithm::Maximum
);
assert_eq!(
suggest_algorithm_for_content("application/json"),
CompressionAlgorithm::Maximum
);
assert_eq!(
suggest_algorithm_for_content("video/mp4"),
CompressionAlgorithm::None
);
}
#[test]
fn test_empty_data() {
let mut compressor = Compressor::new(CompressionAlgorithm::Balanced);
let data = b"";
let compressed = compressor.compress(data).unwrap();
assert_eq!(compressed, data);
let decompressed = compressor.decompress(&compressed).unwrap();
assert_eq!(decompressed, data);
}
#[test]
fn test_reset_stats() {
let mut compressor = Compressor::new(CompressionAlgorithm::Balanced);
let data = b"Test data";
compressor.compress(data).unwrap();
assert_eq!(compressor.stats().compressions, 1);
compressor.reset_stats();
assert_eq!(compressor.stats().compressions, 0);
}
#[test]
fn test_rle_compression() {
let data = b"AAAAAAAAAA";
let compressed = compress_rle(data);
let decompressed = decompress_rle(&compressed).unwrap();
assert_eq!(decompressed, data);
}
}