1#![allow(dead_code)]
12#![allow(missing_docs)]
13
14use crate::error::{IoError, Result};
15use oxiarc_archive::{lz4, zstd};
16use std::io::Write as _;
17use std::time::Instant;
18
19#[derive(Debug, Clone)]
27pub struct ZstdCompressor {
28 pub level: u8,
30}
31
32impl ZstdCompressor {
33 pub fn new() -> Self {
35 Self { level: 3 }
36 }
37
38 pub fn with_level(level: u8) -> Self {
40 Self { level }
41 }
42
43 pub fn compress(&self, data: &[u8]) -> Result<Vec<u8>> {
45 let writer = zstd::ZstdWriter::new();
46 writer
47 .compress(data)
48 .map_err(|e| IoError::CompressionError(format!("zstd compress: {e}")))
49 }
50
51 pub fn decompress(&self, data: &[u8]) -> Result<Vec<u8>> {
53 use std::io::Cursor;
54 let cursor = Cursor::new(data);
55 let mut reader = zstd::ZstdReader::new(cursor)
56 .map_err(|e| IoError::DecompressionError(format!("zstd reader: {e}")))?;
57 reader
58 .decompress()
59 .map_err(|e| IoError::DecompressionError(format!("zstd decompress: {e}")))
60 }
61
62 pub fn ratio(&self, data: &[u8]) -> f64 {
65 match self.compress(data) {
66 Ok(c) if !c.is_empty() => data.len() as f64 / c.len() as f64,
67 _ => 0.0,
68 }
69 }
70}
71
72impl Default for ZstdCompressor {
73 fn default() -> Self {
74 Self::new()
75 }
76}
77
78#[derive(Debug, Clone)]
87pub struct LZ4Compressor;
88
89impl LZ4Compressor {
90 pub fn new() -> Self {
92 Self
93 }
94
95 pub fn compress(&self, data: &[u8]) -> Result<Vec<u8>> {
97 let mut writer = lz4::Lz4Writer::new(Vec::new());
98 writer
99 .write_compressed(data)
100 .map_err(|e| IoError::CompressionError(format!("lz4 compress: {e}")))?;
101 Ok(writer.into_inner())
102 }
103
104 pub fn decompress(&self, data: &[u8]) -> Result<Vec<u8>> {
106 use std::io::Cursor;
107 let cursor = Cursor::new(data);
108 let mut reader = lz4::Lz4Reader::new(cursor)
109 .map_err(|e| IoError::DecompressionError(format!("lz4 reader: {e}")))?;
110 reader
111 .decompress()
112 .map_err(|e| IoError::DecompressionError(format!("lz4 decompress: {e}")))
113 }
114
115 pub fn ratio(&self, data: &[u8]) -> f64 {
117 match self.compress(data) {
118 Ok(c) if !c.is_empty() => data.len() as f64 / c.len() as f64,
119 _ => 0.0,
120 }
121 }
122}
123
124impl Default for LZ4Compressor {
125 fn default() -> Self {
126 Self::new()
127 }
128}
129
130pub struct RunLengthEncoding;
141
142impl RunLengthEncoding {
143 pub fn encode(data: &[u8]) -> Vec<u8> {
145 if data.is_empty() {
146 return Vec::new();
147 }
148 let mut out = Vec::new();
149 let mut count = 1u8;
150 let mut current = data[0];
151
152 for &b in &data[1..] {
153 if b == current && count < 255 {
154 count += 1;
155 } else {
156 out.push(count);
157 out.push(current);
158 current = b;
159 count = 1;
160 }
161 }
162 out.push(count);
163 out.push(current);
164 out
165 }
166
167 pub fn decode(encoded: &[u8]) -> Result<Vec<u8>> {
169 if !encoded.len().is_multiple_of(2) {
170 return Err(IoError::DecompressionError(
171 "RLE: encoded length must be even".to_string(),
172 ));
173 }
174 let mut out = Vec::new();
175 let mut i = 0;
176 while i + 1 < encoded.len() {
177 let count = encoded[i] as usize;
178 let byte = encoded[i + 1];
179 for _ in 0..count {
180 out.push(byte);
181 }
182 i += 2;
183 }
184 Ok(out)
185 }
186
187 pub fn ratio(data: &[u8]) -> f64 {
189 let encoded = Self::encode(data);
190 if encoded.is_empty() {
191 return 1.0;
192 }
193 data.len() as f64 / encoded.len() as f64
194 }
195}
196
197pub struct DeltaEncoding;
208
209impl DeltaEncoding {
210 pub fn encode_i64(data: &[i64]) -> Vec<u8> {
215 let n = data.len();
216 let mut out = Vec::with_capacity(8 + n * 8);
217 out.extend_from_slice(&(n as u64).to_le_bytes());
218 if n == 0 {
219 return out;
220 }
221 out.extend_from_slice(&data[0].to_le_bytes());
222 for i in 1..n {
223 let delta = data[i].wrapping_sub(data[i - 1]);
224 out.extend_from_slice(&delta.to_le_bytes());
225 }
226 out
227 }
228
229 pub fn decode_i64(encoded: &[u8]) -> Result<Vec<i64>> {
231 if encoded.len() < 8 {
232 return Err(IoError::DecompressionError("Delta: too short".to_string()));
233 }
234 let n = u64::from_le_bytes(
235 encoded[..8]
236 .try_into()
237 .map_err(|_| IoError::DecompressionError("Delta: bad length prefix".to_string()))?,
238 ) as usize;
239 if n == 0 {
240 return Ok(Vec::new());
241 }
242 if encoded.len() < 8 + n * 8 {
243 return Err(IoError::DecompressionError(
244 "Delta: encoded data too short".to_string(),
245 ));
246 }
247 let mut out = Vec::with_capacity(n);
248 let first = i64::from_le_bytes(
249 encoded[8..16]
250 .try_into()
251 .map_err(|_| IoError::DecompressionError("Delta: bad first value".to_string()))?,
252 );
253 out.push(first);
254 let mut prev = first;
255 for i in 1..n {
256 let offset = 8 + i * 8;
257 let delta =
258 i64::from_le_bytes(encoded[offset..offset + 8].try_into().map_err(|_| {
259 IoError::DecompressionError("Delta: bad delta value".to_string())
260 })?);
261 let val = prev.wrapping_add(delta);
262 out.push(val);
263 prev = val;
264 }
265 Ok(out)
266 }
267
268 pub fn encode_f64(data: &[f64], scale: f64) -> Vec<u8> {
273 let ints: Vec<i64> = data.iter().map(|&v| (v * scale).round() as i64).collect();
274 Self::encode_i64(&ints)
275 }
276
277 pub fn decode_f64(encoded: &[u8], scale: f64) -> Result<Vec<f64>> {
279 let ints = Self::decode_i64(encoded)?;
280 Ok(ints.iter().map(|&v| v as f64 / scale).collect())
281 }
282}
283
284pub struct FrameCompressor {
298 pub frame_size: usize,
300 pub codec: FrameCodec,
302}
303
304#[derive(Debug, Clone, Copy, PartialEq, Eq)]
306pub enum FrameCodec {
307 None,
309 Lz4,
311 Zstd,
313 Rle,
315}
316
317const FRAME_MAGIC: &[u8; 4] = b"FRCM";
318const FRAME_VERSION: u8 = 1;
319
320impl FrameCompressor {
321 pub fn new() -> Self {
323 Self {
324 frame_size: 64 * 1024,
325 codec: FrameCodec::Lz4,
326 }
327 }
328
329 pub fn with_frame_size(mut self, size: usize) -> Self {
331 self.frame_size = size.max(64); self
333 }
334
335 pub fn with_codec(mut self, codec: FrameCodec) -> Self {
337 self.codec = codec;
338 self
339 }
340
341 pub fn compress(&self, data: &[u8]) -> Result<Vec<u8>> {
343 let frames: Vec<&[u8]> = data.chunks(self.frame_size).collect();
344 let num_frames = frames.len() as u32;
345
346 let mut compressed_frames: Vec<Vec<u8>> = Vec::with_capacity(frames.len());
348 for frame in &frames {
349 let c = self.compress_frame(frame)?;
350 compressed_frames.push(c);
351 }
352
353 let total_payload: usize = compressed_frames.iter().map(|f| 8 + f.len()).sum();
355 let mut out = Vec::with_capacity(10 + total_payload);
356 out.extend_from_slice(FRAME_MAGIC);
357 out.push(FRAME_VERSION);
358 out.push(self.codec as u8);
359 out.extend_from_slice(&num_frames.to_le_bytes());
360
361 for (cf, orig) in compressed_frames.iter().zip(frames.iter()) {
362 out.extend_from_slice(&(cf.len() as u32).to_le_bytes());
363 out.extend_from_slice(&(orig.len() as u32).to_le_bytes());
364 out.extend_from_slice(cf);
365 }
366 Ok(out)
367 }
368
369 pub fn decompress(&self, data: &[u8]) -> Result<Vec<u8>> {
371 if data.len() < 10 {
372 return Err(IoError::DecompressionError("Frame: too short".to_string()));
373 }
374 if &data[..4] != FRAME_MAGIC {
375 return Err(IoError::DecompressionError("Frame: bad magic".to_string()));
376 }
377 if data[4] != FRAME_VERSION {
378 return Err(IoError::DecompressionError(format!(
379 "Frame: unsupported version {}",
380 data[4]
381 )));
382 }
383 let codec = FrameCodec::from_u8(data[5]).ok_or_else(|| {
384 IoError::DecompressionError(format!("Frame: unknown codec {}", data[5]))
385 })?;
386 let num_frames = u32::from_le_bytes([data[6], data[7], data[8], data[9]]) as usize;
387
388 let mut out = Vec::new();
389 let mut pos = 10usize;
390 for _ in 0..num_frames {
391 if pos + 8 > data.len() {
392 return Err(IoError::DecompressionError(
393 "Frame: truncated header".to_string(),
394 ));
395 }
396 let comp_size =
397 u32::from_le_bytes([data[pos], data[pos + 1], data[pos + 2], data[pos + 3]])
398 as usize;
399 let _orig_size =
400 u32::from_le_bytes([data[pos + 4], data[pos + 5], data[pos + 6], data[pos + 7]])
401 as usize;
402 pos += 8;
403 if pos + comp_size > data.len() {
404 return Err(IoError::DecompressionError(
405 "Frame: truncated data".to_string(),
406 ));
407 }
408 let frame_data = &data[pos..pos + comp_size];
409 pos += comp_size;
410
411 let decompressed = Self::decompress_frame_with_codec(frame_data, codec)?;
412 out.extend_from_slice(&decompressed);
413 }
414 Ok(out)
415 }
416
417 fn compress_frame(&self, frame: &[u8]) -> Result<Vec<u8>> {
418 match self.codec {
419 FrameCodec::None => Ok(frame.to_vec()),
420 FrameCodec::Lz4 => {
421 let c = LZ4Compressor::new();
422 c.compress(frame)
423 }
424 FrameCodec::Zstd => {
425 let c = ZstdCompressor::new();
426 c.compress(frame)
427 }
428 FrameCodec::Rle => Ok(RunLengthEncoding::encode(frame)),
429 }
430 }
431
432 fn decompress_frame_with_codec(frame: &[u8], codec: FrameCodec) -> Result<Vec<u8>> {
433 match codec {
434 FrameCodec::None => Ok(frame.to_vec()),
435 FrameCodec::Lz4 => LZ4Compressor::new().decompress(frame),
436 FrameCodec::Zstd => ZstdCompressor::new().decompress(frame),
437 FrameCodec::Rle => RunLengthEncoding::decode(frame),
438 }
439 }
440}
441
442impl Default for FrameCompressor {
443 fn default() -> Self {
444 Self::new()
445 }
446}
447
448impl FrameCodec {
449 fn from_u8(v: u8) -> Option<Self> {
450 match v {
451 0 => Some(Self::None),
452 1 => Some(Self::Lz4),
453 2 => Some(Self::Zstd),
454 3 => Some(Self::Rle),
455 _ => None,
456 }
457 }
458}
459
460#[derive(Debug, Clone)]
466pub struct BenchmarkResult {
467 pub codec: String,
469 pub input_size: usize,
471 pub compressed_size: usize,
473 pub ratio: f64,
475 pub compress_mibps: f64,
477 pub decompress_mibps: f64,
479 pub compress_us: u64,
481 pub decompress_us: u64,
483}
484
485impl BenchmarkResult {
486 pub fn summary(&self) -> String {
488 format!(
489 "{}: ratio={:.2}x compress={:.1} MiB/s decompress={:.1} MiB/s ({} → {} bytes)",
490 self.codec,
491 self.ratio,
492 self.compress_mibps,
493 self.decompress_mibps,
494 self.input_size,
495 self.compressed_size,
496 )
497 }
498}
499
500pub struct CompressionBenchmark {
502 pub warmup_runs: usize,
504 pub timed_runs: usize,
506}
507
508impl CompressionBenchmark {
509 pub fn new() -> Self {
511 Self {
512 warmup_runs: 1,
513 timed_runs: 5,
514 }
515 }
516
517 pub fn with_warmup(mut self, n: usize) -> Self {
519 self.warmup_runs = n;
520 self
521 }
522
523 pub fn with_timed_runs(mut self, n: usize) -> Self {
525 self.timed_runs = n.max(1);
526 self
527 }
528
529 pub fn bench_zstd(&self, data: &[u8]) -> Result<BenchmarkResult> {
531 let c = ZstdCompressor::new();
532 let compressed = c.compress(data)?;
533 self.measure(
534 "Zstd",
535 data,
536 &compressed,
537 || c.compress(data),
538 || c.decompress(&compressed),
539 )
540 }
541
542 pub fn bench_lz4(&self, data: &[u8]) -> Result<BenchmarkResult> {
544 let c = LZ4Compressor::new();
545 let compressed = c.compress(data)?;
546 self.measure(
547 "LZ4",
548 data,
549 &compressed,
550 || c.compress(data),
551 || c.decompress(&compressed),
552 )
553 }
554
555 pub fn bench_rle(&self, data: &[u8]) -> Result<BenchmarkResult> {
557 let encoded = RunLengthEncoding::encode(data);
558 self.measure(
559 "RLE",
560 data,
561 &encoded,
562 || Ok(RunLengthEncoding::encode(data)),
563 || RunLengthEncoding::decode(&encoded),
564 )
565 }
566
567 pub fn bench_frame_lz4(&self, data: &[u8]) -> Result<BenchmarkResult> {
569 let fc = FrameCompressor::new();
570 let compressed = fc.compress(data)?;
571 self.measure(
572 "Frame-LZ4",
573 data,
574 &compressed,
575 || fc.compress(data),
576 || fc.decompress(&compressed),
577 )
578 }
579
580 pub fn run_all(&self, data: &[u8]) -> Vec<BenchmarkResult> {
582 let mut results = Vec::new();
583 if let Ok(r) = self.bench_zstd(data) {
584 results.push(r);
585 }
586 if let Ok(r) = self.bench_lz4(data) {
587 results.push(r);
588 }
589 if let Ok(r) = self.bench_rle(data) {
590 results.push(r);
591 }
592 if let Ok(r) = self.bench_frame_lz4(data) {
593 results.push(r);
594 }
595 results
596 }
597
598 fn measure<C, D>(
599 &self,
600 name: &str,
601 data: &[u8],
602 compressed: &[u8],
603 compress_fn: C,
604 decompress_fn: D,
605 ) -> Result<BenchmarkResult>
606 where
607 C: Fn() -> Result<Vec<u8>>,
608 D: Fn() -> Result<Vec<u8>>,
609 {
610 for _ in 0..self.warmup_runs {
612 let _ = compress_fn();
613 let _ = decompress_fn();
614 }
615
616 let mut total_compress_us = 0u64;
618 for _ in 0..self.timed_runs {
619 let t = Instant::now();
620 compress_fn()?;
621 total_compress_us += t.elapsed().as_micros() as u64;
622 }
623 let avg_compress_us = total_compress_us / self.timed_runs as u64;
624
625 let mut total_decompress_us = 0u64;
627 for _ in 0..self.timed_runs {
628 let t = Instant::now();
629 decompress_fn()?;
630 total_decompress_us += t.elapsed().as_micros() as u64;
631 }
632 let avg_decompress_us = total_decompress_us / self.timed_runs as u64;
633
634 let input_size = data.len();
635 let compressed_size = compressed.len();
636 let ratio = if compressed_size == 0 {
637 0.0
638 } else {
639 input_size as f64 / compressed_size as f64
640 };
641
642 let to_mibps = |us: u64| -> f64 {
643 if us == 0 {
644 return f64::INFINITY;
645 }
646 (input_size as f64 / (1024.0 * 1024.0)) / (us as f64 / 1_000_000.0)
647 };
648
649 Ok(BenchmarkResult {
650 codec: name.to_string(),
651 input_size,
652 compressed_size,
653 ratio,
654 compress_mibps: to_mibps(avg_compress_us),
655 decompress_mibps: to_mibps(avg_decompress_us),
656 compress_us: avg_compress_us,
657 decompress_us: avg_decompress_us,
658 })
659 }
660}
661
662impl Default for CompressionBenchmark {
663 fn default() -> Self {
664 Self::new()
665 }
666}
667
668#[cfg(test)]
673mod tests {
674 use super::*;
675
676 const REPETITIVE: &[u8] = b"aaaaaaaaabbbbbbbbbbccccccccddddddddeeeeeeee";
677 const TEXT: &[u8] =
678 b"The quick brown fox jumps over the lazy dog. Scientific data compression!";
679
680 #[test]
681 fn test_zstd_roundtrip() {
682 let c = ZstdCompressor::new();
683 let enc = c.compress(REPETITIVE).unwrap();
684 let dec = c.decompress(&enc).unwrap();
685 assert_eq!(dec, REPETITIVE);
686 }
687
688 #[test]
689 fn test_lz4_roundtrip() {
690 let c = LZ4Compressor::new();
691 let enc = c.compress(TEXT).unwrap();
692 let dec = c.decompress(&enc).unwrap();
693 assert_eq!(dec, TEXT);
694 }
695
696 #[test]
697 fn test_rle_roundtrip() {
698 let dec = RunLengthEncoding::decode(&RunLengthEncoding::encode(REPETITIVE)).unwrap();
699 assert_eq!(dec, REPETITIVE);
700 }
701
702 #[test]
703 fn test_rle_empty() {
704 let enc = RunLengthEncoding::encode(&[]);
705 assert!(enc.is_empty());
706 let dec = RunLengthEncoding::decode(&[]).unwrap();
707 assert!(dec.is_empty());
708 }
709
710 #[test]
711 fn test_rle_long_run() {
712 let data = vec![42u8; 512]; let enc = RunLengthEncoding::encode(&data);
714 let dec = RunLengthEncoding::decode(&enc).unwrap();
715 assert_eq!(dec, data);
716 }
717
718 #[test]
719 fn test_delta_i64_roundtrip() {
720 let values: Vec<i64> = (0..100).map(|i| i * 7 + 1000).collect();
721 let enc = DeltaEncoding::encode_i64(&values);
722 let dec = DeltaEncoding::decode_i64(&enc).unwrap();
723 assert_eq!(dec, values);
724 }
725
726 #[test]
727 fn test_delta_f64_roundtrip() {
728 let values: Vec<f64> = (0..50).map(|i| i as f64 * 0.1).collect();
729 let enc = DeltaEncoding::encode_f64(&values, 1000.0);
730 let dec = DeltaEncoding::decode_f64(&enc, 1000.0).unwrap();
731 for (a, b) in values.iter().zip(dec.iter()) {
732 assert!((a - b).abs() < 0.001, "mismatch: {a} vs {b}");
733 }
734 }
735
736 #[test]
737 fn test_delta_empty() {
738 let enc = DeltaEncoding::encode_i64(&[]);
739 let dec = DeltaEncoding::decode_i64(&enc).unwrap();
740 assert!(dec.is_empty());
741 }
742
743 #[test]
744 fn test_frame_compressor_roundtrip_lz4() {
745 let data: Vec<u8> = (0u8..=255).cycle().take(200_000).collect();
746 let fc = FrameCompressor::new()
747 .with_frame_size(16 * 1024)
748 .with_codec(FrameCodec::Lz4);
749 let enc = fc.compress(&data).unwrap();
750 let dec = fc.decompress(&enc).unwrap();
751 assert_eq!(dec, data);
752 }
753
754 #[test]
755 fn test_frame_compressor_roundtrip_rle() {
756 let data = vec![0xABu8; 50_000];
757 let fc = FrameCompressor::new().with_codec(FrameCodec::Rle);
758 let enc = fc.compress(&data).unwrap();
759 let dec = fc.decompress(&enc).unwrap();
760 assert_eq!(dec, data);
761 }
762
763 #[test]
764 fn test_frame_compressor_bad_magic() {
765 let bad = b"XXXX\x01\x01\x00\x00\x00\x00".to_vec();
766 let fc = FrameCompressor::new();
767 assert!(fc.decompress(&bad).is_err());
768 }
769
770 #[test]
771 fn test_benchmark_runs() {
772 let data: Vec<u8> = (0u8..=255).cycle().take(10_000).collect();
773 let bm = CompressionBenchmark::new()
774 .with_warmup(0)
775 .with_timed_runs(1);
776 let results = bm.run_all(&data);
777 assert!(results.len() >= 2);
779 for r in &results {
780 assert!(r.ratio > 0.0);
781 assert!(!r.summary().is_empty());
782 }
783 }
784}