1use std::time::{SystemTime, UNIX_EPOCH};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
6pub enum Duration {
7 Seconds(u64),
8 Minutes(u64),
9 Hours(u64),
10 Days(u64),
11}
12
13impl Duration {
14 pub fn seconds(n: u64) -> Self {
15 Duration::Seconds(n)
16 }
17 pub fn minutes(n: u64) -> Self {
18 Duration::Minutes(n)
19 }
20 pub fn hours(n: u64) -> Self {
21 Duration::Hours(n)
22 }
23 pub fn days(n: u64) -> Self {
24 Duration::Days(n)
25 }
26
27 pub fn as_seconds(&self) -> u64 {
28 match self {
29 Duration::Seconds(n) => *n,
30 Duration::Minutes(n) => n * 60,
31 Duration::Hours(n) => n * 3600,
32 Duration::Days(n) => n * 86400,
33 }
34 }
35
36 pub fn is_short_interval(&self) -> bool {
37 self.as_seconds() <= 300 }
39}
40
41#[derive(Debug, Clone)]
42pub struct RuleConfig {
43 pub interval: Duration,
44 pub limit: u32,
45}
46
47impl RuleConfig {
48 pub fn new(interval: Duration, limit: u32) -> Self {
49 Self { interval, limit }
50 }
51}
52
53#[derive(Debug, Clone)]
54pub struct RequestRecord {
55 pub count: u32,
56 pub window_start: u64,
57 pub timestamps: Vec<u64>,
58}
59
60impl RequestRecord {
61 pub fn new(is_short_interval: bool) -> Self {
62 Self {
63 count: 0,
64 window_start: current_timestamp(),
65 timestamps: if is_short_interval {
66 Vec::new()
67 } else {
68 Vec::with_capacity(16)
69 },
70 }
71 }
72
73 pub fn add_request(&mut self, is_short_interval: bool, window_size: u64) {
74 let now = current_timestamp();
75
76 if is_short_interval {
77 if now.saturating_sub(self.window_start) >= window_size {
78 self.window_start = now;
79 self.count = 1;
80 } else {
81 self.count += 1;
82 }
83 } else {
84 self.timestamps.push(now);
85 let cutoff = now.saturating_sub(window_size);
86 self.timestamps.retain(|&t| t > cutoff);
87 self.count = self.timestamps.len() as u32;
88 }
89 }
90
91 pub fn is_limit_exceeded(&self, limit: u32, is_short_interval: bool, window_size: u64) -> bool {
92 let now = current_timestamp();
93 if is_short_interval {
94 if now.saturating_sub(self.window_start) >= window_size {
95 false
96 } else {
97 self.count >= limit
98 }
99 } else {
100 let cutoff = now.saturating_sub(window_size);
101 let valid_requests = self.timestamps.iter().filter(|&&t| t > cutoff).count() as u32;
102 valid_requests >= limit
103 }
104 }
105
106 pub fn memory_usage(&self) -> usize {
107 std::mem::size_of::<Self>() + self.timestamps.capacity() * std::mem::size_of::<u64>()
108 }
109
110 pub fn should_cleanup(&self, max_age_seconds: u64) -> bool {
111 let now = current_timestamp();
112 let last_activity = if !self.timestamps.is_empty() {
113 *self.timestamps.last().unwrap_or(&self.window_start)
114 } else {
115 self.window_start
116 };
117 now.saturating_sub(last_activity) > max_age_seconds
118 }
119}
120
121pub fn current_timestamp() -> u64 {
122 SystemTime::now()
123 .duration_since(UNIX_EPOCH)
124 .expect("Time went backwards")
125 .as_secs()
126}