use std::result::Result as StdResult;
use rand::Rng;
use tor_error::{Bug, internal};
use tor_guardmgr::vanguards::{Layer, VanguardMgr};
use tor_linkspec::HasRelayIds;
use tor_netdir::{NetDir, Relay};
use tor_relay_selection::{RelayExclusion, RelaySelector};
use tor_rtcompat::Runtime;
use crate::path::{MaybeOwnedRelay, TorPath};
use crate::{Error, Result};
pub(super) struct PathBuilder<'n, 'a, RT: Runtime, R: Rng> {
hops: Vec<MaybeOwnedRelay<'n>>,
netdir: &'n NetDir,
vanguards: &'a VanguardMgr<RT>,
rng: &'a mut R,
last_hop_kind: HopKind,
}
#[derive(Copy, Clone, Debug, PartialEq, derive_more::Display)]
enum HopKind {
Guard,
Vanguard(Layer),
Middle,
}
impl<'n, 'a, RT: Runtime, R: Rng> PathBuilder<'n, 'a, RT, R> {
pub(super) fn new(
rng: &'a mut R,
netdir: &'n NetDir,
vanguards: &'a VanguardMgr<RT>,
l1_guard: MaybeOwnedRelay<'n>,
) -> Self {
Self {
hops: vec![l1_guard],
netdir,
vanguards,
rng,
last_hop_kind: HopKind::Guard,
}
}
pub(super) fn add_vanguard(
mut self,
selector: &RelaySelector<'n>,
layer: Layer,
) -> Result<Self> {
let selector = selector_excluding_neighbors(selector, &self.hops);
let vanguard: MaybeOwnedRelay = self
.vanguards
.select_vanguard(&mut self.rng, self.netdir, layer, &selector)?
.into();
let () = self.add_hop(vanguard, HopKind::Vanguard(layer))?;
Ok(self)
}
pub(super) fn add_middle(mut self, selector: &RelaySelector<'n>) -> Result<Self> {
let middle =
select_middle_for_vanguard_circ(&self.hops, self.netdir, selector, self.rng)?.into();
let () = self.add_hop(middle, HopKind::Middle)?;
Ok(self)
}
pub(super) fn build(self) -> Result<TorPath<'n>> {
use HopKind::*;
use Layer::*;
match self.last_hop_kind {
Vanguard(Layer3) | Middle => Ok(TorPath::new_multihop_from_maybe_owned(self.hops)),
_ => Err(internal!(
"tried to build TorPath from incomplete PathBuilder (last_hop_kind={})",
self.last_hop_kind
)
.into()),
}
}
fn add_hop(&mut self, hop: MaybeOwnedRelay<'n>, hop_kind: HopKind) -> StdResult<(), Bug> {
self.update_last_hop_kind(hop_kind)?;
self.hops.push(hop);
Ok(())
}
fn update_last_hop_kind(&mut self, kind: HopKind) -> StdResult<(), Bug> {
use HopKind::*;
use Layer::*;
match (self.last_hop_kind, kind) {
(Guard, Vanguard(Layer2))
| (Vanguard(Layer2), Vanguard(Layer3))
| (Vanguard(Layer2), Middle)
| (Vanguard(Layer3), Middle) => {
self.last_hop_kind = kind;
}
(_, _) => {
return Err(internal!(
"tried to build an invalid vanguard path: cannot add a {kind} hop after {}",
self.last_hop_kind
));
}
}
Ok(())
}
}
fn exclude_identities<'a, T: HasRelayIds + 'a>(exclude_ids: &[&T]) -> RelayExclusion<'a> {
RelayExclusion::exclude_identities(
exclude_ids
.iter()
.flat_map(|relay| relay.identities())
.map(|id| id.to_owned())
.collect(),
)
}
fn exclude_neighbors<'n, T: HasRelayIds + 'n>(hops: &[T]) -> RelayExclusion<'n> {
let skip_n = 2;
let neighbors = hops.iter().rev().take(skip_n).collect::<Vec<&T>>();
exclude_identities(&neighbors[..])
}
pub(crate) fn select_middle_for_vanguard_circ<'n, R: Rng, T: HasRelayIds + 'n>(
hops: &[T],
netdir: &'n NetDir,
selector: &RelaySelector<'n>,
rng: &mut R,
) -> Result<Relay<'n>> {
let selector = selector_excluding_neighbors(selector, hops);
let (extra_hop, info) = selector.select_relay(rng, netdir);
extra_hop.ok_or_else(|| Error::NoRelay {
path_kind: "onion-service vanguard circuit",
role: "extra hop",
problem: info.to_string(),
})
}
fn selector_excluding_neighbors<'n, T: HasRelayIds + 'n>(
selector: &RelaySelector<'n>,
hops: &[T],
) -> RelaySelector<'n> {
let mut selector = selector.clone();
let neighbor_exclusion = exclude_neighbors(hops);
selector.push_restriction(neighbor_exclusion.into());
selector
}