Skip to main content

streaming_crypto/core_api/compression/codecs/
deflate.rs

1//! Deflate (zlib wrapper) via flate2 with streaming enc/dec.
2
3use std::io::Read;
4use std::io::Write;
5use flate2::{Compression, write::ZlibEncoder, read::ZlibDecoder};
6
7use crate::compression::compute_checksum;
8use crate::compression::types::{Compressor, Decompressor, CompressionError};
9use crate::compression::verify_checksum;
10
11pub struct DeflateCompressor {
12    level: Compression,
13}
14
15impl DeflateCompressor {
16    pub fn new(level: i32) -> Result<Box<dyn Compressor + Send>, CompressionError> {
17        let lvl = match level {
18            0..=9 => Compression::new(level as u32),
19            _ => Compression::default(),
20        };
21        Ok(Box::new(Self { level: lvl }))
22    }
23}
24
25impl Compressor for DeflateCompressor {
26    fn compress_chunk(&mut self, input: &[u8], out: &mut Vec<u8>) -> Result<(), CompressionError> {
27        // Encode this chunk as its own zlib stream
28        let mut enc = ZlibEncoder::new(Vec::new(), self.level);
29        enc.write_all(input)
30            .map_err(|e| CompressionError::CodecProcessFailed { codec: "deflate".into(), msg: e.to_string() })?;
31        let compressed = enc.finish()
32            .map_err(|e| CompressionError::CodecProcessFailed { codec: "deflate".into(), msg: e.to_string() })?;
33
34        // Prefix with original plaintext length (u32, LE) — matches Zstd/LZ4-flex policy
35        let orig_len = input.len() as u32;
36        out.extend_from_slice(&orig_len.to_le_bytes());
37        out.extend_from_slice(&compressed);
38
39        // Append CRC32 of original plaintext
40        let checksum = compute_checksum(&input, None);
41        out.extend_from_slice(&checksum.to_le_bytes());
42
43        Ok(())
44    }
45
46    fn finish(&mut self, _out: &mut Vec<u8>) -> Result<(), CompressionError> {
47        // No-op: every frame is finalized independently
48        Ok(())
49    }
50}
51
52pub struct DeflateDecompressor;
53
54impl DeflateDecompressor {
55    pub fn new() -> Result<Box<dyn Decompressor + Send>, CompressionError> {
56        Ok(Box::new(Self))
57    }
58}
59
60impl Decompressor for DeflateDecompressor {
61    fn decompress_chunk(&mut self, input: &[u8], out: &mut Vec<u8>) -> Result<(), CompressionError> {
62        if input.len() < 8 {
63            return Err(CompressionError::CodecProcessFailed {
64                codec: "deflate".into(),
65                msg: "input too short for length+checksum prefix".into(),
66            });
67        }
68
69        // Read original length prefix
70        let orig_len = u32::from_le_bytes(input[0..4].try_into().unwrap()) as usize;
71
72        // compressed data is everything except the last 4 bytes
73        let compressed = &input[4..input.len() - 4];
74        let checksum_bytes = &input[input.len() - 4..];
75        let expected_crc = u32::from_le_bytes(checksum_bytes.try_into().unwrap());
76
77        // Decode an entire zlib stream for this frame
78        let mut dec = ZlibDecoder::new(compressed);
79        let mut decompressed = Vec::new();
80        dec.read_to_end(&mut decompressed)
81            .map_err(|e| CompressionError::CodecProcessFailed { codec: "deflate".into(), msg: e.to_string() })?;
82
83        // Optional sanity check: verify decoded size matches prefix
84        if decompressed.len() != orig_len {
85            return Err(CompressionError::CodecProcessFailed {
86                codec: "deflate".into(),
87                msg: format!("decoded size {} != prefix {}", decompressed.len(), orig_len),
88            });
89        }
90
91        // Verify checksum
92        let actual_crc = compute_checksum(&decompressed, None);
93        verify_checksum(expected_crc, actual_crc, "deflate".into())?;
94        
95        out.extend_from_slice(&decompressed);
96        Ok(())
97    }
98}