1use std::fmt;
4use std::sync::Arc;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
8pub enum Strictness {
9 Strict,
11 #[default]
13 Lenient,
14}
15
16pub trait Budget: Send + Sync {
19 fn charge(&self, units: u64) -> bool;
21}
22
23#[derive(Clone)]
26pub struct Limits {
27 pub max_depth: u32,
30 pub max_objects: u32,
32 pub max_decoded_size: u64,
34 pub max_compression_ratio: u64,
37 pub ratio_floor: u64,
40 pub max_xref_sections: u32,
42 pub max_object_size: u64,
44 pub max_pages: u32,
46 pub budget: Option<Arc<dyn Budget>>,
48}
49
50impl Default for Limits {
51 fn default() -> Self {
52 Self {
53 max_depth: 256,
54 max_objects: 8_388_607,
55 max_decoded_size: 512 << 20,
56 max_compression_ratio: 1100,
57 ratio_floor: 16 << 20,
58 max_xref_sections: 1024,
59 max_object_size: 64 << 20,
60 max_pages: 1_000_000,
61 budget: None,
62 }
63 }
64}
65
66impl fmt::Debug for Limits {
67 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
68 f.debug_struct("Limits")
69 .field("max_depth", &self.max_depth)
70 .field("max_objects", &self.max_objects)
71 .field("max_decoded_size", &self.max_decoded_size)
72 .field("max_compression_ratio", &self.max_compression_ratio)
73 .field("ratio_floor", &self.ratio_floor)
74 .field("max_xref_sections", &self.max_xref_sections)
75 .field("max_object_size", &self.max_object_size)
76 .field("max_pages", &self.max_pages)
77 .field("budget", &self.budget.is_some())
78 .finish()
79 }
80}
81
82impl Limits {
83 pub fn charge(&self, units: u64) -> Result<(), crate::Error> {
85 match &self.budget {
86 Some(b) if !b.charge(units) => Err(crate::Error::Limit(LimitKind::Budget)),
87 _ => Ok(()),
88 }
89 }
90}
91
92#[derive(Debug, Clone, Copy, PartialEq, Eq)]
94pub enum LimitKind {
95 Depth,
97 Objects,
99 DecodedSize,
101 CompressionRatio,
103 XrefSections,
105 ObjectSize,
107 Pages,
109 Budget,
111}
112
113impl fmt::Display for LimitKind {
114 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
115 f.write_str(match self {
116 Self::Depth => "nesting depth",
117 Self::Objects => "object count",
118 Self::DecodedSize => "decoded stream size",
119 Self::CompressionRatio => "compression ratio",
120 Self::XrefSections => "xref section count",
121 Self::ObjectSize => "object size",
122 Self::Pages => "page count",
123 Self::Budget => "CPU budget",
124 })
125 }
126}
127
128#[cfg(test)]
129mod tests {
130 use super::*;
131
132 struct Allow(u64);
133
134 impl Budget for Allow {
135 fn charge(&self, units: u64) -> bool {
136 units <= self.0
137 }
138 }
139
140 #[test]
141 fn budget_hook() {
142 let mut limits = Limits::default();
143 assert!(limits.charge(u64::MAX).is_ok());
144 limits.budget = Some(Arc::new(Allow(10)));
145 assert!(limits.charge(10).is_ok());
146 assert_eq!(
147 limits.charge(11),
148 Err(crate::Error::Limit(LimitKind::Budget))
149 );
150 assert!(format!("{limits:?}").contains("budget: true"));
151 }
152
153 #[test]
154 fn display() {
155 assert_eq!(LimitKind::DecodedSize.to_string(), "decoded stream size");
156 assert_eq!(Strictness::default(), Strictness::Lenient);
157 }
158}