1use crate::{Error, Result, MAGIC_COMPRESSED};
4use alloc::vec::Vec;
5use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
9#[repr(u8)]
10pub enum CompressionAlgorithm {
11 None = 0x00,
13 Gzip = 0x01,
15 Brotli = 0x02,
17}
18
19impl CompressionAlgorithm {
20 pub fn from_byte(byte: u8) -> Result<Self> {
22 match byte {
23 0x00 => Ok(CompressionAlgorithm::None),
24 0x01 => Ok(CompressionAlgorithm::Gzip),
25 0x02 => Ok(CompressionAlgorithm::Brotli),
26 _ => Err(Error::UnsupportedAlgorithm(byte)),
27 }
28 }
29
30 pub fn name(&self) -> &'static str {
32 match self {
33 CompressionAlgorithm::None => "none",
34 CompressionAlgorithm::Gzip => "gzip",
35 CompressionAlgorithm::Brotli => "brotli",
36 }
37 }
38
39 pub fn from_name(name: &str) -> Result<Self> {
41 match name.to_lowercase().as_str() {
42 "none" => Ok(CompressionAlgorithm::None),
43 "gzip" => Ok(CompressionAlgorithm::Gzip),
44 "brotli" => Ok(CompressionAlgorithm::Brotli),
45 _ => Err(Error::UnsupportedAlgorithm(0)),
46 }
47 }
48}
49
50#[derive(Debug, Clone, Serialize, Deserialize)]
52pub struct CompressionResult {
53 pub compressed: Vec<u8>,
55 pub algorithm: CompressionAlgorithm,
57 pub original_size: usize,
59 pub compressed_size: usize,
61 pub compression_ratio: f64,
63}
64
65#[derive(Debug, Clone)]
67pub struct CompressionOptions {
68 pub algorithm: CompressionAlgorithm,
70 pub min_size_threshold: usize,
72 pub level: u32,
74}
75
76impl Default for CompressionOptions {
77 fn default() -> Self {
78 Self {
79 algorithm: CompressionAlgorithm::Brotli,
80 min_size_threshold: 100,
81 level: 6,
82 }
83 }
84}
85
86pub fn compress(data: &[u8], options: Option<CompressionOptions>) -> Result<CompressionResult> {
88 let opts = options.unwrap_or_default();
89 let original_size = data.len();
90
91 if original_size < opts.min_size_threshold {
93 return Ok(CompressionResult {
94 compressed: data.to_vec(),
95 algorithm: CompressionAlgorithm::None,
96 original_size,
97 compressed_size: original_size,
98 compression_ratio: 1.0,
99 });
100 }
101
102 if opts.algorithm == CompressionAlgorithm::None {
104 return Ok(CompressionResult {
105 compressed: data.to_vec(),
106 algorithm: CompressionAlgorithm::None,
107 original_size,
108 compressed_size: original_size,
109 compression_ratio: 1.0,
110 });
111 }
112
113 let (compressed, algorithm) = match opts.algorithm {
114 CompressionAlgorithm::Brotli => compress_brotli(data, opts.level)?,
115 CompressionAlgorithm::Gzip => compress_gzip(data, opts.level)?,
116 CompressionAlgorithm::None => (data.to_vec(), CompressionAlgorithm::None),
117 };
118
119 let compressed_size = compressed.len();
120 let compression_ratio = compressed_size as f64 / original_size as f64;
121
122 if compression_ratio < 0.9 {
124 Ok(CompressionResult {
125 compressed,
126 algorithm,
127 original_size,
128 compressed_size,
129 compression_ratio,
130 })
131 } else {
132 Ok(CompressionResult {
133 compressed: data.to_vec(),
134 algorithm: CompressionAlgorithm::None,
135 original_size,
136 compressed_size: original_size,
137 compression_ratio: 1.0,
138 })
139 }
140}
141
142pub fn decompress(data: &[u8], algorithm: CompressionAlgorithm) -> Result<Vec<u8>> {
144 match algorithm {
145 CompressionAlgorithm::None => Ok(data.to_vec()),
146 CompressionAlgorithm::Gzip => decompress_gzip(data),
147 CompressionAlgorithm::Brotli => decompress_brotli(data),
148 }
149}
150
151fn compress_brotli(data: &[u8], level: u32) -> Result<(Vec<u8>, CompressionAlgorithm)> {
153 use brotli::enc::BrotliEncoderParams;
154
155 let mut output = Vec::new();
156 let mut params = BrotliEncoderParams::default();
157 params.quality = level as i32;
158
159 brotli::BrotliCompress(&mut std::io::Cursor::new(data), &mut output, ¶ms)
160 .map_err(|e| Error::CompressionFailed(e.to_string()))?;
161
162 Ok((output, CompressionAlgorithm::Brotli))
163}
164
165fn decompress_brotli(data: &[u8]) -> Result<Vec<u8>> {
167 let mut output = Vec::new();
168
169 brotli::BrotliDecompress(&mut std::io::Cursor::new(data), &mut output)
170 .map_err(|e| Error::DecompressionFailed(e.to_string()))?;
171
172 Ok(output)
173}
174
175fn compress_gzip(data: &[u8], level: u32) -> Result<(Vec<u8>, CompressionAlgorithm)> {
177 use flate2::write::GzEncoder;
178 use flate2::Compression;
179 use std::io::Write;
180
181 let mut encoder = GzEncoder::new(Vec::new(), Compression::new(level));
182 encoder
183 .write_all(data)
184 .map_err(|e| Error::CompressionFailed(e.to_string()))?;
185
186 let output = encoder
187 .finish()
188 .map_err(|e| Error::CompressionFailed(e.to_string()))?;
189
190 Ok((output, CompressionAlgorithm::Gzip))
191}
192
193fn decompress_gzip(data: &[u8]) -> Result<Vec<u8>> {
195 use flate2::read::GzDecoder;
196 use std::io::Read;
197
198 let mut decoder = GzDecoder::new(data);
199 let mut output = Vec::new();
200
201 decoder
202 .read_to_end(&mut output)
203 .map_err(|e| Error::DecompressionFailed(e.to_string()))?;
204
205 Ok(output)
206}
207
208pub fn serialize_with_header(result: &CompressionResult) -> Vec<u8> {
210 let original_size = result.original_size as u32;
211 let mut output = Vec::with_capacity(7 + result.compressed.len());
212
213 output.extend_from_slice(MAGIC_COMPRESSED);
215 output.push(result.algorithm as u8);
217 output.extend_from_slice(&original_size.to_be_bytes());
219 output.extend_from_slice(&result.compressed);
221
222 output
223}
224
225pub fn deserialize_with_header(data: &[u8]) -> Result<(Vec<u8>, CompressionAlgorithm, usize)> {
227 if data.len() < 7 {
228 return Err(Error::TruncatedPayload {
229 expected: 7,
230 actual: data.len(),
231 });
232 }
233
234 if &data[0..2] != MAGIC_COMPRESSED {
236 return Err(Error::InvalidFormat);
237 }
238
239 let algorithm = CompressionAlgorithm::from_byte(data[2])?;
241
242 let original_size = u32::from_be_bytes([data[3], data[4], data[5], data[6]]) as usize;
244
245 let compressed = data[7..].to_vec();
247
248 Ok((compressed, algorithm, original_size))
249}
250
251#[cfg(test)]
252mod tests {
253 use super::*;
254
255 #[test]
256 fn test_gzip_roundtrip() {
257 let data = b"Hello, World! This is a test message that should be compressed.";
258
259 let result = compress(
260 data,
261 Some(CompressionOptions {
262 algorithm: CompressionAlgorithm::Gzip,
263 min_size_threshold: 10,
264 level: 6,
265 }),
266 )
267 .unwrap();
268
269 let decompressed = decompress(&result.compressed, result.algorithm).unwrap();
270 assert_eq!(data, &decompressed[..]);
271 }
272
273 #[test]
274 fn test_brotli_roundtrip() {
275 let data = b"Hello, World! This is a test message that should be compressed with Brotli.";
276
277 let result = compress(
278 data,
279 Some(CompressionOptions {
280 algorithm: CompressionAlgorithm::Brotli,
281 min_size_threshold: 10,
282 level: 6,
283 }),
284 )
285 .unwrap();
286
287 let decompressed = decompress(&result.compressed, result.algorithm).unwrap();
288 assert_eq!(data, &decompressed[..]);
289 }
290
291 #[test]
292 fn test_skip_small_data() {
293 let data = b"tiny";
294
295 let result = compress(
296 data,
297 Some(CompressionOptions {
298 algorithm: CompressionAlgorithm::Brotli,
299 min_size_threshold: 100, level: 6,
301 }),
302 )
303 .unwrap();
304
305 assert_eq!(result.algorithm, CompressionAlgorithm::None);
306 assert_eq!(result.compressed, data);
307 }
308
309 #[test]
310 fn test_header_serialization() {
311 let data = b"Test data for header serialization test with enough content.";
312
313 let result = compress(
314 data,
315 Some(CompressionOptions {
316 algorithm: CompressionAlgorithm::Gzip,
317 min_size_threshold: 10,
318 level: 6,
319 }),
320 )
321 .unwrap();
322
323 let serialized = serialize_with_header(&result);
324 let (compressed, algorithm, original_size) = deserialize_with_header(&serialized).unwrap();
325
326 assert_eq!(algorithm, result.algorithm);
327 assert_eq!(original_size, result.original_size);
328 assert_eq!(compressed, result.compressed);
329 }
330}