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    /// Freshness window for peer-verified direct reachability evidence.
30    pub reachability_ttl: Duration,
31
32    /// Interval between background save operations (default: 5 minutes)
33    pub save_interval: Duration,
34
35    /// Interval between quality score recalculations (default: 1 hour)
36    pub quality_update_interval: Duration,
37
38    /// Interval between stale peer cleanup (default: 6 hours)
39    pub cleanup_interval: Duration,
40
41    /// Minimum peers required before saving (prevents empty cache overwrite)
42    pub min_peers_to_save: usize,
43
44    /// Enable file locking for multi-process safety
45    pub enable_file_locking: bool,
46
47    /// Quality score weights
48    pub weights: QualityWeights,
49}
50
51/// Weights for quality score calculation
52#[derive(Debug, Clone)]
53pub struct QualityWeights {
54    /// Weight for success rate component (default: 0.4)
55    pub success_rate: f64,
56    /// Weight for RTT component (default: 0.25)
57    pub rtt: f64,
58    /// Weight for age/freshness component (default: 0.15)
59    pub freshness: f64,
60    /// Weight for capability bonuses (default: 0.2)
61    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), // 7 days
71            reachability_ttl: crate::reachability::DIRECT_REACHABILITY_TTL,
72            save_interval: Duration::from_secs(5 * 60), // 5 minutes
73            quality_update_interval: Duration::from_secs(3600), // 1 hour
74            cleanup_interval: Duration::from_secs(6 * 3600), // 6 hours
75            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    /// Create a new configuration builder
95    pub fn builder() -> BootstrapCacheConfigBuilder {
96        BootstrapCacheConfigBuilder::default()
97    }
98}
99
100/// Builder for BootstrapCacheConfig
101#[derive(Default)]
102pub struct BootstrapCacheConfigBuilder {
103    config: BootstrapCacheConfig,
104}
105
106impl BootstrapCacheConfigBuilder {
107    /// Set the cache directory
108    pub fn cache_dir(mut self, dir: impl Into<PathBuf>) -> Self {
109        self.config.cache_dir = dir.into();
110        self
111    }
112
113    /// Set maximum number of peers
114    pub fn max_peers(mut self, max: usize) -> Self {
115        self.config.max_peers = max;
116        self
117    }
118
119    /// Set epsilon for exploration rate (clamped to 0.0-1.0)
120    pub fn epsilon(mut self, epsilon: f64) -> Self {
121        self.config.epsilon = epsilon.clamp(0.0, 1.0);
122        self
123    }
124
125    /// Set freshness window for peer-verified direct reachability evidence.
126    pub fn reachability_ttl(mut self, ttl: Duration) -> Self {
127        self.config.reachability_ttl = ttl;
128        self
129    }
130
131    /// Set stale threshold duration
132    pub fn stale_threshold(mut self, duration: Duration) -> Self {
133        self.config.stale_threshold = duration;
134        self
135    }
136
137    /// Set save interval
138    pub fn save_interval(mut self, duration: Duration) -> Self {
139        self.config.save_interval = duration;
140        self
141    }
142
143    /// Set quality update interval
144    pub fn quality_update_interval(mut self, duration: Duration) -> Self {
145        self.config.quality_update_interval = duration;
146        self
147    }
148
149    /// Set cleanup interval
150    pub fn cleanup_interval(mut self, duration: Duration) -> Self {
151        self.config.cleanup_interval = duration;
152        self
153    }
154
155    /// Set minimum peers required to save
156    pub fn min_peers_to_save(mut self, min: usize) -> Self {
157        self.config.min_peers_to_save = min;
158        self
159    }
160
161    /// Enable or disable file locking
162    pub fn enable_file_locking(mut self, enable: bool) -> Self {
163        self.config.enable_file_locking = enable;
164        self
165    }
166
167    /// Set quality weights
168    pub fn weights(mut self, weights: QualityWeights) -> Self {
169        self.config.weights = weights;
170        self
171    }
172
173    /// Build the configuration
174    pub fn build(self) -> BootstrapCacheConfig {
175        self.config
176    }
177}
178
179fn default_cache_dir() -> PathBuf {
180    // Prefer TMPDIR for sandbox compatibility (Claude Code sets this to /tmp/claude)
181    if let Ok(tmpdir) = std::env::var("TMPDIR") {
182        return PathBuf::from(tmpdir).join("ant-quic-cache");
183    }
184
185    // Try platform-specific cache directory, fallback to current directory
186    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}