hopr_transport/
multiaddrs.rs1use multiaddr::{Multiaddr, Protocol};
2
3pub fn strip_p2p_protocol(ma: &Multiaddr) -> Multiaddr {
5 Multiaddr::from_iter(ma.iter().filter(|v| !matches!(v, Protocol::P2p(_))))
6}
7
8pub fn is_dns(ma: &Multiaddr) -> bool {
10 ma.iter()
11 .next()
12 .map(|proto| matches!(proto, Protocol::Dns(_) | Protocol::Dns4(_) | Protocol::Dns6(_)))
13 .unwrap_or(false)
14}
15
16pub fn is_supported(ma: &Multiaddr) -> bool {
18 ma.iter()
19 .next()
20 .map(|proto| {
21 matches!(
22 proto,
23 Protocol::Ip4(_) | Protocol::Ip6(_) | Protocol::Dns(_) | Protocol::Dns4(_) | Protocol::Dns6(_)
24 )
25 })
26 .unwrap_or(false)
27}
28
29#[cfg(test)]
30mod tests {
31 use std::str::FromStr;
32
33 use anyhow::Context;
34 use rstest::rstest;
35
36 use super::*;
37
38 fn is_private(ma: &Multiaddr) -> bool {
40 ma.iter()
41 .next()
42 .map(|proto| match proto {
43 Protocol::Ip4(ip) => ip.is_private(),
44 Protocol::Dns(domain) => domain.to_ascii_lowercase() == "localhost",
45 _ => false,
46 })
47 .unwrap_or(false)
48 }
49
50 #[test]
51 fn private_multiaddresses_are_shown_as_private() -> anyhow::Result<()> {
52 assert!(!is_private(&Multiaddr::from_str("/ip4/33.42.112.22/udp/9090/quic")?));
53
54 assert!(is_private(&Multiaddr::from_str("/ip4/192.168.1.23/udp/9090/quic")?));
55
56 Ok(())
57 }
58
59 #[test]
62 fn strip_p2p_protocol_should_remove_trailing_p2p_component() -> anyhow::Result<()> {
63 let ma =
64 Multiaddr::from_str("/ip4/127.0.0.1/tcp/9091/p2p/16Uiu2HAmA5h1q1jtAPMaJch5CRnSBMjGNq22mfMDHSgMPC27cW1B")
65 .context("parsing multiaddr with p2p")?;
66
67 let stripped = strip_p2p_protocol(&ma);
68 let expected = Multiaddr::from_str("/ip4/127.0.0.1/tcp/9091").context("parsing expected")?;
69
70 assert_eq!(stripped, expected);
71
72 Ok(())
73 }
74
75 #[test]
76 fn strip_p2p_protocol_should_be_noop_when_no_p2p_present() -> anyhow::Result<()> {
77 let ma = Multiaddr::from_str("/ip4/127.0.0.1/tcp/9091").context("parsing multiaddr")?;
78
79 let stripped = strip_p2p_protocol(&ma);
80
81 assert_eq!(stripped, ma);
82
83 Ok(())
84 }
85
86 #[test]
87 fn strip_p2p_protocol_should_return_empty_for_p2p_only_address() -> anyhow::Result<()> {
88 let ma = Multiaddr::from_str("/p2p/16Uiu2HAmA5h1q1jtAPMaJch5CRnSBMjGNq22mfMDHSgMPC27cW1B")
89 .context("parsing p2p-only multiaddr")?;
90
91 let stripped = strip_p2p_protocol(&ma);
92
93 assert!(stripped.is_empty());
94
95 Ok(())
96 }
97
98 #[rstest]
101 #[case("/dns/example.com/tcp/443", true)]
102 #[case("/dns4/example.com/tcp/443", true)]
103 #[case("/dns6/example.com/tcp/443", true)]
104 #[case("/ip4/127.0.0.1/tcp/9091", false)]
105 #[case("/ip6/::1/tcp/9091", false)]
106 fn is_dns_should_detect_dns_variants(#[case] addr: &str, #[case] expected: bool) -> anyhow::Result<()> {
107 let ma = Multiaddr::from_str(addr).context("parsing multiaddr")?;
108
109 assert_eq!(is_dns(&ma), expected, "is_dns mismatch for {addr}");
110
111 Ok(())
112 }
113
114 #[test]
115 fn is_dns_should_return_false_for_empty_multiaddr() {
116 assert!(!is_dns(&Multiaddr::empty()));
117 }
118
119 #[rstest]
122 #[case("/ip4/127.0.0.1/tcp/9091", true)]
123 #[case("/ip6/::1/tcp/9091", true)]
124 #[case("/dns/example.com/tcp/443", true)]
125 #[case("/dns4/localhost/tcp/5543", true)]
126 #[case("/dns6/localhost/tcp/5543", true)]
127 #[case("/memory/1234", false)]
128 fn is_supported_should_identify_supported_protocols(
129 #[case] addr: &str,
130 #[case] expected: bool,
131 ) -> anyhow::Result<()> {
132 let ma = Multiaddr::from_str(addr).context("parsing multiaddr")?;
133
134 assert_eq!(is_supported(&ma), expected, "is_supported mismatch for {addr}");
135
136 Ok(())
137 }
138
139 #[test]
140 fn is_supported_should_return_false_for_empty_multiaddr() {
141 assert!(!is_supported(&Multiaddr::empty()));
142 }
143}