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::unnecessary_wraps)]
197
198 use tor_llcrypto::pk::rsa::RsaIdentity;
199
200 use crate::fallback::FallbackDir;
201
202 use super::*;
203
204 #[test]
205 fn build_network() {
206 let dflt = NetworkConfig::default();
207
208 let mut bld = NetworkConfig::builder();
210 let cfg = bld.build().unwrap();
211 assert_eq!(
212 cfg.authorities.v3idents().len(),
213 dflt.authorities.v3idents().len()
214 );
215 assert_eq!(cfg.fallback_caches.len(), dflt.fallback_caches.len());
216
217 bld.authorities
220 .set_v3idents(vec![[b'?'; 20].into(), [b'!'; 20].into()]);
221 assert!(bld.build().is_err());
222
223 bld.set_fallback_caches(vec![{
224 let mut bld = FallbackDir::builder();
225 bld.rsa_identity([b'x'; 20].into())
226 .ed_identity([b'y'; 32].into());
227 bld.orports().push("127.0.0.1:99".parse().unwrap());
228 bld.orports().push("[::]:99".parse().unwrap());
229 bld
230 }]);
231 let cfg = bld.build().unwrap();
232 assert_eq!(cfg.authorities.v3idents().len(), 2);
233 assert_eq!(cfg.fallback_caches.len(), 1);
234 }
235
236 #[test]
237 fn deserialize() {
238 let mut netcfg_prop330: NetworkConfigBuilder = toml::from_str(
239 "
240 [authorities]
241 v3idents = [
242 \"911F7C74212214823DDBDE3044B5B1AF3EFB98A0\",
243 \"46C4A4492D103A8C5CA544AC653B51C7B9AC8692\",
244 \"28D4680EA9C3660D1028FC40BACAC1319414581E\",
245 \"3817C9EB7E41C957594D0D9BCD6C7D7D718479C2\",
246 ]",
247 )
248 .unwrap();
249
250 assert_eq!(netcfg_prop330.authorities.v3idents().len(), 4);
251 assert_eq!(
252 *netcfg_prop330.authorities.v3idents(),
253 vec![
254 RsaIdentity::from_hex("911F7C74212214823DDBDE3044B5B1AF3EFB98A0").unwrap(),
255 RsaIdentity::from_hex("46C4A4492D103A8C5CA544AC653B51C7B9AC8692").unwrap(),
256 RsaIdentity::from_hex("28D4680EA9C3660D1028FC40BACAC1319414581E").unwrap(),
257 RsaIdentity::from_hex("3817C9EB7E41C957594D0D9BCD6C7D7D718479C2").unwrap(),
258 ]
259 );
260 }
261}