Skip to main content

compression_codecs/mzstd/
params.rs

1//! This module contains mzstd-specific types for async-compression.
2//!
3//! The parameter types mirror `zstd::params` where the two backends overlap, so switching between
4//! them only needs a type change.
5
6use compression_core::Level;
7use mzstd::{
8    CompressionParams, DecodeLimits, DecoderConfig, EncoderConfig, FrameMode, LdmParams,
9    LongDistanceMatching,
10};
11use std::convert::TryFrom;
12
13/// A compression parameter for the `mzstd` backend. This is a stable wrapper around mzstd's own
14/// encoder configuration, to abstract over different versions of the mzstd library.
15///
16/// As with the `zstd` backend, a value of `0` selects the default for the parameter, and the last
17/// value given for a parameter wins.
18///
19/// See the [zstd documentation](https://facebook.github.io/zstd/zstd_manual.html) for more
20/// information on these parameters.
21#[derive(Copy, Clone, Debug, PartialEq, Eq)]
22pub struct CParameter(CParameterInner);
23
24#[derive(Copy, Clone, Debug, PartialEq, Eq)]
25enum CParameterInner {
26    WindowLog(u32),
27    HashLog(u32),
28    ChainLog(u32),
29    SearchLog(u32),
30    MinMatch(u32),
31    TargetLength(u32),
32    EnableLongDistanceMatching(bool),
33    LdmHashLog(u32),
34    LdmMinMatch(u32),
35    LdmBucketSizeLog(u32),
36    LdmHashRateLog(u32),
37    ContentSizeFlag(bool),
38    ChecksumFlag(bool),
39}
40
41impl CParameter {
42    pub fn quality(level: Level) -> i32 {
43        // NOTE: zstd's "fastest" level is -131072 which can create outputs larger than inputs.
44        // Like the `zstd` backend, this chooses a "fastest" level which has a more-or-less
45        // equivalent compression ratio to gzip's fastest mode. Precise levels may still go
46        // negative.
47        const OUR_FASTEST: i32 = 1;
48
49        match level {
50            Level::Fastest => OUR_FASTEST,
51            Level::Best => mzstd::Level::MAX.get(),
52            Level::Precise(quality) => {
53                quality.clamp(mzstd::Level::MIN.get(), mzstd::Level::MAX.get())
54            }
55            _ => mzstd::Level::DEFAULT.get(),
56        }
57    }
58
59    /// Window size in bytes (as a power of two)
60    pub fn window_log(value: u32) -> Self {
61        Self(CParameterInner::WindowLog(value))
62    }
63
64    /// Size of the initial probe table in 4-byte entries (as a power of two)
65    pub fn hash_log(value: u32) -> Self {
66        Self(CParameterInner::HashLog(value))
67    }
68
69    /// Size of the multi-probe table in 4-byte entries (as a power of two)
70    pub fn chain_log(value: u32) -> Self {
71        Self(CParameterInner::ChainLog(value))
72    }
73
74    /// Number of search attempts (as a power of two)
75    pub fn search_log(value: u32) -> Self {
76        Self(CParameterInner::SearchLog(value))
77    }
78
79    /// Minimum size of matches searched for
80    pub fn min_match(value: u32) -> Self {
81        Self(CParameterInner::MinMatch(value))
82    }
83
84    /// Strategy-dependent length modifier
85    pub fn target_length(value: u32) -> Self {
86        Self(CParameterInner::TargetLength(value))
87    }
88
89    /// Enable long-distance matching mode to look for and emit long-distance references.
90    ///
91    /// This increases the default window size.
92    pub fn enable_long_distance_matching(value: bool) -> Self {
93        Self(CParameterInner::EnableLongDistanceMatching(value))
94    }
95
96    /// Size of the long-distance matching table (as a power of two)
97    ///
98    /// Only used when long-distance matching is explicitly enabled.
99    pub fn ldm_hash_log(value: u32) -> Self {
100        Self(CParameterInner::LdmHashLog(value))
101    }
102
103    /// Minimum size of long-distance matches searched for
104    ///
105    /// Only used when long-distance matching is explicitly enabled.
106    pub fn ldm_min_match(value: u32) -> Self {
107        Self(CParameterInner::LdmMinMatch(value))
108    }
109
110    /// Size of each bucket in the LDM hash table for collision resolution (as a power of two)
111    ///
112    /// Only used when long-distance matching is explicitly enabled.
113    pub fn ldm_bucket_size_log(value: u32) -> Self {
114        Self(CParameterInner::LdmBucketSizeLog(value))
115    }
116
117    /// Frequency of using the LDM hash table (as a power of two)
118    ///
119    /// Only used when long-distance matching is explicitly enabled.
120    pub fn ldm_hash_rate_log(value: u32) -> Self {
121        Self(CParameterInner::LdmHashRateLog(value))
122    }
123
124    /// Emit the size of the content (default: true).
125    pub fn content_size_flag(value: bool) -> Self {
126        Self(CParameterInner::ContentSizeFlag(value))
127    }
128
129    /// Emit a checksum (default: false).
130    pub fn checksum_flag(value: bool) -> Self {
131        Self(CParameterInner::ChecksumFlag(value))
132    }
133}
134
135/// Builds the encoder configuration for `level` and `params`.
136///
137/// Levels outside the supported range are clamped, as zstd does. Parameter values are passed on
138/// unchecked, so out-of-range values are reported when the compressor is built. As in zstd, mzstd
139/// stores `0` as unset, which restores the parameter's default.
140pub(crate) fn encoder_config(level: i32, params: &[CParameter]) -> EncoderConfig {
141    let level = level.clamp(mzstd::Level::MIN.get(), mzstd::Level::MAX.get());
142    // The clamp above keeps `level` inside the range `Level` accepts.
143    let mut config = EncoderConfig::new().with_level(mzstd::Level::try_from(level).unwrap());
144    let mut compression = CompressionParams::default();
145    let mut ldm = LdmParams::default();
146    let mut ldm_enabled = None;
147
148    for param in params {
149        match param.0 {
150            CParameterInner::WindowLog(value) => compression = compression.with_window_log(value),
151            CParameterInner::HashLog(value) => compression = compression.with_hash_log(value),
152            CParameterInner::ChainLog(value) => compression = compression.with_chain_log(value),
153            CParameterInner::SearchLog(value) => compression = compression.with_search_log(value),
154            CParameterInner::MinMatch(value) => compression = compression.with_min_match(value),
155            CParameterInner::TargetLength(value) => {
156                compression = compression.with_target_length(value)
157            }
158            CParameterInner::EnableLongDistanceMatching(value) => ldm_enabled = Some(value),
159            CParameterInner::LdmHashLog(value) => ldm = ldm.with_hash_log(value),
160            CParameterInner::LdmMinMatch(value) => ldm = ldm.with_min_match(value),
161            CParameterInner::LdmBucketSizeLog(value) => ldm = ldm.with_bucket_size_log(value),
162            CParameterInner::LdmHashRateLog(value) => ldm = ldm.with_hash_rate_log(value),
163            CParameterInner::ContentSizeFlag(value) => config = config.with_content_size(value),
164            CParameterInner::ChecksumFlag(value) => config = config.with_checksum(value),
165        }
166    }
167
168    // The LDM parameters only apply when long-distance matching is explicitly enabled, which may
169    // come after them.
170    let long_distance = match ldm_enabled {
171        None => LongDistanceMatching::Auto,
172        Some(false) => LongDistanceMatching::Disabled,
173        Some(true) => LongDistanceMatching::Enabled(ldm),
174    };
175
176    config
177        .with_params(compression)
178        .with_long_distance_matching(long_distance)
179}
180
181/// A decompression parameter for the `mzstd` backend. This is a stable wrapper around mzstd's own
182/// decoder configuration, to abstract over different versions of the mzstd library.
183///
184/// See the [zstd documentation](https://facebook.github.io/zstd/zstd_manual.html) for more
185/// information on these parameters.
186#[derive(Copy, Clone, Debug, PartialEq, Eq)]
187pub struct DParameter(DParameterInner);
188
189#[derive(Copy, Clone, Debug, PartialEq, Eq)]
190enum DParameterInner {
191    WindowLogMax(u32),
192}
193
194impl DParameter {
195    /// Maximum window size in bytes (as a power of two)
196    pub fn window_log_max(value: u32) -> Self {
197        Self(DParameterInner::WindowLogMax(value))
198    }
199}
200
201/// Builds the decoder configuration for `params`.
202///
203/// Decoding stops after one frame, as the `zstd` backend does; further frames are read as
204/// separate members.
205///
206/// # Panics
207///
208/// Panics if a parameter is out of range, as the `zstd` backend does.
209pub(crate) fn decoder_config(params: &[DParameter]) -> DecoderConfig {
210    let mut limits = DecodeLimits::DEFAULT;
211    for param in params {
212        match param.0 {
213            DParameterInner::WindowLogMax(0) => limits = DecodeLimits::DEFAULT,
214            DParameterInner::WindowLogMax(value) => {
215                let log = u8::try_from(value).unwrap_or(u8::MAX);
216                limits = limits.with_max_window_log(log).unwrap();
217            }
218        }
219    }
220
221    DecoderConfig::DEFAULT
222        .with_frame_mode(FrameMode::Single)
223        .with_limits(limits)
224}