Skip to main content

hexz_core/algo/compression/
mod.rs

1//! Compression codecs for snapshot blocks.
2//!
3//! Provides the `Compressor` trait and concrete implementations (LZ4, Zstd).
4//! The format layer uses this trait to encode and decode blocks without
5//! depending on concrete algorithms.
6
7use hexz_common::Result;
8use std::fmt::Debug;
9
10/// Pluggable interface for block-oriented compressors.
11///
12/// **Architectural intent:** Allows snapshot writers and readers to operate
13/// against an abstraction rather than a specific compression library, making
14/// it possible to add or swap algorithms without touching the format layer.
15///
16/// **Constraints:** Implementations must be thread-safe and stateless or
17/// internally synchronized; all methods are expected to be pure functions of
18/// their inputs.
19pub trait Compressor: Send + Sync + Debug {
20    /// Compresses `data` into an owned buffer.
21    ///
22    /// **Architectural intent:** Encodes a single logical block using the
23    /// compressor's native framing.
24    ///
25    /// **Constraints:** The caller is responsible for choosing an appropriate
26    /// block size; extremely large inputs may cause excessive memory usage.
27    fn compress(&self, data: &[u8]) -> Result<Vec<u8>>;
28
29    /// Decompresses an encoded block into a new buffer.
30    ///
31    /// **Architectural intent:** Reverses `compress`, returning the original
32    /// block bytes or failing if corruption or format errors are detected.
33    ///
34    /// **Constraints:** The input must have been produced by a compatible
35    /// encoder; malformed data must surface as a `Error::Compression`.
36    fn decompress(&self, data: &[u8]) -> Result<Vec<u8>>;
37
38    /// Compresses `data` into a caller-provided buffer, enabling buffer reuse.
39    ///
40    /// **Architectural intent:** Eliminates per-call allocation in hot write
41    /// paths by reusing a `Vec` across iterations.
42    ///
43    /// **Default impl:** Falls back to `compress()` and copies the result.
44    fn compress_into(&self, data: &[u8], out: &mut Vec<u8>) -> Result<()> {
45        let compressed = self.compress(data)?;
46        out.clear();
47        out.extend_from_slice(&compressed);
48        Ok(())
49    }
50
51    /// Decompresses an encoded block into a caller-provided buffer.
52    ///
53    /// **Architectural intent:** Enables buffer reuse for hot paths to reduce
54    /// allocation pressure.
55    ///
56    /// **Constraints:** Implementations may fail if `out` is too small for the
57    /// decompressed payload; the return value is the number of bytes written.
58    fn decompress_into(&self, data: &[u8], out: &mut [u8]) -> Result<usize>;
59}
60
61/// LZ4 compression backend.
62///
63/// Provides a fast, lightweight `Compressor` implementation tuned for
64/// low-latency reads.
65pub mod lz4;
66
67/// Zstandard compression backend with optional dictionary support.
68///
69/// Used when higher compression ratios or trained dictionaries are desired.
70pub mod zstd;
71
72pub use lz4::Lz4Compressor;
73pub use zstd::ZstdCompressor;
74
75use crate::format::header::CompressionType;
76use hexz_common::constants::DEFAULT_ZSTD_LEVEL;
77
78/// Create a compressor from a [`CompressionType`] enum.
79pub fn create_compressor(
80    comp_type: CompressionType,
81    level: Option<i32>,
82    dictionary: Option<Vec<u8>>,
83) -> Box<dyn Compressor> {
84    match comp_type {
85        CompressionType::Lz4 => Box::new(Lz4Compressor::new()),
86        CompressionType::Zstd => Box::new(ZstdCompressor::new(
87            level.unwrap_or(DEFAULT_ZSTD_LEVEL),
88            dictionary,
89        )),
90    }
91}
92
93/// Parse a compression string ("lz4"/"zstd") and create a compressor + type.
94///
95/// Returns an error for unrecognized compression names.
96pub fn create_compressor_from_str(
97    s: &str,
98    level: Option<i32>,
99    dictionary: Option<Vec<u8>>,
100) -> hexz_common::Result<(Box<dyn Compressor>, CompressionType)> {
101    match s {
102        "zstd" => Ok((
103            create_compressor(CompressionType::Zstd, level, dictionary),
104            CompressionType::Zstd,
105        )),
106        "lz4" => Ok((
107            create_compressor(CompressionType::Lz4, level, dictionary),
108            CompressionType::Lz4,
109        )),
110        other => Err(hexz_common::Error::Format(format!(
111            "Unknown compression type '{}'. Supported: lz4, zstd",
112            other
113        ))),
114    }
115}