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.
78use std::net::SocketAddr;
910use crate::{EncodedLinkSpec, LinkSpec, OwnedChanTargetBuilder, RelayIdType};
11use itertools::Itertools as _;
1213/// 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.
27Standard,
28}
2930impl OwnedChanTargetBuilder {
31/// Construct an [`OwnedChanTargetBuilder`] from a list of [`LinkSpec`],
32 /// validating it according to a given level of [`Strictness`].
33pub 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.
38let _ = strictness;
3940// There must be exactly one Ed25519 identity.
41let 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() {
49Some(_) => ChanTargetDecodeError::DuplicatedId(RelayIdType::Ed25519),
50None => ChanTargetDecodeError::MissingId(RelayIdType::Ed25519),
51 })?;
5253// There must be exactly one RSA identity.
54let 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() {
62Some(_) => ChanTargetDecodeError::DuplicatedId(RelayIdType::Rsa),
63None => ChanTargetDecodeError::MissingId(RelayIdType::Rsa),
64 })?;
6566let 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.
74if !addrs.iter().any(|addr| addr.is_ipv4()) {
75return Err(ChanTargetDecodeError::MissingAddr);
76 }
77let mut builder = OwnedChanTargetBuilder::default();
7879 builder
80 .ed_identity(*ed_id)
81 .rsa_identity(*rsa_id)
82 .addrs(addrs);
83Ok(builder)
84 }
8586/// As `from_linkspecs`, but take a list of encoded linkspecs and fail if
87 /// any are known to be ill-formed.
88pub 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.
94let linkspecs_decoded = linkspecs
95 .iter()
96 .map(|ls| ls.parse())
97 .collect::<Result<Vec<_>, _>>()
98 .map_err(ChanTargetDecodeError::MisformedLinkSpec)?;
99Self::from_linkspecs(strictness, &linkspecs_decoded)
100 }
101}
102103/// 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")]
110MissingId(RelayIdType),
111/// A required identity key was included more than once.
112#[error("Duplicated a {0} identity key")]
113DuplicatedId(RelayIdType),
114/// A required address type was missing.
115#[error("Missing a required address type")]
116MissingAddr,
117/// Couldn't parse a provided linkspec of recognized type.
118#[error("Mis-formatted link specifier")]
119MisformedLinkSpec(#[source] tor_bytes::Error),
120}
121122#[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 @@ -->
137138use crate::OwnedChanTarget;
139140use super::*;
141#[test]
142fn decode_ok() {
143let 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();
152153let 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 ];
159let ct2 = OwnedChanTargetBuilder::from_linkspecs(Strictness::Standard, &ls)
160 .unwrap()
161 .build()
162 .unwrap();
163assert_eq!(format!("{:?}", &ct), format!("{:?}", ct2));
164 }
165166#[test]
167fn decode_errs() {
168use ChanTargetDecodeError as E;
169use RelayIdType as ID;
170171let ipv4 = LinkSpec::OrPort("127.0.0.1".parse().unwrap(), 11);
172let ipv6 = LinkSpec::OrPort("::1".parse().unwrap(), 99);
173let ed = LinkSpec::Ed25519Id([42; 32].into());
174let rsa = LinkSpec::RsaId([45; 20].into());
175let err_from = |lst: &[&LinkSpec]| {
176 OwnedChanTargetBuilder::from_linkspecs(
177 Strictness::Standard,
178&lst.iter().map(|ls| (*ls).clone()).collect::<Vec<_>>()[..],
179 )
180 .err()
181 };
182183assert!(err_from(&[&ipv4, &ipv6, &ed, &rsa]).is_none());
184assert!(err_from(&[&ipv4, &ed, &rsa]).is_none());
185assert!(matches!(
186 err_from(&[&ipv4, &ed, &ed, &rsa]),
187Some(E::DuplicatedId(ID::Ed25519))
188 ));
189assert!(matches!(
190 err_from(&[&ipv4, &ed, &rsa, &rsa]),
191Some(E::DuplicatedId(ID::Rsa))
192 ));
193assert!(matches!(
194 err_from(&[&ipv4, &rsa]),
195Some(E::MissingId(ID::Ed25519))
196 ));
197assert!(matches!(
198 err_from(&[&ipv4, &ed]),
199Some(E::MissingId(ID::Rsa))
200 ));
201assert!(matches!(
202 err_from(&[&ipv6, &ed, &rsa]),
203Some(E::MissingAddr)
204 ));
205 }
206}