1
//! Functions for applying the correct weights to relays when choosing
2
//! a relay at random.
3
//!
4
//! The weight to use when picking a relay depends on several factors:
5
//!
6
//! - The relay's *apparent bandwidth*.  (This is ideally measured by a set of
7
//!   bandwidth authorities, but if no bandwidth authorities are running (as on
8
//!   a test network), we might fall back either to relays' self-declared
9
//!   values, or we might treat all relays as having equal bandwidth.)
10
//! - The role that we're selecting a relay to play.  (See [`WeightRole`]).
11
//! - The flags that a relay has in the consensus, and their scarcity.  If a
12
//!   relay provides particularly scarce functionality, we might choose not to
13
//!   use it for other roles, or to use it less commonly for them.
14

            
15
use crate::params::NetParameters;
16
use bitflags::bitflags;
17
use tor_netdoc::doc::netstatus::{self, MdConsensus, MdConsensusRouterStatus, NetParams};
18

            
19
/// Helper: Calculate the function we should use to find initial relay
20
/// bandwidths.
21
742
fn pick_bandwidth_fn<'a, I>(mut weights: I) -> BandwidthFn
22
742
where
23
742
    I: Clone + Iterator<Item = &'a netstatus::RelayWeight>,
24
742
{
25
921
    let has_measured = weights.clone().any(|w| w.is_measured());
26
839
    let has_nonzero = weights.clone().any(|w| w.is_nonzero());
27
938
    let has_nonzero_measured = weights.any(|w| w.is_measured() && w.is_nonzero());
28
742

            
29
742
    if !has_nonzero {
30
        // If every value is zero, we should just pretend everything has
31
        // bandwidth == 1.
32
18
        BandwidthFn::Uniform
33
724
    } else if !has_measured {
34
        // If there are no measured values, then we can look at unmeasured
35
        // weights.
36
33
        BandwidthFn::IncludeUnmeasured
37
691
    } else if has_nonzero_measured {
38
        // Otherwise, there are measured values; we should look at those only, if
39
        // any of them is nonzero.
40
706
        BandwidthFn::MeasuredOnly
41
    } else {
42
        // This is a bit of an ugly case: We have measured values, but they're
43
        // all zero.  If this happens, the bandwidth authorities exist but they
44
        // very confused: we should fall back to uniform weighting.
45
1
        BandwidthFn::Uniform
46
    }
47
758
}
48

            
49
/// Internal: how should we find the base bandwidth of each relay?  This
50
/// value is global over a whole directory, and depends on the bandwidth
51
/// weights in the consensus.
52
7
#[derive(Copy, Clone, Debug, PartialEq)]
53
enum BandwidthFn {
54
    /// There are no weights at all in the consensus: weight every
55
    /// relay as 1.
56
    Uniform,
57
    /// There are no measured weights in the consensus: count
58
    /// unmeasured weights as the weights for relays.
59
    IncludeUnmeasured,
60
    /// There are measured relays in the consensus; only use those.
61
    MeasuredOnly,
62
}
63

            
64
impl BandwidthFn {
65
    /// Apply this function to the measured or unmeasured bandwidth
66
    /// of a single relay.
67
    fn apply(&self, w: &netstatus::RelayWeight) -> u32 {
68
        use netstatus::RelayWeight::*;
69
        use BandwidthFn::*;
70
3134392
        match (self, w) {
71
            (Uniform, _) => 1,
72
1537
            (IncludeUnmeasured, Unmeasured(u)) => *u,
73
1
            (IncludeUnmeasured, Measured(m)) => *m,
74
62
            (MeasuredOnly, Unmeasured(_)) => 0,
75
3134714
            (MeasuredOnly, Measured(m)) => *m,
76
            (_, _) => 0,
77
        }
78
3134751
    }
79
}
80

            
81
/// Possible ways to weight relays when selecting them a random.
82
///
83
/// Relays are weighted by a function of their bandwidth that
84
/// depends on how scarce that "kind" of bandwidth is.  For
85
/// example, if Exit bandwidth is rare, then Exits should be
86
/// less likely to get chosen for the middle hop of a path.
87
#[derive(Clone, Debug, Copy)]
88
#[non_exhaustive]
89
pub enum WeightRole {
90
    /// Selecting a relay to use as a guard
91
    Guard,
92
    /// Selecting a relay to use as a middle relay in a circuit.
93
    Middle,
94
    /// Selecting a relay to use to deliver traffic to the internet.
95
    Exit,
96
    /// Selecting a relay for a one-hop BEGIN_DIR directory request.
97
    BeginDir,
98
    /// Selecting a relay with no additional weight beyond its bandwidth.
99
    Unweighted,
100
}
101

            
102
/// Description for how to weight a single kind of relay for each WeightRole.
103
#[derive(Clone, Debug, Copy)]
104
struct RelayWeight {
105
    /// How to weight this kind of relay when picking a guard relay.
106
    as_guard: u32,
107
    /// How to weight this kind of relay when picking a middle relay.
108
    as_middle: u32,
109
    /// How to weight this kind of relay when picking a exit relay.
110
    as_exit: u32,
111
    /// How to weight this kind of relay when picking a one-hop BEGIN_DIR.
112
    as_dir: u32,
113
}
114

            
115
impl std::ops::Mul<u32> for RelayWeight {
116
    type Output = Self;
117
2968
    fn mul(self, rhs: u32) -> Self {
118
2968
        RelayWeight {
119
2968
            as_guard: self.as_guard * rhs,
120
2968
            as_middle: self.as_middle * rhs,
121
2968
            as_exit: self.as_exit * rhs,
122
2968
            as_dir: self.as_dir * rhs,
123
2968
        }
124
2968
    }
125
}
126
impl std::ops::Div<u32> for RelayWeight {
127
    type Output = Self;
128
2936
    fn div(self, rhs: u32) -> Self {
129
2936
        RelayWeight {
130
2936
            as_guard: self.as_guard / rhs,
131
2936
            as_middle: self.as_middle / rhs,
132
2936
            as_exit: self.as_exit / rhs,
133
2936
            as_dir: self.as_dir / rhs,
134
2936
        }
135
2936
    }
136
}
137

            
138
impl RelayWeight {
139
    /// Return the largest weight that we give for this kind of relay.
140
    // The unwrap() is safe because array is nonempty.
141
    #[allow(clippy::unwrap_used)]
142
5856
    fn max_weight(&self) -> u32 {
143
5856
        [self.as_guard, self.as_middle, self.as_exit, self.as_dir]
144
5856
            .iter()
145
5856
            .max()
146
5856
            .copied()
147
5856
            .unwrap()
148
5856
    }
149
    /// Return the weight we should give this kind of relay's
150
    /// bandwidth for a given role.
151
3110270
    fn for_role(&self, role: WeightRole) -> u32 {
152
3110270
        match role {
153
1011827
            WeightRole::Guard => self.as_guard,
154
1290316
            WeightRole::Middle => self.as_middle,
155
433382
            WeightRole::Exit => self.as_exit,
156
347534
            WeightRole::BeginDir => self.as_dir,
157
27211
            WeightRole::Unweighted => 1,
158
        }
159
3110270
    }
160
}
161

            
162
bitflags! {
163
    /// A kind of relay, for the purposes of selecting a relay by weight.
164
    ///
165
    /// Relays can have or lack the Guard flag, the Exit flag, and the
166
    /// V2Dir flag. All together, this makes 8 kinds of relays.
167
    struct WeightKind: u8 {
168
        /// Flag in weightkind for Guard relays.
169
        const GUARD = 1 << 0;
170
        /// Flag in weightkind for Exit relays.
171
        const EXIT = 1 << 1;
172
        /// Flag in weightkind for V2Dir relays.
173
        const DIR = 1 << 2;
174
    }
175
}
176

            
177
impl WeightKind {
178
    /// Return the appropriate WeightKind for a relay.
179
3116089
    fn for_rs(rs: &MdConsensusRouterStatus) -> Self {
180
3116089
        let mut r = WeightKind::empty();
181
3116089
        if rs.is_flagged_guard() {
182
2025105
            r |= WeightKind::GUARD;
183
2025105
        }
184
3116089
        if rs.is_flagged_exit() {
185
1795274
            r |= WeightKind::EXIT;
186
1795274
        }
187
3116089
        if rs.is_flagged_v2dir() {
188
3100624
            r |= WeightKind::DIR;
189
3100624
        }
190
3116089
        r
191
3116089
    }
192
    /// Return the index to use for this kind of a relay within a WeightSet.
193
3103632
    fn idx(self) -> usize {
194
3103632
        self.bits as usize
195
3103632
    }
196
}
197

            
198
/// Information derived from a consensus to use when picking relays by
199
/// weighted bandwidth.
200
#[derive(Debug, Clone)]
201
pub(crate) struct WeightSet {
202
    /// How to find the bandwidth to use when picking a relay by weighted
203
    /// bandwidth.
204
    ///
205
    /// (This tells us us whether to count unmeasured relays, whether
206
    /// to look at bandwidths at all, etc.)
207
    bandwidth_fn: BandwidthFn,
208
    /// Number of bits that we need to right-shift our weighted products
209
    /// so that their sum won't overflow u64::MAX.
210
    shift: u8,
211
    /// A set of RelayWeight values, indexed by [`WeightKind::idx`], used
212
    /// to weight different kinds of relays.
213
    w: [RelayWeight; 8],
214
}
215

            
216
impl WeightSet {
217
    /// Find the actual 64-bit weight to use for a given routerstatus when
218
    /// considering it for a given role.
219
    ///
220
    /// NOTE: This function _does not_ consider whether the relay in question
221
    /// actually matches the given role.  For example, if `role` is Guard
222
    /// we don't check whether or not `rs` actually has the Guard flag.
223
3117397
    pub(crate) fn weight_rs_for_role(&self, rs: &MdConsensusRouterStatus, role: WeightRole) -> u64 {
224
3117397
        self.weight_bw_for_role(WeightKind::for_rs(rs), rs.weight(), role)
225
3117397
    }
226

            
227
    /// Find the 64-bit weight to report for a relay of `kind` whose weight in
228
    /// the consensus is `relay_weight` when using it for `role`.
229
3102603
    fn weight_bw_for_role(
230
3102603
        &self,
231
3102603
        kind: WeightKind,
232
3102603
        relay_weight: &netstatus::RelayWeight,
233
3102603
        role: WeightRole,
234
3102603
    ) -> u64 {
235
3102603
        let ws = &self.w[kind.idx()];
236
3102603

            
237
3102603
        let router_bw = self.bandwidth_fn.apply(relay_weight);
238
3102603
        // Note a subtlety here: we multiply the two values _before_
239
3102603
        // we shift, to improve accuracy.  We know that this will be
240
3102603
        // safe, since the inputs are both u32, and so cannot overflow
241
3102603
        // a u64.
242
3102603
        let router_weight = u64::from(router_bw) * u64::from(ws.for_role(role));
243
3102603
        router_weight >> self.shift
244
3102603
    }
245

            
246
    /// Compute the correct WeightSet for a provided MdConsensus.
247
737
    pub(crate) fn from_consensus(consensus: &MdConsensus, params: &NetParameters) -> Self {
248
2659
        let bandwidth_fn = pick_bandwidth_fn(consensus.relays().iter().map(|rs| rs.weight()));
249
737
        let weight_scale = params.bw_weight_scale.into();
250
737

            
251
737
        let total_bw = consensus
252
737
            .relays()
253
737
            .iter()
254
27672
            .map(|rs| u64::from(bandwidth_fn.apply(rs.weight())))
255
737
            .sum();
256
737
        let p = consensus.bandwidth_weights();
257
737

            
258
737
        Self::from_parts(bandwidth_fn, total_bw, weight_scale, p).validate(consensus)
259
737
    }
260

            
261
    /// Compute the correct WeightSet given a bandwidth function, a
262
    /// weight-scaling parameter, a total amount of bandwidth for all
263
    /// relays in the consensus, and a set of bandwidth parameters.
264
738
    fn from_parts(
265
738
        bandwidth_fn: BandwidthFn,
266
738
        total_bw: u64,
267
738
        weight_scale: u32,
268
738
        p: &NetParams<i32>,
269
738
    ) -> Self {
270
738
        /// Find a single RelayWeight, given the names that its bandwidth
271
738
        /// parameters have. The `g` parameter is the weight as a guard, the
272
738
        /// `m` parameter is the weight as a middle relay, the `e` parameter is
273
738
        /// the weight as an exit, and the `d` parameter is the weight as a
274
738
        /// directory.
275
738
        #[allow(clippy::many_single_char_names)]
276
3000
        fn single(p: &NetParams<i32>, g: &str, m: &str, e: &str, d: &str) -> RelayWeight {
277
3000
            RelayWeight {
278
3000
                as_guard: w_param(p, g),
279
3000
                as_middle: w_param(p, m),
280
3000
                as_exit: w_param(p, e),
281
3000
                as_dir: w_param(p, d),
282
3000
            }
283
3000
        }
284
738

            
285
738
        // Prevent division by zero in case we're called with a bogus
286
738
        // input.  (That shouldn't be possible.)
287
738
        let weight_scale = weight_scale.max(1);
288
738

            
289
738
        // For non-V2Dir relays, we have names for most of their weights.
290
738
        //
291
738
        // (There is no Wge, since we only use Guard relays as guards.  By the
292
738
        // same logic, Wme has no reason to exist, but according to the spec it
293
738
        // does.)
294
738
        let w_none = single(p, "Wgm", "Wmm", "Wem", "Wbm");
295
738
        let w_guard = single(p, "Wgg", "Wmg", "Weg", "Wbg");
296
738
        let w_exit = single(p, "---", "Wme", "Wee", "Wbe");
297
738
        let w_both = single(p, "Wgd", "Wmd", "Wed", "Wbd");
298
738

            
299
738
        // Note that the positions of the elements in this array need to
300
738
        // match the values returned by WeightKind.as_idx().
301
738
        let w = [
302
738
            w_none,
303
738
            w_guard,
304
738
            w_exit,
305
738
            w_both,
306
738
            // The V2Dir values are the same as the non-V2Dir values, except
307
738
            // each is multiplied by an additional factor.
308
738
            //
309
738
            // (We don't need to check for overflow here, since the
310
738
            // authorities make sure that the inputs don't get too big.)
311
738
            (w_none * w_param(p, "Wmb")) / weight_scale,
312
738
            (w_guard * w_param(p, "Wgb")) / weight_scale,
313
738
            (w_exit * w_param(p, "Web")) / weight_scale,
314
738
            (w_both * w_param(p, "Wdb")) / weight_scale,
315
738
        ];
316
738

            
317
738
        // This is the largest weight value.
318
738
        // The unwrap() is safe because `w` is nonempty.
319
738
        #[allow(clippy::unwrap_used)]
320
738
        let w_max = w.iter().map(RelayWeight::max_weight).max().unwrap();
321
738

            
322
738
        // We want "shift" such that (total * w_max) >> shift <= u64::max
323
738
        let shift = calculate_shift(total_bw, u64::from(w_max)) as u8;
324
738

            
325
738
        WeightSet {
326
738
            bandwidth_fn,
327
738
            shift,
328
738
            w,
329
738
        }
330
738
    }
331

            
332
    /// Assert that we have correctly computed our shift values so that
333
    /// our total weighted bws do not exceed u64::MAX.
334
737
    fn validate(self, consensus: &MdConsensus) -> Self {
335
        use WeightRole::*;
336
3749
        for role in [Guard, Middle, Exit, BeginDir, Unweighted] {
337
3749
            let _: u64 = consensus
338
3749
                .relays()
339
3749
                .iter()
340
136962
                .map(|rs| self.weight_rs_for_role(rs, role))
341
135905
                .fold(0_u64, |a, b| {
342
135905
                    a.checked_add(b)
343
135905
                        .expect("Incorrect relay weight calculation: total exceeded u64::MAX!")
344
135905
                });
345
3749
        }
346
753
        self
347
753
    }
348
}
349

            
350
/// The value to return if a weight parameter is absent.
351
///
352
/// (If there are no weights at all, then it's correct to set them all to 1,
353
/// and just use the bandwidths.  If _some_ are present and some are absent,
354
/// then the spec doesn't say what to do, but this behavior appears
355
/// reasonable.)
356
const DFLT_WEIGHT: i32 = 1;
357

            
358
/// Return the weight param named 'kwd' in p.
359
///
360
/// Returns DFLT_WEIGHT if there is no such parameter, and 0
361
/// if `kwd` is "---".
362
14920
fn w_param(p: &NetParams<i32>, kwd: &str) -> u32 {
363
14920
    if kwd == "---" {
364
754
        0
365
    } else {
366
14166
        clamp_to_pos(*p.get(kwd).unwrap_or(&DFLT_WEIGHT))
367
    }
368
14920
}
369

            
370
/// If `inp` is less than 0, return 0.  Otherwise return `inp` as a u32.
371
14219
fn clamp_to_pos(inp: i32) -> u32 {
372
14219
    // (The spec says that we might encounter negative values here, though
373
14219
    // we never actually generate them, and don't plan to generate them.)
374
14219
    if inp < 0 {
375
2
        0
376
    } else {
377
14217
        inp as u32
378
    }
379
14219
}
380

            
381
/// Compute a 'shift' value such that `(a * b) >> shift` will be contained
382
/// inside 64 bits.
383
742
fn calculate_shift(a: u64, b: u64) -> u32 {
384
742
    let bits_for_product = log2_upper(a) + log2_upper(b);
385
742
    if bits_for_product < 64 {
386
740
        0
387
    } else {
388
2
        bits_for_product - 64
389
    }
390
742
}
391

            
392
/// Return an upper bound for the log2 of n.
393
///
394
/// This function overestimates whenever n is a power of two, but that doesn't
395
/// much matter for the uses we're giving it here.
396
1489
fn log2_upper(n: u64) -> u32 {
397
1489
    64 - n.leading_zeros()
398
1489
}
399

            
400
#[cfg(test)]
401
mod test {
402
    #![allow(clippy::unwrap_used)]
403
    use super::*;
404
    use netstatus::RelayWeight as RW;
405
    use std::net::SocketAddr;
406
    use std::time::{Duration, SystemTime};
407
    use tor_netdoc::doc::netstatus::{Lifetime, RelayFlags, RouterStatusBuilder};
408

            
409
    #[test]
410
    fn t_clamp() {
411
        assert_eq!(clamp_to_pos(32), 32);
412
        assert_eq!(clamp_to_pos(std::i32::MAX), std::i32::MAX as u32);
413
        assert_eq!(clamp_to_pos(0), 0);
414
        assert_eq!(clamp_to_pos(-1), 0);
415
        assert_eq!(clamp_to_pos(std::i32::MIN), 0);
416
    }
417

            
418
    #[test]
419
    fn t_log2() {
420
        assert_eq!(log2_upper(std::u64::MAX), 64);
421
        assert_eq!(log2_upper(0), 0);
422
        assert_eq!(log2_upper(1), 1);
423
        assert_eq!(log2_upper(63), 6);
424
        assert_eq!(log2_upper(64), 7); // a little buggy but harmless.
425
    }
426

            
427
    #[test]
428
    fn t_calc_shift() {
429
        assert_eq!(calculate_shift(1 << 20, 1 << 20), 0);
430
        assert_eq!(calculate_shift(1 << 50, 1 << 10), 0);
431
        assert_eq!(calculate_shift(1 << 32, 1 << 33), 3);
432
        assert!(((1_u64 << 32) >> 3).checked_mul(1_u64 << 33).is_some());
433
        assert_eq!(calculate_shift(432 << 40, 7777 << 40), 38);
434
        assert!(((432_u64 << 40) >> 38)
435
            .checked_mul(7777_u64 << 40)
436
            .is_some());
437
    }
438

            
439
    #[test]
440
    fn t_pick_bwfunc() {
441
        let empty = [];
442
        assert_eq!(pick_bandwidth_fn(empty.iter()), BandwidthFn::Uniform);
443

            
444
        let all_zero = [RW::Unmeasured(0), RW::Measured(0), RW::Unmeasured(0)];
445
        assert_eq!(pick_bandwidth_fn(all_zero.iter()), BandwidthFn::Uniform);
446

            
447
        let all_unmeasured = [RW::Unmeasured(9), RW::Unmeasured(2222)];
448
        assert_eq!(
449
            pick_bandwidth_fn(all_unmeasured.iter()),
450
            BandwidthFn::IncludeUnmeasured
451
        );
452

            
453
        let some_measured = [
454
            RW::Unmeasured(10),
455
            RW::Measured(7),
456
            RW::Measured(4),
457
            RW::Unmeasured(0),
458
        ];
459
        assert_eq!(
460
            pick_bandwidth_fn(some_measured.iter()),
461
            BandwidthFn::MeasuredOnly
462
        );
463

            
464
        // This corresponds to an open question in
465
        // `pick_bandwidth_fn`, about what to do when the only nonzero
466
        // weights are unmeasured.
467
        let measured_all_zero = [RW::Unmeasured(10), RW::Measured(0)];
468
        assert_eq!(
469
            pick_bandwidth_fn(measured_all_zero.iter()),
470
            BandwidthFn::Uniform
471
        );
472
    }
473

            
474
    #[test]
475
    fn t_apply_bwfn() {
476
        use netstatus::RelayWeight::*;
477
        use BandwidthFn::*;
478

            
479
        assert_eq!(Uniform.apply(&Measured(7)), 1);
480
        assert_eq!(Uniform.apply(&Unmeasured(0)), 1);
481

            
482
        assert_eq!(IncludeUnmeasured.apply(&Measured(7)), 7);
483
        assert_eq!(IncludeUnmeasured.apply(&Unmeasured(8)), 8);
484

            
485
        assert_eq!(MeasuredOnly.apply(&Measured(9)), 9);
486
        assert_eq!(MeasuredOnly.apply(&Unmeasured(10)), 0);
487
    }
488

            
489
    // From a fairly recent Tor consensus.
490
    const TESTVEC_PARAMS: &str =
491
        "Wbd=0 Wbe=0 Wbg=4096 Wbm=10000 Wdb=10000 Web=10000 Wed=10000 Wee=10000 Weg=10000 Wem=10000 Wgb=10000 Wgd=0 Wgg=5904 Wgm=5904 Wmb=10000 Wmd=0 Wme=0 Wmg=4096 Wmm=10000";
492

            
493
    #[test]
494
    fn t_weightset_basic() {
495
        let total_bandwidth = 1_000_000_000;
496
        let params = TESTVEC_PARAMS.parse().unwrap();
497
        let ws = WeightSet::from_parts(BandwidthFn::MeasuredOnly, total_bandwidth, 10000, &params);
498

            
499
        assert_eq!(ws.bandwidth_fn, BandwidthFn::MeasuredOnly);
500
        assert_eq!(ws.shift, 0);
501

            
502
        assert_eq!(ws.w[0].as_guard, 5904);
503
        assert_eq!(ws.w[(WeightKind::GUARD.bits()) as usize].as_guard, 5904);
504
        assert_eq!(ws.w[(WeightKind::EXIT.bits()) as usize].as_exit, 10000);
505
        assert_eq!(
506
            ws.w[(WeightKind::EXIT | WeightKind::GUARD).bits() as usize].as_dir,
507
            0
508
        );
509
        assert_eq!(
510
            ws.w[(WeightKind::GUARD | WeightKind::DIR).bits() as usize].as_dir,
511
            4096
512
        );
513
        assert_eq!(
514
            ws.w[(WeightKind::GUARD | WeightKind::DIR).bits() as usize].as_dir,
515
            4096
516
        );
517

            
518
        assert_eq!(
519
            ws.weight_bw_for_role(
520
                WeightKind::GUARD | WeightKind::DIR,
521
                &RW::Unmeasured(7777),
522
                WeightRole::Guard
523
            ),
524
            0
525
        );
526

            
527
        assert_eq!(
528
            ws.weight_bw_for_role(
529
                WeightKind::GUARD | WeightKind::DIR,
530
                &RW::Measured(7777),
531
                WeightRole::Guard
532
            ),
533
            7777 * 5904
534
        );
535

            
536
        assert_eq!(
537
            ws.weight_bw_for_role(
538
                WeightKind::GUARD | WeightKind::DIR,
539
                &RW::Measured(7777),
540
                WeightRole::Middle
541
            ),
542
            7777 * 4096
543
        );
544

            
545
        assert_eq!(
546
            ws.weight_bw_for_role(
547
                WeightKind::GUARD | WeightKind::DIR,
548
                &RW::Measured(7777),
549
                WeightRole::Exit
550
            ),
551
            7777 * 10000
552
        );
553

            
554
        assert_eq!(
555
            ws.weight_bw_for_role(
556
                WeightKind::GUARD | WeightKind::DIR,
557
                &RW::Measured(7777),
558
                WeightRole::BeginDir
559
            ),
560
            7777 * 4096
561
        );
562

            
563
        assert_eq!(
564
            ws.weight_bw_for_role(
565
                WeightKind::GUARD | WeightKind::DIR,
566
                &RW::Measured(7777),
567
                WeightRole::Unweighted
568
            ),
569
            7777
570
        );
571

            
572
        // Now try those last few with routerstatuses.
573
        let rs = rs_builder()
574
            .set_flags(RelayFlags::GUARD | RelayFlags::V2DIR)
575
            .weight(RW::Measured(7777))
576
            .build()
577
            .unwrap();
578
        assert_eq!(ws.weight_rs_for_role(&rs, WeightRole::Exit), 7777 * 10000);
579
        assert_eq!(
580
            ws.weight_rs_for_role(&rs, WeightRole::BeginDir),
581
            7777 * 4096
582
        );
583
        assert_eq!(ws.weight_rs_for_role(&rs, WeightRole::Unweighted), 7777);
584
    }
585

            
586
    /// Return a routerstatus builder set up to deliver a routerstatus
587
    /// with most features disabled.
588
    fn rs_builder() -> RouterStatusBuilder<[u8; 32]> {
589
        MdConsensus::builder()
590
            .rs()
591
            .identity([9; 20].into())
592
            .add_or_port(SocketAddr::from(([127, 0, 0, 1], 9001)))
593
            .doc_digest([9; 32])
594
            .protos("".parse().unwrap())
595
            .clone()
596
    }
597

            
598
    #[test]
599
    fn weight_flags() {
600
        let rs1 = rs_builder().set_flags(RelayFlags::EXIT).build().unwrap();
601
        assert_eq!(WeightKind::for_rs(&rs1), WeightKind::EXIT);
602

            
603
        let rs1 = rs_builder().set_flags(RelayFlags::GUARD).build().unwrap();
604
        assert_eq!(WeightKind::for_rs(&rs1), WeightKind::GUARD);
605

            
606
        let rs1 = rs_builder().set_flags(RelayFlags::V2DIR).build().unwrap();
607
        assert_eq!(WeightKind::for_rs(&rs1), WeightKind::DIR);
608

            
609
        let rs1 = rs_builder().build().unwrap();
610
        assert_eq!(WeightKind::for_rs(&rs1), WeightKind::empty());
611

            
612
        let rs1 = rs_builder().set_flags(RelayFlags::all()).build().unwrap();
613
        assert_eq!(
614
            WeightKind::for_rs(&rs1),
615
            WeightKind::EXIT | WeightKind::GUARD | WeightKind::DIR
616
        );
617
    }
618

            
619
    #[test]
620
    fn weightset_from_consensus() {
621
        use rand::Rng;
622
        let now = SystemTime::now();
623
        let one_hour = Duration::new(3600, 0);
624
        let mut rng = rand::thread_rng();
625
        let mut bld = MdConsensus::builder();
626
        bld.consensus_method(34)
627
            .lifetime(Lifetime::new(now, now + one_hour, now + 2 * one_hour).unwrap())
628
            .weights(TESTVEC_PARAMS.parse().unwrap());
629

            
630
        // We're going to add a huge amount of unmeasured bandwidth,
631
        // and a reasonable amount of  measured bandwidth.
632
        for _ in 0..10 {
633
            rs_builder()
634
                .identity(rng.gen::<[u8; 20]>().into()) // random id
635
                .weight(RW::Unmeasured(1_000_000))
636
                .set_flags(RelayFlags::GUARD | RelayFlags::EXIT)
637
                .build_into(&mut bld)
638
                .unwrap();
639
        }
640
        for n in 0..30 {
641
            rs_builder()
642
                .identity(rng.gen::<[u8; 20]>().into()) // random id
643
                .weight(RW::Measured(1_000 * n))
644
                .set_flags(RelayFlags::GUARD | RelayFlags::EXIT)
645
                .build_into(&mut bld)
646
                .unwrap();
647
        }
648

            
649
        let consensus = bld.testing_consensus().unwrap();
650
        let params = NetParameters::default();
651
        let ws = WeightSet::from_consensus(&consensus, &params);
652

            
653
        assert_eq!(ws.bandwidth_fn, BandwidthFn::MeasuredOnly);
654
        assert_eq!(ws.shift, 0);
655
        assert_eq!(ws.w[0].as_guard, 5904);
656
        assert_eq!(ws.w[5].as_guard, 5904);
657
        assert_eq!(ws.w[5].as_middle, 4096);
658
    }
659
}