Skip to main content

ant_quic/bootstrap_cache/
config.rs

1// Copyright 2024 Saorsa Labs Ltd.
2//
3// This Saorsa Network Software is licensed under the General Public License (GPL), version 3.
4// Please see the file LICENSE-GPL, or visit <http://www.gnu.org/licenses/> for the full text.
5//
6// Full details available at https://saorsalabs.com/licenses
7
8//! Bootstrap cache configuration.
9
10use std::path::PathBuf;
11use std::time::Duration;
12
13/// Configuration for the bootstrap cache
14#[derive(Debug, Clone)]
15pub struct BootstrapCacheConfig {
16    /// Directory for cache files
17    pub cache_dir: PathBuf,
18
19    /// Maximum number of peers to cache (default: 30,000 per ADR-007)
20    pub max_peers: usize,
21
22    /// Epsilon for exploration rate (default: 0.1 = 10%)
23    /// Higher values = more exploration of unknown peers
24    pub epsilon: f64,
25
26    /// Time after which peers are considered stale (default: 7 days)
27    pub stale_threshold: Duration,
28
29    /// Interval between background save operations (default: 5 minutes)
30    pub save_interval: Duration,
31
32    /// Interval between quality score recalculations (default: 1 hour)
33    pub quality_update_interval: Duration,
34
35    /// Interval between stale peer cleanup (default: 6 hours)
36    pub cleanup_interval: Duration,
37
38    /// Minimum peers required before saving (prevents empty cache overwrite)
39    pub min_peers_to_save: usize,
40
41    /// Enable file locking for multi-process safety
42    pub enable_file_locking: bool,
43
44    /// Quality score weights
45    pub weights: QualityWeights,
46}
47
48/// Weights for quality score calculation
49#[derive(Debug, Clone)]
50pub struct QualityWeights {
51    /// Weight for success rate component (default: 0.4)
52    pub success_rate: f64,
53    /// Weight for RTT component (default: 0.25)
54    pub rtt: f64,
55    /// Weight for age/freshness component (default: 0.15)
56    pub freshness: f64,
57    /// Weight for capability bonuses (default: 0.2)
58    pub capabilities: f64,
59}
60
61impl Default for BootstrapCacheConfig {
62    fn default() -> Self {
63        Self {
64            cache_dir: default_cache_dir(),
65            max_peers: 30_000,
66            epsilon: 0.1,
67            stale_threshold: Duration::from_secs(7 * 24 * 3600), // 7 days
68            save_interval: Duration::from_secs(5 * 60),          // 5 minutes
69            quality_update_interval: Duration::from_secs(3600),  // 1 hour
70            cleanup_interval: Duration::from_secs(6 * 3600),     // 6 hours
71            min_peers_to_save: 10,
72            enable_file_locking: true,
73            weights: QualityWeights::default(),
74        }
75    }
76}
77
78impl Default for QualityWeights {
79    fn default() -> Self {
80        Self {
81            success_rate: 0.4,
82            rtt: 0.25,
83            freshness: 0.15,
84            capabilities: 0.2,
85        }
86    }
87}
88
89impl BootstrapCacheConfig {
90    /// Create a new configuration builder
91    pub fn builder() -> BootstrapCacheConfigBuilder {
92        BootstrapCacheConfigBuilder::default()
93    }
94}
95
96/// Builder for BootstrapCacheConfig
97#[derive(Default)]
98pub struct BootstrapCacheConfigBuilder {
99    config: BootstrapCacheConfig,
100}
101
102impl BootstrapCacheConfigBuilder {
103    /// Set the cache directory
104    pub fn cache_dir(mut self, dir: impl Into<PathBuf>) -> Self {
105        self.config.cache_dir = dir.into();
106        self
107    }
108
109    /// Set maximum number of peers
110    pub fn max_peers(mut self, max: usize) -> Self {
111        self.config.max_peers = max;
112        self
113    }
114
115    /// Set epsilon for exploration rate (clamped to 0.0-1.0)
116    pub fn epsilon(mut self, epsilon: f64) -> Self {
117        self.config.epsilon = epsilon.clamp(0.0, 1.0);
118        self
119    }
120
121    /// Set stale threshold duration
122    pub fn stale_threshold(mut self, duration: Duration) -> Self {
123        self.config.stale_threshold = duration;
124        self
125    }
126
127    /// Set save interval
128    pub fn save_interval(mut self, duration: Duration) -> Self {
129        self.config.save_interval = duration;
130        self
131    }
132
133    /// Set quality update interval
134    pub fn quality_update_interval(mut self, duration: Duration) -> Self {
135        self.config.quality_update_interval = duration;
136        self
137    }
138
139    /// Set cleanup interval
140    pub fn cleanup_interval(mut self, duration: Duration) -> Self {
141        self.config.cleanup_interval = duration;
142        self
143    }
144
145    /// Set minimum peers required to save
146    pub fn min_peers_to_save(mut self, min: usize) -> Self {
147        self.config.min_peers_to_save = min;
148        self
149    }
150
151    /// Enable or disable file locking
152    pub fn enable_file_locking(mut self, enable: bool) -> Self {
153        self.config.enable_file_locking = enable;
154        self
155    }
156
157    /// Set quality weights
158    pub fn weights(mut self, weights: QualityWeights) -> Self {
159        self.config.weights = weights;
160        self
161    }
162
163    /// Build the configuration
164    pub fn build(self) -> BootstrapCacheConfig {
165        self.config
166    }
167}
168
169fn default_cache_dir() -> PathBuf {
170    // Prefer TMPDIR for sandbox compatibility (Claude Code sets this to /tmp/claude)
171    if let Ok(tmpdir) = std::env::var("TMPDIR") {
172        return PathBuf::from(tmpdir).join("ant-quic-cache");
173    }
174
175    // Try platform-specific cache directory, fallback to current directory
176    if let Some(cache_dir) = dirs::cache_dir() {
177        cache_dir.join("ant-quic")
178    } else if let Some(home) = dirs::home_dir() {
179        home.join(".cache").join("ant-quic")
180    } else {
181        PathBuf::from(".ant-quic-cache")
182    }
183}
184
185#[cfg(test)]
186mod tests {
187    use super::*;
188
189    #[test]
190    fn test_default_config() {
191        let config = BootstrapCacheConfig::default();
192        assert_eq!(config.max_peers, 30_000);
193        assert!((config.epsilon - 0.1).abs() < f64::EPSILON);
194        assert_eq!(config.stale_threshold, Duration::from_secs(7 * 24 * 3600));
195    }
196
197    #[test]
198    fn test_builder() {
199        let config = BootstrapCacheConfig::builder()
200            .max_peers(10_000)
201            .epsilon(0.2)
202            .cache_dir("/tmp/test")
203            .build();
204
205        assert_eq!(config.max_peers, 10_000);
206        assert!((config.epsilon - 0.2).abs() < f64::EPSILON);
207        assert_eq!(config.cache_dir, PathBuf::from("/tmp/test"));
208    }
209
210    #[test]
211    fn test_epsilon_clamping() {
212        let config = BootstrapCacheConfig::builder().epsilon(1.5).build();
213        assert!((config.epsilon - 1.0).abs() < f64::EPSILON);
214
215        let config = BootstrapCacheConfig::builder().epsilon(-0.5).build();
216        assert!(config.epsilon.abs() < f64::EPSILON);
217    }
218}