1
//! Implements a usable view of Tor network parameters.
2
//!
3
//! The Tor consensus document contains a number of 'network
4
//! parameters', which are integer-valued items voted on by the
5
//! directory authorities.  They are used to tune the behavior of
6
//! numerous aspects of the network.
7
//! A set of Tor network parameters
8
//!
9
//! The Tor consensus document contains a number of 'network
10
//! parameters', which are integer-valued items voted on by the
11
//! directory authorities.  These parameters are used to tune the
12
//! behavior of numerous aspects of the network.
13
//!
14
//! This type differs from
15
//! [`NetParams`](tor_netdoc::doc::netstatus::NetParams) in that it
16
//! only exposes a set of parameters recognized by arti.  In return
17
//! for this restriction, it makes sure that the values it gives are
18
//! in range, and provides default values for any parameters that are
19
//! missing.
20

            
21
use std::convert::TryInto;
22
use tor_units::{
23
    BoundedInt32, IntegerDays, IntegerMilliseconds, IntegerSeconds, Percentage, SendMeVersion,
24
};
25

            
26
/// An object that can be constructed from an i32, with saturating semantics.
27
pub trait FromInt32Saturating {
28
    /// Construct an instance of this object from `val`.
29
    ///
30
    /// If `val` is too low, treat it as the lowest value that would be
31
    /// valid.  If `val` is too high, treat it as the highest value that
32
    /// would be valid.
33
    fn from_saturating(val: i32) -> Self;
34
}
35

            
36
impl FromInt32Saturating for i32 {
37
    fn from_saturating(val: i32) -> Self {
38
        val
39
    }
40
}
41
impl<const L: i32, const H: i32> FromInt32Saturating for BoundedInt32<L, H> {
42
1229
    fn from_saturating(val: i32) -> Self {
43
1229
        Self::saturating_new(val)
44
1229
    }
45
}
46
impl<T: Copy + Into<f64> + FromInt32Saturating> FromInt32Saturating for Percentage<T> {
47
13
    fn from_saturating(val: i32) -> Self {
48
13
        Self::new(T::from_saturating(val))
49
13
    }
50
}
51
impl<T: FromInt32Saturating + TryInto<u64>> FromInt32Saturating for IntegerMilliseconds<T> {
52
3
    fn from_saturating(val: i32) -> Self {
53
3
        Self::new(T::from_saturating(val))
54
3
    }
55
}
56
impl<T: FromInt32Saturating + TryInto<u64>> FromInt32Saturating for IntegerSeconds<T> {
57
6
    fn from_saturating(val: i32) -> Self {
58
6
        Self::new(T::from_saturating(val))
59
6
    }
60
}
61
impl<T: FromInt32Saturating + TryInto<u64>> FromInt32Saturating for IntegerDays<T> {
62
3
    fn from_saturating(val: i32) -> Self {
63
3
        Self::new(T::from_saturating(val))
64
3
    }
65
}
66
impl FromInt32Saturating for SendMeVersion {
67
5
    fn from_saturating(val: i32) -> Self {
68
5
        Self::new(val.clamp(0, 255) as u8)
69
5
    }
70
}
71

            
72
/// A macro to help us declare the net parameters object.  It lets us
73
/// put the information about each parameter in just one place, even
74
/// though it will later get split between the struct declaration, the
75
/// Default implementation, and the implementation of
76
/// `saturating_update_override`.
77
macro_rules! declare_net_parameters {
78
    {
79
        $(#[$s_meta:meta])* $s_v:vis struct $s_name:ident {
80
            $(
81
                $(#[$p_meta:meta])* $p_v:vis
82
                    $p_name:ident : $p_type:ty
83
                    = ($p_dflt:expr) from $p_string:literal
84
            ),*
85
            $( , )?
86
        }
87
    } =>
88
    {
89
        $(#[$s_meta])* $s_v struct $s_name {
90
            $(
91
                $(#[$p_meta])* $p_v $p_name : $p_type
92
            ),*
93
        }
94

            
95
        impl $s_name {
96
            /// Try to construct an instance of with its default values.
97
            ///
98
            /// (This should always succeed, unless one of the default values
99
            /// is out-of-bounds for the type.)
100
1066
            fn default_values() -> Result<Self, tor_units::Error> {
101
1066
                Ok(Self {
102
1066
                    $( $p_name : $p_dflt.try_into()? ),*
103
                })
104
1049
            }
105
            /// Replace the current value for the parameter identified in the
106
            /// consensus with `key` with a new value `val`.
107
            ///
108
            /// Uses saturating semantics if the new value is out-of-range.
109
            ///
110
            /// Returns true if the key was recognized, and false otherwise.
111
1238
            fn set_saturating(&mut self, key: &str, val: i32) -> bool {
112
1238
                match key {
113
1238
                    $( $p_string => self.$p_name = {
114
707
                        type T = $p_type;
115
707
                        T::from_saturating(val)
116
707
                    }, )*
117
4
                    _ => return false,
118
                }
119
1234
                true
120
1238
            }
121
        }
122
    }
123
}
124

            
125
declare_net_parameters! {
126

            
127
/// This structure holds recognized configuration parameters. All values are type-safe,
128
/// and where applicable clamped to be within range.
129
#[derive(Clone, Debug)]
130
#[non_exhaustive]
131
pub struct NetParameters {
132
    /// A weighting factor for bandwidth calculations
133
    pub bw_weight_scale: BoundedInt32<1, { i32::MAX }> = (10_000)
134
        from "bwweightscale",
135
    /// If true, do not attempt to learn circuit-build timeouts at all.
136
    pub cbt_learning_disabled: BoundedInt32<0, 1> = (0)
137
        from "cbtdisabled",
138
    /// Number of histograms bins to consider when estimating Xm for a
139
    /// Pareto-based circuit timeout estimator.
140
    pub cbt_num_xm_modes: BoundedInt32<1, 20> = (10)
141
        from "cbtnummodes",
142
    /// How many recent circuit success/timeout statuses do we remember
143
    /// when trying to tell if our circuit timeouts are too low?
144
    pub cbt_success_count: BoundedInt32<3, 1_000> = (20)
145
        from "cbtrecentcount",
146
    /// How many timeouts (in the last `cbt_success_count` observations)
147
    /// indicates that our circuit timeouts are too low?
148
    pub cbt_max_timeouts: BoundedInt32<3, 10_000> = (18)
149
        from "cbtmaxtimeouts",
150
    /// Smallest number of circuit build times we have to view in order to use
151
    /// our Pareto-based circuit timeout estimator.
152
    pub cbt_min_circs_for_estimate: BoundedInt32<1, 10_000> = (100)
153
        from "cbtmincircs",
154
    /// Quantile to use when determining the correct circuit timeout value
155
    /// with our Pareto estimator.
156
    ///
157
    /// (We continue building circuits after this timeout, but only
158
    /// for build-time measurement purposes.)
159
    pub cbt_timeout_quantile: Percentage<BoundedInt32<10, 99>> = (80)
160
        from "cbtquantile",
161
    /// Quantile to use when determining when to abandon circuits completely
162
    /// with our Pareto estimator.
163
    pub cbt_abandon_quantile: Percentage<BoundedInt32<10, 99>> = (99)
164
        from "cbtclosequantile",
165
    /// Lowest permissible timeout value for Pareto timeout estimator.
166
    pub cbt_min_timeout: IntegerMilliseconds<BoundedInt32<10, { i32::MAX }>> = (10)
167
        from "cbtmintimeout",
168
    /// Timeout value to use for our Pareto timeout estimator when we have
169
    /// no initial estimate.
170
    pub cbt_initial_timeout: IntegerMilliseconds<BoundedInt32<10, { i32::MAX }>> = (60_000)
171
        from "cbtinitialtimeout",
172
    /// When we don't have a good build-time estimate yet, how long
173
    /// (in seconds) do we wait between trying to launch build-time
174
    /// testing circuits through the network?
175
    pub cbt_testing_delay: IntegerSeconds<BoundedInt32<1, { i32::MAX }>> = (10)
176
        from "cbttestfreq",
177
    /// How many circuits can be open before we will no longer
178
    /// consider launching testing circuits to learn average build
179
    /// times?
180
    pub cbt_max_open_circuits_for_testing: BoundedInt32<0, 14> = (10)
181
        from "cbtmaxopencircs",
182

            
183
    /// The maximum cell window size?
184
    pub circuit_window: BoundedInt32<100, 1000> = (1_000)
185
        from "circwindow",
186
    /// The decay parameter for circuit priority
187
    pub circuit_priority_half_life: IntegerMilliseconds<BoundedInt32<1, { i32::MAX }>> = (30_000)
188
        from "CircuitPriorityHalflifeMsec",
189
    /// Whether to perform circuit extensions by Ed25519 ID
190
    pub extend_by_ed25519_id: BoundedInt32<0, 1> = (0)
191
        from "ExtendByEd25519ID",
192

            
193
    /// If we have excluded so many possible guards that the
194
    /// available fraction is below this threshold, we should use a different
195
    /// guard sample. (TODO: not actually implemented)
196
    pub guard_meaningful_restriction: Percentage<BoundedInt32<1,100>> = (20)
197
        from "guard-meaningful-restriction-percent",
198

            
199
    /// We should warn the user if they have excluded so many guards
200
    /// that the available fraction is below this threshold.
201
    pub guard_extreme_restriction: Percentage<BoundedInt32<1,100>> = (1)
202
        from "guard-extreme-restriction-percent",
203

            
204
    /// How long should we keep an unconfirmed guard (one we have not
205
    /// contacted) before removing it from the guard sample?
206
    pub guard_lifetime_unconfirmed: IntegerDays<BoundedInt32<1, 3650>> = (120)
207
        from "guard-lifetime-days",
208

            
209
    /// How long should we keep a _confirmed_ guard (one we have contacted)
210
    /// before removing it from the guard sample?
211
    pub guard_lifetime_confirmed: IntegerDays<BoundedInt32<1, 3650>> = (60)
212
        from "guard-confirmed-min-lifetime-days",
213

            
214
    /// If all circuits have failed for this interval, then treat the internet
215
    /// as "probably down", and treat any guard failures in that interval
216
    /// as unproven.
217
    pub guard_internet_likely_down: IntegerSeconds<BoundedInt32<1, {i32::MAX}>> = (600)
218
        from "guard-internet-likely-down-interval",
219
    /// Largest number of guards that a client should try to maintain in
220
    /// a sample of possible guards.
221
    pub guard_max_sample_size: BoundedInt32<1, {i32::MAX}> = (60)
222
        from "guard-max-sample-size",
223
    /// Largest fraction of guard bandwidth on the network that a client
224
    /// should try to remain in a sample of possible guards.
225
    pub guard_max_sample_threshold: Percentage<BoundedInt32<1,100>> = (20)
226
        from "guard-max-sample-threshold",
227

            
228
    /// If the client ever has fewer than this many guards in their sample,
229
    /// after filtering out unusable guards, they should try to add more guards
230
    /// to the sample (if allowed).
231
    pub guard_filtered_min_sample_size: BoundedInt32<1,{i32::MAX}> = (20)
232
        from "guard-min-filtered-sample-size",
233

            
234
    /// The number of confirmed guards that the client should treat as
235
    /// "primary guards".
236
    pub guard_n_primary: BoundedInt32<1,{i32::MAX}> = (3)
237
        from "guard-n-primary-guards",
238
    /// The number of primary guards that the client should use in parallel.
239
    /// Other primary guards won't get used unless earlier ones are down.
240
    pub guard_use_parallelism: BoundedInt32<1, {i32::MAX}> = (1)
241
        from "guard-n-primary-guards-to-use",
242
    /// The number of primary guards that the client should use in
243
    /// parallel.  Other primary directory guards won't get used
244
    /// unless earlier ones are down.
245
    pub guard_dir_use_parallelism: BoundedInt32<1, {i32::MAX}> = (3)
246
        from "guard-n-primary-dir-guards-to-use",
247

            
248
    /// When trying to confirm nonprimary guards, if a guard doesn't
249
    /// answer for more than this long in seconds, treat any lower-
250
    /// priority guards as possibly usable.
251
    pub guard_nonprimary_connect_timeout: IntegerSeconds<BoundedInt32<1,{i32::MAX}>> = (15)
252
        from "guard-nonprimary-guard-connect-timeout",
253
    /// When trying to confirm nonprimary guards, if a guard doesn't
254
    /// answer for more than _this_ long in seconds, treat it as down.
255
    pub guard_nonprimary_idle_timeout: IntegerSeconds<BoundedInt32<1,{i32::MAX}>> = (600)
256
        from "guard-nonprimary-guard-idle-timeout",
257
    /// If a guard has been unlisted in the consensus for at least this
258
    /// long, remove it from the consensus.
259
    pub guard_remove_unlisted_after: IntegerDays<BoundedInt32<1,3650>> = (20)
260
        from "guard-remove-unlisted-guards-after-days",
261

            
262

            
263
    /// The minimum threshold for circuit patch construction
264
    pub min_circuit_path_threshold: Percentage<BoundedInt32<25, 95>> = (60)
265
        from "min_paths_for_circs_pct",
266

            
267
    /// The minimum sendme version to accept.
268
    pub sendme_accept_min_version: SendMeVersion = (0)
269
        from "sendme_accept_min_version",
270
    /// The minimum sendme version to transmit.
271
    pub sendme_emit_min_version: SendMeVersion = (0)
272
        from "sendme_emit_min_version",
273

            
274
    /// How long should never-used client circuits stay available,
275
    /// in the steady state?
276
    pub unused_client_circ_timeout: IntegerSeconds<BoundedInt32<60, 86_400>> = (30*60)
277
        from "nf_conntimeout_clients",
278
    /// When we're learning circuit timeouts, how long should never-used client
279
    /// circuits stay available?
280
    pub unused_client_circ_timeout_while_learning_cbt: IntegerSeconds<BoundedInt32<10, 60_000>> = (3*60)
281
        from "cbtlearntimeout",
282

            
283
}
284

            
285
}
286

            
287
impl Default for NetParameters {
288
1050
    fn default() -> Self {
289
1050
        NetParameters::default_values().expect("Default parameters were out-of-bounds")
290
1050
    }
291
}
292

            
293
impl NetParameters {
294
    /// Construct a new NetParameters from a given list of key=value parameters.
295
    ///
296
    /// Unrecognized parameters are ignored.
297
16
    pub fn from_map(p: &tor_netdoc::doc::netstatus::NetParams<i32>) -> Self {
298
16
        let mut params = NetParameters::default();
299
16
        let _ = params.saturating_update(p.iter());
300
16
        params
301
16
    }
302

            
303
    /// Replace a list of parameters, using the logic of
304
    /// `set_saturating`.
305
    ///
306
    /// Return a vector of the parameter names we didn't recognize.
307
1084
    pub(crate) fn saturating_update<'a, S>(
308
1084
        &mut self,
309
1084
        iter: impl Iterator<Item = (S, &'a i32)>,
310
1084
    ) -> Vec<S>
311
1084
    where
312
1084
        S: AsRef<str>,
313
1084
    {
314
1084
        let mut unrecognized = Vec::new();
315
2306
        for (k, v) in iter {
316
1222
            if !self.set_saturating(k.as_ref(), *v) {
317
4
                unrecognized.push(k);
318
1218
            }
319
        }
320
1084
        unrecognized
321
1084
    }
322
}
323

            
324
#[cfg(test)]
325
#[allow(clippy::many_single_char_names)]
326
#[allow(clippy::unwrap_used)]
327
#[allow(clippy::cognitive_complexity)]
328
mod test {
329
    use super::*;
330
    use std::string::String;
331

            
332
    #[test]
333
    fn empty_list() {
334
        let mut x = NetParameters::default();
335
        let y = Vec::<(&String, &i32)>::new();
336
        let u = x.saturating_update(y.into_iter());
337
        assert!(u.is_empty());
338
    }
339

            
340
    #[test]
341
    fn unknown_parameter() {
342
        let mut x = NetParameters::default();
343
        let mut y = Vec::<(&String, &i32)>::new();
344
        let k = &String::from("This_is_not_a_real_key");
345
        let v = &456;
346
        y.push((k, v));
347
        let u = x.saturating_update(y.into_iter());
348
        assert_eq!(u, vec![&String::from("This_is_not_a_real_key")]);
349
    }
350

            
351
    // #[test]
352
    // fn duplicate_parameter() {}
353

            
354
    #[test]
355
    fn single_good_parameter() {
356
        let mut x = NetParameters::default();
357
        let mut y = Vec::<(&String, &i32)>::new();
358
        let k = &String::from("min_paths_for_circs_pct");
359
        let v = &54;
360
        y.push((k, v));
361
        let z = x.saturating_update(y.into_iter());
362
        assert!(z.is_empty());
363
        assert_eq!(x.min_circuit_path_threshold.as_percent().get(), 54);
364
    }
365

            
366
    #[test]
367
    fn multiple_good_parameters() {
368
        let mut x = NetParameters::default();
369
        let mut y = Vec::<(&String, &i32)>::new();
370
        let k = &String::from("min_paths_for_circs_pct");
371
        let v = &54;
372
        y.push((k, v));
373
        let k = &String::from("circwindow");
374
        let v = &900;
375
        y.push((k, v));
376
        let z = x.saturating_update(y.into_iter());
377
        assert!(z.is_empty());
378
        assert_eq!(x.min_circuit_path_threshold.as_percent().get(), 54);
379
        assert_eq!(x.circuit_window.get(), 900);
380
    }
381

            
382
    #[test]
383
    fn good_out_of_range() {
384
        let mut x = NetParameters::default();
385
        let mut y = Vec::<(&String, &i32)>::new();
386
        let k = &String::from("sendme_accept_min_version");
387
        let v = &30;
388
        y.push((k, v));
389
        let k = &String::from("min_paths_for_circs_pct");
390
        let v = &255;
391
        y.push((k, v));
392
        let z = x.saturating_update(y.into_iter());
393
        assert!(z.is_empty());
394
        assert_eq!(x.sendme_accept_min_version.get(), 30);
395
        assert_eq!(x.min_circuit_path_threshold.as_percent().get(), 95);
396
    }
397

            
398
    #[test]
399
    fn good_invalid_rep() {
400
        let mut x = NetParameters::default();
401
        let mut y = Vec::<(&String, &i32)>::new();
402
        let k = &String::from("sendme_accept_min_version");
403
        let v = &30;
404
        y.push((k, v));
405
        let k = &String::from("min_paths_for_circs_pct");
406
        let v = &9000;
407
        y.push((k, v));
408
        let z = x.saturating_update(y.into_iter());
409
        assert!(z.is_empty());
410
        assert_eq!(x.sendme_accept_min_version.get(), 30);
411
        assert_eq!(x.min_circuit_path_threshold.as_percent().get(), 95);
412
    }
413

            
414
    // #[test]
415
    // fn good_duplicate() {}
416
    #[test]
417
    fn good_unknown() {
418
        let mut x = NetParameters::default();
419
        let mut y = Vec::<(&String, &i32)>::new();
420
        let k = &String::from("sendme_accept_min_version");
421
        let v = &30;
422
        y.push((k, v));
423
        let k = &String::from("not_a_real_parameter");
424
        let v = &9000;
425
        y.push((k, v));
426
        let z = x.saturating_update(y.into_iter());
427
        assert_eq!(z, vec![&String::from("not_a_real_parameter")]);
428
        assert_eq!(x.sendme_accept_min_version.get(), 30);
429
    }
430

            
431
    #[test]
432
    fn from_consensus() {
433
        let mut p = NetParameters::default();
434
        let mut mp: std::collections::HashMap<String, i32> = std::collections::HashMap::new();
435
        mp.insert("bwweightscale".to_string(), 70);
436
        mp.insert("min_paths_for_circs_pct".to_string(), 45);
437
        mp.insert("im_a_little_teapot".to_string(), 1);
438
        mp.insert("circwindow".to_string(), 99999);
439
        mp.insert("ExtendByEd25519ID".to_string(), 1);
440

            
441
        let z = p.saturating_update(mp.iter());
442
        assert_eq!(z, vec![&String::from("im_a_little_teapot")]);
443

            
444
        assert_eq!(p.bw_weight_scale.get(), 70);
445
        assert_eq!(p.min_circuit_path_threshold.as_percent().get(), 45);
446
        let b_val: bool = p.extend_by_ed25519_id.into();
447
        assert!(b_val);
448
    }
449

            
450
    #[test]
451
    fn all_parameters() {
452
        use std::convert::TryFrom;
453
        use std::time::Duration;
454
        let mut p = NetParameters::default();
455
        let mp = [
456
            ("bwweightscale", 10),
457
            ("cbtdisabled", 1),
458
            ("cbtnummodes", 11),
459
            ("cbtrecentcount", 12),
460
            ("cbtmaxtimeouts", 13),
461
            ("cbtmincircs", 5),
462
            ("cbtquantile", 61),
463
            ("cbtclosequantile", 15),
464
            ("cbtlearntimeout", 1900),
465
            ("cbtmintimeout", 2020),
466
            ("cbtinitialtimeout", 2050),
467
            ("cbttestfreq", 110),
468
            ("cbtmaxopencircs", 14),
469
            ("circwindow", 999),
470
            ("CircuitPriorityHalflifeMsec", 222),
471
            ("guard-lifetime-days", 36),
472
            ("guard-confirmed-min-lifetime-days", 37),
473
            ("guard-internet-likely-down-interval", 38),
474
            ("guard-max-sample-size", 39),
475
            ("guard-max-sample-threshold", 40),
476
            ("guard-min-filtered-sample-size", 41),
477
            ("guard-n-primary-guards", 42),
478
            ("guard-n-primary-guards-to-use", 43),
479
            ("guard-n-primary-dir-guards-to-use", 44),
480
            ("guard-nonprimary-guard-connect-timeout", 45),
481
            ("guard-nonprimary-guard-idle-timeout", 46),
482
            ("guard-remove-unlisted-guards-after-days", 47),
483
            ("guard-meaningful-restriction-percent", 12),
484
            ("guard-extreme-restriction-percent", 3),
485
            ("ExtendByEd25519ID", 0),
486
            ("min_paths_for_circs_pct", 51),
487
            ("nf_conntimeout_clients", 606),
488
            ("sendme_accept_min_version", 31),
489
            ("sendme_emit_min_version", 32),
490
        ];
491
        let ignored = p.saturating_update(mp.iter().map(|(a, b)| (a, b)));
492
        assert!(ignored.is_empty());
493

            
494
        assert_eq!(p.bw_weight_scale.get(), 10);
495
        assert!(bool::from(p.cbt_learning_disabled));
496
        assert_eq!(p.cbt_num_xm_modes.get(), 11);
497
        assert_eq!(p.cbt_success_count.get(), 12);
498
        assert_eq!(p.cbt_max_timeouts.get(), 13);
499
        assert_eq!(p.cbt_min_circs_for_estimate.get(), 5);
500
        assert_eq!(p.cbt_timeout_quantile.as_percent().get(), 61);
501
        assert_eq!(p.cbt_abandon_quantile.as_percent().get(), 15);
502
        assert_eq!(
503
            Duration::try_from(p.unused_client_circ_timeout_while_learning_cbt).unwrap(),
504
            Duration::from_secs(1900)
505
        );
506
        assert_eq!(
507
            Duration::try_from(p.cbt_min_timeout).unwrap(),
508
            Duration::from_millis(2020)
509
        );
510
        assert_eq!(
511
            Duration::try_from(p.cbt_initial_timeout).unwrap(),
512
            Duration::from_millis(2050)
513
        );
514
        assert_eq!(
515
            Duration::try_from(p.cbt_testing_delay).unwrap(),
516
            Duration::from_secs(110)
517
        );
518
        assert_eq!(p.cbt_max_open_circuits_for_testing.get(), 14);
519
        assert_eq!(p.circuit_window.get(), 999);
520
        assert_eq!(
521
            Duration::try_from(p.circuit_priority_half_life).unwrap(),
522
            Duration::from_millis(222)
523
        );
524
        assert!(!bool::from(p.extend_by_ed25519_id));
525
        assert_eq!(p.min_circuit_path_threshold.as_percent().get(), 51);
526
        assert_eq!(
527
            Duration::try_from(p.unused_client_circ_timeout).unwrap(),
528
            Duration::from_secs(606)
529
        );
530
        assert_eq!(p.sendme_accept_min_version.get(), 31);
531
        assert_eq!(p.sendme_emit_min_version.get(), 32);
532

            
533
        assert_eq!(
534
            Duration::try_from(p.guard_lifetime_unconfirmed).unwrap(),
535
            Duration::from_secs(86400 * 36)
536
        );
537
        assert_eq!(
538
            Duration::try_from(p.guard_lifetime_confirmed).unwrap(),
539
            Duration::from_secs(86400 * 37)
540
        );
541
        assert_eq!(
542
            Duration::try_from(p.guard_internet_likely_down).unwrap(),
543
            Duration::from_secs(38)
544
        );
545
        assert_eq!(p.guard_max_sample_size.get(), 39);
546
        assert_eq!(p.guard_max_sample_threshold.as_percent().get(), 40);
547
        assert_eq!(p.guard_filtered_min_sample_size.get(), 41);
548
        assert_eq!(p.guard_n_primary.get(), 42);
549
        assert_eq!(p.guard_use_parallelism.get(), 43);
550
        assert_eq!(p.guard_dir_use_parallelism.get(), 44);
551
        assert_eq!(
552
            Duration::try_from(p.guard_nonprimary_connect_timeout).unwrap(),
553
            Duration::from_secs(45)
554
        );
555
        assert_eq!(
556
            Duration::try_from(p.guard_nonprimary_idle_timeout).unwrap(),
557
            Duration::from_secs(46)
558
        );
559
        assert_eq!(
560
            Duration::try_from(p.guard_remove_unlisted_after).unwrap(),
561
            Duration::from_secs(86400 * 47)
562
        );
563
        assert_eq!(p.guard_meaningful_restriction.as_percent().get(), 12);
564
        assert_eq!(p.guard_extreme_restriction.as_percent().get(), 3);
565
    }
566
}