1use crate::authority::{Authority, AuthorityBuilder, AuthorityList, AuthorityListBuilder};
12use crate::retry::{DownloadSchedule, DownloadScheduleBuilder};
13use crate::storage::DynStore;
14use crate::Result;
15use tor_checkable::timed::TimerangeBound;
16use tor_config::{define_list_builder_accessors, impl_standard_builder, ConfigBuildError};
17use tor_guardmgr::fallback::FallbackDirBuilder;
18use tor_netdoc::doc::netstatus::{self, Lifetime};
19
20use derive_builder::Builder;
21use serde::{Deserialize, Serialize};
22use std::path::PathBuf;
23use std::time::Duration;
24
25#[derive(Debug, Clone, Builder, Eq, PartialEq)]
34#[builder(build_fn(validate = "Self::validate", error = "ConfigBuildError"))]
35#[builder(derive(Debug, Serialize, Deserialize))]
36pub struct NetworkConfig {
37 #[builder(sub_builder, setter(custom))]
49 pub(crate) fallback_caches: tor_guardmgr::fallback::FallbackList,
50
51 #[builder(sub_builder, setter(custom))]
62 pub(crate) authorities: AuthorityList,
63}
64
65impl_standard_builder! { NetworkConfig }
66
67define_list_builder_accessors! {
68 struct NetworkConfigBuilder {
69 pub fallback_caches: [FallbackDirBuilder],
70 pub authorities: [AuthorityBuilder],
71 }
72}
73
74impl NetworkConfig {
75 pub fn fallback_caches(&self) -> &tor_guardmgr::fallback::FallbackList {
77 &self.fallback_caches
78 }
79}
80
81impl NetworkConfigBuilder {
82 fn validate(&self) -> std::result::Result<(), ConfigBuildError> {
84 if self.opt_authorities().is_some() && self.opt_fallback_caches().is_none() {
85 return Err(ConfigBuildError::Inconsistent {
86 fields: vec!["authorities".to_owned(), "fallbacks".to_owned()],
87 problem: "Non-default authorities are use, but the fallback list is not overridden"
88 .to_owned(),
89 });
90 }
91
92 Ok(())
93 }
94}
95
96#[derive(Debug, Clone, Builder, Eq, PartialEq)]
102#[builder(build_fn(error = "ConfigBuildError"))]
103#[builder(derive(Debug, Serialize, Deserialize))]
104pub struct DownloadScheduleConfig {
105 #[builder(
107 sub_builder,
108 field(build = "self.retry_bootstrap.build_retry_bootstrap()?")
109 )]
110 #[builder_field_attr(serde(default))]
111 pub(crate) retry_bootstrap: DownloadSchedule,
112
113 #[builder(sub_builder)]
115 #[builder_field_attr(serde(default))]
116 pub(crate) retry_consensus: DownloadSchedule,
117
118 #[builder(sub_builder)]
120 #[builder_field_attr(serde(default))]
121 pub(crate) retry_certs: DownloadSchedule,
122
123 #[builder(
125 sub_builder,
126 field(build = "self.retry_microdescs.build_retry_microdescs()?")
127 )]
128 #[builder_field_attr(serde(default))]
129 pub(crate) retry_microdescs: DownloadSchedule,
130}
131
132impl_standard_builder! { DownloadScheduleConfig }
133
134#[derive(Debug, Clone, Builder, Eq, PartialEq)]
142#[builder(derive(Debug, Serialize, Deserialize))]
143#[builder(build_fn(error = "ConfigBuildError"))]
144pub struct DirTolerance {
145 #[builder(default = "Duration::from_secs(24 * 60 * 60)")]
151 #[builder_field_attr(serde(default, with = "humantime_serde::option"))]
152 pub(crate) pre_valid_tolerance: Duration,
153
154 #[builder(default = "Duration::from_secs(3 * 24 * 60 * 60)")]
166 #[builder_field_attr(serde(default, with = "humantime_serde::option"))]
167 pub(crate) post_valid_tolerance: Duration,
168}
169
170impl_standard_builder! { DirTolerance }
171
172impl DirTolerance {
173 pub(crate) fn extend_tolerance<B>(&self, timebound: TimerangeBound<B>) -> TimerangeBound<B> {
176 timebound
177 .extend_tolerance(self.post_valid_tolerance)
178 .extend_pre_tolerance(self.pre_valid_tolerance)
179 }
180
181 pub(crate) fn extend_lifetime(&self, lifetime: &Lifetime) -> Lifetime {
184 Lifetime::new(
185 lifetime.valid_after() - self.pre_valid_tolerance,
186 lifetime.fresh_until(),
187 lifetime.valid_until() + self.post_valid_tolerance,
188 )
189 .expect("Logic error when constructing lifetime")
190 }
191}
192
193#[derive(Debug, Clone)]
214#[cfg_attr(test, derive(Default))]
215#[allow(clippy::exhaustive_structs)]
216pub struct DirMgrConfig {
217 pub cache_dir: PathBuf,
222
223 pub cache_trust: fs_mistrust::Mistrust,
225
226 pub network: NetworkConfig,
228
229 pub schedule: DownloadScheduleConfig,
240
241 pub tolerance: DirTolerance,
243
244 pub override_net_params: netstatus::NetParams<i32>,
254
255 pub extensions: DirMgrExtensions,
260}
261
262impl DirMgrConfig {
263 pub(crate) fn open_store(&self, readonly: bool) -> Result<DynStore> {
268 Ok(Box::new(
269 crate::storage::SqliteStore::from_path_and_mistrust(
270 &self.cache_dir,
271 &self.cache_trust,
272 readonly,
273 )?,
274 ))
275 }
276
277 pub fn authorities(&self) -> &[Authority] {
279 &self.network.authorities
280 }
281
282 pub fn fallbacks(&self) -> &tor_guardmgr::fallback::FallbackList {
284 &self.network.fallback_caches
285 }
286
287 pub(crate) fn update_from_config(&self, new_config: &DirMgrConfig) -> DirMgrConfig {
292 DirMgrConfig {
294 cache_dir: self.cache_dir.clone(),
295 cache_trust: self.cache_trust.clone(),
296 network: NetworkConfig {
297 fallback_caches: new_config.network.fallback_caches.clone(),
298 authorities: self.network.authorities.clone(),
299 },
300 schedule: new_config.schedule.clone(),
301 tolerance: new_config.tolerance.clone(),
302 override_net_params: new_config.override_net_params.clone(),
303 extensions: new_config.extensions.clone(),
304 }
305 }
306
307 #[cfg(feature = "experimental-api")]
312 pub fn update_config(&self, new_config: &DirMgrConfig) -> DirMgrConfig {
313 self.update_from_config(new_config)
314 }
315}
316
317#[derive(Debug, Clone, Default)]
319#[non_exhaustive]
320pub struct DirMgrExtensions {
321 #[cfg(feature = "dirfilter")]
323 pub filter: crate::filter::FilterConfig,
324}
325
326#[cfg(test)]
327mod test {
328 #![allow(clippy::bool_assert_comparison)]
330 #![allow(clippy::clone_on_copy)]
331 #![allow(clippy::dbg_macro)]
332 #![allow(clippy::mixed_attributes_style)]
333 #![allow(clippy::print_stderr)]
334 #![allow(clippy::print_stdout)]
335 #![allow(clippy::single_char_pattern)]
336 #![allow(clippy::unwrap_used)]
337 #![allow(clippy::unchecked_duration_subtraction)]
338 #![allow(clippy::useless_vec)]
339 #![allow(clippy::needless_pass_by_value)]
340 #![allow(clippy::unnecessary_wraps)]
342 use super::*;
343 use tempfile::tempdir;
344
345 #[test]
346 fn simplest_config() -> Result<()> {
347 let tmp = tempdir().unwrap();
348
349 let dir = DirMgrConfig {
350 cache_dir: tmp.path().into(),
351 ..Default::default()
352 };
353
354 assert!(dir.authorities().len() >= 3);
355 assert!(dir.fallbacks().len() >= 3);
356
357 Ok(())
360 }
361
362 #[test]
363 fn build_network() -> Result<()> {
364 use tor_guardmgr::fallback::FallbackDir;
365
366 let dflt = NetworkConfig::default();
367
368 let mut bld = NetworkConfig::builder();
370 let cfg = bld.build().unwrap();
371 assert_eq!(cfg.authorities.len(), dflt.authorities.len());
372 assert_eq!(cfg.fallback_caches.len(), dflt.fallback_caches.len());
373
374 bld.set_authorities(vec![
377 Authority::builder()
378 .name("Hello")
379 .v3ident([b'?'; 20].into())
380 .clone(),
381 Authority::builder()
382 .name("world")
383 .v3ident([b'!'; 20].into())
384 .clone(),
385 ]);
386 assert!(bld.build().is_err());
387
388 bld.set_fallback_caches(vec![{
389 let mut bld = FallbackDir::builder();
390 bld.rsa_identity([b'x'; 20].into())
391 .ed_identity([b'y'; 32].into());
392 bld.orports().push("127.0.0.1:99".parse().unwrap());
393 bld.orports().push("[::]:99".parse().unwrap());
394 bld
395 }]);
396 let cfg = bld.build().unwrap();
397 assert_eq!(cfg.authorities.len(), 2);
398 assert_eq!(cfg.fallback_caches.len(), 1);
399
400 Ok(())
401 }
402
403 #[test]
404 fn build_schedule() -> Result<()> {
405 use std::time::Duration;
406 let mut bld = DownloadScheduleConfig::builder();
407
408 let cfg = bld.build().unwrap();
409 assert_eq!(cfg.retry_microdescs.parallelism(), 4);
410 assert_eq!(cfg.retry_microdescs.n_attempts(), 3);
411 assert_eq!(cfg.retry_bootstrap.n_attempts(), 128);
412
413 bld.retry_consensus().attempts(7);
414 bld.retry_consensus().initial_delay(Duration::new(86400, 0));
415 bld.retry_consensus().parallelism(1);
416 bld.retry_bootstrap().attempts(4);
417 bld.retry_bootstrap().initial_delay(Duration::new(3600, 0));
418 bld.retry_bootstrap().parallelism(1);
419
420 bld.retry_certs().attempts(5);
421 bld.retry_certs().initial_delay(Duration::new(3600, 0));
422 bld.retry_certs().parallelism(1);
423 bld.retry_microdescs().attempts(6);
424 bld.retry_microdescs().initial_delay(Duration::new(3600, 0));
425 bld.retry_microdescs().parallelism(1);
426
427 let cfg = bld.build().unwrap();
428 assert_eq!(cfg.retry_microdescs.parallelism(), 1);
429 assert_eq!(cfg.retry_microdescs.n_attempts(), 6);
430 assert_eq!(cfg.retry_bootstrap.n_attempts(), 4);
431 assert_eq!(cfg.retry_consensus.n_attempts(), 7);
432 assert_eq!(cfg.retry_certs.n_attempts(), 5);
433
434 Ok(())
435 }
436
437 #[test]
438 fn build_dirmgrcfg() -> Result<()> {
439 let mut bld = DirMgrConfig::default();
440 let tmp = tempdir().unwrap();
441
442 bld.override_net_params.set("circwindow".into(), 999);
443 bld.cache_dir = tmp.path().into();
444
445 assert_eq!(bld.override_net_params.get("circwindow").unwrap(), &999);
446
447 Ok(())
448 }
449}