1use std::collections::HashMap;
7use std::time::{Duration, SystemTime, UNIX_EPOCH};
8
9use crate::SecurityError;
10
11const DEFAULT_EXPIRATION_SECS: u64 = 300;
13
14const DEFAULT_MAX_NONCES: usize = 10000;
16
17pub struct NonceStore {
19 nonces: HashMap<u64, u64>,
21 max_nonces: usize,
23 expiration_secs: u64,
25}
26
27impl NonceStore {
28 pub fn new() -> Self {
30 Self {
31 nonces: HashMap::new(),
32 max_nonces: DEFAULT_MAX_NONCES,
33 expiration_secs: DEFAULT_EXPIRATION_SECS,
34 }
35 }
36
37 pub fn with_config(max_nonces: usize, expiration_secs: u64) -> Self {
39 Self {
40 nonces: HashMap::with_capacity(max_nonces),
41 max_nonces,
42 expiration_secs,
43 }
44 }
45
46 pub fn current_timestamp() -> u64 {
48 SystemTime::now()
49 .duration_since(UNIX_EPOCH)
50 .unwrap_or(Duration::ZERO)
51 .as_secs()
52 }
53
54 pub fn is_expired(&self, timestamp: u64) -> bool {
56 let now = Self::current_timestamp();
57 if timestamp > now {
58 return timestamp > now + 60;
60 }
61 now - timestamp > self.expiration_secs
62 }
63
64 pub fn check_nonce(&mut self, nonce: u64, timestamp: u64) -> Result<(), SecurityError> {
69 if self.is_expired(timestamp) {
71 return Err(SecurityError::ExpiredTimestamp);
72 }
73
74 if self.nonces.contains_key(&nonce) {
76 return Err(SecurityError::ReplayAttack);
77 }
78
79 if self.nonces.len() >= self.max_nonces {
81 self.cleanup_expired();
82 if self.nonces.len() >= self.max_nonces {
83 return Err(SecurityError::CapacityExceeded);
84 }
85 }
86
87 self.nonces.insert(nonce, timestamp);
89 Ok(())
90 }
91
92 pub fn cleanup_expired(&mut self) {
94 let now = Self::current_timestamp();
95 self.nonces
96 .retain(|_, &mut ts| now.saturating_sub(ts) <= self.expiration_secs);
97 }
98
99 pub fn len(&self) -> usize {
101 self.nonces.len()
102 }
103
104 pub fn is_empty(&self) -> bool {
106 self.nonces.is_empty()
107 }
108
109 pub fn clear(&mut self) {
111 self.nonces.clear();
112 }
113}
114
115impl Default for NonceStore {
116 fn default() -> Self {
117 Self::new()
118 }
119}
120
121#[cfg(test)]
122mod tests {
123 use super::*;
124
125 #[test]
126 fn test_nonce_store_basic() {
127 let mut store = NonceStore::new();
128 let now = NonceStore::current_timestamp();
129
130 assert!(store.check_nonce(12345, now).is_ok());
132 assert_eq!(store.len(), 1);
133
134 assert_eq!(
136 store.check_nonce(12345, now),
137 Err(SecurityError::ReplayAttack)
138 );
139
140 assert!(store.check_nonce(67890, now).is_ok());
142 assert_eq!(store.len(), 2);
143 }
144
145 #[test]
146 fn test_expired_timestamp() {
147 let mut store = NonceStore::with_config(100, 60); let now = NonceStore::current_timestamp();
149
150 assert!(store.check_nonce(1, now).is_ok());
152
153 let old = now.saturating_sub(120); assert_eq!(
156 store.check_nonce(2, old),
157 Err(SecurityError::ExpiredTimestamp)
158 );
159 }
160
161 #[test]
162 fn test_cleanup_expired() {
163 let mut store = NonceStore::with_config(100, 1); let now = NonceStore::current_timestamp();
165
166 store.nonces.insert(1, now);
168 store.nonces.insert(2, now.saturating_sub(10)); store.nonces.insert(3, now.saturating_sub(10)); store.cleanup_expired();
172
173 assert_eq!(store.len(), 1);
175 assert!(store.nonces.contains_key(&1));
176 }
177
178 #[test]
179 fn test_future_timestamp() {
180 let mut store = NonceStore::new();
181 let now = NonceStore::current_timestamp();
182
183 assert!(store.check_nonce(1, now + 30).is_ok());
185
186 assert_eq!(
188 store.check_nonce(2, now + 120),
189 Err(SecurityError::ExpiredTimestamp)
190 );
191 }
192
193 #[test]
194 fn test_clear() {
195 let mut store = NonceStore::new();
196 let now = NonceStore::current_timestamp();
197
198 store.check_nonce(1, now).unwrap();
199 store.check_nonce(2, now).unwrap();
200 assert_eq!(store.len(), 2);
201
202 store.clear();
203 assert!(store.is_empty());
204
205 assert!(store.check_nonce(1, now).is_ok());
207 }
208}