Mountain/IPC/Encryption/MessageCompressor.rs
1//! # Message Compressor (IPC Encryption)
2//!
3//! ## RESPONSIBILITIES
4//! This module provides message compression using Gzip to optimize IPC message
5//! transfer. It reduces payload size for better performance, especially for
6//! large messages or high-frequency communication.
7//!
8//! ## ARCHITECTURAL ROLE
9//! This module is part of the performance optimization layer in the IPC
10//! architecture, reducing bandwidth usage and improving transfer speeds.
11//!
12//! ## KEY COMPONENTS
13//!
14//! - **MessageCompressor**: Main compression structure with configurable
15//! compression level
16//!
17//! ## ERROR HANDLING
18//! Compression and decompression operations return Result types with
19//! descriptive error messages for failures.
20//!
21//! ## LOGGING
22//! Debug-level logging for compression statistics, error for failures.
23//!
24//! ## PERFORMANCE CONSIDERATIONS
25//! - Compression level 6 provides good balance between speed and ratio
26//! - Batch size 10 aggregates small messages for efficiency
27//! - Gzip provides widely compatible compression format
28//!
29//! ## TODO
30//! - Add compression algorithm selection (LZ4, Zstd)
31//! - Implement adaptive compression based on message size
32//! - Add compression ratio tracking and optimization
33//! - Implement streaming compression for very large messages
34
35use std::io::{Read, Write};
36
37use flate2::{Compression, read::GzDecoder, write::GzEncoder};
38use log::debug;
39
40use super::super::Message::TauriIPCMessage;
41
42/// Message compression utility for optimizing IPC message transfer
43///
44/// This structure provides Gzip-based compression to reduce the size of IPC
45/// messages, improving transfer speed and reducing bandwidth usage.
46///
47/// ## Compression Flow
48///
49/// ```text
50/// Multiple TauriIPCMessage
51/// |
52/// | 1. Serialize to JSON
53/// v
54/// Serialized JSON bytes
55/// |
56/// | 2. Compress with Gzip
57/// v
58/// Compressed bytes (smaller)
59/// |
60/// | 3. Base64 encode for transport
61/// v
62/// Base64 string (sendable via IPC)
63/// ```
64///
65/// ## Decompression Flow
66///
67/// ```text
68/// Base64 string (received via IPC)
69/// |
70/// | 1. Base64 decode
71/// v
72/// Compressed bytes
73/// |
74/// | 2. Decompress with Gzip
75/// v
76/// Serialized JSON bytes
77/// |
78/// | 3. Deserialize to TauriIPCMessage[]
79/// v
80/// Multiple TauriIPCMessage
81/// ```
82///
83/// ## Compression Levels
84///
85/// Compression levels range from 0 (fastest, no compression) to 9 (slowest,
86/// best compression). The recommended level is 6 for a good balance.
87///
88/// | Level | Speed | Ratio | Use Case |
89/// |-------|-------|-------|----------|
90/// | 0 | Fastest | 1:1 | Testing/debugging |
91/// | 1-3 | Fast | 2:1-3:1 | Real-time systems |
92/// | 4-6 | Medium | 3:1-5:1 | General use |
93/// | 7-9 | Slow | 5:1-7:1 | Bandwidth-constrained |
94///
95/// ## Example Usage
96///
97/// ```rust,ignore
98/// let compressor = MessageCompressor::new(6, 10);
99///
100/// // Compress messages
101/// let messages = vec![message1, message2, message3];
102/// let compressed = compressor.compress_messages(messages)?;
103///
104/// // Decompress messages
105/// let decompressed = compressor.decompress_messages(&compressed)?;
106/// ```
107pub struct MessageCompressor {
108 /// Gzip compression level (0-9, where 0 is no compression)
109 CompressionLevel:u32,
110
111 /// Minimum number of messages required for batch processing
112 BatchSize:usize,
113}
114
115impl MessageCompressor {
116 /// Create a new message compressor with specified parameters
117 ///
118 /// ## Parameters
119 /// - `CompressionLevel`: Gzip compression level (0-9, default 6)
120 /// - `BatchSize`: Minimum messages for batch processing (default 10)
121 ///
122 /// ## Example
123 ///
124 /// ```rust,ignore
125 /// let compressor = MessageCompressor::new(6, 10);
126 /// ```
127 pub fn new(CompressionLevel:u32, BatchSize:usize) -> Self {
128 debug!(
129 "[MessageCompressor] Created with level: {}, batch size: {}",
130 CompressionLevel, BatchSize
131 );
132 Self { CompressionLevel, BatchSize }
133 }
134
135 /// Compress messages using Gzip for efficient transfer
136 ///
137 /// This method serializes multiple messages to JSON and compresses them
138 /// using Gzip, significantly reducing the payload size.
139 ///
140 /// ## Parameters
141 /// - `Messages`: Vector of TauriIPCMessage to compress
142 ///
143 /// ## Returns
144 /// - `Ok(Vec<u8>)`: Compressed message data
145 /// - `Err(String)`: Error message if compression fails
146 ///
147 /// ## Example
148 ///
149 /// ```rust,ignore
150 /// let messages = vec![msg1, msg2, msg3];
151 /// let compressed = compressor.compress_messages(messages)?;
152 /// ```
153 pub fn compress_messages(&self, Messages:Vec<TauriIPCMessage>) -> Result<Vec<u8>, String> {
154 debug!("[MessageCompressor] Compressing {} messages", Messages.len());
155
156 // Serialize messages to JSON
157 let SerializedMessages =
158 serde_json::to_vec(&Messages).map_err(|e| format!("Failed to serialize messages: {}", e))?;
159
160 let original_size = SerializedMessages.len();
161
162 // Compress using Gzip
163 let mut encoder = GzEncoder::new(Vec::new(), Compression::new(self.CompressionLevel));
164 encoder
165 .write_all(&SerializedMessages)
166 .map_err(|e| format!("Failed to compress messages: {}", e))?;
167
168 let compressed_data = encoder.finish().map_err(|e| format!("Failed to finish compression: {}", e))?;
169
170 let compressed_size = compressed_data.len();
171 let ratio = if original_size > 0 {
172 (compressed_size as f64 / original_size as f64) * 100.0
173 } else {
174 100.0
175 };
176
177 debug!(
178 "[MessageCompressor] Compression complete: {} -> {} bytes ({:.1}%)",
179 original_size, compressed_size, ratio
180 );
181
182 Ok(compressed_data)
183 }
184
185 /// Decompress messages from compressed data
186 ///
187 /// This method decompresses Gzip-compressed data and deserializes it back
188 /// into TauriIPCMessage objects.
189 ///
190 /// ## Parameters
191 /// - `CompressedData`: Compressed message data
192 ///
193 /// ## Returns
194 /// - `Ok(Vec<TauriIPCMessage>)`: Decompressed messages
195 /// - `Err(String)`: Error message if decompression fails
196 ///
197 /// ## Example
198 ///
199 /// ```rust,ignore
200 /// let messages = compressor.decompress_messages(&compressed_data)?;
201 /// ```
202 pub fn decompress_messages(&self, CompressedData:&[u8]) -> Result<Vec<TauriIPCMessage>, String> {
203 debug!("[MessageCompressor] Decompressing {} bytes", CompressedData.len());
204
205 let compressed_size = CompressedData.len();
206
207 // Decompress using Gzip
208 let mut decoder = GzDecoder::new(CompressedData);
209 let mut DecompressedData = Vec::new();
210 decoder
211 .read_to_end(&mut DecompressedData)
212 .map_err(|e| format!("Failed to decompress data: {}", e))?;
213
214 let decompressed_size = DecompressedData.len();
215
216 // Deserialize messages with explicit type annotation
217 let messages:Vec<TauriIPCMessage> =
218 serde_json::from_slice(&DecompressedData).map_err(|e| format!("Failed to deserialize messages: {}", e))?;
219
220 debug!(
221 "[MessageCompressor] Decompression complete: {} -> {} bytes, {} messages",
222 compressed_size,
223 decompressed_size,
224 messages.len()
225 );
226
227 Ok(messages)
228 }
229
230 /// Check if messages should be batched for compression
231 ///
232 /// This method determines if the number of messages meets the threshold
233 /// for batch compression.
234 ///
235 /// ## Parameters
236 /// - `MessagesCount`: Number of messages to check
237 ///
238 /// ## Returns
239 /// - `true`: Should batch (meets minimum threshold)
240 /// - `false`: Should not batch (below threshold)
241 ///
242 /// ## Example
243 ///
244 /// ```rust,ignore
245 /// if compressor.should_batch(messages.len()) {
246 /// // Batch compress
247 /// } else {
248 /// // Send individually
249 /// }
250 /// ```
251 pub fn should_batch(&self, MessagesCount:usize) -> bool {
252 let should_batch = MessagesCount >= self.BatchSize;
253 debug!(
254 "[MessageCompressor] Batch check: {} >= {} = {}",
255 MessagesCount, self.BatchSize, should_batch
256 );
257 should_batch
258 }
259
260 /// Get the compression level
261 pub fn compression_level(&self) -> u32 { self.CompressionLevel }
262
263 /// Get the batch size threshold
264 pub fn batch_size(&self) -> usize { self.BatchSize }
265
266 /// Create a compressor with default settings (level 6, batch size 10)
267 pub fn default() -> Self { Self::new(6, 10) }
268
269 /// Create a fast compressor (level 3, batch size 5)
270 pub fn fast() -> Self { Self::new(3, 5) }
271
272 /// Create a maximum compression compressor (level 9, batch size 20)
273 pub fn max() -> Self { Self::new(9, 20) }
274}
275
276#[cfg(test)]
277#[allow(unused_imports)]
278mod tests {
279 use super::*;
280
281 fn create_test_message(id:u32) -> TauriIPCMessage {
282 TauriIPCMessage::new(
283 format!("test_channel_{}", id),
284 serde_json::json!({
285 "id": id,
286 "data": "test data that should compress well when repeated many times across multiple messages".repeat(10)
287 }),
288 Some("test_sender".to_string()),
289 )
290 }
291
292 #[test]
293 fn test_compressor_creation() {
294 let compressor = MessageCompressor::new(6, 10);
295 assert_eq!(compressor.compression_level(), 6);
296 assert_eq!(compressor.batch_size(), 10);
297 }
298
299 #[test]
300 fn test_default_compressor() {
301 let compressor = MessageCompressor::default();
302 assert_eq!(compressor.compression_level(), 6);
303 assert_eq!(compressor.batch_size(), 10);
304 }
305
306 #[test]
307 fn test_fast_compressor() {
308 let compressor = MessageCompressor::fast();
309 assert_eq!(compressor.compression_level(), 3);
310 assert_eq!(compressor.batch_size(), 5);
311 }
312
313 #[test]
314 fn test_max_compressor() {
315 let compressor = MessageCompressor::max();
316 assert_eq!(compressor.compression_level(), 9);
317 assert_eq!(compressor.batch_size(), 20);
318 }
319
320 #[test]
321 fn test_should_batch() {
322 let compressor = MessageCompressor::new(6, 10);
323 assert!(!compressor.should_batch(5));
324 assert!(compressor.should_batch(10));
325 assert!(compressor.should_batch(15));
326 }
327
328 #[test]
329 fn test_compress_and_decompress() {
330 let compressor = MessageCompressor::default();
331 let original_messages = vec![create_test_message(1), create_test_message(2), create_test_message(3)];
332
333 // Compress
334 let compressed = compressor.compress_messages(original_messages.clone()).unwrap();
335 assert!(!compressed.is_empty());
336
337 // Decompress
338 let decompressed = compressor.decompress_messages(&compressed).unwrap();
339 assert_eq!(decompressed.len(), original_messages.len());
340
341 // Verify content
342 for i in 0..original_messages.len() {
343 assert_eq!(decompressed[i].channel, original_messages[i].channel);
344 }
345 }
346
347 #[test]
348 fn test_compression_ratio() {
349 let compressor = MessageCompressor::default();
350
351 // Create large messages that should compress well
352 let messages:Vec<TauriIPCMessage> = (0..20).map(|i| create_test_message(i)).collect();
353
354 let compressed = compressor.compress_messages(messages.clone()).unwrap();
355
356 // Compressed size should be significantly smaller
357 let original_data = serde_json::to_vec(&messages).unwrap();
358 assert!(compressed.len() < original_data.len());
359 }
360
361 #[test]
362 fn test_empty_messages() {
363 let compressor = MessageCompressor::default();
364 let messages = vec![];
365
366 let compressed = compressor.compress_messages(messages).unwrap();
367 let decompressed = compressor.decompress_messages(&compressed).unwrap();
368
369 assert!(decompressed.is_empty());
370 }
371
372 #[test]
373 fn test_single_message() {
374 let compressor = MessageCompressor::default();
375 let messages = vec![create_test_message(1)];
376
377 let compressed = compressor.compress_messages(messages.clone()).unwrap();
378 let decompressed = compressor.decompress_messages(&compressed).unwrap();
379
380 assert_eq!(decompressed.len(), 1);
381 assert_eq!(decompressed[0].channel, messages[0].channel);
382 }
383}