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 tor_units::{
22
    BoundedInt32, IntegerDays, IntegerMilliseconds, IntegerMinutes, IntegerSeconds, Percentage,
23
    SendMeVersion,
24
};
25

            
26
/// Upper limit for channel padding timeouts
27
///
28
/// This is just a safety catch which might help prevent integer overflow,
29
/// and also might prevent a client getting permantently stuck in a state
30
/// where it ought to send padding but never does.
31
///
32
/// The actual value is stolen from C Tor as per
33
///   <https://gitlab.torproject.org/tpo/core/arti/-/merge_requests/586#note_2813638>
34
/// pending an update to the specifications
35
///   <https://gitlab.torproject.org/tpo/core/torspec/-/issues/120>
36
pub const CHANNEL_PADDING_TIMEOUT_UPPER_BOUND: i32 = 60_000;
37

            
38
/// An object that can be constructed from an i32, with saturating semantics.
39
pub trait FromInt32Saturating {
40
    /// Construct an instance of this object from `val`.
41
    ///
42
    /// If `val` is too low, treat it as the lowest value that would be
43
    /// valid.  If `val` is too high, treat it as the highest value that
44
    /// would be valid.
45
    fn from_saturating(val: i32) -> Self;
46
}
47

            
48
impl FromInt32Saturating for i32 {
49
    fn from_saturating(val: i32) -> Self {
50
        val
51
    }
52
}
53
impl<const L: i32, const H: i32> FromInt32Saturating for BoundedInt32<L, H> {
54
9898
    fn from_saturating(val: i32) -> Self {
55
9898
        Self::saturating_new(val)
56
9898
    }
57
}
58
impl<T: Copy + Into<f64> + FromInt32Saturating> FromInt32Saturating for Percentage<T> {
59
650
    fn from_saturating(val: i32) -> Self {
60
650
        Self::new(T::from_saturating(val))
61
650
    }
62
}
63
impl<T: FromInt32Saturating + TryInto<u64>> FromInt32Saturating for IntegerMilliseconds<T> {
64
716
    fn from_saturating(val: i32) -> Self {
65
716
        Self::new(T::from_saturating(val))
66
716
    }
67
}
68
impl<T: FromInt32Saturating + TryInto<u64>> FromInt32Saturating for IntegerSeconds<T> {
69
12
    fn from_saturating(val: i32) -> Self {
70
12
        Self::new(T::from_saturating(val))
71
12
    }
72
}
73
impl<T: FromInt32Saturating + TryInto<u64>> FromInt32Saturating for IntegerMinutes<T> {
74
4
    fn from_saturating(val: i32) -> Self {
75
4
        Self::new(T::from_saturating(val))
76
4
    }
77
}
78
impl<T: FromInt32Saturating + TryInto<u64>> FromInt32Saturating for IntegerDays<T> {
79
6
    fn from_saturating(val: i32) -> Self {
80
6
        Self::new(T::from_saturating(val))
81
6
    }
82
}
83
impl FromInt32Saturating for SendMeVersion {
84
10
    fn from_saturating(val: i32) -> Self {
85
10
        Self::new(val.clamp(0, 255) as u8)
86
10
    }
87
}
88

            
89
/// A macro to help us declare the net parameters object.  It lets us
90
/// put the information about each parameter in just one place, even
91
/// though it will later get split between the struct declaration, the
92
/// Default implementation, and the implementation of
93
/// `saturating_update_override`.
94
macro_rules! declare_net_parameters {
95
    {
96
        $(#[$s_meta:meta])* $s_v:vis struct $s_name:ident {
97
            $(
98
                $(#[$p_meta:meta])* $p_v:vis
99
                    $p_name:ident : $p_type:ty
100
                    = ($p_dflt:expr) from $p_string:literal
101
            ),*
102
            $( , )?
103
        }
104
    } =>
105
    {
106
        $(#[$s_meta])* $s_v struct $s_name {
107
            $(
108
                $(#[$p_meta])* $p_v $p_name : $p_type
109
            ),*
110
        }
111

            
112
        impl $s_name {
113
            /// Try to construct an instance of with its default values.
114
            ///
115
            /// (This should always succeed, unless one of the default values
116
            /// is out-of-bounds for the type.)
117
9734
            fn default_values() -> Result<Self, tor_units::Error> {
118
                Ok(Self {
119
9734
                    $( $p_name : $p_dflt.try_into()? ),*
120
                })
121
9734
            }
122
            /// Replace the current value for the parameter identified in the
123
            /// consensus with `key` with a new value `val`.
124
            ///
125
            /// Uses saturating semantics if the new value is out-of-range.
126
            ///
127
            /// Returns true if the key was recognized, and false otherwise.
128
9916
            fn set_saturating(&mut self, key: &str, val: i32) -> bool {
129
9916
                match key {
130
8
                    $( $p_string => self.$p_name = {
131
                        type T = $p_type;
132
                        T::from_saturating(val)
133
                    }, )*
134
8
                    _ => return false,
135
                }
136
9908
                true
137
9916
            }
138
        }
139
    }
140
}
141

            
142
declare_net_parameters! {
143

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

            
200
    /// The maximum cell window size?
201
    pub circuit_window: BoundedInt32<100, 1000> = (1_000)
202
        from "circwindow",
203
    /// The decay parameter for circuit priority
204
    pub circuit_priority_half_life: IntegerMilliseconds<BoundedInt32<1, { i32::MAX }>> = (30_000)
205
        from "CircuitPriorityHalflifeMsec",
206
    /// Whether to perform circuit extensions by Ed25519 ID
207
    pub extend_by_ed25519_id: BoundedInt32<0, 1> = (0)
208
        from "ExtendByEd25519ID",
209

            
210
    /// If we have excluded so many possible guards that the
211
    /// available fraction is below this threshold, we should use a different
212
    /// guard sample.
213
    pub guard_meaningful_restriction: Percentage<BoundedInt32<1,100>> = (20)
214
        from "guard-meaningful-restriction-percent",
215

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

            
221
    /// How long should we keep an unconfirmed guard (one we have not
222
    /// contacted) before removing it from the guard sample?
223
    pub guard_lifetime_unconfirmed: IntegerDays<BoundedInt32<1, 3650>> = (120)
224
        from "guard-lifetime-days",
225

            
226
    /// How long should we keep a _confirmed_ guard (one we have contacted)
227
    /// before removing it from the guard sample?
228
    pub guard_lifetime_confirmed: IntegerDays<BoundedInt32<1, 3650>> = (60)
229
        from "guard-confirmed-min-lifetime-days",
230

            
231
    /// If all circuits have failed for this interval, then treat the internet
232
    /// as "probably down", and treat any guard failures in that interval
233
    /// as unproven.
234
    pub guard_internet_likely_down: IntegerSeconds<BoundedInt32<1, {i32::MAX}>> = (600)
235
        from "guard-internet-likely-down-interval",
236
    /// Largest number of guards that a client should try to maintain in
237
    /// a sample of possible guards.
238
    pub guard_max_sample_size: BoundedInt32<1, {i32::MAX}> = (60)
239
        from "guard-max-sample-size",
240
    /// Largest fraction of guard bandwidth on the network that a client
241
    /// should try to remain in a sample of possible guards.
242
    pub guard_max_sample_threshold: Percentage<BoundedInt32<1,100>> = (20)
243
        from "guard-max-sample-threshold",
244

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

            
251
    /// The number of confirmed guards that the client should treat as
252
    /// "primary guards".
253
    pub guard_n_primary: BoundedInt32<1,{i32::MAX}> = (3)
254
        from "guard-n-primary-guards",
255
    /// The number of primary guards that the client should use in parallel.
256
    /// Other primary guards won't get used unless earlier ones are down.
257
    pub guard_use_parallelism: BoundedInt32<1, {i32::MAX}> = (1)
258
        from "guard-n-primary-guards-to-use",
259
    /// The number of primary guards that the client should use in
260
    /// parallel.  Other primary directory guards won't get used
261
    /// unless earlier ones are down.
262
    pub guard_dir_use_parallelism: BoundedInt32<1, {i32::MAX}> = (3)
263
        from "guard-n-primary-dir-guards-to-use",
264

            
265
    /// When trying to confirm nonprimary guards, if a guard doesn't
266
    /// answer for more than this long in seconds, treat any lower-
267
    /// priority guards as possibly usable.
268
    pub guard_nonprimary_connect_timeout: IntegerSeconds<BoundedInt32<1,{i32::MAX}>> = (15)
269
        from "guard-nonprimary-guard-connect-timeout",
270
    /// When trying to confirm nonprimary guards, if a guard doesn't
271
    /// answer for more than _this_ long in seconds, treat it as down.
272
    pub guard_nonprimary_idle_timeout: IntegerSeconds<BoundedInt32<1,{i32::MAX}>> = (600)
273
        from "guard-nonprimary-guard-idle-timeout",
274
    /// If a guard has been unlisted in the consensus for at least this
275
    /// long, remove it from the consensus.
276
    pub guard_remove_unlisted_after: IntegerDays<BoundedInt32<1,3650>> = (20)
277
        from "guard-remove-unlisted-guards-after-days",
278

            
279

            
280
    /// The minimum threshold for circuit patch construction
281
    pub min_circuit_path_threshold: Percentage<BoundedInt32<25, 95>> = (60)
282
        from "min_paths_for_circs_pct",
283

            
284
    /// Channel padding, low end of random padding interval, milliseconds
285
    ///
286
    /// `nf_ito` stands for "netflow inactive timeout".
287
    pub nf_ito_low: IntegerMilliseconds<BoundedInt32<0, CHANNEL_PADDING_TIMEOUT_UPPER_BOUND>> = (1500)
288
        from "nf_ito_low",
289
    /// Channel padding, high end of random padding interval, milliseconds
290
    pub nf_ito_high: IntegerMilliseconds<BoundedInt32<0, CHANNEL_PADDING_TIMEOUT_UPPER_BOUND>> = (9500)
291
        from "nf_ito_high",
292
    /// Channel padding, low end of random padding interval (reduced padding) milliseconds
293
    pub nf_ito_low_reduced: IntegerMilliseconds<BoundedInt32<0, CHANNEL_PADDING_TIMEOUT_UPPER_BOUND>> = (9000)
294
        from "nf_ito_low_reduced",
295
    /// Channel padding, high end of random padding interval (reduced padding) , milliseconds
296
    pub nf_ito_high_reduced: IntegerMilliseconds<BoundedInt32<0, CHANNEL_PADDING_TIMEOUT_UPPER_BOUND>> = (14000)
297
        from "nf_ito_high_reduced",
298

            
299
    /// The minimum sendme version to accept.
300
    pub sendme_accept_min_version: SendMeVersion = (0)
301
        from "sendme_accept_min_version",
302
    /// The minimum sendme version to transmit.
303
    pub sendme_emit_min_version: SendMeVersion = (0)
304
        from "sendme_emit_min_version",
305

            
306
    /// How long should never-used client circuits stay available,
307
    /// in the steady state?
308
    pub unused_client_circ_timeout: IntegerSeconds<BoundedInt32<60, 86_400>> = (30*60)
309
        from "nf_conntimeout_clients",
310
    /// When we're learning circuit timeouts, how long should never-used client
311
    /// circuits stay available?
312
    pub unused_client_circ_timeout_while_learning_cbt: IntegerSeconds<BoundedInt32<10, 60_000>> = (3*60)
313
        from "cbtlearntimeout",
314

            
315
    /// The minimum number of SENDME acks required to estimate RTT and/or bandwidth.
316
    pub cc_min_sendme_acks: BoundedInt32<2, 20> = (5)
317
        from "cc_bwe_min",
318
    /// The "N" parameter in N-EWMA smoothing of RTT and/or bandwidth estimation, specified as a
319
    /// percentage of the number of SENDME acks in a congestion window.
320
    ///
321
    /// A percentage over 100% indicates smoothing with more than one congestion window's worth
322
    /// of SENDMEs.
323
    pub cc_ewma_n_by_sendme_acks: Percentage<BoundedInt32<1, 255>> = (50)
324
        from "cc_ewma_cwnd_pct",
325
    /// The maximum value of the "N" parameter in N-EWMA smoothing of RTT and/or bandwidth
326
    /// estimation.
327
    pub cc_ewma_n_max: BoundedInt32<2, {i32::MAX}> = (10)
328
        from "cc_ewma_max",
329
    /// How many cells a SENDME acks under the congestion-control regime.
330
    pub cc_sendme_cell_ack_count: BoundedInt32<1, 255> = (31)
331
        from "cc_sendme_inc",
332
    /// How often we update our congestion window, per congestion window worth of packets.
333
    /// (For example, if this is 2, we will update the window twice every window.)
334
    pub cc_cwnd_inc_rate: BoundedInt32<1, 250> = (1)
335
        from "cc_cwnd_inc_rate",
336

            
337
    /// Lower bound on the number of INTRODUCE2 cells to allow per introduction
338
    /// circuit before the service decides to rotate to a new introduction
339
    /// circuit.
340
    pub hs_introcirc_requests_min: BoundedInt32<0, {i32::MAX}> = (16384)
341
        from "hs_intro_min_introduce2",
342

            
343
    /// Upper bound on the number of INTRODUCE2 cells to allow per introduction
344
    /// circuit before the service decides to rotate to a new introduction
345
    /// circuit.
346
    pub hs_introcirc_requests_max: BoundedInt32<0, {i32::MAX}> = (32768)
347
        from "hs_intro_max_introduce2",
348

            
349
    /// Lower bound on the lifetime of an introduction point.
350
    pub hs_intro_min_lifetime: IntegerSeconds<BoundedInt32<0, {i32::MAX}>> = (18 * 60 * 60)
351
        from "hs_intro_min_lifetime",
352

            
353
    /// Upper bound on the lifetime of an introduction point.
354
    pub hs_intro_max_lifetime: IntegerSeconds<BoundedInt32<0, {i32::MAX}>> = (24 * 60 * 60)
355
        from "hs_intro_max_lifetime",
356

            
357
    /// Number of "extra" introduction points that an onion service is allowed
358
    /// to open based on demand.
359
    pub hs_intro_num_extra_intropoints: BoundedInt32<0, 128> = (2)
360
        from "hs_intro_num_extra",
361

            
362
    /// The duration of a time period, as used in the onion service directory
363
    /// protocol.
364
    ///
365
    /// During each "time period", each onion service gets a different blinded
366
    /// ID, and the hash ring gets a new layout.
367
    pub hsdir_timeperiod_length: IntegerMinutes<BoundedInt32<30, 14400>> = (1440)
368
        from "hsdir_interval",
369

            
370
    /// The number of positions at the hash ring where an onion service
371
    /// descriptor should be stored.
372
    pub hsdir_n_replicas: BoundedInt32<1, 16> = (2)
373
        from "hsdir_n_replicas",
374

            
375
    /// The number of HSDir instances, at each position in the hash ring, that
376
    /// should be considered when downloading an onion service descriptor.
377
    pub hsdir_spread_fetch: BoundedInt32<1, 128> = (3)
378
        from "hsdir_spread_fetch",
379

            
380
    /// The number of HSDir instances, at each position in the hash ring, that
381
    /// should be considered when uploading an onion service descriptor.
382
    pub hsdir_spread_store: BoundedInt32<1,128> = (4)
383
        from "hsdir_spread_store",
384

            
385
    /// Largest allowable v3 onion service size (in bytes).
386
    pub hsdir_max_desc_size: BoundedInt32<1, {i32::MAX}> = (50_000)
387
        from "HSV3MaxDescriptorSize",
388

            
389
    /// Largest number of failures to rendezvous that an onion service should
390
    /// allow for a request.
391
    pub hs_service_rendezvous_failures_max: BoundedInt32<1, 10> = (2)
392
        from "hs_service_max_rdv_failures",
393

            
394
    /// If set to 1, introduction points use the INTRODUCE1 rate limiting
395
    /// defense when no `DosParams` are sent.
396
    ///
397
    /// See <https://spec.torproject.org/param-spec.html#HiddenServiceEnableIntroDoSDefense>
398
    pub hs_intro_dos_enabled: BoundedInt32<0, 1> = (0)
399
        from "HiddenServiceEnableIntroDoSDefense",
400

            
401
    /// Default _rate_ value for an introduction point to use for INTRODUCE1 rate
402
    /// limiting when no `DosParams` value is sent, in messages per second.
403
    ///
404
    /// See
405
    /// <https://spec.torproject.org/param-spec.html#HiddenServiceEnableIntroDoSBurstPerSec>
406
    pub hs_intro_dos_max_burst: BoundedInt32<0, {i32::MAX}> = (200)
407
        from "HiddenServiceEnableIntroDoSBurstPerSec",
408

            
409
    /// Default _burst_ value for an introduction point to use for INTRODUCE1 rate
410
    /// limiting when no `DosParams` value is sent.
411
    ///
412
    /// See
413
    /// <https://spec.torproject.org/param-spec.html#HiddenServiceEnableIntroDoSRatePerSec>
414
    pub hs_intro_dos_rate: BoundedInt32<0, {i32::MAX}> = (25)
415
        from  "HiddenServiceEnableIntroDoSRatePerSec",
416

            
417
    /// The type of vanguards to use by default when building onion service circuits:
418
    ///
419
    /// ```text
420
    ///    0: No vanguards.
421
    ///    1: Lite vanguards.
422
    ///    2: Full vanguards.
423
    /// ```
424
    ///
425
    /// See
426
    /// <https://spec.torproject.org/param-spec.html#vanguards>
427
    pub vanguards_enabled: BoundedInt32<0, 2> = (1)
428
        from "vanguards-enabled",
429

            
430
    /// If higher than `vanguards-enabled`,
431
    /// and we are running an onion service,
432
    /// we use this level for all our onion service circuits:
433
    ///
434
    /// ```text
435
    ///    0: No vanguards.
436
    ///    1: Lite vanguards.
437
    ///    2: Full vanguards.
438
    /// ```
439
    ///
440
    /// See
441
    /// <https://spec.torproject.org/param-spec.html#vanguards>
442
    pub vanguards_hs_service: BoundedInt32<0, 2> = (2)
443
        from "vanguards-hs-service",
444

            
445
    /// The number of vanguards in the L2 vanguard set.
446
    ///
447
    /// See
448
    /// <https://spec.torproject.org/param-spec.html#vanguards>
449
    pub guard_hs_l2_number: BoundedInt32<1, {i32::MAX}> = (4)
450
        from  "guard-hs-l2-number",
451

            
452
    /// The minimum lifetime of L2 vanguards.
453
    ///
454
    /// See
455
    /// <https://spec.torproject.org/param-spec.html#vanguards>
456
    pub guard_hs_l2_lifetime_min: IntegerSeconds<BoundedInt32<1, {i32::MAX}>> = (86400)
457
        from  "guard-hs-l2-lifetime-min",
458

            
459
    /// The maximum lifetime of L2 vanguards.
460
    ///
461
    /// See
462
    /// <https://spec.torproject.org/param-spec.html#vanguards>
463
    pub guard_hs_l2_lifetime_max: IntegerSeconds<BoundedInt32<1, {i32::MAX}>> = (1036800)
464
        from  "guard-hs-l2-lifetime-max",
465

            
466
    /// The number of vanguards in the L3 vanguard set.
467
    ///
468
    /// See
469
    /// <https://spec.torproject.org/param-spec.html#vanguards>
470
    pub guard_hs_l3_number: BoundedInt32<1, {i32::MAX}> = (8)
471
        from  "guard-hs-l3-number",
472

            
473
    /// The minimum lifetime of L3 vanguards.
474
    ///
475
    /// See
476
    /// <https://spec.torproject.org/param-spec.html#vanguards>
477
    pub guard_hs_l3_lifetime_min: IntegerSeconds<BoundedInt32<1, {i32::MAX}>> = (3600)
478
        from  "guard-hs-l3-lifetime-min",
479

            
480
    /// The maximum lifetime of L3 vanguards.
481
    ///
482
    /// See
483
    /// <https://spec.torproject.org/param-spec.html#vanguards>
484
    pub guard_hs_l3_lifetime_max: IntegerSeconds<BoundedInt32<1, {i32::MAX}>> = (172800)
485
        from  "guard-hs-l3-lifetime-max",
486
}
487

            
488
}
489

            
490
impl Default for NetParameters {
491
9734
    fn default() -> Self {
492
9734
        NetParameters::default_values().expect("Default parameters were out-of-bounds")
493
9734
    }
494
}
495

            
496
// This impl is a bit silly, but it makes the `params` method on NetDirProvider
497
// work out.
498
impl AsRef<NetParameters> for NetParameters {
499
234
    fn as_ref(&self) -> &NetParameters {
500
234
        self
501
234
    }
502
}
503

            
504
impl NetParameters {
505
    /// Construct a new NetParameters from a given list of key=value parameters.
506
    ///
507
    /// Unrecognized parameters are ignored.
508
550
    pub fn from_map(p: &tor_netdoc::doc::netstatus::NetParams<i32>) -> Self {
509
550
        let mut params = NetParameters::default();
510
550
        let _ = params.saturating_update(p.iter());
511
550
        params
512
550
    }
513

            
514
    /// Replace a list of parameters, using the logic of
515
    /// `set_saturating`.
516
    ///
517
    /// Return a vector of the parameter names we didn't recognize.
518
13346
    pub(crate) fn saturating_update<'a, S>(
519
13346
        &mut self,
520
13346
        iter: impl Iterator<Item = (S, &'a i32)>,
521
13346
    ) -> Vec<S>
522
13346
    where
523
13346
        S: AsRef<str>,
524
13346
    {
525
13346
        let mut unrecognized = Vec::new();
526
23262
        for (k, v) in iter {
527
9916
            if !self.set_saturating(k.as_ref(), *v) {
528
8
                unrecognized.push(k);
529
9908
            }
530
        }
531
13346
        unrecognized
532
13346
    }
533
}
534

            
535
#[cfg(test)]
536
#[allow(clippy::many_single_char_names)]
537
#[allow(clippy::unwrap_used)]
538
#[allow(clippy::cognitive_complexity)]
539
mod test {
540
    // @@ begin test lint list maintained by maint/add_warning @@
541
    #![allow(clippy::bool_assert_comparison)]
542
    #![allow(clippy::clone_on_copy)]
543
    #![allow(clippy::dbg_macro)]
544
    #![allow(clippy::mixed_attributes_style)]
545
    #![allow(clippy::print_stderr)]
546
    #![allow(clippy::print_stdout)]
547
    #![allow(clippy::single_char_pattern)]
548
    #![allow(clippy::unwrap_used)]
549
    #![allow(clippy::unchecked_duration_subtraction)]
550
    #![allow(clippy::useless_vec)]
551
    #![allow(clippy::needless_pass_by_value)]
552
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
553
    use super::*;
554
    use std::string::String;
555

            
556
    #[test]
557
    fn empty_list() {
558
        let mut x = NetParameters::default();
559
        let y = Vec::<(&String, &i32)>::new();
560
        let u = x.saturating_update(y.into_iter());
561
        assert!(u.is_empty());
562
    }
563

            
564
    #[test]
565
    fn unknown_parameter() {
566
        let mut x = NetParameters::default();
567
        let mut y = Vec::<(&String, &i32)>::new();
568
        let k = &String::from("This_is_not_a_real_key");
569
        let v = &456;
570
        y.push((k, v));
571
        let u = x.saturating_update(y.into_iter());
572
        assert_eq!(u, vec![&String::from("This_is_not_a_real_key")]);
573
    }
574

            
575
    // #[test]
576
    // fn duplicate_parameter() {}
577

            
578
    #[test]
579
    fn single_good_parameter() {
580
        let mut x = NetParameters::default();
581
        let mut y = Vec::<(&String, &i32)>::new();
582
        let k = &String::from("min_paths_for_circs_pct");
583
        let v = &54;
584
        y.push((k, v));
585
        let z = x.saturating_update(y.into_iter());
586
        assert!(z.is_empty());
587
        assert_eq!(x.min_circuit_path_threshold.as_percent().get(), 54);
588
    }
589

            
590
    #[test]
591
    fn multiple_good_parameters() {
592
        let mut x = NetParameters::default();
593
        let mut y = Vec::<(&String, &i32)>::new();
594
        let k = &String::from("min_paths_for_circs_pct");
595
        let v = &54;
596
        y.push((k, v));
597
        let k = &String::from("circwindow");
598
        let v = &900;
599
        y.push((k, v));
600
        let z = x.saturating_update(y.into_iter());
601
        assert!(z.is_empty());
602
        assert_eq!(x.min_circuit_path_threshold.as_percent().get(), 54);
603
        assert_eq!(x.circuit_window.get(), 900);
604
    }
605

            
606
    #[test]
607
    fn good_out_of_range() {
608
        let mut x = NetParameters::default();
609
        let mut y = Vec::<(&String, &i32)>::new();
610
        let k = &String::from("sendme_accept_min_version");
611
        let v = &30;
612
        y.push((k, v));
613
        let k = &String::from("min_paths_for_circs_pct");
614
        let v = &255;
615
        y.push((k, v));
616
        let z = x.saturating_update(y.into_iter());
617
        assert!(z.is_empty());
618
        assert_eq!(x.sendme_accept_min_version.get(), 30);
619
        assert_eq!(x.min_circuit_path_threshold.as_percent().get(), 95);
620
    }
621

            
622
    #[test]
623
    fn good_invalid_rep() {
624
        let mut x = NetParameters::default();
625
        let mut y = Vec::<(&String, &i32)>::new();
626
        let k = &String::from("sendme_accept_min_version");
627
        let v = &30;
628
        y.push((k, v));
629
        let k = &String::from("min_paths_for_circs_pct");
630
        let v = &9000;
631
        y.push((k, v));
632
        let z = x.saturating_update(y.into_iter());
633
        assert!(z.is_empty());
634
        assert_eq!(x.sendme_accept_min_version.get(), 30);
635
        assert_eq!(x.min_circuit_path_threshold.as_percent().get(), 95);
636
    }
637

            
638
    // #[test]
639
    // fn good_duplicate() {}
640
    #[test]
641
    fn good_unknown() {
642
        let mut x = NetParameters::default();
643
        let mut y = Vec::<(&String, &i32)>::new();
644
        let k = &String::from("sendme_accept_min_version");
645
        let v = &30;
646
        y.push((k, v));
647
        let k = &String::from("not_a_real_parameter");
648
        let v = &9000;
649
        y.push((k, v));
650
        let z = x.saturating_update(y.into_iter());
651
        assert_eq!(z, vec![&String::from("not_a_real_parameter")]);
652
        assert_eq!(x.sendme_accept_min_version.get(), 30);
653
    }
654

            
655
    #[test]
656
    fn from_consensus() {
657
        let mut p = NetParameters::default();
658
        let mut mp: std::collections::HashMap<String, i32> = std::collections::HashMap::new();
659
        mp.insert("bwweightscale".to_string(), 70);
660
        mp.insert("min_paths_for_circs_pct".to_string(), 45);
661
        mp.insert("im_a_little_teapot".to_string(), 1);
662
        mp.insert("circwindow".to_string(), 99999);
663
        mp.insert("ExtendByEd25519ID".to_string(), 1);
664

            
665
        let z = p.saturating_update(mp.iter());
666
        assert_eq!(z, vec![&String::from("im_a_little_teapot")]);
667

            
668
        assert_eq!(p.bw_weight_scale.get(), 70);
669
        assert_eq!(p.min_circuit_path_threshold.as_percent().get(), 45);
670
        let b_val: bool = p.extend_by_ed25519_id.into();
671
        assert!(b_val);
672
    }
673

            
674
    #[test]
675
    // TODO remove when this upstream bug is fixed
676
    ///  https://github.com/rust-lang/rust-clippy/issues/11764
677
    #[allow(clippy::map_identity)]
678
    fn all_parameters() {
679
        use std::time::Duration;
680
        let mut p = NetParameters::default();
681
        let mp = [
682
            ("bwweightscale", 10),
683
            ("cbtdisabled", 1),
684
            ("cbtnummodes", 11),
685
            ("cbtrecentcount", 12),
686
            ("cbtmaxtimeouts", 13),
687
            ("cbtmincircs", 5),
688
            ("cbtquantile", 61),
689
            ("cbtclosequantile", 15),
690
            ("cbtlearntimeout", 1900),
691
            ("cbtmintimeout", 2020),
692
            ("cbtinitialtimeout", 2050),
693
            ("cbttestfreq", 110),
694
            ("cbtmaxopencircs", 14),
695
            ("circwindow", 999),
696
            ("CircuitPriorityHalflifeMsec", 222),
697
            ("guard-lifetime-days", 36),
698
            ("guard-confirmed-min-lifetime-days", 37),
699
            ("guard-internet-likely-down-interval", 38),
700
            ("guard-max-sample-size", 39),
701
            ("guard-max-sample-threshold", 40),
702
            ("guard-min-filtered-sample-size", 41),
703
            ("guard-n-primary-guards", 42),
704
            ("guard-n-primary-guards-to-use", 43),
705
            ("guard-n-primary-dir-guards-to-use", 44),
706
            ("guard-nonprimary-guard-connect-timeout", 45),
707
            ("guard-nonprimary-guard-idle-timeout", 46),
708
            ("guard-remove-unlisted-guards-after-days", 47),
709
            ("guard-meaningful-restriction-percent", 12),
710
            ("guard-extreme-restriction-percent", 3),
711
            ("ExtendByEd25519ID", 0),
712
            ("min_paths_for_circs_pct", 51),
713
            ("nf_conntimeout_clients", 606),
714
            ("nf_ito_low", 1_000),
715
            ("nf_ito_high", 20_000),
716
            ("nf_ito_low_reduced", 3_000),
717
            ("nf_ito_high_reduced", 40_000),
718
            ("sendme_accept_min_version", 31),
719
            ("sendme_emit_min_version", 32),
720
        ];
721
        let ignored = p.saturating_update(mp.iter().map(|(a, b)| (a, b)));
722
        assert!(ignored.is_empty());
723

            
724
        assert_eq!(p.bw_weight_scale.get(), 10);
725
        assert!(bool::from(p.cbt_learning_disabled));
726
        assert_eq!(p.cbt_num_xm_modes.get(), 11);
727
        assert_eq!(p.cbt_success_count.get(), 12);
728
        assert_eq!(p.cbt_max_timeouts.get(), 13);
729
        assert_eq!(p.cbt_min_circs_for_estimate.get(), 5);
730
        assert_eq!(p.cbt_timeout_quantile.as_percent().get(), 61);
731
        assert_eq!(p.cbt_abandon_quantile.as_percent().get(), 15);
732
        assert_eq!(p.nf_ito_low.as_millis().get(), 1_000);
733
        assert_eq!(p.nf_ito_high.as_millis().get(), 20_000);
734
        assert_eq!(p.nf_ito_low_reduced.as_millis().get(), 3_000);
735
        assert_eq!(p.nf_ito_high_reduced.as_millis().get(), 40_000);
736
        assert_eq!(
737
            Duration::try_from(p.unused_client_circ_timeout_while_learning_cbt).unwrap(),
738
            Duration::from_secs(1900)
739
        );
740
        assert_eq!(
741
            Duration::try_from(p.cbt_min_timeout).unwrap(),
742
            Duration::from_millis(2020)
743
        );
744
        assert_eq!(
745
            Duration::try_from(p.cbt_initial_timeout).unwrap(),
746
            Duration::from_millis(2050)
747
        );
748
        assert_eq!(
749
            Duration::try_from(p.cbt_testing_delay).unwrap(),
750
            Duration::from_secs(110)
751
        );
752
        assert_eq!(p.cbt_max_open_circuits_for_testing.get(), 14);
753
        assert_eq!(p.circuit_window.get(), 999);
754
        assert_eq!(
755
            Duration::try_from(p.circuit_priority_half_life).unwrap(),
756
            Duration::from_millis(222)
757
        );
758
        assert!(!bool::from(p.extend_by_ed25519_id));
759
        assert_eq!(p.min_circuit_path_threshold.as_percent().get(), 51);
760
        assert_eq!(
761
            Duration::try_from(p.unused_client_circ_timeout).unwrap(),
762
            Duration::from_secs(606)
763
        );
764
        assert_eq!(p.sendme_accept_min_version.get(), 31);
765
        assert_eq!(p.sendme_emit_min_version.get(), 32);
766

            
767
        assert_eq!(
768
            Duration::try_from(p.guard_lifetime_unconfirmed).unwrap(),
769
            Duration::from_secs(86400 * 36)
770
        );
771
        assert_eq!(
772
            Duration::try_from(p.guard_lifetime_confirmed).unwrap(),
773
            Duration::from_secs(86400 * 37)
774
        );
775
        assert_eq!(
776
            Duration::try_from(p.guard_internet_likely_down).unwrap(),
777
            Duration::from_secs(38)
778
        );
779
        assert_eq!(p.guard_max_sample_size.get(), 39);
780
        assert_eq!(p.guard_max_sample_threshold.as_percent().get(), 40);
781
        assert_eq!(p.guard_filtered_min_sample_size.get(), 41);
782
        assert_eq!(p.guard_n_primary.get(), 42);
783
        assert_eq!(p.guard_use_parallelism.get(), 43);
784
        assert_eq!(p.guard_dir_use_parallelism.get(), 44);
785
        assert_eq!(
786
            Duration::try_from(p.guard_nonprimary_connect_timeout).unwrap(),
787
            Duration::from_secs(45)
788
        );
789
        assert_eq!(
790
            Duration::try_from(p.guard_nonprimary_idle_timeout).unwrap(),
791
            Duration::from_secs(46)
792
        );
793
        assert_eq!(
794
            Duration::try_from(p.guard_remove_unlisted_after).unwrap(),
795
            Duration::from_secs(86400 * 47)
796
        );
797
        assert_eq!(p.guard_meaningful_restriction.as_percent().get(), 12);
798
        assert_eq!(p.guard_extreme_restriction.as_percent().get(), 3);
799
    }
800
}