ant_quic/bootstrap_cache/
config.rs1use std::path::PathBuf;
11use std::time::Duration;
12
13#[derive(Debug, Clone)]
15pub struct BootstrapCacheConfig {
16 pub cache_dir: PathBuf,
18
19 pub max_peers: usize,
21
22 pub epsilon: f64,
25
26 pub stale_threshold: Duration,
28
29 pub reachability_ttl: Duration,
31
32 pub save_interval: Duration,
34
35 pub quality_update_interval: Duration,
37
38 pub cleanup_interval: Duration,
40
41 pub min_peers_to_save: usize,
43
44 pub enable_file_locking: bool,
46
47 pub persist: bool,
54
55 pub weights: QualityWeights,
57}
58
59#[derive(Debug, Clone)]
61pub struct QualityWeights {
62 pub success_rate: f64,
64 pub rtt: f64,
66 pub freshness: f64,
68 pub capabilities: f64,
70}
71
72impl Default for BootstrapCacheConfig {
73 fn default() -> Self {
74 Self {
75 cache_dir: default_cache_dir(),
76 max_peers: 30_000,
77 epsilon: 0.1,
78 stale_threshold: Duration::from_secs(7 * 24 * 3600), reachability_ttl: crate::reachability::DIRECT_REACHABILITY_TTL,
80 save_interval: Duration::from_secs(5 * 60), quality_update_interval: Duration::from_secs(3600), cleanup_interval: Duration::from_secs(6 * 3600), min_peers_to_save: 10,
84 enable_file_locking: true,
85 persist: true,
86 weights: QualityWeights::default(),
87 }
88 }
89}
90
91impl Default for QualityWeights {
92 fn default() -> Self {
93 Self {
94 success_rate: 0.4,
95 rtt: 0.25,
96 freshness: 0.15,
97 capabilities: 0.2,
98 }
99 }
100}
101
102impl BootstrapCacheConfig {
103 pub fn builder() -> BootstrapCacheConfigBuilder {
105 BootstrapCacheConfigBuilder::default()
106 }
107}
108
109#[derive(Default)]
111pub struct BootstrapCacheConfigBuilder {
112 config: BootstrapCacheConfig,
113}
114
115impl BootstrapCacheConfigBuilder {
116 pub fn cache_dir(mut self, dir: impl Into<PathBuf>) -> Self {
118 self.config.cache_dir = dir.into();
119 self
120 }
121
122 pub fn max_peers(mut self, max: usize) -> Self {
124 self.config.max_peers = max;
125 self
126 }
127
128 pub fn epsilon(mut self, epsilon: f64) -> Self {
130 self.config.epsilon = epsilon.clamp(0.0, 1.0);
131 self
132 }
133
134 pub fn reachability_ttl(mut self, ttl: Duration) -> Self {
136 self.config.reachability_ttl = ttl;
137 self
138 }
139
140 pub fn stale_threshold(mut self, duration: Duration) -> Self {
142 self.config.stale_threshold = duration;
143 self
144 }
145
146 pub fn save_interval(mut self, duration: Duration) -> Self {
148 self.config.save_interval = duration;
149 self
150 }
151
152 pub fn quality_update_interval(mut self, duration: Duration) -> Self {
154 self.config.quality_update_interval = duration;
155 self
156 }
157
158 pub fn cleanup_interval(mut self, duration: Duration) -> Self {
160 self.config.cleanup_interval = duration;
161 self
162 }
163
164 pub fn min_peers_to_save(mut self, min: usize) -> Self {
166 self.config.min_peers_to_save = min;
167 self
168 }
169
170 pub fn enable_file_locking(mut self, enable: bool) -> Self {
172 self.config.enable_file_locking = enable;
173 self
174 }
175
176 pub fn persist(mut self, persist: bool) -> Self {
178 self.config.persist = persist;
179 self
180 }
181
182 pub fn weights(mut self, weights: QualityWeights) -> Self {
184 self.config.weights = weights;
185 self
186 }
187
188 pub fn build(self) -> BootstrapCacheConfig {
190 self.config
191 }
192}
193
194fn default_cache_dir() -> PathBuf {
195 if let Ok(tmpdir) = std::env::var("TMPDIR") {
197 return PathBuf::from(tmpdir).join("ant-quic-cache");
198 }
199
200 if let Some(cache_dir) = dirs::cache_dir() {
202 cache_dir.join("ant-quic")
203 } else if let Some(home) = dirs::home_dir() {
204 home.join(".cache").join("ant-quic")
205 } else {
206 PathBuf::from(".ant-quic-cache")
207 }
208}
209
210#[cfg(test)]
211mod tests {
212 use super::*;
213
214 #[test]
215 fn test_default_config() {
216 let config = BootstrapCacheConfig::default();
217 assert_eq!(config.max_peers, 30_000);
218 assert!((config.epsilon - 0.1).abs() < f64::EPSILON);
219 assert_eq!(config.stale_threshold, Duration::from_secs(7 * 24 * 3600));
220 }
221
222 #[test]
223 fn test_builder() {
224 let config = BootstrapCacheConfig::builder()
225 .max_peers(10_000)
226 .epsilon(0.2)
227 .cache_dir("/tmp/test")
228 .build();
229
230 assert_eq!(config.max_peers, 10_000);
231 assert!((config.epsilon - 0.2).abs() < f64::EPSILON);
232 assert_eq!(config.cache_dir, PathBuf::from("/tmp/test"));
233 }
234
235 #[test]
236 fn test_epsilon_clamping() {
237 let config = BootstrapCacheConfig::builder().epsilon(1.5).build();
238 assert!((config.epsilon - 1.0).abs() < f64::EPSILON);
239
240 let config = BootstrapCacheConfig::builder().epsilon(-0.5).build();
241 assert!(config.epsilon.abs() < f64::EPSILON);
242 }
243
244 #[test]
245 fn default_config_sets_all_intervals_and_flags() {
246 let config = BootstrapCacheConfig::default();
247 assert_eq!(
248 config.reachability_ttl,
249 crate::reachability::DIRECT_REACHABILITY_TTL
250 );
251 assert_eq!(config.save_interval, Duration::from_secs(5 * 60));
252 assert_eq!(config.quality_update_interval, Duration::from_secs(3600));
253 assert_eq!(config.cleanup_interval, Duration::from_secs(6 * 3600));
254 assert_eq!(config.min_peers_to_save, 10);
255 assert!(config.enable_file_locking);
256 assert!(config.persist);
257 assert!(
258 config.cache_dir.ends_with("ant-quic-cache") || config.cache_dir.ends_with("ant-quic")
259 );
260 }
261
262 #[test]
263 fn quality_weights_default_sum_to_one() {
264 let weights = QualityWeights::default();
265 assert!((weights.success_rate - 0.4).abs() < f64::EPSILON);
266 assert!((weights.rtt - 0.25).abs() < f64::EPSILON);
267 assert!((weights.freshness - 0.15).abs() < f64::EPSILON);
268 assert!((weights.capabilities - 0.2).abs() < f64::EPSILON);
269 let total = weights.success_rate + weights.rtt + weights.freshness + weights.capabilities;
270 assert!((total - 1.0).abs() < f64::EPSILON);
271 }
272
273 #[test]
274 fn builder_sets_all_duration_fields() {
275 let config = BootstrapCacheConfig::builder()
276 .stale_threshold(Duration::from_secs(1))
277 .reachability_ttl(Duration::from_secs(2))
278 .save_interval(Duration::from_secs(3))
279 .quality_update_interval(Duration::from_secs(4))
280 .cleanup_interval(Duration::from_secs(5))
281 .build();
282
283 assert_eq!(config.stale_threshold, Duration::from_secs(1));
284 assert_eq!(config.reachability_ttl, Duration::from_secs(2));
285 assert_eq!(config.save_interval, Duration::from_secs(3));
286 assert_eq!(config.quality_update_interval, Duration::from_secs(4));
287 assert_eq!(config.cleanup_interval, Duration::from_secs(5));
288 }
289
290 #[test]
291 fn builder_sets_save_threshold_and_locking() {
292 let config = BootstrapCacheConfig::builder()
293 .min_peers_to_save(0)
294 .enable_file_locking(false)
295 .build();
296
297 assert_eq!(config.min_peers_to_save, 0);
298 assert!(!config.enable_file_locking);
299 }
300
301 #[test]
302 fn builder_replaces_quality_weights() {
303 let weights = QualityWeights {
304 success_rate: 1.0,
305 rtt: 2.0,
306 freshness: 3.0,
307 capabilities: 4.0,
308 };
309 let config = BootstrapCacheConfig::builder()
310 .weights(weights.clone())
311 .build();
312
313 assert!((config.weights.success_rate - weights.success_rate).abs() < f64::EPSILON);
314 assert!((config.weights.rtt - weights.rtt).abs() < f64::EPSILON);
315 assert!((config.weights.freshness - weights.freshness).abs() < f64::EPSILON);
316 assert!((config.weights.capabilities - weights.capabilities).abs() < f64::EPSILON);
317 }
318
319 #[test]
320 fn config_clone_preserves_custom_values() {
321 let config = BootstrapCacheConfig::builder()
322 .cache_dir("relative/cache")
323 .max_peers(42)
324 .epsilon(0.75)
325 .min_peers_to_save(3)
326 .enable_file_locking(false)
327 .build();
328 let cloned = config.clone();
329
330 assert_eq!(cloned.cache_dir, PathBuf::from("relative/cache"));
331 assert_eq!(cloned.max_peers, 42);
332 assert!((cloned.epsilon - 0.75).abs() < f64::EPSILON);
333 assert_eq!(cloned.min_peers_to_save, 3);
334 assert!(!cloned.enable_file_locking);
335 }
336}