specter/transport/
session.rs1use std::collections::HashMap;
7use std::sync::{Arc, Mutex};
8use std::time::{Duration, Instant};
9
10#[derive(Debug, Clone)]
16pub struct SessionCache {
17 inner: Arc<Mutex<SessionCacheInner>>,
18}
19
20#[derive(Debug)]
21struct SessionCacheInner {
22 tickets: HashMap<String, SessionTicket>,
24 max_age: Duration,
26}
27
28#[derive(Debug, Clone)]
29struct SessionTicket {
30 data: Vec<u8>,
32 received_at: Instant,
34 max_age: Duration,
36}
37
38impl SessionCache {
39 pub fn new() -> Self {
41 Self {
42 inner: Arc::new(Mutex::new(SessionCacheInner {
43 tickets: HashMap::new(),
44 max_age: Duration::from_secs(86400), })),
46 }
47 }
48
49 pub fn with_max_age(max_age: Duration) -> Self {
51 Self {
52 inner: Arc::new(Mutex::new(SessionCacheInner {
53 tickets: HashMap::new(),
54 max_age,
55 })),
56 }
57 }
58
59 pub fn store_ticket(&self, host: &str, ticket_data: Vec<u8>, max_age: Option<Duration>) {
61 let mut inner = self.inner.lock().expect("Session cache mutex poisoned");
62 let max_age = max_age.unwrap_or(inner.max_age);
63
64 inner.tickets.insert(
65 host.to_string(),
66 SessionTicket {
67 data: ticket_data,
68 received_at: Instant::now(),
69 max_age,
70 },
71 );
72 }
73
74 pub fn get_ticket(&self, host: &str) -> Option<Vec<u8>> {
76 let mut inner = self.inner.lock().expect("Session cache mutex poisoned");
77
78 if let Some(ticket) = inner.tickets.get(host) {
79 if ticket.received_at.elapsed() < ticket.max_age {
81 return Some(ticket.data.clone());
82 } else {
83 inner.tickets.remove(host);
85 }
86 }
87
88 None
89 }
90
91 pub fn clear(&self) {
93 let mut inner = self.inner.lock().expect("Session cache mutex poisoned");
94 inner.tickets.clear();
95 }
96
97 pub fn cleanup_expired(&self) {
99 let mut inner = self.inner.lock().expect("Session cache mutex poisoned");
100 inner
101 .tickets
102 .retain(|_, ticket| ticket.received_at.elapsed() < ticket.max_age);
103 }
104
105 pub fn len(&self) -> usize {
107 let inner = self.inner.lock().expect("Session cache mutex poisoned");
108 inner.tickets.len()
109 }
110
111 pub fn is_empty(&self) -> bool {
113 self.len() == 0
114 }
115}
116
117impl Default for SessionCache {
118 fn default() -> Self {
119 Self::new()
120 }
121}
122
123#[cfg(test)]
124mod tests {
125 use super::*;
126
127 #[test]
128 fn test_session_cache_store_and_retrieve() {
129 let cache = SessionCache::new();
130 cache.store_ticket("example.com", vec![1, 2, 3], None);
131
132 assert_eq!(cache.get_ticket("example.com"), Some(vec![1, 2, 3]));
133 assert_eq!(cache.get_ticket("other.com"), None);
134 }
135
136 #[test]
137 fn test_session_cache_expiration() {
138 let cache = SessionCache::with_max_age(Duration::from_secs(1));
139 cache.store_ticket("example.com", vec![1, 2, 3], None);
140
141 assert_eq!(cache.get_ticket("example.com"), Some(vec![1, 2, 3]));
142
143 std::thread::sleep(Duration::from_secs(2));
145
146 assert_eq!(cache.get_ticket("example.com"), None);
148 assert_eq!(cache.len(), 0);
149 }
150
151 #[test]
152 fn test_session_cache_clear() {
153 let cache = SessionCache::new();
154 cache.store_ticket("example.com", vec![1, 2, 3], None);
155 cache.store_ticket("other.com", vec![4, 5, 6], None);
156
157 assert_eq!(cache.len(), 2);
158 cache.clear();
159 assert_eq!(cache.len(), 0);
160 }
161}