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
184
    pub fn from_linkspecs(
34
184
        strictness: Strictness,
35
184
        linkspecs: &[LinkSpec],
36
184
    ) -> Result<Self, ChanTargetDecodeError> {
37
184
        // We ignore the strictness for now, since there is only one variant.
38
184
        let _ = strictness;
39

            
40
        // There must be exactly one Ed25519 identity.
41
184
        let ed_id = linkspecs
42
184
            .iter()
43
677
            .filter_map(|ls| match ls {
44
184
                LinkSpec::Ed25519Id(ed) => Some(ed),
45
482
                _ => None,
46
677
            })
47
184
            .exactly_one()
48
186
            .map_err(|mut e| match e.next() {
49
2
                Some(_) => ChanTargetDecodeError::DuplicatedId(RelayIdType::Ed25519),
50
2
                None => ChanTargetDecodeError::MissingId(RelayIdType::Ed25519),
51
186
            })?;
52

            
53
        // There must be exactly one RSA identity.
54
180
        let rsa_id = linkspecs
55
180
            .iter()
56
665
            .filter_map(|ls| match ls {
57
180
                LinkSpec::RsaId(rsa) => Some(rsa),
58
476
                _ => None,
59
665
            })
60
180
            .exactly_one()
61
182
            .map_err(|mut e| match e.next() {
62
2
                Some(_) => ChanTargetDecodeError::DuplicatedId(RelayIdType::Rsa),
63
2
                None => ChanTargetDecodeError::MissingId(RelayIdType::Rsa),
64
182
            })?;
65

            
66
176
        let addrs: Vec<SocketAddr> = linkspecs
67
176
            .iter()
68
651
            .filter_map(|ls| match ls {
69
292
                LinkSpec::OrPort(addr, port) => Some(SocketAddr::new(*addr, *port)),
70
352
                _ => None,
71
651
            })
72
176
            .collect();
73
176
        // There must be at least one IPv4 ORPort.
74
185
        if !addrs.iter().any(|addr| addr.is_ipv4()) {
75
2
            return Err(ChanTargetDecodeError::MissingAddr);
76
174
        }
77
174
        let mut builder = OwnedChanTargetBuilder::default();
78
174

            
79
174
        builder
80
174
            .ed_identity(*ed_id)
81
174
            .rsa_identity(*rsa_id)
82
174
            .addrs(addrs);
83
174
        Ok(builder)
84
184
    }
85

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

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

            
122
#[cfg(test)]
123
mod test {
124
    // @@ begin test lint list maintained by maint/add_warning @@
125
    #![allow(clippy::bool_assert_comparison)]
126
    #![allow(clippy::clone_on_copy)]
127
    #![allow(clippy::dbg_macro)]
128
    #![allow(clippy::mixed_attributes_style)]
129
    #![allow(clippy::print_stderr)]
130
    #![allow(clippy::print_stdout)]
131
    #![allow(clippy::single_char_pattern)]
132
    #![allow(clippy::unwrap_used)]
133
    #![allow(clippy::unchecked_duration_subtraction)]
134
    #![allow(clippy::useless_vec)]
135
    #![allow(clippy::needless_pass_by_value)]
136
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
137

            
138
    use crate::OwnedChanTarget;
139

            
140
    use super::*;
141
    #[test]
142
    fn decode_ok() {
143
        let ct = OwnedChanTarget::builder()
144
            .addrs(vec![
145
                "[::1]:99".parse().unwrap(),
146
                "127.0.0.1:11".parse().unwrap(),
147
            ])
148
            .ed_identity([42; 32].into())
149
            .rsa_identity([45; 20].into())
150
            .build()
151
            .unwrap();
152

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

            
166
    #[test]
167
    fn decode_errs() {
168
        use ChanTargetDecodeError as E;
169
        use RelayIdType as ID;
170

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

            
183
        assert!(err_from(&[&ipv4, &ipv6, &ed, &rsa]).is_none());
184
        assert!(err_from(&[&ipv4, &ed, &rsa]).is_none());
185
        assert!(matches!(
186
            err_from(&[&ipv4, &ed, &ed, &rsa]),
187
            Some(E::DuplicatedId(ID::Ed25519))
188
        ));
189
        assert!(matches!(
190
            err_from(&[&ipv4, &ed, &rsa, &rsa]),
191
            Some(E::DuplicatedId(ID::Rsa))
192
        ));
193
        assert!(matches!(
194
            err_from(&[&ipv4, &rsa]),
195
            Some(E::MissingId(ID::Ed25519))
196
        ));
197
        assert!(matches!(
198
            err_from(&[&ipv4, &ed]),
199
            Some(E::MissingId(ID::Rsa))
200
        ));
201
        assert!(matches!(
202
            err_from(&[&ipv6, &ed, &rsa]),
203
            Some(E::MissingAddr)
204
        ));
205
    }
206
}