crush_core/plugin/
default.rs1use crate::error::{PluginError, Result};
7use crate::plugin::{CompressionAlgorithm, PluginMetadata, COMPRESSION_ALGORITHMS};
8use flate2::read::{DeflateDecoder, DeflateEncoder};
9use flate2::Compression;
10use linkme::distributed_slice;
11use std::io::Read;
12use std::sync::atomic::{AtomicBool, Ordering};
13use std::sync::Arc;
14
15pub struct DeflatePlugin;
20
21impl CompressionAlgorithm for DeflatePlugin {
22 fn name(&self) -> &'static str {
23 "deflate"
24 }
25
26 fn metadata(&self) -> PluginMetadata {
27 PluginMetadata {
28 name: "deflate",
29 version: "1.0.0",
30 magic_number: [0x43, 0x52, 0x01, 0x00],
32 throughput: 200.0,
34 compression_ratio: 0.35,
36 description: "Standard DEFLATE compression (RFC 1951)",
37 }
38 }
39
40 fn compress(&self, input: &[u8], cancel_flag: Arc<AtomicBool>) -> Result<Vec<u8>> {
41 if cancel_flag.load(Ordering::Acquire) {
43 return Err(PluginError::Cancelled.into());
44 }
45
46 let mut encoder = DeflateEncoder::new(input, Compression::default());
47 let mut compressed = Vec::new();
48
49 let mut buffer = vec![0u8; 64 * 1024]; loop {
52 if cancel_flag.load(Ordering::Acquire) {
53 return Err(PluginError::Cancelled.into());
54 }
55
56 match encoder.read(&mut buffer) {
57 Ok(0) => break, Ok(n) => compressed.extend_from_slice(&buffer[..n]),
59 Err(e) => {
60 return Err(PluginError::OperationFailed(format!(
61 "DEFLATE compression failed: {e}"
62 ))
63 .into())
64 }
65 }
66 }
67
68 Ok(compressed)
69 }
70
71 fn decompress(&self, input: &[u8], cancel_flag: Arc<AtomicBool>) -> Result<Vec<u8>> {
72 if cancel_flag.load(Ordering::Acquire) {
74 return Err(PluginError::Cancelled.into());
75 }
76
77 let mut decoder = DeflateDecoder::new(input);
78 let mut decompressed = Vec::new();
79
80 let mut buffer = vec![0u8; 64 * 1024]; loop {
83 if cancel_flag.load(Ordering::Acquire) {
84 return Err(PluginError::Cancelled.into());
85 }
86
87 match decoder.read(&mut buffer) {
88 Ok(0) => break, Ok(n) => decompressed.extend_from_slice(&buffer[..n]),
90 Err(e) => {
91 return Err(PluginError::OperationFailed(format!(
92 "DEFLATE decompression failed: {e}"
93 ))
94 .into())
95 }
96 }
97 }
98
99 Ok(decompressed)
100 }
101
102 fn detect(&self, _file_header: &[u8]) -> bool {
103 true
107 }
108}
109
110#[distributed_slice(COMPRESSION_ALGORITHMS)]
112static DEFLATE_PLUGIN: &dyn CompressionAlgorithm = &DeflatePlugin;
113
114#[cfg(test)]
115mod tests {
116 use super::*;
117
118 #[test]
119 fn test_deflate_metadata() {
120 let plugin = DeflatePlugin;
121 let metadata = plugin.metadata();
122
123 assert_eq!(metadata.name, "deflate");
124 assert_eq!(metadata.magic_number, [0x43, 0x52, 0x01, 0x00]);
125 }
126
127 #[test]
128 #[allow(clippy::unwrap_used)]
129 fn test_deflate_roundtrip() {
130 let plugin = DeflatePlugin;
131 let cancel_flag = Arc::new(AtomicBool::new(false));
132
133 let original = b"Hello, DEFLATE! This is a test of the compression algorithm.";
134 let compressed = plugin.compress(original, Arc::clone(&cancel_flag)).unwrap();
135 let decompressed = plugin.decompress(&compressed, cancel_flag).unwrap();
136
137 assert_eq!(original.as_slice(), decompressed.as_slice());
138 }
139
140 #[test]
141 #[allow(clippy::unwrap_used)]
142 fn test_deflate_empty() {
143 let plugin = DeflatePlugin;
144 let cancel_flag = Arc::new(AtomicBool::new(false));
145
146 let original = b"";
147 let compressed = plugin.compress(original, Arc::clone(&cancel_flag)).unwrap();
148 let decompressed = plugin.decompress(&compressed, cancel_flag).unwrap();
149
150 assert_eq!(original.as_slice(), decompressed.as_slice());
151 }
152
153 #[test]
154 #[allow(clippy::unwrap_used)]
155 fn test_deflate_cancellation() {
156 let plugin = DeflatePlugin;
157 let cancel_flag = Arc::new(AtomicBool::new(true)); let original = b"This should be cancelled";
160 let result = plugin.compress(original, cancel_flag);
161
162 assert!(result.is_err());
163 let err = result.unwrap_err();
165 assert!(
166 matches!(
167 err,
168 crate::error::CrushError::Plugin(crate::error::PluginError::Cancelled)
169 ),
170 "Expected PluginError::Cancelled, got: {err:?}"
171 );
172 }
173
174 #[test]
175 #[allow(clippy::unwrap_used)]
176 fn test_deflate_decompress_invalid_data() {
177 let plugin = DeflatePlugin;
178 let cancel_flag = Arc::new(AtomicBool::new(false));
179
180 let invalid_data = b"This is not compressed data!";
182 let result = plugin.decompress(invalid_data, cancel_flag);
183
184 assert!(result.is_err());
185 let err_msg = result.unwrap_err().to_string();
186 assert!(err_msg.contains("DEFLATE decompression failed"));
187 }
188
189 #[test]
190 #[allow(clippy::unwrap_used)]
191 fn test_deflate_decompress_cancellation() {
192 let plugin = DeflatePlugin;
193 let cancel_flag = Arc::new(AtomicBool::new(true)); let data = b"Some data";
196 let result = plugin.decompress(data, cancel_flag);
197
198 assert!(result.is_err());
199 let err = result.unwrap_err();
200 assert!(
201 matches!(
202 err,
203 crate::error::CrushError::Plugin(crate::error::PluginError::Cancelled)
204 ),
205 "Expected PluginError::Cancelled, got: {err:?}"
206 );
207 }
208
209 #[test]
210 fn test_deflate_detect() {
211 let plugin = DeflatePlugin;
212
213 assert!(plugin.detect(b"any data"));
215 assert!(plugin.detect(&[]));
216 assert!(plugin.detect(b"\x00\x01\x02\x03"));
217 }
218
219 #[test]
220 fn test_deflate_name() {
221 let plugin = DeflatePlugin;
222 assert_eq!(plugin.name(), "deflate");
223 }
224
225 #[test]
226 #[allow(clippy::unwrap_used)]
227 fn test_deflate_large_data() {
228 let plugin = DeflatePlugin;
229 let cancel_flag = Arc::new(AtomicBool::new(false));
230
231 let original = vec![0x42u8; 128 * 1024]; let compressed = plugin
234 .compress(&original, Arc::clone(&cancel_flag))
235 .unwrap();
236 let decompressed = plugin.decompress(&compressed, cancel_flag).unwrap();
237
238 assert_eq!(original, decompressed);
239 assert!(compressed.len() < original.len());
241 }
242
243 #[test]
244 fn test_deflate_metadata_values() {
245 let plugin = DeflatePlugin;
246 let metadata = plugin.metadata();
247
248 assert_eq!(metadata.version, "1.0.0");
249 assert!(metadata.throughput > 0.0);
250 assert!(metadata.compression_ratio > 0.0 && metadata.compression_ratio <= 1.0);
251 assert!(!metadata.description.is_empty());
252 }
253}