1
//! Analyze a list of link specifiers as a `OwnedChanTarget`.
2
//!
3
//! This functionality is used in the onion service subsystem, and for relays.
4
//! The onion service subsystem uses this to decode a description of a relay as
5
//! provided in a HsDesc or an INTRODUCE2 message; relays use this to handle
6
//! EXTEND2 messages and figure out where to send a circuit.
7

            
8
use std::net::SocketAddr;
9

            
10
use crate::{EncodedLinkSpec, LinkSpec, OwnedChanTargetBuilder, RelayIdType};
11
use itertools::Itertools as _;
12

            
13
/// A rule for how strictly to parse a list of LinkSpecifiers when converting it into
14
/// an [`OwnedChanTarget`](crate::OwnedChanTarget).
15
//
16
// For now, there is only one level of strictness, but it is all but certain
17
// that we will add more in the future.
18
#[derive(Debug, Clone, Copy)]
19
#[non_exhaustive]
20
pub enum Strictness {
21
    /// Enforce the standard rules described in `tor-spec`:
22
    ///
23
    /// Namely:
24
    ///   * There must be exactly one Ed25519 identity.
25
    ///   * There must be exactly one RSA identity.
26
    ///   * There must be at least one IPv4 ORPort.
27
    Standard,
28
}
29

            
30
impl OwnedChanTargetBuilder {
31
    /// Construct an [`OwnedChanTargetBuilder`] from a list of [`LinkSpec`],
32
    /// validating it according to a given level of [`Strictness`].
33
    ///
34
    // TODO: replace Itertools::exactly_one() with a stdlib equivalent when there is one.
35
    //
36
    // See issue #48919 <https://github.com/rust-lang/rust/issues/48919>
37
    #[allow(unstable_name_collisions)]
38
3229
    pub fn from_linkspecs(
39
3229
        strictness: Strictness,
40
3229
        linkspecs: &[LinkSpec],
41
3229
    ) -> Result<Self, ChanTargetDecodeError> {
42
        // We ignore the strictness for now, since there is only one variant.
43
3229
        let _ = strictness;
44

            
45
        // There must be exactly one Ed25519 identity.
46
3229
        let ed_id = linkspecs
47
3229
            .iter()
48
11638
            .filter_map(|ls| match ls {
49
3229
                LinkSpec::Ed25519Id(ed) => Some(ed),
50
8350
                _ => None,
51
11579
            })
52
3229
            .exactly_one()
53
3231
            .map_err(|mut e| match e.next() {
54
2
                Some(_) => ChanTargetDecodeError::DuplicatedId(RelayIdType::Ed25519),
55
2
                None => ChanTargetDecodeError::MissingId(RelayIdType::Ed25519),
56
6
            })?;
57

            
58
        // There must be exactly one RSA identity.
59
3225
        let rsa_id = linkspecs
60
3225
            .iter()
61
11626
            .filter_map(|ls| match ls {
62
3225
                LinkSpec::RsaId(rsa) => Some(rsa),
63
8344
                _ => None,
64
11569
            })
65
3225
            .exactly_one()
66
3227
            .map_err(|mut e| match e.next() {
67
2
                Some(_) => ChanTargetDecodeError::DuplicatedId(RelayIdType::Rsa),
68
2
                None => ChanTargetDecodeError::MissingId(RelayIdType::Rsa),
69
6
            })?;
70

            
71
3221
        let addrs: Vec<SocketAddr> = linkspecs
72
3221
            .iter()
73
11612
            .filter_map(|ls| match ls {
74
5115
                LinkSpec::OrPort(addr, port) => Some(SocketAddr::new(*addr, *port)),
75
6442
                _ => None,
76
11557
            })
77
3221
            .collect();
78
        // There must be at least one IPv4 ORPort.
79
3278
        if !addrs.iter().any(|addr| addr.is_ipv4()) {
80
2
            return Err(ChanTargetDecodeError::MissingAddr);
81
3219
        }
82
3219
        let mut builder = OwnedChanTargetBuilder::default();
83

            
84
3219
        builder
85
3219
            .ed_identity(*ed_id)
86
3219
            .rsa_identity(*rsa_id)
87
3219
            .addrs(addrs);
88
3219
        Ok(builder)
89
3229
    }
90

            
91
    /// As `from_linkspecs`, but take a list of encoded linkspecs and fail if
92
    /// any are known to be ill-formed.
93
3213
    pub fn from_encoded_linkspecs(
94
3213
        strictness: Strictness,
95
3213
        linkspecs: &[EncodedLinkSpec],
96
3213
    ) -> Result<Self, ChanTargetDecodeError> {
97
        // Decode the link specifiers and use them to find out what we can about
98
        // this relay.
99
3213
        let linkspecs_decoded = linkspecs
100
3213
            .iter()
101
11580
            .map(|ls| ls.parse())
102
3213
            .collect::<Result<Vec<_>, _>>()
103
3213
            .map_err(ChanTargetDecodeError::MisformedLinkSpec)?;
104
3213
        Self::from_linkspecs(strictness, &linkspecs_decoded)
105
3213
    }
106
}
107

            
108
/// An error that occurred while constructing a `ChanTarget` from a set of link
109
/// specifiers.
110
#[derive(Clone, Debug, thiserror::Error)]
111
#[non_exhaustive]
112
pub enum ChanTargetDecodeError {
113
    /// A required identity key was missing.
114
    #[error("Missing a required {0} identity key")]
115
    MissingId(RelayIdType),
116
    /// A required identity key was included more than once.
117
    #[error("Duplicated a {0} identity key")]
118
    DuplicatedId(RelayIdType),
119
    /// A required address type was missing.
120
    #[error("Missing a required address type")]
121
    MissingAddr,
122
    /// Couldn't parse a provided linkspec of recognized type.
123
    #[error("Mis-formatted link specifier")]
124
    MisformedLinkSpec(#[source] tor_bytes::Error),
125
}
126

            
127
#[cfg(test)]
128
mod test {
129
    // @@ begin test lint list maintained by maint/add_warning @@
130
    #![allow(clippy::bool_assert_comparison)]
131
    #![allow(clippy::clone_on_copy)]
132
    #![allow(clippy::dbg_macro)]
133
    #![allow(clippy::mixed_attributes_style)]
134
    #![allow(clippy::print_stderr)]
135
    #![allow(clippy::print_stdout)]
136
    #![allow(clippy::single_char_pattern)]
137
    #![allow(clippy::unwrap_used)]
138
    #![allow(clippy::unchecked_time_subtraction)]
139
    #![allow(clippy::useless_vec)]
140
    #![allow(clippy::needless_pass_by_value)]
141
    #![allow(clippy::string_slice)] // See arti#2571
142
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
143

            
144
    use crate::OwnedChanTarget;
145

            
146
    use super::*;
147
    #[test]
148
    fn decode_ok() {
149
        let ct = OwnedChanTarget::builder()
150
            .addrs(vec![
151
                "[::1]:99".parse().unwrap(),
152
                "127.0.0.1:11".parse().unwrap(),
153
            ])
154
            .ed_identity([42; 32].into())
155
            .rsa_identity([45; 20].into())
156
            .build()
157
            .unwrap();
158

            
159
        let ls = vec![
160
            LinkSpec::OrPort("::1".parse().unwrap(), 99),
161
            LinkSpec::OrPort("127.0.0.1".parse().unwrap(), 11),
162
            LinkSpec::Ed25519Id([42; 32].into()),
163
            LinkSpec::RsaId([45; 20].into()),
164
        ];
165
        let ct2 = OwnedChanTargetBuilder::from_linkspecs(Strictness::Standard, &ls)
166
            .unwrap()
167
            .build()
168
            .unwrap();
169
        assert_eq!(format!("{:?}", &ct), format!("{:?}", ct2));
170
    }
171

            
172
    #[test]
173
    fn decode_errs() {
174
        use ChanTargetDecodeError as E;
175
        use RelayIdType as ID;
176

            
177
        let ipv4 = LinkSpec::OrPort("127.0.0.1".parse().unwrap(), 11);
178
        let ipv6 = LinkSpec::OrPort("::1".parse().unwrap(), 99);
179
        let ed = LinkSpec::Ed25519Id([42; 32].into());
180
        let rsa = LinkSpec::RsaId([45; 20].into());
181
        let err_from = |lst: &[&LinkSpec]| {
182
            OwnedChanTargetBuilder::from_linkspecs(
183
                Strictness::Standard,
184
                &lst.iter().map(|ls| (*ls).clone()).collect::<Vec<_>>()[..],
185
            )
186
            .err()
187
        };
188

            
189
        assert!(err_from(&[&ipv4, &ipv6, &ed, &rsa]).is_none());
190
        assert!(err_from(&[&ipv4, &ed, &rsa]).is_none());
191
        assert!(matches!(
192
            err_from(&[&ipv4, &ed, &ed, &rsa]),
193
            Some(E::DuplicatedId(ID::Ed25519))
194
        ));
195
        assert!(matches!(
196
            err_from(&[&ipv4, &ed, &rsa, &rsa]),
197
            Some(E::DuplicatedId(ID::Rsa))
198
        ));
199
        assert!(matches!(
200
            err_from(&[&ipv4, &rsa]),
201
            Some(E::MissingId(ID::Ed25519))
202
        ));
203
        assert!(matches!(
204
            err_from(&[&ipv4, &ed]),
205
            Some(E::MissingId(ID::Rsa))
206
        ));
207
        assert!(matches!(
208
            err_from(&[&ipv6, &ed, &rsa]),
209
            Some(E::MissingAddr)
210
        ));
211
    }
212
}