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}