Skip to main content

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