tentacle/
utils.rs

1use url::Url;
2
3use crate::{
4    multiaddr::{Multiaddr, Protocol},
5    secio::PeerId,
6};
7use std::{
8    borrow::Cow,
9    iter::{self},
10    net::{IpAddr, SocketAddr},
11};
12
13/// This module create a `DnsResolver` future task to DNS resolver
14#[cfg(not(target_family = "wasm"))]
15pub mod dns;
16
17/// Check if the ip address is reachable.
18/// Copy from std::net::IpAddr::is_global
19pub fn is_reachable(ip: IpAddr) -> bool {
20    match ip {
21        IpAddr::V4(ipv4) => {
22            !ipv4.is_private()
23                && !ipv4.is_loopback()
24                && !ipv4.is_link_local()
25                && !ipv4.is_broadcast()
26                && !ipv4.is_documentation()
27                && !ipv4.is_unspecified()
28        }
29        IpAddr::V6(ipv6) => {
30            let scope = if ipv6.is_multicast() {
31                match ipv6.segments()[0] & 0x000f {
32                    1 => Some(false),
33                    2 => Some(false),
34                    3 => Some(false),
35                    4 => Some(false),
36                    5 => Some(false),
37                    8 => Some(false),
38                    14 => Some(true),
39                    _ => None,
40                }
41            } else {
42                None
43            };
44            match scope {
45                Some(true) => true,
46                None => {
47                    !(ipv6.is_multicast()
48                      || ipv6.is_loopback()
49                      // && !ipv6.is_unicast_link_local()
50                      || ((ipv6.segments()[0] & 0xffc0) == 0xfe80)
51                      // && !ipv6.is_unicast_site_local()
52                      || ((ipv6.segments()[0] & 0xffc0) == 0xfec0)
53                      // && !ipv6.is_unique_local()
54                      || ((ipv6.segments()[0] & 0xfe00) == 0xfc00)
55                      || ipv6.is_unspecified()
56                      // && !ipv6.is_documentation()
57                      || ((ipv6.segments()[0] == 0x2001) && (ipv6.segments()[1] == 0xdb8)))
58                }
59                _ => false,
60            }
61        }
62    }
63}
64
65/// Change multiaddr to socketaddr
66pub fn multiaddr_to_socketaddr(addr: &Multiaddr) -> Option<SocketAddr> {
67    let mut iter = addr.iter().peekable();
68
69    while iter.peek().is_some() {
70        match iter.peek() {
71            Some(Protocol::Ip4(_)) | Some(Protocol::Ip6(_)) => (),
72            _ => {
73                // ignore is true
74                let _ignore = iter.next();
75                continue;
76            }
77        }
78
79        let proto1 = iter.next()?;
80        let proto2 = iter.next()?;
81
82        match (proto1, proto2) {
83            (Protocol::Ip4(ip), Protocol::Tcp(port)) => {
84                return Some(SocketAddr::new(ip.into(), port));
85            }
86            (Protocol::Ip6(ip), Protocol::Tcp(port)) => {
87                return Some(SocketAddr::new(ip.into(), port));
88            }
89            _ => (),
90        }
91    }
92
93    None
94}
95
96/// convert socket address to multiaddr
97pub fn socketaddr_to_multiaddr(address: SocketAddr) -> Multiaddr {
98    let proto = match address.ip() {
99        IpAddr::V4(ip) => Protocol::Ip4(ip),
100        IpAddr::V6(ip) => Protocol::Ip6(ip),
101    };
102    iter::once(proto)
103        .chain(iter::once(Protocol::Tcp(address.port())))
104        .collect()
105}
106
107/// Get peer id from multiaddr
108pub fn extract_peer_id(addr: &Multiaddr) -> Option<PeerId> {
109    let mut iter = addr.iter();
110
111    iter.find_map(|proto| {
112        if let Protocol::P2P(raw_bytes) = proto {
113            PeerId::from_bytes(raw_bytes.to_vec()).ok()
114        } else {
115            None
116        }
117    })
118}
119
120/// Transport type on tentacle
121#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
122pub enum TransportType {
123    /// Websocket
124    Ws,
125    /// Websocket with tls
126    Wss,
127    /// Tcp
128    Tcp,
129    /// Tcp with tls
130    Tls,
131    /// Memory
132    Memory,
133    /// Onion
134    Onion,
135}
136
137/// Confirm the transport used by multiaddress
138pub fn find_type(addr: &Multiaddr) -> TransportType {
139    let mut iter = addr.iter();
140
141    iter.find_map(|proto| {
142        if let Protocol::Ws = proto {
143            Some(TransportType::Ws)
144        } else if let Protocol::Wss = proto {
145            Some(TransportType::Wss)
146        } else if let Protocol::Tls(_) = proto {
147            Some(TransportType::Tls)
148        } else if let Protocol::Memory(_) = proto {
149            Some(TransportType::Memory)
150        } else if let Protocol::Onion3(_) = proto {
151            Some(TransportType::Onion)
152        } else {
153            None
154        }
155    })
156    .unwrap_or(TransportType::Tcp)
157}
158
159/// Function to redact the username and password from a URL:
160/// This function takes a URL of the form "https://user:password@example.com/path?key=value#hash"
161/// and returns a modified version where the password is replaced with "****", resulting in:
162/// "https://user:****@example.com/path?key=value#hash".
163/// If the URL does not contain a username or password, it is returned unchanged.
164/// Returns a `Cow<'_, Url>` to manage ownership efficiently, using `Borrowed` when possible.
165///
166/// # Parameters
167/// - `url`: A reference to a `Url` object representing the original input URL.
168///
169/// # Returns
170/// - A `Cow<'_, Url>` object representing the URL with redacted credentials.
171pub fn redact_auth_from_url(url: &Url) -> Cow<'_, Url> {
172    let mut owned_url = url.clone();
173    let mut modified = false;
174
175    if url.username() != "" && owned_url.set_username("****").is_ok() {
176        modified = true;
177    }
178
179    if url.password().is_some() && owned_url.set_password(Some("****")).is_ok() {
180        modified = true;
181    }
182
183    if modified {
184        Cow::Owned(owned_url)
185    } else {
186        Cow::Borrowed(url)
187    }
188}
189
190#[cfg(test)]
191mod test {
192    use crate::{
193        multiaddr::Multiaddr,
194        secio::SecioKeyPair,
195        utils::{extract_peer_id, multiaddr_to_socketaddr},
196    };
197
198    #[test]
199    fn parser_peer_id_from_multiaddr() {
200        let peer_id = SecioKeyPair::secp256k1_generated().peer_id();
201        let addr_1: Multiaddr = format!("/ip4/127.0.0.1/tcp/1337/p2p/{}", peer_id.to_base58())
202            .parse()
203            .unwrap();
204        let addr_2: Multiaddr = format!("/p2p/{}", peer_id.to_base58()).parse().unwrap();
205
206        let second = extract_peer_id(&addr_1).unwrap();
207        let third = extract_peer_id(&addr_2).unwrap();
208        assert_eq!(peer_id, second);
209        assert_eq!(peer_id, third);
210    }
211
212    #[test]
213    fn parser_socket_addr_from_multiaddr() {
214        let peer_id = SecioKeyPair::secp256k1_generated().peer_id();
215        let addr_1: Multiaddr = format!("/ip4/127.0.0.1/tcp/1337/p2p/{}", peer_id.to_base58())
216            .parse()
217            .unwrap();
218        let addr_2: Multiaddr = format!("/p2p/{}/ip4/127.0.0.1/tcp/1337", peer_id.to_base58())
219            .parse()
220            .unwrap();
221        let addr_3: Multiaddr = "/ip4/127.0.0.1/tcp/1337".parse().unwrap();
222
223        let second = multiaddr_to_socketaddr(&addr_1).unwrap();
224        let third = multiaddr_to_socketaddr(&addr_2).unwrap();
225        let fourth = multiaddr_to_socketaddr(&addr_3).unwrap();
226        assert_eq!(second, "127.0.0.1:1337".parse().unwrap());
227        assert_eq!(third, "127.0.0.1:1337".parse().unwrap());
228        assert_eq!(fourth, "127.0.0.1:1337".parse().unwrap());
229    }
230
231    #[test]
232    #[should_panic]
233    fn parser_socket_addr_fail() {
234        let peer_id = SecioKeyPair::secp256k1_generated().peer_id();
235        let addr: Multiaddr = format!("/ip4/127.0.0.1/p2p/{}/tcp/1337", peer_id.to_base58())
236            .parse()
237            .unwrap();
238        multiaddr_to_socketaddr(&addr).unwrap();
239    }
240}