1use serde::{Deserialize, Serialize};
32use std::io::{self, Read, Write};
33use thiserror::Error;
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
37pub enum CompressionAlgorithm {
38 None,
40 Fast,
42 Balanced,
44 Maximum,
46}
47
48impl Default for CompressionAlgorithm {
49 #[inline]
50 fn default() -> Self {
51 Self::Balanced
52 }
53}
54
55impl CompressionAlgorithm {
56 #[must_use]
58 #[inline]
59 pub const fn level(&self) -> i32 {
60 match self {
61 Self::None => 0,
62 Self::Fast => 1,
63 Self::Balanced => 6,
64 Self::Maximum => 9,
65 }
66 }
67
68 #[must_use]
70 #[inline]
71 pub const fn is_none(&self) -> bool {
72 matches!(self, Self::None)
73 }
74}
75
76#[derive(Debug, Error)]
78pub enum CompressionError {
79 #[error("IO error: {0}")]
81 Io(#[from] io::Error),
82
83 #[error("Compression failed: {0}")]
85 CompressionFailed(String),
86
87 #[error("Decompression failed: {0}")]
89 DecompressionFailed(String),
90
91 #[error("Invalid compressed data")]
93 InvalidData,
94}
95
96#[derive(Debug, Clone)]
98pub struct Compressor {
99 algorithm: CompressionAlgorithm,
100 stats: CompressionStats,
101}
102
103#[derive(Debug, Clone, Default, Serialize, Deserialize)]
105pub struct CompressionStats {
106 pub bytes_in: u64,
108
109 pub bytes_out: u64,
111
112 pub compressions: u64,
114
115 pub decompressions: u64,
117}
118
119impl CompressionStats {
120 #[must_use]
122 #[inline]
123 pub fn compression_ratio(&self) -> f64 {
124 if self.bytes_in == 0 {
125 0.0
126 } else {
127 1.0 - (self.bytes_out as f64 / self.bytes_in as f64)
128 }
129 }
130
131 #[must_use]
133 #[inline]
134 pub const fn bytes_saved(&self) -> u64 {
135 self.bytes_in.saturating_sub(self.bytes_out)
136 }
137
138 #[must_use]
140 #[inline]
141 pub fn avg_ratio(&self) -> f64 {
142 if self.compressions == 0 {
143 0.0
144 } else {
145 self.compression_ratio()
146 }
147 }
148}
149
150impl Compressor {
151 #[must_use]
153 pub fn new(algorithm: CompressionAlgorithm) -> Self {
154 Self {
155 algorithm,
156 stats: CompressionStats::default(),
157 }
158 }
159
160 #[inline]
162 #[must_use]
163 pub const fn algorithm(&self) -> CompressionAlgorithm {
164 self.algorithm
165 }
166
167 #[inline]
169 #[must_use]
170 pub const fn stats(&self) -> &CompressionStats {
171 &self.stats
172 }
173
174 #[inline]
176 pub fn reset_stats(&mut self) {
177 self.stats = CompressionStats::default();
178 }
179
180 pub fn compress(&mut self, data: &[u8]) -> Result<Vec<u8>, CompressionError> {
182 if self.algorithm.is_none() || data.is_empty() {
183 return Ok(data.to_vec());
184 }
185
186 let original_len = data.len();
187 let compressed = match self.algorithm {
188 CompressionAlgorithm::None => data.to_vec(),
189 CompressionAlgorithm::Fast => {
190 compress_rle(data)
192 }
193 CompressionAlgorithm::Balanced | CompressionAlgorithm::Maximum => {
194 compress_deflate(data, self.algorithm.level())
196 .map_err(|e| CompressionError::CompressionFailed(e.to_string()))?
197 }
198 };
199
200 self.stats.bytes_in += original_len as u64;
202 self.stats.bytes_out += compressed.len() as u64;
203 self.stats.compressions += 1;
204
205 Ok(compressed)
206 }
207
208 pub fn decompress(&mut self, data: &[u8]) -> Result<Vec<u8>, CompressionError> {
210 if self.algorithm.is_none() || data.is_empty() {
211 return Ok(data.to_vec());
212 }
213
214 let decompressed = match self.algorithm {
215 CompressionAlgorithm::None => data.to_vec(),
216 CompressionAlgorithm::Fast => {
217 decompress_rle(data).map_err(|_| CompressionError::InvalidData)?
218 }
219 CompressionAlgorithm::Balanced | CompressionAlgorithm::Maximum => {
220 decompress_deflate(data)
221 .map_err(|e| CompressionError::DecompressionFailed(e.to_string()))?
222 }
223 };
224
225 self.stats.decompressions += 1;
226 Ok(decompressed)
227 }
228
229 pub fn compress_with_header(&mut self, data: &[u8]) -> Result<Vec<u8>, CompressionError> {
231 let compressed = self.compress(data)?;
232 let mut result = Vec::with_capacity(compressed.len() + 1);
233 result.push(self.algorithm as u8);
234 result.extend_from_slice(&compressed);
235 Ok(result)
236 }
237
238 pub fn decompress_with_header(&mut self, data: &[u8]) -> Result<Vec<u8>, CompressionError> {
240 if data.is_empty() {
241 return Err(CompressionError::InvalidData);
242 }
243
244 let _algorithm = data[0];
245 self.decompress(&data[1..])
246 }
247}
248
249fn compress_rle(data: &[u8]) -> Vec<u8> {
251 if data.is_empty() {
252 return Vec::new();
253 }
254
255 let mut result = Vec::with_capacity(data.len());
256 let mut i = 0;
257
258 while i < data.len() {
259 let byte = data[i];
260 let mut count = 1;
261
262 while i + count < data.len() && data[i + count] == byte && count < 255 {
263 count += 1;
264 }
265
266 if count >= 3 {
267 result.push(255); result.push(count as u8);
270 result.push(byte);
271 } else {
272 for _ in 0..count {
274 result.push(byte);
275 }
276 }
277
278 i += count;
279 }
280
281 result
282}
283
284fn decompress_rle(data: &[u8]) -> Result<Vec<u8>, CompressionError> {
286 let mut result = Vec::with_capacity(data.len() * 2);
287 let mut i = 0;
288
289 while i < data.len() {
290 if data[i] == 255 && i + 2 < data.len() {
291 let count = data[i + 1] as usize;
292 let byte = data[i + 2];
293 result.extend(std::iter::repeat_n(byte, count));
294 i += 3;
295 } else {
296 result.push(data[i]);
297 i += 1;
298 }
299 }
300
301 Ok(result)
302}
303
304fn compress_deflate(data: &[u8], level: i32) -> io::Result<Vec<u8>> {
306 use flate2::Compression;
307 use flate2::write::DeflateEncoder;
308
309 let mut encoder = DeflateEncoder::new(Vec::new(), Compression::new(level as u32));
310 encoder.write_all(data)?;
311 encoder.finish()
312}
313
314fn decompress_deflate(data: &[u8]) -> io::Result<Vec<u8>> {
316 use flate2::read::DeflateDecoder;
317
318 let mut decoder = DeflateDecoder::new(data);
319 let mut result = Vec::new();
320 decoder.read_to_end(&mut result)?;
321 Ok(result)
322}
323
324#[must_use]
326pub fn suggest_algorithm_for_content(content_type: &str) -> CompressionAlgorithm {
327 match content_type {
328 t if t.contains("jpeg") || t.contains("jpg") => CompressionAlgorithm::None,
330 t if t.contains("png") => CompressionAlgorithm::None,
331 t if t.contains("gif") => CompressionAlgorithm::None,
332 t if t.contains("mp4") || t.contains("webm") => CompressionAlgorithm::None,
333 t if t.contains("mp3") || t.contains("ogg") => CompressionAlgorithm::None,
334 t if t.contains("zip") || t.contains("gzip") => CompressionAlgorithm::None,
335
336 t if t.contains("text") || t.contains("json") || t.contains("xml") => {
338 CompressionAlgorithm::Maximum
339 }
340 t if t.contains("html") || t.contains("css") || t.contains("javascript") => {
341 CompressionAlgorithm::Balanced
342 }
343
344 _ => CompressionAlgorithm::Balanced,
346 }
347}
348
349#[cfg(test)]
350mod tests {
351 use super::*;
352
353 #[test]
354 fn test_compress_decompress_none() {
355 let mut compressor = Compressor::new(CompressionAlgorithm::None);
356 let data = b"Hello, World!";
357
358 let compressed = compressor.compress(data).unwrap();
359 assert_eq!(compressed, data);
360
361 let decompressed = compressor.decompress(&compressed).unwrap();
362 assert_eq!(decompressed, data);
363 }
364
365 #[test]
366 fn test_compress_decompress_fast() {
367 let mut compressor = Compressor::new(CompressionAlgorithm::Fast);
368 let data = b"AAAAAAAAAA";
369
370 let compressed = compressor.compress(data).unwrap();
371 let decompressed = compressor.decompress(&compressed).unwrap();
372 assert_eq!(decompressed, data);
373 }
374
375 #[test]
376 fn test_compress_decompress_balanced() {
377 let mut compressor = Compressor::new(CompressionAlgorithm::Balanced);
378 let data = b"Hello, CHIE Protocol! ".repeat(100);
379
380 let compressed = compressor.compress(&data).unwrap();
381 assert!(compressed.len() < data.len());
382
383 let decompressed = compressor.decompress(&compressed).unwrap();
384 assert_eq!(decompressed, data);
385 }
386
387 #[test]
388 fn test_compress_decompress_maximum() {
389 let mut compressor = Compressor::new(CompressionAlgorithm::Maximum);
390 let data = b"Test data ".repeat(50);
391
392 let compressed = compressor.compress(&data).unwrap();
393 assert!(compressed.len() < data.len());
394
395 let decompressed = compressor.decompress(&compressed).unwrap();
396 assert_eq!(decompressed, data);
397 }
398
399 #[test]
400 fn test_compression_stats() {
401 let mut compressor = Compressor::new(CompressionAlgorithm::Balanced);
402 let data = b"Test ".repeat(100);
403
404 compressor.compress(&data).unwrap();
405
406 let stats = compressor.stats();
407 assert_eq!(stats.compressions, 1);
408 assert_eq!(stats.bytes_in, data.len() as u64);
409 assert!(stats.bytes_out < stats.bytes_in);
410 assert!(stats.compression_ratio() > 0.0);
411 }
412
413 #[test]
414 fn test_compress_with_header() {
415 let mut compressor = Compressor::new(CompressionAlgorithm::Balanced);
416 let data = b"Hello, World!";
417
418 let compressed = compressor.compress_with_header(data).unwrap();
419 assert_eq!(compressed[0], CompressionAlgorithm::Balanced as u8);
420
421 let decompressed = compressor.decompress_with_header(&compressed).unwrap();
422 assert_eq!(decompressed, data);
423 }
424
425 #[test]
426 fn test_suggest_algorithm_for_content() {
427 assert_eq!(
428 suggest_algorithm_for_content("image/jpeg"),
429 CompressionAlgorithm::None
430 );
431 assert_eq!(
432 suggest_algorithm_for_content("text/plain"),
433 CompressionAlgorithm::Maximum
434 );
435 assert_eq!(
436 suggest_algorithm_for_content("application/json"),
437 CompressionAlgorithm::Maximum
438 );
439 assert_eq!(
440 suggest_algorithm_for_content("video/mp4"),
441 CompressionAlgorithm::None
442 );
443 }
444
445 #[test]
446 fn test_empty_data() {
447 let mut compressor = Compressor::new(CompressionAlgorithm::Balanced);
448 let data = b"";
449
450 let compressed = compressor.compress(data).unwrap();
451 assert_eq!(compressed, data);
452
453 let decompressed = compressor.decompress(&compressed).unwrap();
454 assert_eq!(decompressed, data);
455 }
456
457 #[test]
458 fn test_reset_stats() {
459 let mut compressor = Compressor::new(CompressionAlgorithm::Balanced);
460 let data = b"Test data";
461
462 compressor.compress(data).unwrap();
463 assert_eq!(compressor.stats().compressions, 1);
464
465 compressor.reset_stats();
466 assert_eq!(compressor.stats().compressions, 0);
467 }
468
469 #[test]
470 fn test_rle_compression() {
471 let data = b"AAAAAAAAAA";
472 let compressed = compress_rle(data);
473 let decompressed = decompress_rle(&compressed).unwrap();
474 assert_eq!(decompressed, data);
475 }
476}