Skip to main content

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};
38
39use super::super::Message::Types::TauriIPCMessage;
40use crate::dev_log;
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		dev_log!(
129			"encryption",
130			"[MessageCompressor] Created with level: {}, batch size: {}",
131			CompressionLevel,
132			BatchSize
133		);
134
135		Self { CompressionLevel, BatchSize }
136	}
137
138	/// Compress messages using Gzip for efficient transfer
139	///
140	/// This method serializes multiple messages to JSON and compresses them
141	/// using Gzip, significantly reducing the payload size.
142	///
143	/// ## Parameters
144	/// - `Messages`: Vector of TauriIPCMessage to compress
145	///
146	/// ## Returns
147	/// - `Ok(Vec<u8>)`: Compressed message data
148	/// - `Err(String)`: Error message if compression fails
149	///
150	/// ## Example
151	///
152	/// ```rust,ignore
153	/// let messages = vec![msg1, msg2, msg3];
154	/// let compressed = compressor.compress_messages(messages)?;
155	/// ```
156	pub fn compress_messages(&self, Messages:Vec<TauriIPCMessage>) -> Result<Vec<u8>, String> {
157		dev_log!("encryption", "[MessageCompressor] Compressing {} messages", Messages.len());
158
159		// Serialize messages to JSON
160		let SerializedMessages =
161			serde_json::to_vec(&Messages).map_err(|e| format!("Failed to serialize messages: {}", e))?;
162
163		let original_size = SerializedMessages.len();
164
165		// Compress using Gzip
166		let mut encoder = GzEncoder::new(Vec::new(), Compression::new(self.CompressionLevel));
167
168		encoder
169			.write_all(&SerializedMessages)
170			.map_err(|e| format!("Failed to compress messages: {}", e))?;
171
172		let compressed_data = encoder.finish().map_err(|e| format!("Failed to finish compression: {}", e))?;
173
174		let compressed_size = compressed_data.len();
175
176		let ratio = if original_size > 0 {
177			(compressed_size as f64 / original_size as f64) * 100.0
178		} else {
179			100.0
180		};
181
182		dev_log!(
183			"encryption",
184			"[MessageCompressor] Compression complete: {} -> {} bytes ({:.1}%)",
185			original_size,
186			compressed_size,
187			ratio
188		);
189
190		Ok(compressed_data)
191	}
192
193	/// Decompress messages from compressed data
194	///
195	/// This method decompresses Gzip-compressed data and deserializes it back
196	/// into TauriIPCMessage objects.
197	///
198	/// ## Parameters
199	/// - `CompressedData`: Compressed message data
200	///
201	/// ## Returns
202	/// - `Ok(Vec<TauriIPCMessage>)`: Decompressed messages
203	/// - `Err(String)`: Error message if decompression fails
204	///
205	/// ## Example
206	///
207	/// ```rust,ignore
208	/// let messages = compressor.decompress_messages(&compressed_data)?;
209	/// ```
210	pub fn decompress_messages(&self, CompressedData:&[u8]) -> Result<Vec<TauriIPCMessage>, String> {
211		dev_log!("encryption", "[MessageCompressor] Decompressing {} bytes", CompressedData.len());
212
213		let compressed_size = CompressedData.len();
214
215		// Decompress using Gzip
216		let mut decoder = GzDecoder::new(CompressedData);
217
218		let mut DecompressedData = Vec::new();
219
220		decoder
221			.read_to_end(&mut DecompressedData)
222			.map_err(|e| format!("Failed to decompress data: {}", e))?;
223
224		let decompressed_size = DecompressedData.len();
225
226		// Deserialize messages with explicit type annotation
227		let messages:Vec<TauriIPCMessage> =
228			serde_json::from_slice(&DecompressedData).map_err(|e| format!("Failed to deserialize messages: {}", e))?;
229
230		dev_log!(
231			"encryption",
232			"[MessageCompressor] Decompression complete: {} -> {} bytes, {} messages",
233			compressed_size,
234			decompressed_size,
235			messages.len()
236		);
237
238		Ok(messages)
239	}
240
241	/// Check if messages should be batched for compression
242	///
243	/// This method determines if the number of messages meets the threshold
244	/// for batch compression.
245	///
246	/// ## Parameters
247	/// - `MessagesCount`: Number of messages to check
248	///
249	/// ## Returns
250	/// - `true`: Should batch (meets minimum threshold)
251	/// - `false`: Should not batch (below threshold)
252	///
253	/// ## Example
254	///
255	/// ```rust,ignore
256	/// if compressor.should_batch(messages.len()) {
257	///     // Batch compress
258	/// } else {
259	///     // Send individually
260	/// }
261	/// ```
262	pub fn should_batch(&self, MessagesCount:usize) -> bool {
263		let should_batch = MessagesCount >= self.BatchSize;
264
265		dev_log!(
266			"encryption",
267			"[MessageCompressor] Batch check: {} >= {} = {}",
268			MessagesCount,
269			self.BatchSize,
270			should_batch
271		);
272
273		should_batch
274	}
275
276	/// Get the compression level
277	pub fn compression_level(&self) -> u32 { self.CompressionLevel }
278
279	/// Get the batch size threshold
280	pub fn batch_size(&self) -> usize { self.BatchSize }
281
282	/// Create a compressor with default settings (level 6, batch size 10)
283	pub fn default() -> Self { Self::new(6, 10) }
284
285	/// Create a fast compressor (level 3, batch size 5)
286	pub fn fast() -> Self { Self::new(3, 5) }
287
288	/// Create a maximum compression compressor (level 9, batch size 20)
289	pub fn max() -> Self { Self::new(9, 20) }
290}
291
292#[cfg(test)]
293#[allow(unused_imports)]
294mod tests {
295
296	use super::*;
297
298	fn create_test_message(id:u32) -> TauriIPCMessage {
299		TauriIPCMessage::new(
300			format!("test_channel_{}", id),
301			serde_json::json!({
302				"id": id,
303				"data": "test data that should compress well when repeated many times across multiple messages".repeat(10)
304			}),
305			Some("test_sender".to_string()),
306		)
307	}
308
309	#[test]
310	fn test_compressor_creation() {
311		let compressor = MessageCompressor::new(6, 10);
312
313		assert_eq!(compressor.compression_level(), 6);
314
315		assert_eq!(compressor.batch_size(), 10);
316	}
317
318	#[test]
319	fn test_default_compressor() {
320		let compressor = MessageCompressor::default();
321
322		assert_eq!(compressor.compression_level(), 6);
323
324		assert_eq!(compressor.batch_size(), 10);
325	}
326
327	#[test]
328	fn test_fast_compressor() {
329		let compressor = MessageCompressor::fast();
330
331		assert_eq!(compressor.compression_level(), 3);
332
333		assert_eq!(compressor.batch_size(), 5);
334	}
335
336	#[test]
337	fn test_max_compressor() {
338		let compressor = MessageCompressor::max();
339
340		assert_eq!(compressor.compression_level(), 9);
341
342		assert_eq!(compressor.batch_size(), 20);
343	}
344
345	#[test]
346	fn test_should_batch() {
347		let compressor = MessageCompressor::new(6, 10);
348
349		assert!(!compressor.should_batch(5));
350
351		assert!(compressor.should_batch(10));
352
353		assert!(compressor.should_batch(15));
354	}
355
356	#[test]
357	fn test_compress_and_decompress() {
358		let compressor = MessageCompressor::default();
359
360		let original_messages = vec![create_test_message(1), create_test_message(2), create_test_message(3)];
361
362		// Compress
363		let compressed = compressor.compress_messages(original_messages.clone()).unwrap();
364
365		assert!(!compressed.is_empty());
366
367		// Decompress
368		let decompressed = compressor.decompress_messages(&compressed).unwrap();
369
370		assert_eq!(decompressed.len(), original_messages.len());
371
372		// Verify content
373		for i in 0..original_messages.len() {
374			assert_eq!(decompressed[i].channel, original_messages[i].channel);
375		}
376	}
377
378	#[test]
379	fn test_compression_ratio() {
380		let compressor = MessageCompressor::default();
381
382		// Create large messages that should compress well
383		let messages:Vec<TauriIPCMessage> = (0..20).map(|i| create_test_message(i)).collect();
384
385		let compressed = compressor.compress_messages(messages.clone()).unwrap();
386
387		// Compressed size should be significantly smaller
388		let original_data = serde_json::to_vec(&messages).unwrap();
389
390		assert!(compressed.len() < original_data.len());
391	}
392
393	#[test]
394	fn test_empty_messages() {
395		let compressor = MessageCompressor::default();
396
397		let messages = vec![];
398
399		let compressed = compressor.compress_messages(messages).unwrap();
400
401		let decompressed = compressor.decompress_messages(&compressed).unwrap();
402
403		assert!(decompressed.is_empty());
404	}
405
406	#[test]
407	fn test_single_message() {
408		let compressor = MessageCompressor::default();
409
410		let messages = vec![create_test_message(1)];
411
412		let compressed = compressor.compress_messages(messages.clone()).unwrap();
413
414		let decompressed = compressor.decompress_messages(&compressed).unwrap();
415
416		assert_eq!(decompressed.len(), 1);
417
418		assert_eq!(decompressed[0].channel, messages[0].channel);
419	}
420}