1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130
//! Tracking for the path of a client circuit.
use std::fmt::{self, Display};
use safelog::Redactable;
use tor_linkspec::OwnedChanTarget;
use crate::crypto::cell::HopNum;
/// A descriptor of a single hop in a circuit path.
///
/// This enum is not public; we want the freedom to change it as we see fit.
#[derive(Debug, Clone)]
#[non_exhaustive]
pub(super) enum HopDetail {
/// A hop built through a known relay or a set of externally provided
/// linkspecs.
///
/// TODO: Someday we might ant to distinguish the two cases (known relay,
/// externally provided linkspecs). We might want to also record more
/// information about the hop... but we can do all of this in a
/// backward-compatible way, so it doesn't need to happen right now.
Relay(OwnedChanTarget),
/// A hop built using
/// [`extend_virtual`](crate::circuit::ClientCirc::extend_virtual).
///
/// TODO: Perhaps we'd like to remember something about what the virtual hop
/// represents?
#[cfg(feature = "hs-common")]
Virtual,
}
/// A description of a single hop in a [`Path`].
///
/// Each hop can be to a relay or bridge on the Tor network, or a "virtual" hop
/// representing the cryptographic connection between a client and an onion
/// service.
#[derive(Debug, Clone)]
pub struct PathEntry {
/// The actual information about this hop. We use an inner structure here
/// to keep the information private.
inner: HopDetail,
}
impl Display for PathEntry {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.inner {
HopDetail::Relay(ct) => write!(f, "{}", ct),
#[cfg(feature = "hs-common")]
HopDetail::Virtual => write!(f, "<virtual hop>"),
}
}
}
impl Redactable for PathEntry {
fn display_redacted(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.inner {
HopDetail::Relay(ct) => Redactable::display_redacted(ct, f),
#[cfg(feature = "hs-common")]
HopDetail::Virtual => write!(f, "<virtual hop>"),
}
}
}
impl PathEntry {
/// If this hop was built to a known Tor relay or bridge instance, return
/// a reference to a ChanTarget representing that instance.
///
/// Otherwise, return None.
pub fn as_chan_target(&self) -> Option<&impl tor_linkspec::ChanTarget> {
match &self.inner {
HopDetail::Relay(chan_target) => Some(chan_target),
#[cfg(feature = "hs-common")]
HopDetail::Virtual => None,
}
}
}
/// A circuit's path through the network.
///
/// Every path is composed of some number of hops; each hop is typically a
/// bridge or relay on the Tor network.
#[derive(Debug, Default, Clone)]
pub struct Path {
/// Information about the relays on this circuit.
///
/// We only store ChanTarget information here, because it doesn't matter
/// which ntor key we actually used with each hop.
hops: Vec<PathEntry>,
}
impl Path {
/// Return the number of hops in this path
pub fn n_hops(&self) -> usize {
self.hops.len()
}
/// Return a list of all the hops in this path.
pub fn hops(&self) -> &[PathEntry] {
&self.hops[..]
}
/// Return an iterator over all the hops in this path.
pub fn iter(&self) -> impl Iterator<Item = &PathEntry> + '_ {
self.hops.iter()
}
/// Add a hop to this path.
pub(super) fn push_hop(&mut self, target: HopDetail) {
self.hops.push(PathEntry { inner: target });
}
/// Return an OwnedChanTarget representing the first hop of this path.
pub(super) fn first_hop(&self) -> Option<HopDetail> {
self.hops.first().map(|ent| ent.inner.clone())
}
/// Return a copy of all the hops in this path.
pub(super) fn all_hops(&self) -> Vec<HopDetail> {
self.hops.iter().map(|ent| ent.inner.clone()).collect()
}
/// Return the index of the last hop on this path, or `None` if the path is
/// empty (or impossibly long).
pub(super) fn last_hop_num(&self) -> Option<HopNum> {
let n = self.n_hops();
let idx: u8 = n.checked_sub(1)?.try_into().ok()?;
Some(idx.into())
}
}