use crate::Limit;
const DEFAULT_MAX_DECOMPRESSED_BYTES: usize = 16 * 1024 * 1024; const DEFAULT_MAX_COMPRESSED_BYTES: usize = 5 * 1024 * 1024; const DEFAULT_MAX_EXPANSION_RATIO: usize = 100;
const DEFAULT_EXPANSION_SLACK_BYTES: usize = 1024 * 1024;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct ExpansionRatio {
pub(super) ratio: usize,
pub(super) slack_bytes: usize,
}
impl ExpansionRatio {
#[inline]
pub fn new(ratio: usize, slack_bytes: usize) -> Self {
debug_assert!(ratio > 0, "expansion ratio must be greater than zero");
Self { ratio, slack_bytes }
}
}
#[derive(Debug, Clone, Copy)]
pub struct DecompressionLimits {
pub(super) max_decompressed_bytes: Limit<usize>,
pub(super) max_compressed_bytes: Limit<usize>,
pub(super) max_expansion_ratio: Option<ExpansionRatio>,
}
impl Default for DecompressionLimits {
#[inline]
fn default() -> Self {
Self {
max_decompressed_bytes: Limit::Limited(DEFAULT_MAX_DECOMPRESSED_BYTES),
max_compressed_bytes: Limit::Limited(DEFAULT_MAX_COMPRESSED_BYTES),
max_expansion_ratio: Some(ExpansionRatio::new(
DEFAULT_MAX_EXPANSION_RATIO,
DEFAULT_EXPANSION_SLACK_BYTES,
)),
}
}
}
impl DecompressionLimits {
#[inline]
pub fn with_max_decompressed(mut self, limit: Limit<usize>) -> Self {
warn_if_unlimited("decompression max_decompressed_bytes", limit);
self.max_decompressed_bytes = limit;
self
}
#[inline]
pub fn with_max_compressed(mut self, limit: Limit<usize>) -> Self {
warn_if_unlimited("decompression max_compressed_bytes", limit);
self.max_compressed_bytes = limit;
self
}
#[inline]
pub fn with_max_expansion_ratio(mut self, ratio: ExpansionRatio) -> Self {
self.max_expansion_ratio = Some(ratio);
self
}
#[inline]
pub fn without_max_expansion_ratio(mut self) -> Self {
self.max_expansion_ratio = None;
self
}
#[inline]
pub(crate) fn resolved(self) -> ResolvedDecompressionLimits {
ResolvedDecompressionLimits {
max_decompressed_bytes: resolve_limit(
self.max_decompressed_bytes,
DEFAULT_MAX_DECOMPRESSED_BYTES,
),
max_compressed_bytes: resolve_limit(
self.max_compressed_bytes,
DEFAULT_MAX_COMPRESSED_BYTES,
),
max_expansion_ratio: self.max_expansion_ratio,
}
}
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct ResolvedDecompressionLimits {
pub(super) max_decompressed_bytes: Option<usize>,
pub(super) max_compressed_bytes: Option<usize>,
pub(super) max_expansion_ratio: Option<ExpansionRatio>,
}
#[inline]
fn resolve_limit(limit: Limit<usize>, default: usize) -> Option<usize> {
match limit {
Limit::Default => Some(default),
Limit::Limited(value) => Some(value),
Limit::Unlimited => None,
}
}
#[inline]
fn warn_if_unlimited(_name: &str, limit: Limit<usize>) {
if matches!(limit, Limit::Unlimited) {
#[cfg(feature = "tracing")]
tracing::warn!("{_name} is set to Unlimited; decompression safety checks are disabled.");
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Limit;
#[test]
fn expansion_ratio_new_sets_fields() {
let r = ExpansionRatio::new(123, 456);
assert_eq!(r.ratio, 123);
assert_eq!(r.slack_bytes, 456);
}
#[test]
fn decompression_limits_default_values_match_constants() {
let limits = DecompressionLimits::default();
match limits.max_decompressed_bytes {
Limit::Limited(v) => assert_eq!(v, DEFAULT_MAX_DECOMPRESSED_BYTES),
other => panic!(
"expected Limited for max_decompressed_bytes, got: {:?}",
other
),
}
match limits.max_compressed_bytes {
Limit::Limited(v) => assert_eq!(v, DEFAULT_MAX_COMPRESSED_BYTES),
other => panic!(
"expected Limited for max_compressed_bytes, got: {:?}",
other
),
}
let ratio = limits
.max_expansion_ratio
.expect("default max_expansion_ratio must be Some");
assert_eq!(ratio.ratio, DEFAULT_MAX_EXPANSION_RATIO);
assert_eq!(ratio.slack_bytes, DEFAULT_EXPANSION_SLACK_BYTES);
}
#[test]
fn with_max_decompressed_overrides_value() {
let limits = DecompressionLimits::default().with_max_decompressed(Limit::Limited(123));
match limits.max_decompressed_bytes {
Limit::Limited(v) => assert_eq!(v, 123),
other => panic!("expected Limited, got: {:?}", other),
}
}
#[test]
fn with_max_compressed_overrides_value() {
let limits = DecompressionLimits::default().with_max_compressed(Limit::Limited(321));
match limits.max_compressed_bytes {
Limit::Limited(v) => assert_eq!(v, 321),
other => panic!("expected Limited, got: {:?}", other),
}
}
#[test]
fn with_max_expansion_ratio_can_disable_ratio_guard() {
let limits = DecompressionLimits::default().without_max_expansion_ratio();
assert!(limits.max_expansion_ratio.is_none());
}
#[test]
fn with_max_expansion_ratio_can_set_custom_ratio() {
let custom = ExpansionRatio::new(7, 999);
let limits = DecompressionLimits::default().with_max_expansion_ratio(custom);
let r = limits.max_expansion_ratio.unwrap();
assert_eq!(r.ratio, 7);
assert_eq!(r.slack_bytes, 999);
}
#[test]
fn resolved_maps_default_to_some_default_constant() {
let limits = DecompressionLimits {
max_decompressed_bytes: Limit::Default,
max_compressed_bytes: Limit::Default,
max_expansion_ratio: None,
};
let resolved = limits.resolved();
assert_eq!(
resolved.max_decompressed_bytes,
Some(DEFAULT_MAX_DECOMPRESSED_BYTES)
);
assert_eq!(
resolved.max_compressed_bytes,
Some(DEFAULT_MAX_COMPRESSED_BYTES)
);
assert!(resolved.max_expansion_ratio.is_none());
}
#[test]
fn resolved_maps_limited_to_some_value() {
let limits = DecompressionLimits {
max_decompressed_bytes: Limit::Limited(10),
max_compressed_bytes: Limit::Limited(20),
max_expansion_ratio: Some(ExpansionRatio::new(3, 4)),
};
let resolved = limits.resolved();
assert_eq!(resolved.max_decompressed_bytes, Some(10));
assert_eq!(resolved.max_compressed_bytes, Some(20));
let r = resolved.max_expansion_ratio.unwrap();
assert_eq!(r.ratio, 3);
assert_eq!(r.slack_bytes, 4);
}
#[test]
fn resolved_maps_unlimited_to_none() {
let limits = DecompressionLimits {
max_decompressed_bytes: Limit::Unlimited,
max_compressed_bytes: Limit::Unlimited,
max_expansion_ratio: Some(ExpansionRatio::new(1, 2)),
};
let resolved = limits.resolved();
assert_eq!(resolved.max_decompressed_bytes, None);
assert_eq!(resolved.max_compressed_bytes, None);
assert!(resolved.max_expansion_ratio.is_some());
}
#[test]
fn resolve_limit_behavior() {
assert_eq!(resolve_limit(Limit::Default, 111), Some(111));
assert_eq!(resolve_limit(Limit::Limited(222), 111), Some(222));
assert_eq!(resolve_limit(Limit::Unlimited, 111), None);
}
#[test]
#[cfg(debug_assertions)]
#[should_panic(expected = "expansion ratio must be greater than zero")]
fn expansion_ratio_zero_panics_in_debug() {
let _ = ExpansionRatio::new(0, 123);
}
}