tor_linkspec/
decode.rs

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
8use std::net::SocketAddr;
9
10use crate::{EncodedLinkSpec, LinkSpec, OwnedChanTargetBuilder, RelayIdType};
11use 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]
20pub 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
30impl OwnedChanTargetBuilder {
31    /// Construct an [`OwnedChanTargetBuilder`] from a list of [`LinkSpec`],
32    /// validating it according to a given level of [`Strictness`].
33    pub fn from_linkspecs(
34        strictness: Strictness,
35        linkspecs: &[LinkSpec],
36    ) -> Result<Self, ChanTargetDecodeError> {
37        // We ignore the strictness for now, since there is only one variant.
38        let _ = strictness;
39
40        // There must be exactly one Ed25519 identity.
41        let ed_id = linkspecs
42            .iter()
43            .filter_map(|ls| match ls {
44                LinkSpec::Ed25519Id(ed) => Some(ed),
45                _ => None,
46            })
47            .exactly_one()
48            .map_err(|mut e| match e.next() {
49                Some(_) => ChanTargetDecodeError::DuplicatedId(RelayIdType::Ed25519),
50                None => ChanTargetDecodeError::MissingId(RelayIdType::Ed25519),
51            })?;
52
53        // There must be exactly one RSA identity.
54        let rsa_id = linkspecs
55            .iter()
56            .filter_map(|ls| match ls {
57                LinkSpec::RsaId(rsa) => Some(rsa),
58                _ => None,
59            })
60            .exactly_one()
61            .map_err(|mut e| match e.next() {
62                Some(_) => ChanTargetDecodeError::DuplicatedId(RelayIdType::Rsa),
63                None => ChanTargetDecodeError::MissingId(RelayIdType::Rsa),
64            })?;
65
66        let addrs: Vec<SocketAddr> = linkspecs
67            .iter()
68            .filter_map(|ls| match ls {
69                LinkSpec::OrPort(addr, port) => Some(SocketAddr::new(*addr, *port)),
70                _ => None,
71            })
72            .collect();
73        // There must be at least one IPv4 ORPort.
74        if !addrs.iter().any(|addr| addr.is_ipv4()) {
75            return Err(ChanTargetDecodeError::MissingAddr);
76        }
77        let mut builder = OwnedChanTargetBuilder::default();
78
79        builder
80            .ed_identity(*ed_id)
81            .rsa_identity(*rsa_id)
82            .addrs(addrs);
83        Ok(builder)
84    }
85
86    /// As `from_linkspecs`, but take a list of encoded linkspecs and fail if
87    /// any are known to be ill-formed.
88    pub fn from_encoded_linkspecs(
89        strictness: Strictness,
90        linkspecs: &[EncodedLinkSpec],
91    ) -> Result<Self, ChanTargetDecodeError> {
92        // Decode the link specifiers and use them to find out what we can about
93        // this relay.
94        let linkspecs_decoded = linkspecs
95            .iter()
96            .map(|ls| ls.parse())
97            .collect::<Result<Vec<_>, _>>()
98            .map_err(ChanTargetDecodeError::MisformedLinkSpec)?;
99        Self::from_linkspecs(strictness, &linkspecs_decoded)
100    }
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]
107pub 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)]
123mod 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}