1use std::time::Duration;
12
13use derive_builder::Builder;
14use getset::{CopyGetters, Getters};
15use serde::{Deserialize, Serialize};
16use tor_checkable::timed::TimerangeBound;
17use tor_config::{ConfigBuildError, define_list_builder_accessors, impl_standard_builder};
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, Builder, Eq, PartialEq, Getters)]
35#[builder(build_fn(validate = "Self::validate", error = "ConfigBuildError"))]
36#[builder(derive(Debug, Serialize, Deserialize))]
37#[non_exhaustive]
38pub struct NetworkConfig {
39 #[builder(sub_builder, setter(custom))]
51 #[getset(get = "pub")]
52 fallback_caches: FallbackList,
53
54 #[builder(sub_builder)]
65 #[builder_field_attr(serde(default))]
66 #[getset(get = "pub")]
67 authorities: AuthorityContacts,
68}
69
70impl_standard_builder! { NetworkConfig }
71
72define_list_builder_accessors! {
73 struct NetworkConfigBuilder {
74 pub fallback_caches: [FallbackDirBuilder],
75 }
76}
77
78impl NetworkConfigBuilder {
79 fn validate(&self) -> std::result::Result<(), ConfigBuildError> {
81 if self.authorities.opt_v3idents().is_some() && self.opt_fallback_caches().is_none() {
82 return Err(ConfigBuildError::Inconsistent {
83 fields: vec!["authorities".to_owned(), "fallbacks".to_owned()],
84 problem: "Non-default authorities are use, but the fallback list is not overridden"
85 .to_owned(),
86 });
87 }
88
89 Ok(())
90 }
91}
92
93#[derive(Debug, Clone, Builder, Eq, PartialEq, Getters, CopyGetters)]
99#[builder(build_fn(error = "ConfigBuildError"))]
100#[builder(derive(Debug, Serialize, Deserialize))]
101#[non_exhaustive]
102pub struct DownloadScheduleConfig {
103 #[builder(
105 sub_builder,
106 field(build = "self.retry_bootstrap.build_retry_bootstrap()?")
107 )]
108 #[builder_field_attr(serde(default))]
109 #[getset(get_copy = "pub")]
110 retry_bootstrap: DownloadSchedule,
111
112 #[builder(sub_builder)]
114 #[builder_field_attr(serde(default))]
115 #[getset(get_copy = "pub")]
116 retry_consensus: DownloadSchedule,
117
118 #[builder(sub_builder)]
120 #[builder_field_attr(serde(default))]
121 #[getset(get_copy = "pub")]
122 retry_certs: DownloadSchedule,
123
124 #[builder(
126 sub_builder,
127 field(build = "self.retry_microdescs.build_retry_microdescs()?")
128 )]
129 #[builder_field_attr(serde(default))]
130 #[getset(get_copy = "pub")]
131 retry_microdescs: DownloadSchedule,
132}
133
134impl_standard_builder! { DownloadScheduleConfig }
135
136#[derive(Debug, Clone, Builder, Eq, PartialEq, Getters, CopyGetters)]
146#[builder(derive(Debug, Serialize, Deserialize))]
147#[builder(build_fn(error = "ConfigBuildError"))]
148#[non_exhaustive]
149pub struct DirTolerance {
150 #[builder(default = "Duration::from_secs(24 * 60 * 60)")]
156 #[builder_field_attr(serde(default, with = "humantime_serde::option"))]
157 #[getset(get_copy = "pub")]
158 pre_valid_tolerance: Duration,
159
160 #[builder(default = "Duration::from_secs(3 * 24 * 60 * 60)")]
172 #[builder_field_attr(serde(default, with = "humantime_serde::option"))]
173 #[getset(get_copy = "pub")]
174 post_valid_tolerance: Duration,
175}
176
177impl_standard_builder! { DirTolerance }
178
179impl DirTolerance {
180 pub fn extend_tolerance<B>(&self, timebound: TimerangeBound<B>) -> TimerangeBound<B> {
183 timebound
184 .extend_tolerance(self.post_valid_tolerance)
185 .extend_pre_tolerance(self.pre_valid_tolerance)
186 }
187
188 pub fn extend_lifetime(&self, lifetime: &Lifetime) -> Lifetime {
191 Lifetime::new(
192 lifetime.valid_after() - self.pre_valid_tolerance,
193 lifetime.fresh_until(),
194 lifetime.valid_until() + self.post_valid_tolerance,
195 )
196 .expect("Logic error when constructing lifetime")
197 }
198}
199
200#[cfg(test)]
201mod test {
202 #![allow(clippy::bool_assert_comparison)]
204 #![allow(clippy::clone_on_copy)]
205 #![allow(clippy::dbg_macro)]
206 #![allow(clippy::mixed_attributes_style)]
207 #![allow(clippy::print_stderr)]
208 #![allow(clippy::print_stdout)]
209 #![allow(clippy::single_char_pattern)]
210 #![allow(clippy::unwrap_used)]
211 #![allow(clippy::unchecked_time_subtraction)]
212 #![allow(clippy::useless_vec)]
213 #![allow(clippy::needless_pass_by_value)]
214 #![allow(clippy::unnecessary_wraps)]
216
217 use tor_llcrypto::pk::rsa::RsaIdentity;
218
219 use crate::fallback::FallbackDir;
220
221 use super::*;
222
223 #[test]
224 fn build_network() {
225 let dflt = NetworkConfig::default();
226
227 let mut bld = NetworkConfig::builder();
229 let cfg = bld.build().unwrap();
230 assert_eq!(
231 cfg.authorities.v3idents().len(),
232 dflt.authorities.v3idents().len()
233 );
234 assert_eq!(cfg.fallback_caches.len(), dflt.fallback_caches.len());
235
236 bld.authorities
239 .set_v3idents(vec![[b'?'; 20].into(), [b'!'; 20].into()]);
240 assert!(bld.build().is_err());
241
242 bld.set_fallback_caches(vec![{
243 let mut bld = FallbackDir::builder();
244 bld.rsa_identity([b'x'; 20].into())
245 .ed_identity([b'y'; 32].into());
246 bld.orports().push("127.0.0.1:99".parse().unwrap());
247 bld.orports().push("[::]:99".parse().unwrap());
248 bld
249 }]);
250 let cfg = bld.build().unwrap();
251 assert_eq!(cfg.authorities.v3idents().len(), 2);
252 assert_eq!(cfg.fallback_caches.len(), 1);
253 }
254
255 #[test]
256 fn deserialize() {
257 let mut netcfg_prop330: NetworkConfigBuilder = toml::from_str(
258 "
259 [authorities]
260 v3idents = [
261 \"911F7C74212214823DDBDE3044B5B1AF3EFB98A0\",
262 \"46C4A4492D103A8C5CA544AC653B51C7B9AC8692\",
263 \"28D4680EA9C3660D1028FC40BACAC1319414581E\",
264 \"3817C9EB7E41C957594D0D9BCD6C7D7D718479C2\",
265 ]",
266 )
267 .unwrap();
268
269 assert_eq!(netcfg_prop330.authorities.v3idents().len(), 4);
270 assert_eq!(
271 *netcfg_prop330.authorities.v3idents(),
272 vec![
273 RsaIdentity::from_hex("911F7C74212214823DDBDE3044B5B1AF3EFB98A0").unwrap(),
274 RsaIdentity::from_hex("46C4A4492D103A8C5CA544AC653B51C7B9AC8692").unwrap(),
275 RsaIdentity::from_hex("28D4680EA9C3660D1028FC40BACAC1319414581E").unwrap(),
276 RsaIdentity::from_hex("3817C9EB7E41C957594D0D9BCD6C7D7D718479C2").unwrap(),
277 ]
278 );
279 }
280}