1use smoldot::libp2p::{
19 multiaddr::{Multiaddr, Protocol},
20 multihash::Multihash,
21};
22
23use super::{Address, ConnectionType, DnsFamily, MultiStreamAddress};
24use core::{
25 net::{IpAddr, Ipv4Addr, Ipv6Addr},
26 str,
27};
28
29pub enum AddressOrMultiStreamAddress<'a> {
30 Address(Address<'a>),
31 MultiStreamAddress(MultiStreamAddress<'a>),
32}
33
34impl<'a> From<&'a AddressOrMultiStreamAddress<'a>> for ConnectionType {
35 fn from(address: &'a AddressOrMultiStreamAddress<'a>) -> ConnectionType {
36 match address {
37 AddressOrMultiStreamAddress::Address(a) => ConnectionType::from(a),
38 AddressOrMultiStreamAddress::MultiStreamAddress(a) => ConnectionType::from(a),
39 }
40 }
41}
42
43pub fn multiaddr_to_address(
45 multiaddr: &'_ Multiaddr,
46) -> Result<AddressOrMultiStreamAddress<'_>, Error> {
47 let mut iter = multiaddr.iter().fuse();
48
49 let proto1 = iter.next().ok_or(Error::UnknownCombination)?;
50 let proto2 = iter.next().ok_or(Error::UnknownCombination)?;
51 let proto3 = iter.next();
52 let proto4 = iter.next();
53
54 if iter.next().is_some() {
55 return Err(Error::UnknownCombination);
56 }
57
58 Ok(match (proto1, proto2, proto3, proto4) {
59 (Protocol::Ip4(ip), Protocol::Tcp(port), None, None) => {
60 AddressOrMultiStreamAddress::Address(Address::TcpIp {
61 ip: IpAddr::V4(Ipv4Addr::from(ip)),
62 port,
63 })
64 }
65 (Protocol::Ip6(ip), Protocol::Tcp(port), None, None) => {
66 AddressOrMultiStreamAddress::Address(Address::TcpIp {
67 ip: IpAddr::V6(Ipv6Addr::from(ip)),
68 port,
69 })
70 }
71 (
72 Protocol::Dns(addr) | Protocol::Dns4(addr) | Protocol::Dns6(addr),
73 Protocol::Tcp(port),
74 None,
75 None,
76 ) => AddressOrMultiStreamAddress::Address(Address::TcpDns {
77 hostname: str::from_utf8(addr.into_bytes()).map_err(Error::NonUtf8DomainName)?,
78 port,
79 }),
80 (Protocol::Ip4(ip), Protocol::Tcp(port), Some(Protocol::Ws), None) => {
81 AddressOrMultiStreamAddress::Address(Address::WebSocketIp {
82 ip: IpAddr::V4(Ipv4Addr::from(ip)),
83 port,
84 })
85 }
86 (Protocol::Ip6(ip), Protocol::Tcp(port), Some(Protocol::Ws), None) => {
87 AddressOrMultiStreamAddress::Address(Address::WebSocketIp {
88 ip: IpAddr::V6(Ipv6Addr::from(ip)),
89 port,
90 })
91 }
92 (
93 Protocol::Dns(addr) | Protocol::Dns4(addr) | Protocol::Dns6(addr),
94 Protocol::Tcp(port),
95 Some(Protocol::Ws),
96 None,
97 ) => AddressOrMultiStreamAddress::Address(Address::WebSocketDns {
98 hostname: str::from_utf8(addr.into_bytes()).map_err(Error::NonUtf8DomainName)?,
99 port,
100 secure: false,
101 }),
102 (
103 Protocol::Dns(addr) | Protocol::Dns4(addr) | Protocol::Dns6(addr),
104 Protocol::Tcp(port),
105 Some(Protocol::Wss),
106 None,
107 )
108 | (
109 Protocol::Dns(addr) | Protocol::Dns4(addr) | Protocol::Dns6(addr),
110 Protocol::Tcp(port),
111 Some(Protocol::Tls),
112 Some(Protocol::Ws),
113 ) => AddressOrMultiStreamAddress::Address(Address::WebSocketDns {
114 hostname: str::from_utf8(addr.into_bytes()).map_err(Error::NonUtf8DomainName)?,
115 port,
116 secure: true,
117 }),
118
119 (
120 Protocol::Ip4(ip),
121 Protocol::Udp(port),
122 Some(Protocol::WebRtcDirect),
123 Some(Protocol::Certhash(multihash)),
124 ) => AddressOrMultiStreamAddress::MultiStreamAddress(MultiStreamAddress::WebRtc {
125 ip: IpAddr::V4(Ipv4Addr::from(ip)),
126 port,
127 remote_certificate_sha256: certhash_sha256(&multihash)?,
128 }),
129
130 (
131 Protocol::Ip6(ip),
132 Protocol::Udp(port),
133 Some(Protocol::WebRtcDirect),
134 Some(Protocol::Certhash(multihash)),
135 ) => AddressOrMultiStreamAddress::MultiStreamAddress(MultiStreamAddress::WebRtc {
136 ip: IpAddr::V6(Ipv6Addr::from(ip)),
137 port,
138 remote_certificate_sha256: certhash_sha256(&multihash)?,
139 }),
140 (
141 dns @ (Protocol::Dns(addr) | Protocol::Dns4(addr) | Protocol::Dns6(addr)),
142 Protocol::Udp(port),
143 Some(Protocol::WebRtcDirect),
144 Some(Protocol::Certhash(multihash)),
145 ) => AddressOrMultiStreamAddress::MultiStreamAddress(MultiStreamAddress::WebRtcDns {
146 hostname: str::from_utf8(addr.into_bytes()).map_err(Error::NonUtf8DomainName)?,
147 family: match dns {
148 Protocol::Dns(_) => DnsFamily::Any,
149 Protocol::Dns4(_) => DnsFamily::Ipv4,
150 Protocol::Dns6(_) => DnsFamily::Ipv6,
151 _ => unreachable!(),
152 },
153 port,
154 remote_certificate_sha256: certhash_sha256(&multihash)?,
155 }),
156 _ => return Err(Error::UnknownCombination),
157 })
158}
159
160fn certhash_sha256<'a>(multihash: &Multihash<&'a [u8]>) -> Result<&'a [u8; 32], Error> {
162 if multihash.hash_algorithm_code() != 0x12 {
163 return Err(Error::NonSha256Certhash);
164 }
165 <&[u8; 32]>::try_from(multihash.data_ref()).map_err(|_| Error::InvalidMultihashLength)
166}
167
168#[derive(Debug, Clone, derive_more::Display, derive_more::Error)]
169pub enum Error {
170 UnknownCombination,
172
173 NonUtf8DomainName(str::Utf8Error),
181
182 NonSha256Certhash,
185
186 InvalidMultihashLength,
188}
189
190#[cfg(test)]
191mod tests {
192 use super::{AddressOrMultiStreamAddress, Error, multiaddr_to_address};
193 use crate::platform::{ConnectionType, DnsFamily, MultiStreamAddress};
194 use core::net::{IpAddr, Ipv4Addr, Ipv6Addr};
195 use smoldot::libp2p::multiaddr::Multiaddr;
196
197 const CERTHASH: &str = "uEiBsqkcr8pOaNjl6px_v1nBatWMfXB9C_sU8fDat3mZWfQ";
199
200 fn multistream(multiaddr: &Multiaddr) -> MultiStreamAddress<'_> {
201 match multiaddr_to_address(multiaddr).unwrap() {
202 AddressOrMultiStreamAddress::MultiStreamAddress(addr) => addr,
203 AddressOrMultiStreamAddress::Address(addr) => {
204 panic!("expected a multistream address, got {addr:?}")
205 }
206 }
207 }
208
209 fn cert_of(addr: &MultiStreamAddress) -> [u8; 32] {
210 match addr {
211 MultiStreamAddress::WebRtc {
212 remote_certificate_sha256,
213 ..
214 }
215 | MultiStreamAddress::WebRtcDns {
216 remote_certificate_sha256,
217 ..
218 } => **remote_certificate_sha256,
219 }
220 }
221
222 #[test]
223 fn webrtc_with_ip_address() {
224 let ip4: Multiaddr = format!("/ip4/1.2.3.4/udp/30333/webrtc-direct/certhash/{CERTHASH}")
225 .parse()
226 .unwrap();
227 let parsed = multistream(&ip4);
228 assert!(matches!(
229 parsed,
230 MultiStreamAddress::WebRtc { ip: IpAddr::V4(ip), port: 30333, .. }
231 if ip == Ipv4Addr::new(1, 2, 3, 4)
232 ));
233 assert_eq!(ConnectionType::from(&parsed), ConnectionType::WebRtcIpv4);
234
235 let ip6: Multiaddr = format!("/ip6/::1/udp/30333/webrtc-direct/certhash/{CERTHASH}")
236 .parse()
237 .unwrap();
238 let parsed = multistream(&ip6);
239 assert!(matches!(
240 parsed,
241 MultiStreamAddress::WebRtc { ip: IpAddr::V6(ip), port: 30333, .. }
242 if ip == Ipv6Addr::LOCALHOST
243 ));
244 assert_eq!(ConnectionType::from(&parsed), ConnectionType::WebRtcIpv6);
245 }
246
247 #[test]
248 fn webrtc_with_domain_name() {
249 let ip4: Multiaddr = format!("/ip4/1.2.3.4/udp/30333/webrtc-direct/certhash/{CERTHASH}")
250 .parse()
251 .unwrap();
252 let expected_cert = cert_of(&multistream(&ip4));
253
254 for (protocol, family) in [
255 ("dns", DnsFamily::Any),
256 ("dns4", DnsFamily::Ipv4),
257 ("dns6", DnsFamily::Ipv6),
258 ] {
259 let multiaddr: Multiaddr =
260 format!("/{protocol}/example.com/udp/30333/webrtc-direct/certhash/{CERTHASH}")
261 .parse()
262 .unwrap();
263 let parsed = multistream(&multiaddr);
264 assert_eq!(
265 parsed,
266 MultiStreamAddress::WebRtcDns {
267 hostname: "example.com",
268 family,
269 port: 30333,
270 remote_certificate_sha256: &expected_cert,
271 },
272 "{protocol}"
273 );
274 assert_eq!(ConnectionType::from(&parsed), ConnectionType::WebRtcDns);
275 }
276 }
277
278 #[test]
279 fn webrtc_rejected_combinations() {
280 let multiaddr: Multiaddr =
282 format!("/dnsaddr/example.com/udp/1/webrtc-direct/certhash/{CERTHASH}")
283 .parse()
284 .unwrap();
285 assert!(matches!(
286 multiaddr_to_address(&multiaddr),
287 Err(Error::UnknownCombination)
288 ));
289
290 let multiaddr: Multiaddr = "/dns/example.com/udp/1/webrtc-direct".parse().unwrap();
292 assert!(matches!(
293 multiaddr_to_address(&multiaddr),
294 Err(Error::UnknownCombination)
295 ));
296
297 let sha1_certhash = format!("u{}{}", "ERQ", "A".repeat(27));
299 for host in ["dns/example.com", "ip4/1.2.3.4"] {
300 let multiaddr: Multiaddr =
301 format!("/{host}/udp/1/webrtc-direct/certhash/{sha1_certhash}")
302 .parse()
303 .unwrap();
304 assert!(
305 matches!(
306 multiaddr_to_address(&multiaddr),
307 Err(Error::NonSha256Certhash)
308 ),
309 "{host}"
310 );
311 }
312 }
313}