1use crate::access_policy::{AccessClass, Denial};
12use std::collections::BTreeMap;
13
14pub const BASELINE_AUDIT_CAPACITY: u32 = 1024;
17pub const BASELINE_MAX_SNAPSHOT_ITEMS: u32 = 64;
18pub const BASELINE_MAX_SEQUENCE_ITEMS: u32 = 64;
19pub const BASELINE_MAX_DELAY_NS: u64 = 1_000_000_000;
20pub const BASELINE_MAX_SEQUENCE_DURATION_NS: u64 = 1_000_000_000;
21pub const BASELINE_MAX_DEADLINE_NS: u64 = 1_000_000_000;
22
23#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
25pub struct ProxyConfig {
26 pub enabled: bool,
28 pub audit_capacity: u32,
30 pub max_snapshot_items: u32,
32 pub max_sequence_items: u32,
34 pub max_delay_ns: u64,
36 pub max_sequence_duration_ns: u64,
38 pub max_deadline_ns: u64,
40 pub max_ops_per_second: u32,
42}
43
44impl Default for ProxyConfig {
45 fn default() -> Self {
46 Self {
47 enabled: true,
48 audit_capacity: BASELINE_AUDIT_CAPACITY,
49 max_snapshot_items: BASELINE_MAX_SNAPSHOT_ITEMS,
50 max_sequence_items: BASELINE_MAX_SEQUENCE_ITEMS,
51 max_delay_ns: BASELINE_MAX_DELAY_NS,
52 max_sequence_duration_ns: BASELINE_MAX_SEQUENCE_DURATION_NS,
53 max_deadline_ns: BASELINE_MAX_DEADLINE_NS,
54 max_ops_per_second: 0,
55 }
56 }
57}
58
59#[derive(Clone, Debug, PartialEq, Eq)]
61pub enum ConfigNarrowingError {
62 WidenedEnabledState,
63 WidenedAuditCapacity { configured: u32, baseline: u32 },
64 WidenedMaxSnapshotItems { configured: u32, baseline: u32 },
65 WidenedMaxSequenceItems { configured: u32, baseline: u32 },
66 WidenedMaxDelay { configured: u64, baseline: u64 },
67 WidenedMaxSequenceDuration { configured: u64, baseline: u64 },
68 WidenedMaxDeadline { configured: u64, baseline: u64 },
69 WidenedMaxOpsPerSecond { configured: u32, baseline: u32 },
70}
71
72impl std::fmt::Display for ConfigNarrowingError {
73 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
74 match self {
75 Self::WidenedEnabledState => write!(f, "cannot enable proxy when baseline is disabled"),
76 Self::WidenedAuditCapacity { configured, baseline } => {
77 write!(f, "audit_capacity {configured} exceeds baseline {baseline}")
78 }
79 Self::WidenedMaxSnapshotItems { configured, baseline } => {
80 write!(f, "max_snapshot_items {configured} exceeds baseline {baseline}")
81 }
82 Self::WidenedMaxSequenceItems { configured, baseline } => {
83 write!(f, "max_sequence_items {configured} exceeds baseline {baseline}")
84 }
85 Self::WidenedMaxDelay { configured, baseline } => {
86 write!(f, "max_delay_ns {configured} exceeds baseline {baseline}")
87 }
88 Self::WidenedMaxSequenceDuration { configured, baseline } => {
89 write!(f, "max_sequence_duration_ns {configured} exceeds baseline {baseline}")
90 }
91 Self::WidenedMaxDeadline { configured, baseline } => {
92 write!(f, "max_deadline_ns {configured} exceeds baseline {baseline}")
93 }
94 Self::WidenedMaxOpsPerSecond { configured, baseline } => {
95 write!(f, "max_ops_per_second {configured} widens baseline limit {baseline}")
96 }
97 }
98 }
99}
100
101impl std::error::Error for ConfigNarrowingError {}
102
103impl ProxyConfig {
104 pub fn narrow_with(&self, runtime: &Self) -> Result<Self, ConfigNarrowingError> {
106 if !self.enabled && runtime.enabled {
107 return Err(ConfigNarrowingError::WidenedEnabledState);
108 }
109 if runtime.audit_capacity > self.audit_capacity {
110 return Err(ConfigNarrowingError::WidenedAuditCapacity {
111 configured: runtime.audit_capacity,
112 baseline: self.audit_capacity,
113 });
114 }
115 if runtime.max_snapshot_items > self.max_snapshot_items {
116 return Err(ConfigNarrowingError::WidenedMaxSnapshotItems {
117 configured: runtime.max_snapshot_items,
118 baseline: self.max_snapshot_items,
119 });
120 }
121 if runtime.max_sequence_items > self.max_sequence_items {
122 return Err(ConfigNarrowingError::WidenedMaxSequenceItems {
123 configured: runtime.max_sequence_items,
124 baseline: self.max_sequence_items,
125 });
126 }
127 if runtime.max_delay_ns > self.max_delay_ns {
128 return Err(ConfigNarrowingError::WidenedMaxDelay {
129 configured: runtime.max_delay_ns,
130 baseline: self.max_delay_ns,
131 });
132 }
133 if runtime.max_sequence_duration_ns > self.max_sequence_duration_ns {
134 return Err(ConfigNarrowingError::WidenedMaxSequenceDuration {
135 configured: runtime.max_sequence_duration_ns,
136 baseline: self.max_sequence_duration_ns,
137 });
138 }
139 if runtime.max_deadline_ns > self.max_deadline_ns {
140 return Err(ConfigNarrowingError::WidenedMaxDeadline {
141 configured: runtime.max_deadline_ns,
142 baseline: self.max_deadline_ns,
143 });
144 }
145 if self.max_ops_per_second > 0
146 && (runtime.max_ops_per_second == 0
147 || runtime.max_ops_per_second > self.max_ops_per_second)
148 {
149 return Err(ConfigNarrowingError::WidenedMaxOpsPerSecond {
150 configured: runtime.max_ops_per_second,
151 baseline: self.max_ops_per_second,
152 });
153 }
154 Ok(*runtime)
155 }
156}
157
158#[derive(Clone, Debug, PartialEq, Eq)]
160pub struct RateLimiter {
161 max_ops_per_second: u32,
162 window_start_ns: i64,
163 ops_in_window: u32,
164}
165
166impl RateLimiter {
167 pub fn new(max_ops_per_second: u32) -> Self {
168 Self { max_ops_per_second, window_start_ns: 0, ops_in_window: 0 }
169 }
170
171 pub fn check_and_record(&mut self, now_ns: i64) -> Result<(), Denial> {
173 if self.max_ops_per_second == 0 {
174 return Ok(());
175 }
176 let window_duration_ns: i64 = 1_000_000_000;
177 if now_ns - self.window_start_ns >= window_duration_ns {
178 self.window_start_ns = now_ns;
179 self.ops_in_window = 1;
180 Ok(())
181 } else if self.ops_in_window < self.max_ops_per_second {
182 self.ops_in_window += 1;
183 Ok(())
184 } else {
185 Err(Denial::LimitExceeded)
186 }
187 }
188}
189
190#[derive(Clone, Debug, PartialEq, Eq)]
192pub struct AccessLimitEnforcer {
193 global_rate_limiter: RateLimiter,
194 per_class_rate_limiters: BTreeMap<AccessClass, RateLimiter>,
195 max_deadline_ns: u64,
196}
197
198impl AccessLimitEnforcer {
199 pub fn new(max_ops_per_second: u32, max_deadline_ns: u64) -> Self {
200 Self {
201 global_rate_limiter: RateLimiter::new(max_ops_per_second),
202 per_class_rate_limiters: BTreeMap::new(),
203 max_deadline_ns,
204 }
205 }
206
207 pub fn set_class_rate_limit(&mut self, class: AccessClass, max_ops_per_second: u32) {
208 self.per_class_rate_limiters.insert(class, RateLimiter::new(max_ops_per_second));
209 }
210
211 pub fn check_access(&mut self, class: AccessClass, now_ns: i64) -> Result<(), Denial> {
213 self.global_rate_limiter.check_and_record(now_ns)?;
214 if let Some(limiter) = self.per_class_rate_limiters.get_mut(&class) {
215 limiter.check_and_record(now_ns)?;
216 }
217 Ok(())
218 }
219
220 pub fn check_deadline(&self, requested_timeout_ns: i64) -> Result<(), Denial> {
222 if requested_timeout_ns < 0 || (requested_timeout_ns as u64) > self.max_deadline_ns {
223 Err(Denial::LimitExceeded)
224 } else {
225 Ok(())
226 }
227 }
228}
229
230#[cfg(test)]
231mod tests {
232 use super::*;
233
234 #[test]
235 fn test_proxy_config_defaults() {
236 let config = ProxyConfig::default();
237 assert!(config.enabled);
238 assert_eq!(config.audit_capacity, BASELINE_AUDIT_CAPACITY);
239 assert_eq!(config.max_snapshot_items, BASELINE_MAX_SNAPSHOT_ITEMS);
240 assert_eq!(config.max_sequence_items, BASELINE_MAX_SEQUENCE_ITEMS);
241 assert_eq!(config.max_delay_ns, BASELINE_MAX_DELAY_NS);
242 assert_eq!(config.max_sequence_duration_ns, BASELINE_MAX_SEQUENCE_DURATION_NS);
243 assert_eq!(config.max_deadline_ns, BASELINE_MAX_DEADLINE_NS);
244 assert_eq!(config.max_ops_per_second, 0);
245 }
246
247 #[test]
248 fn test_narrow_with_accepts_valid_reductions() {
249 let baseline = ProxyConfig::default();
250 let reduced = ProxyConfig {
251 enabled: false,
252 audit_capacity: 512,
253 max_snapshot_items: 16,
254 max_sequence_items: 16,
255 max_delay_ns: 50_000_000,
256 max_sequence_duration_ns: 500_000_000,
257 max_deadline_ns: 500_000_000,
258 max_ops_per_second: 100,
259 };
260 let narrowed = baseline.narrow_with(&reduced).unwrap();
261 assert_eq!(narrowed, reduced);
262 }
263
264 #[test]
265 fn test_narrow_with_rejects_widening() {
266 let baseline = ProxyConfig { enabled: false, ..ProxyConfig::default() };
267 assert_eq!(
268 baseline.narrow_with(&ProxyConfig::default()),
269 Err(ConfigNarrowingError::WidenedEnabledState)
270 );
271
272 let baseline = ProxyConfig::default();
273 let mut widened = baseline;
274 widened.audit_capacity = 2048;
275 assert!(matches!(
276 baseline.narrow_with(&widened),
277 Err(ConfigNarrowingError::WidenedAuditCapacity { .. })
278 ));
279
280 let mut widened = baseline;
281 widened.max_snapshot_items = 128;
282 assert!(matches!(
283 baseline.narrow_with(&widened),
284 Err(ConfigNarrowingError::WidenedMaxSnapshotItems { .. })
285 ));
286
287 let mut widened = baseline;
288 widened.max_sequence_items = 128;
289 assert!(matches!(
290 baseline.narrow_with(&widened),
291 Err(ConfigNarrowingError::WidenedMaxSequenceItems { .. })
292 ));
293
294 let mut widened = baseline;
295 widened.max_delay_ns = 2_000_000_000;
296 assert!(matches!(
297 baseline.narrow_with(&widened),
298 Err(ConfigNarrowingError::WidenedMaxDelay { .. })
299 ));
300
301 let mut widened = baseline;
302 widened.max_sequence_duration_ns = 2_000_000_000;
303 assert!(matches!(
304 baseline.narrow_with(&widened),
305 Err(ConfigNarrowingError::WidenedMaxSequenceDuration { .. })
306 ));
307
308 let mut widened = baseline;
309 widened.max_deadline_ns = 2_000_000_000;
310 assert!(matches!(
311 baseline.narrow_with(&widened),
312 Err(ConfigNarrowingError::WidenedMaxDeadline { .. })
313 ));
314
315 let baseline = ProxyConfig { max_ops_per_second: 50, ..ProxyConfig::default() };
316 let mut widened = baseline;
317 widened.max_ops_per_second = 100;
318 assert!(matches!(
319 baseline.narrow_with(&widened),
320 Err(ConfigNarrowingError::WidenedMaxOpsPerSecond { .. })
321 ));
322 }
323
324 #[test]
325 fn test_rate_limiter() {
326 let mut limiter = RateLimiter::new(2);
327 let t0 = 1_000_000_000;
328 assert!(limiter.check_and_record(t0).is_ok());
329 assert!(limiter.check_and_record(t0 + 100_000_000).is_ok());
330 assert_eq!(limiter.check_and_record(t0 + 200_000_000), Err(Denial::LimitExceeded));
332
333 let t1 = t0 + 1_000_000_001;
335 assert!(limiter.check_and_record(t1).is_ok());
336 }
337
338 #[test]
339 fn test_access_limit_enforcer_and_deadline() {
340 let mut enforcer = AccessLimitEnforcer::new(100, 1_000_000_000);
341 enforcer.set_class_rate_limit(AccessClass::Poll, 1);
342
343 assert!(enforcer.check_access(AccessClass::ReadOnce, 1_000_000).is_ok());
344 assert!(enforcer.check_access(AccessClass::Poll, 1_000_000).is_ok());
345 assert_eq!(enforcer.check_access(AccessClass::Poll, 1_500_000), Err(Denial::LimitExceeded));
347 assert!(enforcer.check_access(AccessClass::ReadOnce, 1_500_000).is_ok());
349
350 assert!(enforcer.check_deadline(500_000_000).is_ok());
352 assert_eq!(enforcer.check_deadline(1_500_000_000), Err(Denial::LimitExceeded));
353 assert_eq!(enforcer.check_deadline(-1), Err(Denial::LimitExceeded));
354 }
355}