1use rexact_syntax::error::ErrorCode;
10use std::time::Duration;
11
12#[derive(Debug, Clone, Copy)]
15pub struct Clock {
16 #[cfg(not(target_arch = "wasm32"))]
17 start: std::time::Instant,
18}
19
20impl Clock {
21 pub fn now() -> Self {
23 Clock {
24 #[cfg(not(target_arch = "wasm32"))]
25 start: std::time::Instant::now(),
26 }
27 }
28 pub fn elapsed(&self) -> Duration {
30 #[cfg(not(target_arch = "wasm32"))]
31 {
32 self.start.elapsed()
33 }
34 #[cfg(target_arch = "wasm32")]
35 {
36 Duration::ZERO
37 }
38 }
39}
40
41#[derive(Debug, Clone, PartialEq, Eq)]
44pub struct Limits {
45 pub states: u64,
47 pub time: Duration,
49 pub memory_bytes: u64,
51 pub repeat: u64,
53 pub length: u64,
55 pub depth: u32,
57 pub enumerate: u64,
59}
60
61impl Default for Limits {
62 fn default() -> Self {
63 Self {
64 states: 200_000,
65 time: Duration::from_millis(5_000),
66 memory_bytes: 1024 * 1024 * 1024,
67 repeat: 10_000,
68 length: 1_000_000,
69 depth: 250,
70 enumerate: 100_000,
71 }
72 }
73}
74
75#[derive(Debug)]
77pub struct Budget {
78 limits: Limits,
79 start: Clock,
80 states: u64,
81 memory: u64,
82 hit: Option<ErrorCode>,
83}
84
85impl Budget {
86 pub fn new(limits: Limits) -> Self {
88 Self {
89 limits,
90 start: Clock::now(),
91 states: 0,
92 memory: 0,
93 hit: None,
94 }
95 }
96
97 pub fn limits(&self) -> &Limits {
99 &self.limits
100 }
101
102 pub fn limit_hit(&self) -> Option<ErrorCode> {
104 self.hit
105 }
106
107 fn trip(&mut self, code: ErrorCode) -> Result<(), ErrorCode> {
108 self.hit.get_or_insert(code);
110 Err(code)
111 }
112
113 pub fn add_states(&mut self, n: u64) -> Result<(), ErrorCode> {
115 match self.states.checked_add(n) {
116 Some(total) if total <= self.limits.states => {
117 self.states = total;
118 Ok(())
119 }
120 _ => self.trip(ErrorCode::L001),
121 }
122 }
123
124 pub fn add_memory(&mut self, bytes: u64) -> Result<(), ErrorCode> {
126 match self.memory.checked_add(bytes) {
127 Some(total) if total <= self.limits.memory_bytes => {
128 self.memory = total;
129 Ok(())
130 }
131 _ => self.trip(ErrorCode::L003),
132 }
133 }
134
135 pub fn check_time(&mut self) -> Result<(), ErrorCode> {
137 if self.start.elapsed() > self.limits.time {
138 self.trip(ErrorCode::L002)
139 } else {
140 Ok(())
141 }
142 }
143
144 pub fn check_repeat(&mut self, count: u64) -> Result<(), ErrorCode> {
146 if count > self.limits.repeat {
147 self.trip(ErrorCode::L004)
148 } else {
149 Ok(())
150 }
151 }
152
153 pub fn check_length(&mut self, len: u64) -> Result<(), ErrorCode> {
155 if len > self.limits.length {
156 self.trip(ErrorCode::L005)
157 } else {
158 Ok(())
159 }
160 }
161
162 pub fn check_depth(&mut self, depth: u32) -> Result<(), ErrorCode> {
164 if depth > self.limits.depth {
165 self.trip(ErrorCode::L006)
166 } else {
167 Ok(())
168 }
169 }
170
171 pub fn check_enumerate(&mut self, count: u64) -> Result<(), ErrorCode> {
173 if count > self.limits.enumerate {
174 self.trip(ErrorCode::L007)
175 } else {
176 Ok(())
177 }
178 }
179}
180
181#[cfg(test)]
182mod tests {
183 use super::*;
184
185 fn small() -> Limits {
186 Limits {
187 states: 10,
188 repeat: 5,
189 length: 8,
190 depth: 3,
191 enumerate: 4,
192 ..Limits::default()
193 }
194 }
195
196 #[test]
197 fn states_breach_reports_l001_and_remembers_it() {
198 let mut b = Budget::new(small());
199 assert!(b.add_states(10).is_ok());
200 assert_eq!(b.add_states(1), Err(ErrorCode::L001));
201 assert_eq!(b.limit_hit(), Some(ErrorCode::L001));
202 }
203
204 #[test]
205 fn first_breach_wins() {
206 let mut b = Budget::new(small());
207 let _ = b.check_depth(99);
208 let _ = b.check_repeat(99);
209 assert_eq!(b.limit_hit(), Some(ErrorCode::L006));
210 }
211
212 #[test]
213 fn overflow_is_a_breach_not_a_wrap() {
214 let mut b = Budget::new(Limits {
215 states: u64::MAX,
216 ..small()
217 });
218 assert!(b.add_states(u64::MAX).is_ok());
219 assert_eq!(b.add_states(1), Err(ErrorCode::L001));
220 let mut m = Budget::new(Limits {
221 memory_bytes: u64::MAX,
222 ..small()
223 });
224 assert!(m.add_memory(u64::MAX).is_ok());
225 assert_eq!(m.add_memory(1), Err(ErrorCode::L003));
226 }
227
228 #[test]
229 fn boundaries_are_inclusive() {
230 let mut b = Budget::new(small());
231 assert!(b.check_repeat(5).is_ok());
232 assert_eq!(b.check_repeat(6), Err(ErrorCode::L004));
233 let mut b = Budget::new(small());
234 assert!(b.check_length(8).is_ok());
235 assert_eq!(b.check_length(9), Err(ErrorCode::L005));
236 let mut b = Budget::new(small());
237 assert!(b.check_depth(3).is_ok());
238 assert_eq!(b.check_depth(4), Err(ErrorCode::L006));
239 let mut b = Budget::new(small());
240 assert!(b.check_enumerate(4).is_ok());
241 assert_eq!(b.check_enumerate(5), Err(ErrorCode::L007));
242 }
243
244 #[test]
245 fn time_limit_trips_l002() {
246 let mut b = Budget::new(Limits {
247 time: Duration::ZERO,
248 ..small()
249 });
250 std::thread::sleep(Duration::from_millis(2));
251 assert_eq!(b.check_time(), Err(ErrorCode::L002));
252 }
253
254 #[test]
255 fn memory_breach_reports_l003() {
256 let mut b = Budget::new(Limits {
257 memory_bytes: 100,
258 ..small()
259 });
260 assert!(b.add_memory(100).is_ok());
261 assert_eq!(b.add_memory(1), Err(ErrorCode::L003));
262 }
263}