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 weights: QualityWeights,
49}
50
51#[derive(Debug, Clone)]
53pub struct QualityWeights {
54 pub success_rate: f64,
56 pub rtt: f64,
58 pub freshness: f64,
60 pub capabilities: f64,
62}
63
64impl Default for BootstrapCacheConfig {
65 fn default() -> Self {
66 Self {
67 cache_dir: default_cache_dir(),
68 max_peers: 30_000,
69 epsilon: 0.1,
70 stale_threshold: Duration::from_secs(7 * 24 * 3600), reachability_ttl: crate::reachability::DIRECT_REACHABILITY_TTL,
72 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,
76 enable_file_locking: true,
77 weights: QualityWeights::default(),
78 }
79 }
80}
81
82impl Default for QualityWeights {
83 fn default() -> Self {
84 Self {
85 success_rate: 0.4,
86 rtt: 0.25,
87 freshness: 0.15,
88 capabilities: 0.2,
89 }
90 }
91}
92
93impl BootstrapCacheConfig {
94 pub fn builder() -> BootstrapCacheConfigBuilder {
96 BootstrapCacheConfigBuilder::default()
97 }
98}
99
100#[derive(Default)]
102pub struct BootstrapCacheConfigBuilder {
103 config: BootstrapCacheConfig,
104}
105
106impl BootstrapCacheConfigBuilder {
107 pub fn cache_dir(mut self, dir: impl Into<PathBuf>) -> Self {
109 self.config.cache_dir = dir.into();
110 self
111 }
112
113 pub fn max_peers(mut self, max: usize) -> Self {
115 self.config.max_peers = max;
116 self
117 }
118
119 pub fn epsilon(mut self, epsilon: f64) -> Self {
121 self.config.epsilon = epsilon.clamp(0.0, 1.0);
122 self
123 }
124
125 pub fn reachability_ttl(mut self, ttl: Duration) -> Self {
127 self.config.reachability_ttl = ttl;
128 self
129 }
130
131 pub fn stale_threshold(mut self, duration: Duration) -> Self {
133 self.config.stale_threshold = duration;
134 self
135 }
136
137 pub fn save_interval(mut self, duration: Duration) -> Self {
139 self.config.save_interval = duration;
140 self
141 }
142
143 pub fn quality_update_interval(mut self, duration: Duration) -> Self {
145 self.config.quality_update_interval = duration;
146 self
147 }
148
149 pub fn cleanup_interval(mut self, duration: Duration) -> Self {
151 self.config.cleanup_interval = duration;
152 self
153 }
154
155 pub fn min_peers_to_save(mut self, min: usize) -> Self {
157 self.config.min_peers_to_save = min;
158 self
159 }
160
161 pub fn enable_file_locking(mut self, enable: bool) -> Self {
163 self.config.enable_file_locking = enable;
164 self
165 }
166
167 pub fn weights(mut self, weights: QualityWeights) -> Self {
169 self.config.weights = weights;
170 self
171 }
172
173 pub fn build(self) -> BootstrapCacheConfig {
175 self.config
176 }
177}
178
179fn default_cache_dir() -> PathBuf {
180 if let Ok(tmpdir) = std::env::var("TMPDIR") {
182 return PathBuf::from(tmpdir).join("ant-quic-cache");
183 }
184
185 if let Some(cache_dir) = dirs::cache_dir() {
187 cache_dir.join("ant-quic")
188 } else if let Some(home) = dirs::home_dir() {
189 home.join(".cache").join("ant-quic")
190 } else {
191 PathBuf::from(".ant-quic-cache")
192 }
193}
194
195#[cfg(test)]
196mod tests {
197 use super::*;
198
199 #[test]
200 fn test_default_config() {
201 let config = BootstrapCacheConfig::default();
202 assert_eq!(config.max_peers, 30_000);
203 assert!((config.epsilon - 0.1).abs() < f64::EPSILON);
204 assert_eq!(config.stale_threshold, Duration::from_secs(7 * 24 * 3600));
205 }
206
207 #[test]
208 fn test_builder() {
209 let config = BootstrapCacheConfig::builder()
210 .max_peers(10_000)
211 .epsilon(0.2)
212 .cache_dir("/tmp/test")
213 .build();
214
215 assert_eq!(config.max_peers, 10_000);
216 assert!((config.epsilon - 0.2).abs() < f64::EPSILON);
217 assert_eq!(config.cache_dir, PathBuf::from("/tmp/test"));
218 }
219
220 #[test]
221 fn test_epsilon_clamping() {
222 let config = BootstrapCacheConfig::builder().epsilon(1.5).build();
223 assert!((config.epsilon - 1.0).abs() < f64::EPSILON);
224
225 let config = BootstrapCacheConfig::builder().epsilon(-0.5).build();
226 assert!(config.epsilon.abs() < f64::EPSILON);
227 }
228
229 #[test]
230 fn default_config_sets_all_intervals_and_flags() {
231 let config = BootstrapCacheConfig::default();
232 assert_eq!(
233 config.reachability_ttl,
234 crate::reachability::DIRECT_REACHABILITY_TTL
235 );
236 assert_eq!(config.save_interval, Duration::from_secs(5 * 60));
237 assert_eq!(config.quality_update_interval, Duration::from_secs(3600));
238 assert_eq!(config.cleanup_interval, Duration::from_secs(6 * 3600));
239 assert_eq!(config.min_peers_to_save, 10);
240 assert!(config.enable_file_locking);
241 assert!(
242 config.cache_dir.ends_with("ant-quic-cache") || config.cache_dir.ends_with("ant-quic")
243 );
244 }
245
246 #[test]
247 fn quality_weights_default_sum_to_one() {
248 let weights = QualityWeights::default();
249 assert!((weights.success_rate - 0.4).abs() < f64::EPSILON);
250 assert!((weights.rtt - 0.25).abs() < f64::EPSILON);
251 assert!((weights.freshness - 0.15).abs() < f64::EPSILON);
252 assert!((weights.capabilities - 0.2).abs() < f64::EPSILON);
253 let total = weights.success_rate + weights.rtt + weights.freshness + weights.capabilities;
254 assert!((total - 1.0).abs() < f64::EPSILON);
255 }
256
257 #[test]
258 fn builder_sets_all_duration_fields() {
259 let config = BootstrapCacheConfig::builder()
260 .stale_threshold(Duration::from_secs(1))
261 .reachability_ttl(Duration::from_secs(2))
262 .save_interval(Duration::from_secs(3))
263 .quality_update_interval(Duration::from_secs(4))
264 .cleanup_interval(Duration::from_secs(5))
265 .build();
266
267 assert_eq!(config.stale_threshold, Duration::from_secs(1));
268 assert_eq!(config.reachability_ttl, Duration::from_secs(2));
269 assert_eq!(config.save_interval, Duration::from_secs(3));
270 assert_eq!(config.quality_update_interval, Duration::from_secs(4));
271 assert_eq!(config.cleanup_interval, Duration::from_secs(5));
272 }
273
274 #[test]
275 fn builder_sets_save_threshold_and_locking() {
276 let config = BootstrapCacheConfig::builder()
277 .min_peers_to_save(0)
278 .enable_file_locking(false)
279 .build();
280
281 assert_eq!(config.min_peers_to_save, 0);
282 assert!(!config.enable_file_locking);
283 }
284
285 #[test]
286 fn builder_replaces_quality_weights() {
287 let weights = QualityWeights {
288 success_rate: 1.0,
289 rtt: 2.0,
290 freshness: 3.0,
291 capabilities: 4.0,
292 };
293 let config = BootstrapCacheConfig::builder()
294 .weights(weights.clone())
295 .build();
296
297 assert!((config.weights.success_rate - weights.success_rate).abs() < f64::EPSILON);
298 assert!((config.weights.rtt - weights.rtt).abs() < f64::EPSILON);
299 assert!((config.weights.freshness - weights.freshness).abs() < f64::EPSILON);
300 assert!((config.weights.capabilities - weights.capabilities).abs() < f64::EPSILON);
301 }
302
303 #[test]
304 fn config_clone_preserves_custom_values() {
305 let config = BootstrapCacheConfig::builder()
306 .cache_dir("relative/cache")
307 .max_peers(42)
308 .epsilon(0.75)
309 .min_peers_to_save(3)
310 .enable_file_locking(false)
311 .build();
312 let cloned = config.clone();
313
314 assert_eq!(cloned.cache_dir, PathBuf::from("relative/cache"));
315 assert_eq!(cloned.max_peers, 42);
316 assert!((cloned.epsilon - 0.75).abs() < f64::EPSILON);
317 assert_eq!(cloned.min_peers_to_save, 3);
318 assert!(!cloned.enable_file_locking);
319 }
320}