use super::TorPath;
use crate::{DirInfo, Error, Result};
use tor_basic_utils::iter::FilterCount;
use tor_error::bad_api_usage;
use tor_guardmgr::{GuardMgr, GuardMonitor, GuardUsable};
use tor_netdir::WeightRole;
use tor_rtcompat::Runtime;
use rand::Rng;
#[non_exhaustive]
pub struct DirPathBuilder {}
impl Default for DirPathBuilder {
fn default() -> Self {
Self::new()
}
}
impl DirPathBuilder {
pub fn new() -> Self {
DirPathBuilder {}
}
pub fn pick_path<'a, R: Rng, RT: Runtime>(
&self,
rng: &mut R,
netdir: DirInfo<'a>,
guards: Option<&GuardMgr<RT>>,
) -> Result<(TorPath<'a>, Option<GuardMonitor>, Option<GuardUsable>)> {
match (netdir, guards) {
(_, Some(guardmgr)) => {
let guard_usage = tor_guardmgr::GuardUsageBuilder::default()
.kind(tor_guardmgr::GuardUsageKind::OneHopDirectory)
.build()
.expect("Unable to build directory guard usage");
let (guard, mon, usable) = guardmgr.select_guard(guard_usage)?;
Ok((TorPath::new_one_hop_owned(&guard), Some(mon), Some(usable)))
}
(DirInfo::Fallbacks(f), None) => {
let relay = f.choose(rng)?;
Ok((TorPath::new_fallback_one_hop(relay), None, None))
}
(DirInfo::Directory(netdir), None) => {
let mut can_share = FilterCount::default();
let mut correct_usage = FilterCount::default();
let relay = netdir
.pick_relay(rng, WeightRole::BeginDir, |r| {
can_share.count(true) && correct_usage.count(r.is_dir_cache())
})
.ok_or(Error::NoPath {
role: "directory cache",
can_share,
correct_usage,
})?;
Ok((TorPath::new_one_hop(relay), None, None))
}
(DirInfo::Nothing, None) => Err(bad_api_usage!(
"Tried to build a one hop path with no directory, fallbacks, or guard manager"
)
.into()),
}
}
}
#[cfg(test)]
mod test {
#![allow(clippy::bool_assert_comparison)]
#![allow(clippy::clone_on_copy)]
#![allow(clippy::dbg_macro)]
#![allow(clippy::print_stderr)]
#![allow(clippy::print_stdout)]
#![allow(clippy::single_char_pattern)]
#![allow(clippy::unwrap_used)]
use super::*;
use crate::path::assert_same_path_when_owned;
use crate::test::OptDummyGuardMgr;
use std::collections::HashSet;
use tor_basic_utils::test_rng::testing_rng;
use tor_guardmgr::fallback::{FallbackDir, FallbackList};
use tor_guardmgr::TestConfig;
use tor_linkspec::RelayIds;
use tor_netdir::testnet;
#[test]
fn dirpath_relay() {
let netdir = testnet::construct_netdir().unwrap_if_sufficient().unwrap();
let mut rng = testing_rng();
let dirinfo = (&netdir).into();
let guards: OptDummyGuardMgr<'_> = None;
for _ in 0..1000 {
let p = DirPathBuilder::default().pick_path(&mut rng, dirinfo, guards);
let (p, _, _) = p.unwrap();
assert!(p.exit_relay().is_none());
assert_eq!(p.len(), 1);
assert_same_path_when_owned(&p);
if let crate::path::TorPathInner::OneHop(r) = p.inner {
assert!(r.is_dir_cache());
} else {
panic!("Generated the wrong kind of path.");
}
}
}
#[test]
fn dirpath_fallback() {
let fb_owned = vec![
{
let mut bld = FallbackDir::builder();
bld.rsa_identity([0x01; 20].into())
.ed_identity([0x01; 32].into())
.orports()
.push("127.0.0.1:9000".parse().unwrap());
bld.build().unwrap()
},
{
let mut bld = FallbackDir::builder();
bld.rsa_identity([0x03; 20].into())
.ed_identity([0x03; 32].into())
.orports()
.push("127.0.0.1:9003".parse().unwrap());
bld.build().unwrap()
},
];
let fb: FallbackList = fb_owned.clone().into();
let dirinfo = (&fb).into();
let mut rng = testing_rng();
let guards: OptDummyGuardMgr<'_> = None;
for _ in 0..10 {
let p = DirPathBuilder::default().pick_path(&mut rng, dirinfo, guards);
let (p, _, _) = p.unwrap();
assert!(p.exit_relay().is_none());
assert_eq!(p.len(), 1);
assert_same_path_when_owned(&p);
if let crate::path::TorPathInner::FallbackOneHop(f) = p.inner {
assert!(f == &fb_owned[0] || f == &fb_owned[1]);
} else {
panic!("Generated the wrong kind of path.");
}
}
}
#[test]
fn dirpath_no_fallbacks() {
let fb = FallbackList::from([]);
let dirinfo = DirInfo::Fallbacks(&fb);
let mut rng = testing_rng();
let guards: OptDummyGuardMgr<'_> = None;
let err = DirPathBuilder::default().pick_path(&mut rng, dirinfo, guards);
dbg!(err.as_ref().err());
assert!(matches!(
err,
Err(Error::Guard(
tor_guardmgr::PickGuardError::NoCandidatesAvailable
))
));
}
#[test]
fn dirpath_with_guards() {
tor_rtcompat::test_with_all_runtimes!(|rt| async move {
let netdir = testnet::construct_netdir().unwrap_if_sufficient().unwrap();
let mut rng = testing_rng();
let dirinfo = (&netdir).into();
let statemgr = tor_persist::TestingStateMgr::new();
let guards =
tor_guardmgr::GuardMgr::new(rt.clone(), statemgr, &TestConfig::default()).unwrap();
guards.install_test_netdir(&netdir);
let mut distinct_guards = HashSet::new();
for _ in 0..40 {
let (path, mon, usable) = DirPathBuilder::new()
.pick_path(&mut rng, dirinfo, Some(&guards))
.unwrap();
if let crate::path::TorPathInner::OwnedOneHop(relay) = path.inner {
distinct_guards.insert(RelayIds::from_relay_ids(&relay));
mon.unwrap().succeeded();
assert!(usable.unwrap().await.unwrap());
} else {
panic!("Generated the wrong kind of path.");
}
}
assert_eq!(
distinct_guards.len(),
netdir.params().guard_dir_use_parallelism.get() as usize
);
});
}
}