nonce_auth/nonce/storage/
memory.rs1use super::{NonceEntry, NonceStorage, StorageStats};
8use crate::NonceError;
9use crate::nonce::time_utils;
10use async_trait::async_trait;
11use std::collections::HashMap;
12use std::time::Duration;
13
14#[derive(Debug)]
61pub struct MemoryStorage {
62 data: std::sync::Arc<tokio::sync::RwLock<HashMap<String, NonceEntry>>>,
63}
64
65impl MemoryStorage {
66 pub fn new() -> Self {
76 Self {
77 data: std::sync::Arc::new(tokio::sync::RwLock::new(HashMap::new())),
78 }
79 }
80
81 pub fn with_capacity(capacity: usize) -> Self {
99 Self {
100 data: std::sync::Arc::new(tokio::sync::RwLock::new(HashMap::with_capacity(capacity))),
101 }
102 }
103
104 fn make_key(nonce: &str, context: Option<&str>) -> String {
109 match context {
110 Some(ctx) => {
111 let mut key = String::with_capacity(nonce.len() + ctx.len() + 1);
112 key.push_str(nonce);
113 key.push(':');
114 key.push_str(ctx);
115 key
116 }
117 None => {
118 let mut key = String::with_capacity(nonce.len() + 1);
119 key.push_str(nonce);
120 key.push(':');
121 key
122 }
123 }
124 }
125}
126
127#[async_trait]
128impl NonceStorage for MemoryStorage {
129 async fn get(
130 &self,
131 nonce: &str,
132 context: Option<&str>,
133 ) -> Result<Option<NonceEntry>, NonceError> {
134 let key = Self::make_key(nonce, context);
135 let data = self.data.read().await;
136 Ok(data.get(&key).cloned())
137 }
138
139 async fn set(
140 &self,
141 nonce: &str,
142 context: Option<&str>,
143 _ttl: Duration,
144 ) -> Result<(), NonceError> {
145 let key = Self::make_key(nonce, context);
146 let entry = NonceEntry {
147 nonce: nonce.to_string(),
148 created_at: time_utils::current_timestamp()?,
149 context: context.map(|s| s.to_string()),
150 };
151
152 let mut data = self.data.write().await;
153 if data.contains_key(&key) {
154 return Err(NonceError::DuplicateNonce);
155 }
156 data.insert(key, entry);
157 Ok(())
158 }
159
160 async fn exists(&self, nonce: &str, context: Option<&str>) -> Result<bool, NonceError> {
161 let key = Self::make_key(nonce, context);
162 let data = self.data.read().await;
163 Ok(data.contains_key(&key))
164 }
165
166 async fn cleanup_expired(&self, cutoff_time: i64) -> Result<usize, NonceError> {
167 let mut data = self.data.write().await;
168 let initial_count = data.len();
169 data.retain(|_, entry| entry.created_at > cutoff_time);
170 Ok(initial_count - data.len())
171 }
172
173 async fn get_stats(&self) -> Result<StorageStats, NonceError> {
174 let data = self.data.read().await;
175
176 let base_entry_size = std::mem::size_of::<NonceEntry>();
178 let mut total_memory = data.len() * base_entry_size;
179
180 for (key, entry) in data.iter() {
182 total_memory += key.len(); total_memory += entry.nonce.len(); if let Some(ctx) = &entry.context {
185 total_memory += ctx.len(); }
187 }
188
189 total_memory += data.capacity() * std::mem::size_of::<(String, NonceEntry)>();
191
192 Ok(StorageStats {
193 total_records: data.len(),
194 backend_info: format!(
195 "In-memory HashMap storage (~{} bytes, capacity: {})",
196 total_memory,
197 data.capacity()
198 ),
199 })
200 }
201}
202
203impl MemoryStorage {
205 pub async fn batch_set(
239 &self,
240 nonces: Vec<(&str, Option<&str>)>,
241 _ttl: Duration,
242 ) -> Result<usize, NonceError> {
243 let created_at = time_utils::current_timestamp()?;
244 let mut data = self.data.write().await;
245 let mut success_count = 0;
246
247 for (nonce, context) in nonces {
248 let key = Self::make_key(nonce, context);
249
250 if let std::collections::hash_map::Entry::Vacant(e) = data.entry(key) {
251 let entry = NonceEntry {
252 nonce: nonce.to_string(),
253 created_at,
254 context: context.map(|s| s.to_string()),
255 };
256 e.insert(entry);
257 success_count += 1;
258 }
259 }
261
262 Ok(success_count)
263 }
264
265 pub async fn batch_exists(
295 &self,
296 nonces: Vec<(&str, Option<&str>)>,
297 ) -> Result<Vec<bool>, NonceError> {
298 let data = self.data.read().await;
299 let mut results = Vec::with_capacity(nonces.len());
300
301 for (nonce, context) in nonces {
302 let key = Self::make_key(nonce, context);
303 results.push(data.contains_key(&key));
304 }
305
306 Ok(results)
307 }
308
309 pub async fn batch_get(
339 &self,
340 nonces: Vec<(&str, Option<&str>)>,
341 ) -> Result<Vec<Option<NonceEntry>>, NonceError> {
342 let data = self.data.read().await;
343 let mut results = Vec::with_capacity(nonces.len());
344
345 for (nonce, context) in nonces {
346 let key = Self::make_key(nonce, context);
347 results.push(data.get(&key).cloned());
348 }
349
350 Ok(results)
351 }
352}
353
354impl Default for MemoryStorage {
355 fn default() -> Self {
356 Self::new()
357 }
358}
359
360#[cfg(test)]
361mod tests {
362 use super::*;
363 use std::time::{SystemTime, UNIX_EPOCH};
364
365 #[tokio::test]
366 async fn test_memory_storage_basic_operations() -> Result<(), NonceError> {
367 let storage = MemoryStorage::new();
368
369 storage
371 .set("test-nonce", None, Duration::from_secs(300))
372 .await?;
373 assert!(storage.exists("test-nonce", None).await?);
374
375 let entry = storage.get("test-nonce", None).await?;
377 assert!(entry.is_some());
378 let entry = entry.unwrap();
379 assert_eq!(entry.nonce, "test-nonce");
380 assert!(entry.context.is_none());
381
382 Ok(())
383 }
384
385 #[tokio::test]
386 async fn test_memory_storage_duplicate_nonce() -> Result<(), NonceError> {
387 let storage = MemoryStorage::new();
388
389 storage
391 .set("test-nonce", None, Duration::from_secs(300))
392 .await?;
393
394 let result = storage
396 .set("test-nonce", None, Duration::from_secs(300))
397 .await;
398 assert!(matches!(result, Err(NonceError::DuplicateNonce)));
399
400 Ok(())
401 }
402
403 #[tokio::test]
404 async fn test_memory_storage_context_isolation() -> Result<(), NonceError> {
405 let storage = MemoryStorage::new();
406
407 storage
409 .set("test-nonce", Some("context1"), Duration::from_secs(300))
410 .await?;
411 storage
412 .set("test-nonce", Some("context2"), Duration::from_secs(300))
413 .await?;
414
415 assert!(storage.exists("test-nonce", Some("context1")).await?);
417 assert!(storage.exists("test-nonce", Some("context2")).await?);
418
419 assert!(!storage.exists("test-nonce", Some("context3")).await?);
421
422 Ok(())
423 }
424
425 #[tokio::test]
426 async fn test_memory_storage_cleanup() -> Result<(), NonceError> {
427 let storage = MemoryStorage::new();
428
429 storage
431 .set("old-nonce", None, Duration::from_secs(300))
432 .await?;
433 storage
434 .set("new-nonce", None, Duration::from_secs(300))
435 .await?;
436
437 let future_time = SystemTime::now()
439 .duration_since(UNIX_EPOCH)
440 .unwrap()
441 .as_secs() as i64
442 + 3600;
443
444 let removed = storage.cleanup_expired(future_time).await?;
445 assert_eq!(removed, 2);
446
447 assert!(!storage.exists("old-nonce", None).await?);
449 assert!(!storage.exists("new-nonce", None).await?);
450
451 Ok(())
452 }
453
454 #[tokio::test]
455 async fn test_memory_storage_stats() -> Result<(), NonceError> {
456 let storage = MemoryStorage::new();
457
458 let stats = storage.get_stats().await?;
460 assert_eq!(stats.total_records, 0);
461 assert!(stats.backend_info.contains("In-memory"));
462
463 storage
465 .set("nonce1", None, Duration::from_secs(300))
466 .await?;
467 storage
468 .set("nonce2", Some("context"), Duration::from_secs(300))
469 .await?;
470
471 let stats = storage.get_stats().await?;
473 assert_eq!(stats.total_records, 2);
474 assert!(stats.backend_info.contains("In-memory"));
475 assert!(stats.backend_info.contains("bytes"));
476 assert!(stats.backend_info.contains("capacity"));
477
478 Ok(())
479 }
480
481 #[tokio::test]
482 async fn test_memory_storage_with_capacity() -> Result<(), NonceError> {
483 let storage = MemoryStorage::with_capacity(100);
484
485 storage.set("test1", None, Duration::from_secs(300)).await?;
487 storage
488 .set("test2", Some("ctx"), Duration::from_secs(300))
489 .await?;
490
491 let stats = storage.get_stats().await?;
492 assert_eq!(stats.total_records, 2);
493 assert!(stats.backend_info.contains("capacity"));
495
496 Ok(())
497 }
498
499 #[tokio::test]
500 async fn test_batch_operations() -> Result<(), NonceError> {
501 let storage = MemoryStorage::new();
502
503 let nonces = vec![
505 ("batch1", None),
506 ("batch2", Some("ctx1")),
507 ("batch3", Some("ctx2")),
508 ("batch1", None), ];
510
511 let inserted = storage.batch_set(nonces, Duration::from_secs(300)).await?;
512 assert_eq!(inserted, 3); let check_nonces = vec![
516 ("batch1", None),
517 ("batch2", Some("ctx1")),
518 ("batch3", Some("ctx2")),
519 ("batch4", None), ];
521
522 let exists_results = storage.batch_exists(check_nonces).await?;
523 assert_eq!(exists_results, vec![true, true, true, false]);
524
525 let get_nonces = vec![
527 ("batch1", None),
528 ("batch4", None), ];
530
531 let get_results = storage.batch_get(get_nonces).await?;
532 assert!(get_results[0].is_some());
533 assert!(get_results[1].is_none());
534
535 Ok(())
536 }
537
538 #[tokio::test]
539 async fn test_memory_storage_key_generation() {
540 assert_eq!(MemoryStorage::make_key("nonce1", None), "nonce1:");
542 assert_eq!(MemoryStorage::make_key("nonce1", Some("ctx")), "nonce1:ctx");
543 assert_eq!(MemoryStorage::make_key("nonce1", Some("")), "nonce1:");
544 }
545
546 #[tokio::test]
547 async fn test_memory_storage_concurrent_access() -> Result<(), NonceError> {
548 let storage = std::sync::Arc::new(MemoryStorage::new());
549 let mut handles = vec![];
550
551 for i in 0..10 {
553 let storage_clone = std::sync::Arc::clone(&storage);
554 let handle = tokio::spawn(async move {
555 storage_clone
556 .set(&format!("nonce-{i}"), None, Duration::from_secs(300))
557 .await
558 });
559 handles.push(handle);
560 }
561
562 for handle in handles {
564 assert!(handle.await.unwrap().is_ok());
565 }
566
567 let stats = storage.get_stats().await?;
569 assert_eq!(stats.total_records, 10);
570
571 Ok(())
572 }
573
574 #[tokio::test]
575 async fn test_memory_usage_calculation() -> Result<(), NonceError> {
576 let storage = MemoryStorage::new();
577
578 storage.set("short", None, Duration::from_secs(300)).await?;
580 storage
581 .set(
582 "very_long_nonce_name_for_testing",
583 Some("long_context_name"),
584 Duration::from_secs(300),
585 )
586 .await?;
587
588 let stats = storage.get_stats().await?;
589 assert_eq!(stats.total_records, 2);
590
591 assert!(stats.backend_info.contains("bytes"));
593 assert!(stats.backend_info.contains("capacity"));
594
595 Ok(())
596 }
597}