Add a handy function for picking a random relay.

This commit is contained in:
Nick Mathewson 2020-09-08 17:50:08 -04:00
parent 8768222acb
commit 48b9510caa
3 changed files with 46 additions and 2 deletions

View File

@ -14,6 +14,7 @@ signature = "*"
tor-llcrypto = { path="../tor-llcrypto", version= "*" }
hex = "0.4.2"
log = "*"
rand = "*"
[dev-dependencies]
simple-logging = "*"

View File

@ -1,6 +1,5 @@
#![allow(unused)]
mod err;
mod pick;
use tor_checkable::{ExternallySigned, SelfSigned, Timebound};
use tor_netdoc::authcert::AuthCert;
@ -251,6 +250,16 @@ impl NetDir {
}
self.weight_fn.get().unwrap()
}
pub fn pick_relay<'a, R, F>(&'a self, rng: &mut R, reweight: F) -> Option<Relay<'a>>
where
R: rand::Rng,
F: Fn(&Relay<'a>, u32) -> u32,
{
let weight_fn = self.get_weight_fn();
pick::pick_weighted(rng, self.relays(), |r| {
reweight(r, r.get_weight(weight_fn)) as u64
})
}
}
impl<'a> Relay<'a> {

34
tor-netdir/src/pick.rs Normal file
View File

@ -0,0 +1,34 @@
// Performance note: this requires a fast RNG, but doesn't need much
// storage.
pub fn pick_weighted<R, I, F>(rng: &mut R, i: I, weightfn: F) -> Option<I::Item>
where
I: Iterator,
F: Fn(&I::Item) -> u64,
R: rand::Rng,
{
let mut result = None;
let mut weight_so_far: u64 = 0;
// Correctness argument: at the end of each iteration of the loop,
// `result` holds a value chosen with weighted probabability from
// all of the items yielded so far. The loop body preserves this
// invariant.
for item in i {
let w = weightfn(&item);
if w == 0 {
continue;
}
// TODO: panics on overflow. Probably not best.
weight_so_far = weight_so_far.checked_add(w).unwrap();
let x = rng.gen_range(0, weight_so_far);
// TODO: we could probably do this in constant-time, if we are
// worried about a side-channel.
if x < w {
result = Some(item);
}
}
result
}