1
//! Code to construct paths through the Tor network
2
//!
3
//! TODO: I'm not sure this belongs in circmgr, but this is the best place
4
//! I can think of for now.  I'm also not sure this should be public.
5

            
6
pub mod dirpath;
7
pub mod exitpath;
8

            
9
use tor_error::bad_api_usage;
10
use tor_linkspec::{OwnedChanTarget, OwnedCircTarget};
11
use tor_netdir::{fallback::FallbackDir, Relay};
12

            
13
use std::convert::TryFrom;
14

            
15
use crate::usage::ExitPolicy;
16
use crate::Result;
17

            
18
/// A list of Tor relays through the network.
19
pub struct TorPath<'a> {
20
    /// The inner TorPath state.
21
    inner: TorPathInner<'a>,
22
}
23

            
24
/// Non-public helper type to represent the different kinds of Tor path.
25
///
26
/// (This is a separate type to avoid exposing its details to the user.)
27
enum TorPathInner<'a> {
28
    /// A single-hop path for use with a directory cache, when a relay is
29
    /// known.
30
    OneHop(Relay<'a>), // This could just be a routerstatus.
31
    /// A single-hop path for use with a directory cache, when we don't have
32
    /// a consensus.
33
    FallbackOneHop(&'a FallbackDir),
34
    /// A multi-hop path, containing one or more relays.
35
    Path(Vec<Relay<'a>>),
36
}
37

            
38
impl<'a> TorPath<'a> {
39
    /// Create a new one-hop path for use with a directory cache with a known
40
    /// relay.
41
1161
    pub fn new_one_hop(relay: Relay<'a>) -> Self {
42
1161
        Self {
43
1161
            inner: TorPathInner::OneHop(relay),
44
1161
        }
45
1161
    }
46

            
47
    /// Create a new one-hop path for use with a directory cache when we don't
48
    /// have a consensus.
49
10
    pub fn new_fallback_one_hop(fallback_dir: &'a FallbackDir) -> Self {
50
10
        Self {
51
10
            inner: TorPathInner::FallbackOneHop(fallback_dir),
52
10
        }
53
10
    }
54

            
55
    /// Create a new multi-hop path with a given number of ordered relays.
56
3091
    pub fn new_multihop(relays: impl IntoIterator<Item = Relay<'a>>) -> Self {
57
3091
        Self {
58
3091
            inner: TorPathInner::Path(relays.into_iter().collect()),
59
3091
        }
60
3091
    }
61

            
62
    /// Return the final relay in this path, if this is a path for use
63
    /// with exit circuits.
64
    fn exit_relay(&self) -> Option<&Relay<'a>> {
65
5
        match &self.inner {
66
5
            TorPathInner::Path(relays) if !relays.is_empty() => Some(&relays[relays.len() - 1]),
67
1012
            _ => None,
68
        }
69
1015
    }
70

            
71
    /// Return the exit policy of the final relay in this path, if this
72
    /// is a path for use with exit circuits.
73
4
    pub(crate) fn exit_policy(&self) -> Option<ExitPolicy> {
74
4
        self.exit_relay().map(ExitPolicy::from_relay)
75
4
    }
76

            
77
    /// Return the number of relays in this path.
78
    #[allow(clippy::len_without_is_empty)]
79
1102
    pub fn len(&self) -> usize {
80
1102
        use TorPathInner::*;
81
1102
        match &self.inner {
82
1001
            OneHop(_) => 1,
83
10
            FallbackOneHop(_) => 1,
84
91
            Path(p) => p.len(),
85
        }
86
1102
    }
87
}
88

            
89
/// A path composed entirely of owned components.
90
#[derive(Clone, Debug)]
91
pub(crate) enum OwnedPath {
92
    /// A path where we only know how to make circuits via CREATE_FAST.
93
    ChannelOnly(OwnedChanTarget),
94
    /// A path of one or more hops created via normal Tor handshakes.
95
    Normal(Vec<OwnedCircTarget>),
96
}
97

            
98
impl<'a> TryFrom<&TorPath<'a>> for OwnedPath {
99
    type Error = crate::Error;
100
4091
    fn try_from(p: &TorPath<'a>) -> Result<OwnedPath> {
101
        use TorPathInner::*;
102

            
103
3082
        Ok(match &p.inner {
104
10
            FallbackOneHop(h) => OwnedPath::ChannelOnly(OwnedChanTarget::from_chan_target(*h)),
105
999
            OneHop(h) => OwnedPath::Normal(vec![OwnedCircTarget::from_circ_target(h)]),
106
3082
            Path(p) if !p.is_empty() => {
107
3081
                OwnedPath::Normal(p.iter().map(OwnedCircTarget::from_circ_target).collect())
108
            }
109
            Path(_) => {
110
1
                return Err(bad_api_usage!("Path with no entries!").into());
111
            }
112
        })
113
4091
    }
114
}
115

            
116
impl OwnedPath {
117
    /// Return the number of hops in this path.
118
    #[allow(clippy::len_without_is_empty)]
119
15
    pub(crate) fn len(&self) -> usize {
120
15
        match self {
121
4
            OwnedPath::ChannelOnly(_) => 1,
122
11
            OwnedPath::Normal(p) => p.len(),
123
        }
124
15
    }
125
}
126

            
127
/// For testing: make sure that `path` is the same when it is an owned
128
/// path.
129
#[cfg(test)]
130
4090
fn assert_same_path_when_owned(path: &TorPath<'_>) {
131
4090
    #![allow(clippy::unwrap_used)]
132
4090
    use std::convert::TryInto;
133
4090
    use tor_linkspec::ChanTarget;
134
4090
    let owned: OwnedPath = path.try_into().unwrap();
135
4090

            
136
4090
    match (&owned, &path.inner) {
137
10
        (OwnedPath::ChannelOnly(c), TorPathInner::FallbackOneHop(f)) => {
138
10
            assert_eq!(c.ed_identity(), f.ed_identity());
139
        }
140
1000
        (OwnedPath::Normal(p), TorPathInner::OneHop(h)) => {
141
1000
            assert_eq!(p.len(), 1);
142
1000
            assert_eq!(p[0].ed_identity(), h.ed_identity());
143
        }
144
3078
        (OwnedPath::Normal(p1), TorPathInner::Path(p2)) => {
145
3078
            assert_eq!(p1.len(), p2.len());
146
9236
            for (n1, n2) in p1.iter().zip(p2.iter()) {
147
9236
                assert_eq!(n1.ed_identity(), n2.ed_identity());
148
            }
149
        }
150
        (_, _) => {
151
            panic!("Mismatched path types.");
152
        }
153
    }
154
4089
}