1use super::types::{DxMachineError, Result};
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
12pub enum CompressionLevel {
13 #[default]
15 Fast,
16 Default,
18 High,
20}
21
22impl CompressionLevel {
23 pub fn to_zstd_level(self) -> i32 {
25 match self {
26 CompressionLevel::Fast => 1, CompressionLevel::Default => 3, CompressionLevel::High => 19, }
30 }
31}
32
33#[derive(Debug)]
38pub struct DxCompressed {
39 compressed: Vec<u8>,
41 original_size: u32,
43 decompressed: Option<Vec<u8>>,
45}
46
47impl DxCompressed {
48 pub fn new() -> Self {
50 Self {
51 compressed: Vec::new(),
52 original_size: 0,
53 decompressed: None,
54 }
55 }
56
57 pub fn compress(data: &[u8]) -> Self {
61 let compressed = lz4_compress_fast(data);
63 let original_size = data.len() as u32;
64
65 Self {
66 compressed,
67 original_size,
68 decompressed: None,
69 }
70 }
71
72 pub fn compress_level(data: &[u8], _level: CompressionLevel) -> Self {
74 Self::compress(data)
76 }
77
78 #[inline(always)]
80 pub fn compressed_size(&self) -> usize {
81 self.compressed.len()
82 }
83
84 #[inline(always)]
86 pub fn original_size(&self) -> usize {
87 self.original_size as usize
88 }
89
90 #[inline(always)]
92 pub fn ratio(&self) -> f64 {
93 if self.original_size == 0 {
94 return 1.0;
95 }
96 self.compressed.len() as f64 / self.original_size as f64
97 }
98
99 #[inline(always)]
101 pub fn savings(&self) -> f64 {
102 1.0 - self.ratio()
103 }
104
105 #[inline(always)]
107 pub fn as_compressed(&self) -> &[u8] {
108 &self.compressed
109 }
110
111 pub fn decompress(&mut self) -> Result<&[u8]> {
115 if self.decompressed.is_none() {
116 let data = lz4_decompress_fast(&self.compressed)?;
117 self.decompressed = Some(data);
118 }
119
120 Ok(self.decompressed.as_ref().unwrap_or_else(|| unreachable!()))
122 }
123
124 pub fn decompress_owned(&self) -> Result<Vec<u8>> {
126 lz4_decompress_fast(&self.compressed)
127 }
128
129 #[inline(always)]
131 pub fn is_cached(&self) -> bool {
132 self.decompressed.is_some()
133 }
134
135 pub fn clear_cache(&mut self) {
137 self.decompressed = None;
138 }
139
140 pub fn from_compressed(compressed: Vec<u8>, original_size: u32) -> Self {
142 Self {
143 compressed,
144 original_size,
145 decompressed: None,
146 }
147 }
148
149 pub fn to_wire(&self) -> Vec<u8> {
151 self.compressed.clone()
153 }
154
155 pub fn from_wire(data: &[u8]) -> Result<Self> {
157 if data.len() < 4 {
159 return Err(DxMachineError::BufferTooSmall {
160 required: 4,
161 actual: data.len(),
162 });
163 }
164
165 let original_size = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
166 let compressed = data.to_vec();
167
168 Ok(Self {
169 compressed,
170 original_size,
171 decompressed: None,
172 })
173 }
174}
175
176impl Default for DxCompressed {
177 fn default() -> Self {
178 Self::new()
179 }
180}
181
182#[cfg(feature = "compression-lz4")]
185fn lz4_compress_fast(input: &[u8]) -> Vec<u8> {
186 lz4_flex::compress_prepend_size(input)
187}
188
189#[cfg(not(feature = "compression-lz4"))]
190fn lz4_compress_fast(input: &[u8]) -> Vec<u8> {
191 lz4_compress(input)
193}
194
195#[cfg(feature = "compression-lz4")]
197pub(crate) fn lz4_decompress_fast(input: &[u8]) -> Result<Vec<u8>> {
198 lz4_flex::decompress_size_prepended(input)
199 .map_err(|e| DxMachineError::DecompressionFailed(e.to_string()))
200}
201
202#[cfg(not(feature = "compression-lz4"))]
203fn lz4_decompress_fast(input: &[u8]) -> Result<Vec<u8>> {
204 if input.len() < 4 {
206 return Err(DxMachineError::DecompressionFailed(
207 "Input too short".into(),
208 ));
209 }
210 let size = u32::from_le_bytes([input[0], input[1], input[2], input[3]]) as usize;
211 lz4_decompress(&input[4..], size)
212}
213
214#[allow(dead_code)]
216#[cfg(feature = "compression")]
217fn zstd_compress(input: &[u8], level: i32) -> Vec<u8> {
218 zstd::encode_all(input, level).unwrap_or_else(|_| input.to_vec())
219}
220
221#[allow(dead_code)]
222#[cfg(not(feature = "compression"))]
223fn zstd_compress(input: &[u8], _level: i32) -> Vec<u8> {
224 lz4_compress(input)
226}
227
228#[allow(dead_code)]
230#[cfg(feature = "compression")]
231fn zstd_decompress(input: &[u8]) -> Result<Vec<u8>> {
232 zstd::decode_all(input).map_err(|e| DxMachineError::DecompressionFailed(e.to_string()))
233}
234
235#[allow(dead_code)]
236#[cfg(not(feature = "compression"))]
237fn zstd_decompress(input: &[u8]) -> Result<Vec<u8>> {
238 if input.len() < 4 {
241 return Err(DxMachineError::DecompressionFailed(
242 "Input too short".into(),
243 ));
244 }
245 let size = u32::from_le_bytes([input[0], input[1], input[2], input[3]]) as usize;
246 lz4_decompress(&input[4..], size)
247}
248
249#[allow(dead_code)]
253fn lz4_compress(input: &[u8]) -> Vec<u8> {
254 if input.is_empty() {
255 return Vec::new();
256 }
257
258 let mut output = Vec::with_capacity(input.len());
261 let mut i = 0;
262
263 while i < input.len() {
264 let byte = input[i];
266 let mut run_len = 1;
267
268 while i + run_len < input.len() && input[i + run_len] == byte && run_len < 255 {
269 run_len += 1;
270 }
271
272 if run_len >= 4 {
273 output.push(0xFF);
275 output.push(run_len as u8);
276 output.push(byte);
277 i += run_len;
278 } else {
279 let lit_start = i;
281 while i < input.len() {
282 if i + 4 <= input.len() {
284 let b = input[i];
285 if input[i + 1] == b && input[i + 2] == b && input[i + 3] == b {
286 break;
287 }
288 }
289 i += 1;
290 if i - lit_start >= 254 {
291 break;
292 }
293 }
294
295 let lit_len = i - lit_start;
296 if lit_len > 0 {
298 output.push(lit_len as u8);
299 output.extend_from_slice(&input[lit_start..i]);
300 }
301 }
302 }
303
304 output
305}
306
307#[allow(dead_code)] fn lz4_decompress(input: &[u8], expected_size: usize) -> Result<Vec<u8>> {
310 if input.is_empty() {
311 return Ok(Vec::new());
312 }
313
314 let mut output = Vec::with_capacity(expected_size);
315 let mut i = 0;
316
317 while i < input.len() {
318 let marker = input[i];
319 i += 1;
320
321 if marker == 0xFF {
322 if i + 2 > input.len() {
324 return Err(DxMachineError::InvalidData("Truncated RLE sequence".into()));
325 }
326 let run_len = input[i] as usize;
327 let byte = input[i + 1];
328 i += 2;
329
330 output.extend(std::iter::repeat_n(byte, run_len));
331 } else {
332 let lit_len = marker as usize;
334 if i + lit_len > input.len() {
335 return Err(DxMachineError::InvalidData(
336 "Truncated literal sequence".into(),
337 ));
338 }
339 output.extend_from_slice(&input[i..i + lit_len]);
340 i += lit_len;
341 }
342 }
343
344 Ok(output)
345}
346
347pub struct StreamCompressor {
349 chunk_size: usize,
351 chunks: Vec<DxCompressed>,
353 buffer: Vec<u8>,
355}
356
357impl StreamCompressor {
358 pub fn new(chunk_size: usize) -> Self {
363 Self {
364 chunk_size,
365 chunks: Vec::new(),
366 buffer: Vec::with_capacity(chunk_size),
367 }
368 }
369
370 pub fn default_chunk() -> Self {
372 Self::new(64 * 1024)
373 }
374
375 pub fn write(&mut self, data: &[u8]) {
377 let mut remaining = data;
378
379 while !remaining.is_empty() {
380 let space = self.chunk_size - self.buffer.len();
381 let take = remaining.len().min(space);
382
383 self.buffer.extend_from_slice(&remaining[..take]);
384 remaining = &remaining[take..];
385
386 if self.buffer.len() >= self.chunk_size {
387 self.flush_chunk();
388 }
389 }
390 }
391
392 fn flush_chunk(&mut self) {
394 if !self.buffer.is_empty() {
395 let chunk = DxCompressed::compress(&self.buffer);
396 self.chunks.push(chunk);
397 self.buffer.clear();
398 }
399 }
400
401 pub fn finish(mut self) -> Vec<DxCompressed> {
403 self.flush_chunk();
404 self.chunks
405 }
406
407 pub fn chunk_count(&self) -> usize {
409 self.chunks.len()
410 }
411
412 pub fn total_compressed_size(&self) -> usize {
414 self.chunks
415 .iter()
416 .map(|c| c.compressed_size())
417 .sum::<usize>()
418 + self.buffer.len()
419 }
421}
422
423pub struct StreamDecompressor {
425 chunks: Vec<DxCompressed>,
426 current_chunk: usize,
427 current_offset: usize,
428}
429
430impl StreamDecompressor {
431 pub fn new(chunks: Vec<DxCompressed>) -> Self {
433 Self {
434 chunks,
435 current_chunk: 0,
436 current_offset: 0,
437 }
438 }
439
440 pub fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
442 if self.current_chunk >= self.chunks.len() {
443 return Ok(0);
444 }
445
446 let mut written = 0;
447
448 while written < buf.len() && self.current_chunk < self.chunks.len() {
449 let chunk = &mut self.chunks[self.current_chunk];
450 let data = chunk.decompress()?;
451
452 let remaining_in_chunk = data.len() - self.current_offset;
453 let to_copy = (buf.len() - written).min(remaining_in_chunk);
454
455 buf[written..written + to_copy]
456 .copy_from_slice(&data[self.current_offset..self.current_offset + to_copy]);
457
458 written += to_copy;
459 self.current_offset += to_copy;
460
461 if self.current_offset >= data.len() {
462 self.current_chunk += 1;
463 self.current_offset = 0;
464 }
465 }
466
467 Ok(written)
468 }
469
470 pub fn decompress_all(&mut self) -> Result<Vec<u8>> {
472 let total_size: usize = self.chunks.iter().map(|c| c.original_size()).sum();
473 let mut output = Vec::with_capacity(total_size);
474
475 for chunk in &mut self.chunks {
476 let data = chunk.decompress()?;
477 output.extend_from_slice(data);
478 }
479
480 Ok(output)
481 }
482}
483
484#[cfg(feature = "compression-lz4")]
493pub fn compress_lz4(data: &[u8]) -> Result<Vec<u8>> {
494 if data.is_empty() {
495 return Ok(Vec::new());
496 }
497
498 Ok(lz4_flex::compress_prepend_size(data))
499}
500
501#[cfg(not(feature = "compression-lz4"))]
503pub fn compress_lz4(_data: &[u8]) -> Result<Vec<u8>> {
504 Err(DxMachineError::InvalidData(
505 "LZ4 compression not available (enable 'compression-lz4' feature)".into(),
506 ))
507}
508
509#[cfg(feature = "compression-lz4")]
513pub fn decompress_lz4(data: &[u8]) -> Result<Vec<u8>> {
514 if data.is_empty() {
515 return Ok(Vec::new());
516 }
517
518 lz4_flex::decompress_size_prepended(data).map_err(|e| {
519 DxMachineError::DecompressionFailed(format!("LZ4 decompression failed: {}", e))
520 })
521}
522
523#[cfg(not(feature = "compression-lz4"))]
525pub fn decompress_lz4(_data: &[u8]) -> Result<Vec<u8>> {
526 Err(DxMachineError::InvalidData(
527 "LZ4 decompression not available (enable 'compression-lz4' feature)".into(),
528 ))
529}
530
531#[cfg(feature = "compression")]
540pub fn compress_zstd(data: &[u8]) -> Result<Vec<u8>> {
541 compress_zstd_level(data, CompressionLevel::Default)
542}
543
544#[cfg(not(feature = "compression"))]
546pub fn compress_zstd(_data: &[u8]) -> Result<Vec<u8>> {
547 Err(DxMachineError::InvalidData(
548 "Zstd compression not available (enable 'compression' feature)".into(),
549 ))
550}
551
552#[cfg(feature = "compression")]
563pub fn compress_zstd_level(data: &[u8], level: CompressionLevel) -> Result<Vec<u8>> {
564 if data.is_empty() {
565 return Ok(Vec::new());
566 }
567
568 let zstd_level = level.to_zstd_level();
569 zstd::encode_all(data, zstd_level)
570 .map_err(|e| DxMachineError::InvalidData(format!("Zstd compression failed: {}", e)))
571}
572
573#[cfg(not(feature = "compression"))]
575pub fn compress_zstd_level(_data: &[u8], _level: CompressionLevel) -> Result<Vec<u8>> {
576 Err(DxMachineError::InvalidData(
577 "Zstd compression not available (enable 'compression' feature)".into(),
578 ))
579}
580
581#[cfg(feature = "compression")]
586pub fn decompress_zstd(data: &[u8]) -> Result<Vec<u8>> {
587 if data.is_empty() {
588 return Ok(Vec::new());
589 }
590
591 zstd::decode_all(data)
592 .map_err(|e| DxMachineError::InvalidData(format!("Zstd decompression failed: {}", e)))
593}
594
595#[cfg(not(feature = "compression"))]
597pub fn decompress_zstd(_data: &[u8]) -> Result<Vec<u8>> {
598 Err(DxMachineError::InvalidData(
599 "Zstd decompression not available (enable 'compression' feature)".into(),
600 ))
601}
602
603#[cfg(test)]
604mod tests {
605 use super::*;
606
607 #[test]
608 #[cfg(feature = "compression-lz4")]
609 fn test_compress_decompress() {
610 let original = b"Hello, World! This is a test of the compression system.";
611 let mut compressed = DxCompressed::compress(original);
612
613 println!(
615 "Original: {} bytes, Compressed: {} bytes, Ratio: {:.2}",
616 original.len(),
617 compressed.compressed_size(),
618 compressed.ratio()
619 );
620
621 let decompressed = compressed.decompress().unwrap();
623 assert_eq!(decompressed, original);
624 }
625
626 #[test]
627 #[cfg(feature = "compression-lz4")]
628 fn test_compress_repetitive_data() {
629 let original: Vec<u8> = std::iter::repeat_n(b'A', 1000).collect();
631 let compressed = DxCompressed::compress(&original);
632
633 println!(
634 "Repetitive: {} bytes -> {} bytes ({:.1}% savings)",
635 original.len(),
636 compressed.compressed_size(),
637 compressed.savings() * 100.0
638 );
639
640 assert!(compressed.ratio() < 0.1); }
643
644 #[test]
645 #[cfg(feature = "compression-lz4")]
646 fn test_compress_random_data() {
647 let original: Vec<u8> = (0..1000).map(|i| (i % 256) as u8).collect();
649 let compressed = DxCompressed::compress(&original);
650
651 println!(
652 "Sequential: {} bytes -> {} bytes ({:.1}% savings)",
653 original.len(),
654 compressed.compressed_size(),
655 compressed.savings() * 100.0
656 );
657 }
658
659 #[test]
660 #[cfg(feature = "compression-lz4")]
661 fn test_wire_format() {
662 let original = b"Test data for wire format";
663 let compressed = DxCompressed::compress(original);
664
665 let wire = compressed.to_wire();
666 let restored = DxCompressed::from_wire(&wire).unwrap();
667
668 assert_eq!(restored.original_size(), original.len());
669 assert_eq!(restored.compressed_size(), compressed.compressed_size());
670 }
671
672 #[test]
673 #[cfg(feature = "compression-lz4")]
674 fn test_streaming_compressor() {
675 let mut compressor = StreamCompressor::new(32);
676
677 for i in 0..10 {
679 let data: Vec<u8> = (0..20).map(|j| ((i * 20 + j) % 256) as u8).collect();
680 compressor.write(&data);
681 }
682
683 let chunks = compressor.finish();
684 println!("Produced {} chunks", chunks.len());
685
686 let mut decompressor = StreamDecompressor::new(chunks);
688 let output = decompressor.decompress_all().unwrap();
689
690 let expected: Vec<u8> = (0..200).map(|i| (i % 256) as u8).collect();
692 assert_eq!(output, expected);
693 }
694
695 #[test]
696 #[cfg(feature = "compression-lz4")]
697 fn test_cache() {
698 let original = b"Cache test data";
699 let mut compressed = DxCompressed::compress(original);
700
701 assert!(!compressed.is_cached());
702
703 compressed.decompress().unwrap();
704 assert!(compressed.is_cached());
705
706 compressed.clear_cache();
707 assert!(!compressed.is_cached());
708 }
709
710 #[test]
711 #[cfg(feature = "compression-lz4")]
712 fn test_empty_data() {
713 let original: &[u8] = &[];
714 let mut compressed = DxCompressed::compress(original);
715
716 assert_eq!(compressed.original_size(), 0);
717 let decompressed = compressed.decompress().unwrap();
718 assert!(decompressed.is_empty());
719 }
720
721 #[test]
722 #[cfg(feature = "compression")]
723 fn test_zstd_compress_decompress() {
724 let original = b"Hello, World! This is a test of Zstd compression.";
725 let compressed = compress_zstd(original).unwrap();
726
727 println!(
728 "Zstd: Original {} bytes -> Compressed {} bytes ({:.1}% of original)",
729 original.len(),
730 compressed.len(),
731 (compressed.len() as f64 / original.len() as f64) * 100.0
732 );
733
734 let decompressed = decompress_zstd(&compressed).unwrap();
735 assert_eq!(decompressed, original);
736 }
737
738 #[test]
739 #[cfg(feature = "compression")]
740 fn test_zstd_compression_levels() {
741 let original: Vec<u8> = std::iter::repeat_n(b'A', 1000).collect();
742
743 let fast = compress_zstd_level(&original, CompressionLevel::Fast).unwrap();
745 let default = compress_zstd_level(&original, CompressionLevel::Default).unwrap();
746 let high = compress_zstd_level(&original, CompressionLevel::High).unwrap();
747
748 println!("Zstd compression levels on 1000 bytes of 'A':");
749 println!(" Fast (level 1): {} bytes", fast.len());
750 println!(" Default (level 3): {} bytes", default.len());
751 println!(" High (level 19): {} bytes", high.len());
752
753 assert!(high.len() <= default.len());
755 assert!(default.len() <= fast.len());
756
757 assert_eq!(decompress_zstd(&fast).unwrap(), original);
759 assert_eq!(decompress_zstd(&default).unwrap(), original);
760 assert_eq!(decompress_zstd(&high).unwrap(), original);
761 }
762
763 #[test]
764 #[cfg(feature = "compression")]
765 fn test_zstd_empty_data() {
766 let original: &[u8] = &[];
767 let compressed = compress_zstd(original).unwrap();
768 assert!(compressed.is_empty());
769
770 let decompressed = decompress_zstd(&compressed).unwrap();
771 assert!(decompressed.is_empty());
772 }
773
774 #[test]
775 #[cfg(feature = "compression")]
776 fn test_zstd_vs_lz4() {
777 let original: Vec<u8> = std::iter::repeat_n(b'X', 10000).collect();
779
780 let lz4_compressed = lz4_compress(&original);
781 let zstd_compressed = compress_zstd(&original).unwrap();
782
783 println!("Compression comparison on 10KB repetitive data:");
784 println!(
785 " LZ4: {} bytes ({:.1}% of original)",
786 lz4_compressed.len(),
787 (lz4_compressed.len() as f64 / original.len() as f64) * 100.0
788 );
789 println!(
790 " Zstd: {} bytes ({:.1}% of original)",
791 zstd_compressed.len(),
792 (zstd_compressed.len() as f64 / original.len() as f64) * 100.0
793 );
794
795 let lz4_decompressed = lz4_decompress(&lz4_compressed, original.len()).unwrap();
797 let zstd_decompressed = decompress_zstd(&zstd_compressed).unwrap();
798
799 assert_eq!(lz4_decompressed, original);
800 assert_eq!(zstd_decompressed, original);
801 }
802}