tor_proto/tunnel/circuit/
path.rs

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