use std::fmt;
use std::sync::Arc;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Strictness {
Strict,
#[default]
Lenient,
}
pub trait Budget: Send + Sync {
fn charge(&self, units: u64) -> bool;
}
#[derive(Clone)]
pub struct Limits {
pub max_depth: u32,
pub max_objects: u32,
pub max_decoded_size: u64,
pub max_compression_ratio: u64,
pub ratio_floor: u64,
pub max_xref_sections: u32,
pub max_object_size: u64,
pub max_pages: u32,
pub budget: Option<Arc<dyn Budget>>,
}
impl Default for Limits {
fn default() -> Self {
Self {
max_depth: 256,
max_objects: 8_388_607,
max_decoded_size: 512 << 20,
max_compression_ratio: 1100,
ratio_floor: 16 << 20,
max_xref_sections: 1024,
max_object_size: 64 << 20,
max_pages: 1_000_000,
budget: None,
}
}
}
impl fmt::Debug for Limits {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Limits")
.field("max_depth", &self.max_depth)
.field("max_objects", &self.max_objects)
.field("max_decoded_size", &self.max_decoded_size)
.field("max_compression_ratio", &self.max_compression_ratio)
.field("ratio_floor", &self.ratio_floor)
.field("max_xref_sections", &self.max_xref_sections)
.field("max_object_size", &self.max_object_size)
.field("max_pages", &self.max_pages)
.field("budget", &self.budget.is_some())
.finish()
}
}
impl Limits {
pub fn charge(&self, units: u64) -> Result<(), crate::Error> {
match &self.budget {
Some(b) if !b.charge(units) => Err(crate::Error::Limit(LimitKind::Budget)),
_ => Ok(()),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LimitKind {
Depth,
Objects,
DecodedSize,
CompressionRatio,
XrefSections,
ObjectSize,
Pages,
Budget,
}
impl fmt::Display for LimitKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::Depth => "nesting depth",
Self::Objects => "object count",
Self::DecodedSize => "decoded stream size",
Self::CompressionRatio => "compression ratio",
Self::XrefSections => "xref section count",
Self::ObjectSize => "object size",
Self::Pages => "page count",
Self::Budget => "CPU budget",
})
}
}
#[cfg(test)]
mod tests {
use super::*;
struct Allow(u64);
impl Budget for Allow {
fn charge(&self, units: u64) -> bool {
units <= self.0
}
}
#[test]
fn budget_hook() {
let mut limits = Limits::default();
assert!(limits.charge(u64::MAX).is_ok());
limits.budget = Some(Arc::new(Allow(10)));
assert!(limits.charge(10).is_ok());
assert_eq!(
limits.charge(11),
Err(crate::Error::Limit(LimitKind::Budget))
);
assert!(format!("{limits:?}").contains("budget: true"));
}
#[test]
fn display() {
assert_eq!(LimitKind::DecodedSize.to_string(), "decoded stream size");
assert_eq!(Strictness::default(), Strictness::Lenient);
}
}