1use std::net::SocketAddr;
9
10use crate::{EncodedLinkSpec, LinkSpec, OwnedChanTargetBuilder, RelayIdType};
11use itertools::Itertools as _;
12
13#[derive(Debug, Clone, Copy)]
19#[non_exhaustive]
20pub enum Strictness {
21 Standard,
28}
29
30impl OwnedChanTargetBuilder {
31 pub fn from_linkspecs(
34 strictness: Strictness,
35 linkspecs: &[LinkSpec],
36 ) -> Result<Self, ChanTargetDecodeError> {
37 let _ = strictness;
39
40 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 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 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 pub fn from_encoded_linkspecs(
89 strictness: Strictness,
90 linkspecs: &[EncodedLinkSpec],
91 ) -> Result<Self, ChanTargetDecodeError> {
92 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#[derive(Clone, Debug, thiserror::Error)]
106#[non_exhaustive]
107pub enum ChanTargetDecodeError {
108 #[error("Missing a required {0} identity key")]
110 MissingId(RelayIdType),
111 #[error("Duplicated a {0} identity key")]
113 DuplicatedId(RelayIdType),
114 #[error("Missing a required address type")]
116 MissingAddr,
117 #[error("Mis-formatted link specifier")]
119 MisformedLinkSpec(#[source] tor_bytes::Error),
120}
121
122#[cfg(test)]
123mod test {
124 #![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 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}