1use std::time::Duration;
12
13use derive_deftly::Deftly;
14use getset::{CopyGetters, Getters};
15use tor_checkable::timed::TimerangeBound;
16use tor_config::derive::prelude::*;
17use tor_config::{ConfigBuildError, define_list_builder_accessors};
18use tor_netdoc::doc::netstatus::Lifetime;
19
20use crate::{
21 authority::{AuthorityContacts, AuthorityContactsBuilder},
22 fallback::{FallbackDirBuilder, FallbackList, FallbackListBuilder},
23 retry::{DownloadSchedule, DownloadScheduleBuilder},
24};
25
26#[derive(Debug, Clone, Deftly, Eq, PartialEq, Getters)]
35#[derive_deftly(TorConfig)]
36#[deftly(tor_config(pre_build = "Self::validate"))]
37#[non_exhaustive]
38pub struct NetworkConfig {
39 #[deftly(tor_config(sub_builder, setter(skip)))]
51 #[getset(get = "pub")]
52 fallback_caches: FallbackList,
53
54 #[deftly(tor_config(sub_builder))]
65 #[getset(get = "pub")]
66 authorities: AuthorityContacts,
67}
68
69define_list_builder_accessors! {
72 struct NetworkConfigBuilder {
73 pub fallback_caches: [FallbackDirBuilder],
74 }
75}
76
77impl NetworkConfigBuilder {
78 fn validate(&self) -> std::result::Result<(), ConfigBuildError> {
80 if self.authorities.opt_v3idents().is_some() && self.opt_fallback_caches().is_none() {
81 return Err(ConfigBuildError::Inconsistent {
82 fields: vec!["authorities".to_owned(), "fallbacks".to_owned()],
83 problem: "Non-default authorities are use, but the fallback list is not overridden"
84 .to_owned(),
85 });
86 }
87
88 Ok(())
89 }
90}
91
92#[derive(Debug, Clone, Deftly, Eq, PartialEq, Getters, CopyGetters)]
98#[derive_deftly(TorConfig)]
99#[non_exhaustive]
100pub struct DownloadScheduleConfig {
101 #[deftly(tor_config(sub_builder(build_fn = "build_retry_bootstrap")))]
103 #[getset(get_copy = "pub")]
104 retry_bootstrap: DownloadSchedule,
105
106 #[deftly(tor_config(sub_builder))]
108 #[getset(get_copy = "pub")]
109 retry_consensus: DownloadSchedule,
110
111 #[deftly(tor_config(sub_builder))]
113 #[getset(get_copy = "pub")]
114 retry_certs: DownloadSchedule,
115
116 #[deftly(tor_config(sub_builder(build_fn = "build_retry_microdescs")))]
118 #[getset(get_copy = "pub")]
119 retry_microdescs: DownloadSchedule,
120}
121
122#[derive(Debug, Clone, Deftly, Eq, PartialEq, Getters, CopyGetters)]
132#[derive_deftly(TorConfig)]
133#[non_exhaustive]
134pub struct DirTolerance {
135 #[deftly(tor_config(default = "Duration::from_secs(24 * 60 * 60)"))]
141 #[getset(get_copy = "pub")]
142 pre_valid_tolerance: Duration,
143
144 #[deftly(tor_config(default = "Duration::from_secs(3 * 24 * 60 * 60)"))]
156 #[getset(get_copy = "pub")]
157 post_valid_tolerance: Duration,
158}
159
160impl DirTolerance {
161 pub fn extend_tolerance<B>(&self, timebound: TimerangeBound<B>) -> TimerangeBound<B> {
164 timebound
165 .extend_tolerance(self.post_valid_tolerance)
166 .extend_pre_tolerance(self.pre_valid_tolerance)
167 }
168
169 pub fn extend_lifetime(&self, lifetime: &Lifetime) -> Lifetime {
172 Lifetime::new(
173 lifetime.valid_after() - self.pre_valid_tolerance,
174 lifetime.fresh_until(),
175 lifetime.valid_until() + self.post_valid_tolerance,
176 )
177 .expect("Logic error when constructing lifetime")
178 }
179}
180
181#[cfg(test)]
182mod test {
183 #![allow(clippy::bool_assert_comparison)]
185 #![allow(clippy::clone_on_copy)]
186 #![allow(clippy::dbg_macro)]
187 #![allow(clippy::mixed_attributes_style)]
188 #![allow(clippy::print_stderr)]
189 #![allow(clippy::print_stdout)]
190 #![allow(clippy::single_char_pattern)]
191 #![allow(clippy::unwrap_used)]
192 #![allow(clippy::unchecked_time_subtraction)]
193 #![allow(clippy::useless_vec)]
194 #![allow(clippy::needless_pass_by_value)]
195 #![allow(clippy::string_slice)] #![allow(clippy::unnecessary_wraps)]
198
199 use tor_llcrypto::pk::rsa::RsaIdentity;
200
201 use crate::fallback::FallbackDir;
202
203 use super::*;
204
205 #[test]
206 fn build_network() {
207 let dflt = NetworkConfig::default();
208
209 let mut bld = NetworkConfig::builder();
211 let cfg = bld.build().unwrap();
212 assert_eq!(
213 cfg.authorities.v3idents().len(),
214 dflt.authorities.v3idents().len()
215 );
216 assert_eq!(cfg.fallback_caches.len(), dflt.fallback_caches.len());
217
218 bld.authorities
221 .set_v3idents(vec![[b'?'; 20].into(), [b'!'; 20].into()]);
222 assert!(bld.build().is_err());
223
224 bld.set_fallback_caches(vec![{
225 let mut bld = FallbackDir::builder();
226 bld.rsa_identity([b'x'; 20].into())
227 .ed_identity([b'y'; 32].into());
228 bld.orports().push("127.0.0.1:99".parse().unwrap());
229 bld.orports().push("[::]:99".parse().unwrap());
230 bld
231 }]);
232 let cfg = bld.build().unwrap();
233 assert_eq!(cfg.authorities.v3idents().len(), 2);
234 assert_eq!(cfg.fallback_caches.len(), 1);
235 }
236
237 #[test]
238 fn deserialize() {
239 let mut netcfg_prop330: NetworkConfigBuilder = toml::from_str(
240 "
241 [authorities]
242 v3idents = [
243 \"911F7C74212214823DDBDE3044B5B1AF3EFB98A0\",
244 \"46C4A4492D103A8C5CA544AC653B51C7B9AC8692\",
245 \"28D4680EA9C3660D1028FC40BACAC1319414581E\",
246 \"3817C9EB7E41C957594D0D9BCD6C7D7D718479C2\",
247 ]",
248 )
249 .unwrap();
250
251 assert_eq!(netcfg_prop330.authorities.v3idents().len(), 4);
252 assert_eq!(
253 *netcfg_prop330.authorities.v3idents(),
254 vec![
255 RsaIdentity::from_hex("911F7C74212214823DDBDE3044B5B1AF3EFB98A0").unwrap(),
256 RsaIdentity::from_hex("46C4A4492D103A8C5CA544AC653B51C7B9AC8692").unwrap(),
257 RsaIdentity::from_hex("28D4680EA9C3660D1028FC40BACAC1319414581E").unwrap(),
258 RsaIdentity::from_hex("3817C9EB7E41C957594D0D9BCD6C7D7D718479C2").unwrap(),
259 ]
260 );
261 }
262}