1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
//! Analyze a list of link specifiers as a `OwnedChanTarget`.
//!
//! This functionality is used in the onion service subsystem, and for relays.
//! The onion service subsystem uses this to decode a description of a relay as
//! provided in a HsDesc or an INTRODUCE2 message; relays use this to handle
//! EXTEND2 messages and figure out where to send a circuit.

use std::net::SocketAddr;

use crate::{LinkSpec, OwnedChanTargetBuilder, RelayIdType};
use itertools::Itertools as _;

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

impl OwnedChanTargetBuilder {
    /// Construct an [`OwnedChanTargetBuilder`] from a list of [`LinkSpec`],
    /// validating it according to a given level of [`Strictness`].
    pub fn from_linkspecs(
        strictness: Strictness,
        linkspecs: &[LinkSpec],
    ) -> Result<Self, ChanTargetDecodeError> {
        // We ignore the strictness for now, since there is only one variant.
        let _ = strictness;

        // There must be exactly one Ed25519 identity.
        let ed_id = linkspecs
            .iter()
            .filter_map(|ls| match ls {
                LinkSpec::Ed25519Id(ed) => Some(ed),
                _ => None,
            })
            .exactly_one()
            .map_err(|mut e| match e.next() {
                Some(_) => ChanTargetDecodeError::DuplicatedId(RelayIdType::Ed25519),
                None => ChanTargetDecodeError::MissingId(RelayIdType::Ed25519),
            })?;

        // There must be exactly one RSA identity.
        let rsa_id = linkspecs
            .iter()
            .filter_map(|ls| match ls {
                LinkSpec::RsaId(rsa) => Some(rsa),
                _ => None,
            })
            .exactly_one()
            .map_err(|mut e| match e.next() {
                Some(_) => ChanTargetDecodeError::DuplicatedId(RelayIdType::Rsa),
                None => ChanTargetDecodeError::MissingId(RelayIdType::Rsa),
            })?;

        let addrs: Vec<SocketAddr> = linkspecs
            .iter()
            .filter_map(|ls| match ls {
                LinkSpec::OrPort(addr, port) => Some(SocketAddr::new(*addr, *port)),
                _ => None,
            })
            .collect();
        // There must be at least one IPv4 ORPort.
        if !addrs.iter().any(|addr| addr.is_ipv4()) {
            return Err(ChanTargetDecodeError::MissingAddr);
        }
        let mut builder = OwnedChanTargetBuilder::default();

        builder
            .ed_identity(*ed_id)
            .rsa_identity(*rsa_id)
            .addrs(addrs);
        Ok(builder)
    }
}

/// An error that occurred while constructing a `ChanTarget` from a set of link
/// specifiers.
#[derive(Clone, Debug, thiserror::Error)]
#[non_exhaustive]
pub enum ChanTargetDecodeError {
    /// A required identity key was missing.
    #[error("Missing a required {0} identity key")]
    MissingId(RelayIdType),
    /// A required identity key was included more than once.
    #[error("Duplicated a {0} identity key")]
    DuplicatedId(RelayIdType),
    /// A required address type was missing.
    #[error("Missing a required address type")]
    MissingAddr,
}

#[cfg(test)]
mod test {
    // @@ begin test lint list maintained by maint/add_warning @@
    #![allow(clippy::bool_assert_comparison)]
    #![allow(clippy::clone_on_copy)]
    #![allow(clippy::dbg_macro)]
    #![allow(clippy::print_stderr)]
    #![allow(clippy::print_stdout)]
    #![allow(clippy::single_char_pattern)]
    #![allow(clippy::unwrap_used)]
    #![allow(clippy::unchecked_duration_subtraction)]
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->

    use crate::OwnedChanTarget;

    use super::*;
    #[test]
    fn decode_ok() {
        let ct = OwnedChanTarget::builder()
            .addrs(vec![
                "[::1]:99".parse().unwrap(),
                "127.0.0.1:11".parse().unwrap(),
            ])
            .ed_identity([42; 32].into())
            .rsa_identity([45; 20].into())
            .build()
            .unwrap();

        let ls = vec![
            LinkSpec::OrPort("::1".parse().unwrap(), 99),
            LinkSpec::OrPort("127.0.0.1".parse().unwrap(), 11),
            LinkSpec::Ed25519Id([42; 32].into()),
            LinkSpec::RsaId([45; 20].into()),
        ];
        let ct2 = OwnedChanTargetBuilder::from_linkspecs(Strictness::Standard, &ls)
            .unwrap()
            .build()
            .unwrap();
        assert_eq!(format!("{:?}", &ct), format!("{:?}", ct2));
    }

    #[test]
    fn decode_errs() {
        use ChanTargetDecodeError as E;
        use RelayIdType as ID;

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

        assert!(matches!(err_from(&[&ipv4, &ipv6, &ed, &rsa]), None));
        assert!(matches!(err_from(&[&ipv4, &ed, &rsa]), None));
        assert!(matches!(
            err_from(&[&ipv4, &ed, &ed, &rsa]),
            Some(E::DuplicatedId(ID::Ed25519))
        ));
        assert!(matches!(
            err_from(&[&ipv4, &ed, &rsa, &rsa]),
            Some(E::DuplicatedId(ID::Rsa))
        ));
        assert!(matches!(
            err_from(&[&ipv4, &rsa]),
            Some(E::MissingId(ID::Ed25519))
        ));
        assert!(matches!(
            err_from(&[&ipv4, &ed]),
            Some(E::MissingId(ID::Rsa))
        ));
        assert!(matches!(
            err_from(&[&ipv6, &ed, &rsa]),
            Some(E::MissingAddr)
        ));
    }
}