#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub struct Limits {
max_output: u64,
max_ratio: u32,
ratio_floor: u64,
max_header: usize,
max_members: u32,
}
impl Limits {
pub const DEFAULT_MAX_OUTPUT: u64 = 64 << 20;
pub const DEFAULT_RATIO_FLOOR: u64 = 1 << 20;
pub const DEFAULT_MAX_HEADER: usize = 64 << 10;
pub const DEFAULT_MAX_MEMBERS: u32 = 1024;
#[must_use]
pub const fn new() -> Self {
Self {
max_output: Self::DEFAULT_MAX_OUTPUT,
max_ratio: 0,
ratio_floor: Self::DEFAULT_RATIO_FLOOR,
max_header: Self::DEFAULT_MAX_HEADER,
max_members: Self::DEFAULT_MAX_MEMBERS,
}
}
#[must_use]
pub const fn unlimited() -> Self {
Self {
max_output: u64::MAX,
max_ratio: 0,
ratio_floor: Self::DEFAULT_RATIO_FLOOR,
max_header: Self::DEFAULT_MAX_HEADER,
max_members: u32::MAX,
}
}
#[must_use]
pub const fn with_max_output(mut self, bytes: u64) -> Self {
self.max_output = bytes;
self
}
#[must_use]
pub const fn with_max_ratio(mut self, ratio: u32) -> Self {
self.max_ratio = ratio;
self
}
#[must_use]
pub const fn with_ratio_floor(mut self, bytes: u64) -> Self {
self.ratio_floor = bytes;
self
}
#[must_use]
pub const fn with_max_header(mut self, bytes: usize) -> Self {
self.max_header = bytes;
self
}
#[must_use]
pub const fn with_max_members(mut self, members: u32) -> Self {
self.max_members = members;
self
}
#[must_use]
pub const fn max_output(&self) -> u64 {
self.max_output
}
#[must_use]
pub const fn max_ratio(&self) -> u32 {
self.max_ratio
}
#[must_use]
pub const fn ratio_floor(&self) -> u64 {
self.ratio_floor
}
#[must_use]
pub const fn max_header(&self) -> usize {
self.max_header
}
#[must_use]
pub const fn max_members(&self) -> u32 {
self.max_members
}
#[cfg(test)]
#[must_use]
pub(crate) const fn ratio_exceeded(&self, output: u64, input: u64) -> bool {
self.max_ratio != 0
&& output > self.ratio_floor
&& output > input.saturating_mul(self.max_ratio as u64)
}
}
impl Default for Limits {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_limits_defaults_are_fail_closed() {
let l = Limits::new();
assert_eq!(l.max_output(), 64 << 20);
assert_eq!(l.max_ratio(), 0);
assert_eq!(l.ratio_floor(), 1 << 20);
assert_eq!(l.max_header(), 64 << 10);
assert_eq!(l.max_members(), 1024);
assert_eq!(Limits::default(), l);
}
#[test]
fn test_limits_ratio_off_never_exceeded() {
assert!(!Limits::new().ratio_exceeded(u64::MAX, 1));
}
#[test]
fn test_limits_ratio_below_floor_not_checked() {
let l = Limits::new().with_max_ratio(2).with_ratio_floor(100);
assert!(!l.ratio_exceeded(100, 1));
assert!(l.ratio_exceeded(101, 1));
assert!(!l.ratio_exceeded(101, 51));
assert!(l.ratio_exceeded(103, 51));
}
#[test]
fn test_limits_ratio_saturates_on_huge_input() {
let l = Limits::new().with_max_ratio(u32::MAX).with_ratio_floor(0);
assert!(!l.ratio_exceeded(u64::MAX, u64::MAX));
}
#[test]
fn test_limits_unlimited_has_no_output_cap() {
let l = Limits::unlimited();
assert_eq!(l.max_output(), u64::MAX);
assert_eq!(l.max_members(), u32::MAX);
assert_eq!(l.with_max_members(3).max_members(), 3);
}
}