openigtlink_rust/
compression.rs

1//! Message compression support for OpenIGTLink
2//!
3//! Provides compression/decompression functionality for large messages
4//! (images, video, point clouds) to reduce network bandwidth.
5//!
6//! # Supported Algorithms
7//!
8//! - **Deflate (zlib)**: Standard compression, good balance of speed and ratio
9//! - **Gzip**: Compatible with standard gzip format
10//! - **None**: No compression (passthrough)
11//!
12//! # Examples
13//!
14//! ```
15//! use openigtlink_rust::compression::{compress, decompress, CompressionLevel, CompressionType};
16//!
17//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
18//! let data = vec![0u8; 10000]; // Large message
19//!
20//! // Compress with default level
21//! let compressed = compress(&data, CompressionType::Deflate, CompressionLevel::Default)?;
22//! println!("Original: {} bytes, Compressed: {} bytes", data.len(), compressed.len());
23//!
24//! // Decompress
25//! let decompressed = decompress(&compressed, CompressionType::Deflate)?;
26//! assert_eq!(data, decompressed);
27//! # Ok(())
28//! # }
29//! ```
30
31use crate::error::{IgtlError, Result};
32use flate2::read::{DeflateDecoder, GzDecoder};
33use flate2::write::{DeflateEncoder, GzEncoder};
34use flate2::Compression;
35use std::io::{Read, Write};
36use tracing::{debug, info, trace};
37
38/// Compression algorithm type
39#[derive(Debug, Clone, Copy, PartialEq, Eq)]
40pub enum CompressionType {
41    /// No compression
42    None,
43    /// Deflate (zlib) compression
44    Deflate,
45    /// Gzip compression
46    Gzip,
47}
48
49impl CompressionType {
50    /// Get the compression type name
51    pub fn name(&self) -> &'static str {
52        match self {
53            Self::None => "none",
54            Self::Deflate => "deflate",
55            Self::Gzip => "gzip",
56        }
57    }
58
59    /// Check if compression is enabled
60    pub fn is_compressed(&self) -> bool {
61        !matches!(self, Self::None)
62    }
63}
64
65/// Compression level
66#[derive(Debug, Clone, Copy, PartialEq, Eq)]
67pub enum CompressionLevel {
68    /// No compression (level 0)
69    None,
70    /// Fast compression, lower ratio (level 1)
71    Fast,
72    /// Default compression (level 6)
73    Default,
74    /// Best compression, slower (level 9)
75    Best,
76    /// Custom level (0-9)
77    Custom(u32),
78}
79
80impl CompressionLevel {
81    /// Convert to flate2 Compression level
82    fn to_flate2(&self) -> Compression {
83        match self {
84            Self::None => Compression::none(),
85            Self::Fast => Compression::fast(),
86            Self::Default => Compression::default(),
87            Self::Best => Compression::best(),
88            Self::Custom(level) => Compression::new(*level),
89        }
90    }
91
92    /// Get numeric level value
93    pub fn level(&self) -> u32 {
94        match self {
95            Self::None => 0,
96            Self::Fast => 1,
97            Self::Default => 6,
98            Self::Best => 9,
99            Self::Custom(level) => *level,
100        }
101    }
102}
103
104impl Default for CompressionLevel {
105    fn default() -> Self {
106        Self::Default
107    }
108}
109
110/// Compress data using the specified algorithm and level
111///
112/// # Arguments
113///
114/// * `data` - Raw data to compress
115/// * `compression_type` - Compression algorithm to use
116/// * `level` - Compression level
117///
118/// # Returns
119///
120/// Compressed data
121///
122/// # Examples
123///
124/// ```
125/// use openigtlink_rust::compression::{compress, CompressionLevel, CompressionType};
126///
127/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
128/// let data = vec![0u8; 1000];
129/// let compressed = compress(&data, CompressionType::Deflate, CompressionLevel::Best)?;
130/// assert!(compressed.len() < data.len());
131/// # Ok(())
132/// # }
133/// ```
134pub fn compress(
135    data: &[u8],
136    compression_type: CompressionType,
137    level: CompressionLevel,
138) -> Result<Vec<u8>> {
139    trace!(
140        compression_type = compression_type.name(),
141        level = level.level(),
142        input_size = data.len(),
143        "Starting compression"
144    );
145
146    let compressed = match compression_type {
147        CompressionType::None => {
148            debug!("No compression requested, returning original data");
149            data.to_vec()
150        }
151        CompressionType::Deflate => {
152            let mut encoder = DeflateEncoder::new(Vec::new(), level.to_flate2());
153            encoder.write_all(data).map_err(|e| {
154                IgtlError::Io(std::io::Error::new(
155                    e.kind(),
156                    format!("Deflate compression failed: {}", e),
157                ))
158            })?;
159            encoder.finish().map_err(|e| {
160                IgtlError::Io(std::io::Error::new(
161                    e.kind(),
162                    format!("Deflate compression finish failed: {}", e),
163                ))
164            })?
165        }
166        CompressionType::Gzip => {
167            let mut encoder = GzEncoder::new(Vec::new(), level.to_flate2());
168            encoder.write_all(data).map_err(|e| {
169                IgtlError::Io(std::io::Error::new(
170                    e.kind(),
171                    format!("Gzip compression failed: {}", e),
172                ))
173            })?;
174            encoder.finish().map_err(|e| {
175                IgtlError::Io(std::io::Error::new(
176                    e.kind(),
177                    format!("Gzip compression finish failed: {}", e),
178                ))
179            })?
180        }
181    };
182
183    let ratio = if data.len() > 0 {
184        (compressed.len() as f64 / data.len() as f64) * 100.0
185    } else {
186        0.0
187    };
188
189    info!(
190        compression_type = compression_type.name(),
191        level = level.level(),
192        original_size = data.len(),
193        compressed_size = compressed.len(),
194        ratio_pct = format!("{:.1}%", ratio),
195        "Compression completed"
196    );
197
198    Ok(compressed)
199}
200
201/// Decompress data using the specified algorithm
202///
203/// # Arguments
204///
205/// * `data` - Compressed data
206/// * `compression_type` - Compression algorithm used
207///
208/// # Returns
209///
210/// Decompressed data
211///
212/// # Examples
213///
214/// ```
215/// use openigtlink_rust::compression::{compress, decompress, CompressionLevel, CompressionType};
216///
217/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
218/// let original = vec![1, 2, 3, 4, 5];
219/// let compressed = compress(&original, CompressionType::Deflate, CompressionLevel::Default)?;
220/// let decompressed = decompress(&compressed, CompressionType::Deflate)?;
221/// assert_eq!(original, decompressed);
222/// # Ok(())
223/// # }
224/// ```
225pub fn decompress(data: &[u8], compression_type: CompressionType) -> Result<Vec<u8>> {
226    trace!(
227        compression_type = compression_type.name(),
228        compressed_size = data.len(),
229        "Starting decompression"
230    );
231
232    let decompressed = match compression_type {
233        CompressionType::None => {
234            debug!("No decompression needed, returning original data");
235            data.to_vec()
236        }
237        CompressionType::Deflate => {
238            let mut decoder = DeflateDecoder::new(data);
239            let mut decompressed = Vec::new();
240            decoder.read_to_end(&mut decompressed).map_err(|e| {
241                IgtlError::Io(std::io::Error::new(
242                    e.kind(),
243                    format!("Deflate decompression failed: {}", e),
244                ))
245            })?;
246            decompressed
247        }
248        CompressionType::Gzip => {
249            let mut decoder = GzDecoder::new(data);
250            let mut decompressed = Vec::new();
251            decoder.read_to_end(&mut decompressed).map_err(|e| {
252                IgtlError::Io(std::io::Error::new(
253                    e.kind(),
254                    format!("Gzip decompression failed: {}", e),
255                ))
256            })?;
257            decompressed
258        }
259    };
260
261    info!(
262        compression_type = compression_type.name(),
263        compressed_size = data.len(),
264        decompressed_size = decompressed.len(),
265        "Decompression completed"
266    );
267
268    Ok(decompressed)
269}
270
271/// Compression statistics
272#[derive(Debug, Clone)]
273pub struct CompressionStats {
274    /// Original data size
275    pub original_size: usize,
276    /// Compressed data size
277    pub compressed_size: usize,
278    /// Compression ratio (compressed/original)
279    pub ratio: f64,
280    /// Space saved in bytes
281    pub space_saved: usize,
282    /// Compression type used
283    pub compression_type: CompressionType,
284    /// Compression level used
285    pub level: CompressionLevel,
286}
287
288impl CompressionStats {
289    /// Calculate statistics for a compression operation
290    pub fn calculate(
291        original_size: usize,
292        compressed_size: usize,
293        compression_type: CompressionType,
294        level: CompressionLevel,
295    ) -> Self {
296        let ratio = if original_size > 0 {
297            compressed_size as f64 / original_size as f64
298        } else {
299            0.0
300        };
301
302        let space_saved = original_size.saturating_sub(compressed_size);
303
304        Self {
305            original_size,
306            compressed_size,
307            ratio,
308            space_saved,
309            compression_type,
310            level,
311        }
312    }
313
314    /// Get compression ratio as percentage
315    pub fn ratio_percent(&self) -> f64 {
316        self.ratio * 100.0
317    }
318
319    /// Get space saved as percentage
320    pub fn space_saved_percent(&self) -> f64 {
321        if self.original_size > 0 {
322            (self.space_saved as f64 / self.original_size as f64) * 100.0
323        } else {
324            0.0
325        }
326    }
327}
328
329#[cfg(test)]
330mod tests {
331    use super::*;
332
333    #[test]
334    fn test_no_compression() {
335        let data = vec![1, 2, 3, 4, 5];
336        let compressed = compress(&data, CompressionType::None, CompressionLevel::Default).unwrap();
337        assert_eq!(data, compressed);
338
339        let decompressed = decompress(&compressed, CompressionType::None).unwrap();
340        assert_eq!(data, decompressed);
341    }
342
343    #[test]
344    fn test_deflate_compression() {
345        let data = vec![0u8; 1000]; // Highly compressible
346        let compressed =
347            compress(&data, CompressionType::Deflate, CompressionLevel::Default).unwrap();
348
349        // Should be much smaller
350        assert!(compressed.len() < data.len());
351        assert!(compressed.len() < 100); // Should compress very well
352
353        let decompressed = decompress(&compressed, CompressionType::Deflate).unwrap();
354        assert_eq!(data, decompressed);
355    }
356
357    #[test]
358    fn test_gzip_compression() {
359        let data = vec![1u8; 1000];
360        let compressed = compress(&data, CompressionType::Gzip, CompressionLevel::Default).unwrap();
361
362        assert!(compressed.len() < data.len());
363
364        let decompressed = decompress(&compressed, CompressionType::Gzip).unwrap();
365        assert_eq!(data, decompressed);
366    }
367
368    #[test]
369    fn test_compression_levels() {
370        let data = vec![0u8; 10000];
371
372        let fast = compress(&data, CompressionType::Deflate, CompressionLevel::Fast).unwrap();
373        let default =
374            compress(&data, CompressionType::Deflate, CompressionLevel::Default).unwrap();
375        let best = compress(&data, CompressionType::Deflate, CompressionLevel::Best).unwrap();
376
377        // Best should be smallest (or equal for highly compressible data)
378        assert!(best.len() <= default.len());
379        assert!(default.len() <= fast.len() || default.len() < 100); // May be same for zeros
380
381        // All should decompress correctly
382        assert_eq!(data, decompress(&fast, CompressionType::Deflate).unwrap());
383        assert_eq!(
384            data,
385            decompress(&default, CompressionType::Deflate).unwrap()
386        );
387        assert_eq!(data, decompress(&best, CompressionType::Deflate).unwrap());
388    }
389
390    #[test]
391    fn test_random_data_compression() {
392        // Random data is not very compressible
393        let data: Vec<u8> = (0..1000).map(|i| (i * 37 % 256) as u8).collect();
394
395        let compressed =
396            compress(&data, CompressionType::Deflate, CompressionLevel::Default).unwrap();
397
398        // May not compress much, but should still work
399        let decompressed = decompress(&compressed, CompressionType::Deflate).unwrap();
400        assert_eq!(data, decompressed);
401    }
402
403    #[test]
404    fn test_empty_data() {
405        let data = vec![];
406        let compressed =
407            compress(&data, CompressionType::Deflate, CompressionLevel::Default).unwrap();
408        let decompressed = decompress(&compressed, CompressionType::Deflate).unwrap();
409        assert_eq!(data, decompressed);
410    }
411
412    #[test]
413    fn test_compression_stats() {
414        let stats =
415            CompressionStats::calculate(1000, 500, CompressionType::Deflate, CompressionLevel::Default);
416
417        assert_eq!(stats.original_size, 1000);
418        assert_eq!(stats.compressed_size, 500);
419        assert_eq!(stats.ratio, 0.5);
420        assert_eq!(stats.space_saved, 500);
421        assert_eq!(stats.ratio_percent(), 50.0);
422        assert_eq!(stats.space_saved_percent(), 50.0);
423    }
424
425    #[test]
426    fn test_compression_type_names() {
427        assert_eq!(CompressionType::None.name(), "none");
428        assert_eq!(CompressionType::Deflate.name(), "deflate");
429        assert_eq!(CompressionType::Gzip.name(), "gzip");
430    }
431
432    #[test]
433    fn test_compression_level_values() {
434        assert_eq!(CompressionLevel::None.level(), 0);
435        assert_eq!(CompressionLevel::Fast.level(), 1);
436        assert_eq!(CompressionLevel::Default.level(), 6);
437        assert_eq!(CompressionLevel::Best.level(), 9);
438        assert_eq!(CompressionLevel::Custom(5).level(), 5);
439    }
440}