1
//! Tracking for the path of a client circuit.
2

            
3
use std::fmt::{self, Display};
4

            
5
use safelog::Redactable;
6
use tor_linkspec::OwnedChanTarget;
7

            
8
use 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]
15
pub(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)]
39
pub 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

            
45
impl 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

            
55
impl 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

            
65
impl 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
32
    pub fn as_chan_target(&self) -> Option<&impl tor_linkspec::ChanTarget> {
71
32
        match &self.inner {
72
32
            HopDetail::Relay(chan_target) => Some(chan_target),
73
            #[cfg(feature = "hs-common")]
74
            HopDetail::Virtual => None,
75
        }
76
32
    }
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)]
84
pub 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

            
92
impl Path {
93
    /// Return the number of hops in this path
94
128
    pub fn n_hops(&self) -> usize {
95
128
        self.hops.len()
96
128
    }
97

            
98
    /// Return a list of all the hops in this path.
99
32
    pub fn hops(&self) -> &[PathEntry] {
100
32
        &self.hops[..]
101
32
    }
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
696
    pub(crate) fn push_hop(&mut self, target: HopDetail) {
110
696
        self.hops.push(PathEntry { inner: target });
111
696
    }
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
96
    pub(crate) fn all_hops(&self) -> impl DoubleEndedIterator<Item = &HopDetail> + '_ {
120
304
        self.hops.iter().map(|ent| &ent.inner)
121
96
    }
122

            
123
    /// Return the index of the last hop on this path, or `None` if the path is
124
    /// empty (or impossibly long).
125
32
    pub(super) fn last_hop_num(&self) -> Option<HopNum> {
126
32
        let n = self.n_hops();
127
32
        let idx: u8 = n.checked_sub(1)?.try_into().ok()?;
128
32
        Some(idx.into())
129
32
    }
130
}