Skip to main content

hopr_transport/
multiaddrs.rs

1use multiaddr::{Multiaddr, Protocol};
2
3/// Remove the `p2p/<PeerId>` component from a multiaddress
4pub fn strip_p2p_protocol(ma: &Multiaddr) -> Multiaddr {
5    Multiaddr::from_iter(ma.iter().filter(|v| !matches!(v, Protocol::P2p(_))))
6}
7
8/// Check whether the first multiaddress protocol component is a `dns*` component
9pub 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
16/// Check whether the multiaddr protocol component is supported by this library
17pub 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    /// Check whether the first multiaddress protocol component represents a private address
39    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    // -- strip_p2p_protocol ---------------------------------------------------
60
61    #[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    // -- is_dns ---------------------------------------------------------------
99
100    #[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    // -- is_supported ---------------------------------------------------------
120
121    #[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}