ant_quic/bootstrap_cache/
token_store.rs1use crate::bootstrap_cache::BootstrapCache;
11use crate::nat_traversal_api::PeerId;
12use crate::token::TokenStore;
13use bytes::Bytes;
14use std::collections::HashMap;
15use std::sync::{Arc, RwLock};
16use tracing::{debug, warn};
17
18#[derive(Debug)]
23pub struct BootstrapTokenStore {
24 cache: Arc<BootstrapCache>,
26 local_cache: Arc<RwLock<HashMap<String, Vec<u8>>>>,
29}
30
31impl BootstrapTokenStore {
32 pub async fn new(cache: Arc<BootstrapCache>) -> Self {
36 let tokens = cache.get_all_tokens().await;
37 let mut local = HashMap::new();
38
39 for (peer_id, token) in tokens {
40 let key = hex::encode(peer_id.0);
42 local.insert(key, token);
43 }
44
45 debug!(
46 "Initialized BootstrapTokenStore with {} tokens",
47 local.len()
48 );
49
50 Self {
51 cache,
52 local_cache: Arc::new(RwLock::new(local)),
53 }
54 }
55}
56
57impl TokenStore for BootstrapTokenStore {
58 fn insert(&self, server_name: &str, token: Bytes) {
59 let token_vec = token.to_vec();
60
61 if let Ok(mut local) = self.local_cache.write() {
63 local.insert(server_name.to_string(), token_vec.clone());
64 } else {
65 warn!("Failed to acquire write lock on local token cache");
66 }
67
68 if let Ok(bytes) = hex::decode(server_name) {
71 if let Ok(arr) = <[u8; 32]>::try_from(bytes) {
72 let peer_id = PeerId(arr);
73 let cache = self.cache.clone();
74 let token_clone = token_vec;
75
76 tokio::spawn(async move {
78 cache.update_token(peer_id, token_clone).await;
79 });
80 return;
81 }
82 }
83
84 debug!(
88 "Received token for non-PeerId server name: {}, not persisting to disk",
89 server_name
90 );
91 }
92
93 fn take(&self, server_name: &str) -> Option<Bytes> {
94 if let Ok(mut local) = self.local_cache.write() {
95 local.remove(server_name).map(Bytes::from)
96 } else {
97 warn!("Failed to acquire write lock on local token cache");
98 None
99 }
100 }
101}
102
103#[cfg(test)]
104mod tests {
105 use super::*;
106 use crate::bootstrap_cache::BootstrapCacheConfig;
107 use tempfile::TempDir;
108
109 async fn create_test_cache(temp_dir: &TempDir) -> Arc<BootstrapCache> {
110 let config = BootstrapCacheConfig::builder()
111 .cache_dir(temp_dir.path())
112 .max_peers(100)
113 .epsilon(0.0)
114 .min_peers_to_save(1)
115 .build();
116
117 Arc::new(
118 BootstrapCache::open(config)
119 .await
120 .expect("Failed to create cache"),
121 )
122 }
123
124 #[tokio::test]
125 async fn insert_and_take_valid_peer_id() {
126 let temp_dir = TempDir::new().expect("Failed to create temp dir");
127 let cache = create_test_cache(&temp_dir).await;
128 let store = BootstrapTokenStore::new(cache).await;
129
130 let peer_id_hex = hex::encode([0xAB; 32]);
132 let token = Bytes::from_static(b"test_token_data");
133
134 store.insert(&peer_id_hex, token.clone());
136
137 let taken = store.take(&peer_id_hex);
139 assert!(taken.is_some(), "First take should return token");
140 assert_eq!(taken.expect("should have token"), token);
141
142 let taken_again = store.take(&peer_id_hex);
144 assert!(
145 taken_again.is_none(),
146 "Second take should return None (one-shot)"
147 );
148 }
149
150 #[tokio::test]
151 async fn take_nonexistent_returns_none() {
152 let temp_dir = TempDir::new().expect("Failed to create temp dir");
153 let cache = create_test_cache(&temp_dir).await;
154 let store = BootstrapTokenStore::new(cache).await;
155
156 let result = store.take("nonexistent_key");
157 assert!(result.is_none());
158 }
159
160 #[tokio::test]
161 async fn insert_non_peer_id_server_name() {
162 let temp_dir = TempDir::new().expect("Failed to create temp dir");
163 let cache = create_test_cache(&temp_dir).await;
164 let store = BootstrapTokenStore::new(cache).await;
165
166 let test_cases = ["192.168.1.1:8000", "server.example.com", "localhost", "::1"];
168
169 for server_name in test_cases {
170 let token = Bytes::from(format!("token_for_{}", server_name));
171
172 store.insert(server_name, token.clone());
174
175 let taken = store.take(server_name);
177 assert!(
178 taken.is_some(),
179 "Should be able to take token for {}",
180 server_name
181 );
182 assert_eq!(taken.expect("should have token"), token);
183 }
184 }
185
186 #[tokio::test]
187 async fn hex_decode_edge_cases() {
188 let temp_dir = TempDir::new().expect("Failed to create temp dir");
189 let cache = create_test_cache(&temp_dir).await;
190 let store = BootstrapTokenStore::new(cache).await;
191
192 let edge_cases = [
194 "", "abc", "gggg", "00112233", &hex::encode([0xFF; 16]), ];
200
201 for server_name in edge_cases {
202 let token = Bytes::from_static(b"edge_case_token");
203
204 store.insert(server_name, token.clone());
206
207 let taken = store.take(server_name);
209 assert!(
210 taken.is_some(),
211 "Should take token for edge case: '{}'",
212 server_name
213 );
214 }
215 }
216
217 #[tokio::test]
218 async fn multiple_tokens_different_peers() {
219 let temp_dir = TempDir::new().expect("Failed to create temp dir");
220 let cache = create_test_cache(&temp_dir).await;
221 let store = BootstrapTokenStore::new(cache).await;
222
223 let peer1 = hex::encode([0x11; 32]);
225 let peer2 = hex::encode([0x22; 32]);
226 let peer3 = hex::encode([0x33; 32]);
227
228 store.insert(&peer1, Bytes::from_static(b"token1"));
229 store.insert(&peer2, Bytes::from_static(b"token2"));
230 store.insert(&peer3, Bytes::from_static(b"token3"));
231
232 assert_eq!(store.take(&peer1), Some(Bytes::from_static(b"token1")));
234 assert_eq!(store.take(&peer2), Some(Bytes::from_static(b"token2")));
235 assert_eq!(store.take(&peer3), Some(Bytes::from_static(b"token3")));
236
237 assert!(store.take(&peer1).is_none());
239 assert!(store.take(&peer2).is_none());
240 assert!(store.take(&peer3).is_none());
241 }
242
243 #[tokio::test]
244 async fn overwrite_token_for_same_peer() {
245 let temp_dir = TempDir::new().expect("Failed to create temp dir");
246 let cache = create_test_cache(&temp_dir).await;
247 let store = BootstrapTokenStore::new(cache).await;
248
249 let peer_id = hex::encode([0xAA; 32]);
250
251 store.insert(&peer_id, Bytes::from_static(b"first_token"));
253
254 store.insert(&peer_id, Bytes::from_static(b"second_token"));
256
257 let taken = store.take(&peer_id);
259 assert_eq!(
260 taken,
261 Some(Bytes::from_static(b"second_token")),
262 "Should return the most recently inserted token"
263 );
264 }
265}