taxy_api/
multiaddr.rs

1use crate::error::Error;
2use std::{
3    fmt,
4    net::{IpAddr, SocketAddr},
5    str::FromStr,
6};
7
8#[derive(Debug, Default, Clone, PartialEq, Eq)]
9pub struct Multiaddr {
10    protocols: Vec<Protocol>,
11}
12
13impl Multiaddr {
14    pub fn is_tls(&self) -> bool {
15        self.protocols.iter().any(|p| matches!(p, Protocol::Tls))
16    }
17
18    pub fn is_http(&self) -> bool {
19        self.protocols
20            .iter()
21            .any(|p| matches!(p, Protocol::Http(_)))
22    }
23
24    pub fn is_udp(&self) -> bool {
25        self.protocols.iter().any(|p| matches!(p, Protocol::Udp(_)))
26    }
27
28    pub fn socket_addr(&self) -> Result<SocketAddr, Error> {
29        self.ip_addr()
30            .and_then(|ip| self.port().map(|port| SocketAddr::new(ip, port)))
31    }
32
33    pub fn ip_addr(&self) -> Result<IpAddr, Error> {
34        self.protocols
35            .iter()
36            .find_map(|p| match p {
37                Protocol::Ip(addr) => Some(*addr),
38                _ => None,
39            })
40            .ok_or_else(|| Error::InvalidMultiaddr {
41                addr: self.to_string(),
42            })
43    }
44
45    pub fn port(&self) -> Result<u16, Error> {
46        self.protocols
47            .iter()
48            .find_map(|p| match p {
49                Protocol::Tcp(port) => Some(*port),
50                Protocol::Udp(port) => Some(*port),
51                _ => None,
52            })
53            .ok_or_else(|| Error::InvalidMultiaddr {
54                addr: self.to_string(),
55            })
56    }
57
58    pub fn host(&self) -> Result<String, Error> {
59        self.protocols
60            .iter()
61            .find_map(|p| match p {
62                Protocol::Dns(host) => Some(host.clone()),
63                Protocol::Ip(addr) => Some(addr.to_string()),
64                _ => None,
65            })
66            .ok_or_else(|| Error::InvalidMultiaddr {
67                addr: self.to_string(),
68            })
69    }
70
71    pub fn protocol_name(&self) -> &'static str {
72        match (self.is_udp(), self.is_http(), self.is_tls()) {
73            (true, _, _) => "UDP",
74            (false, true, true) => "HTTPS",
75            (false, true, false) => "HTTP",
76            (false, false, true) => "TCP over TLS",
77            (false, false, false) => "TCP",
78        }
79    }
80}
81
82#[derive(Debug, Clone, PartialEq, Eq)]
83pub enum Protocol {
84    Dns(String),
85    Ip(IpAddr),
86    Tcp(u16),
87    Udp(u16),
88    Tls,
89    Http(String),
90}
91
92impl FromStr for Multiaddr {
93    type Err = Error;
94
95    fn from_str(s: &str) -> Result<Self, Self::Err> {
96        let mut protocols = Vec::new();
97        let mut rest = s.trim_start_matches('/');
98        while !rest.is_empty() {
99            let (protocol, next) = rest.split_once('/').unwrap_or((rest, ""));
100            match protocol {
101                "dns" => {
102                    let (host, next) = next.split_once('/').unwrap_or((next, ""));
103                    protocols.push(Protocol::Dns(host.to_string()));
104                    rest = next;
105                }
106                "ip4" | "ip6" => {
107                    let (addr, next) = next.split_once('/').unwrap_or((next, ""));
108                    let addr = addr
109                        .parse::<IpAddr>()
110                        .map_err(|_| Error::InvalidMultiaddr {
111                            addr: s.to_string(),
112                        })?;
113                    protocols.push(Protocol::Ip(addr));
114                    rest = next;
115                }
116                "tcp" => {
117                    let (port, next) = next.split_once('/').unwrap_or((next, ""));
118                    let port = port.parse::<u16>().map_err(|_| Error::InvalidMultiaddr {
119                        addr: s.to_string(),
120                    })?;
121                    protocols.push(Protocol::Tcp(port));
122                    rest = next;
123                }
124                "tls" => {
125                    protocols.push(Protocol::Tls);
126                    rest = next;
127                }
128                "http" => {
129                    protocols.push(Protocol::Http(format!("/{next}")));
130                    rest = "";
131                }
132                "https" => {
133                    protocols.push(Protocol::Tls);
134                    protocols.push(Protocol::Http(format!("/{next}")));
135                    rest = "";
136                }
137                "udp" => {
138                    let (port, next) = next.split_once('/').unwrap_or((next, ""));
139                    let port = port.parse::<u16>().map_err(|_| Error::InvalidMultiaddr {
140                        addr: s.to_string(),
141                    })?;
142                    protocols.push(Protocol::Udp(port));
143                    rest = next;
144                }
145                _ => rest = next,
146            }
147        }
148        Ok(Self { protocols })
149    }
150}
151
152impl fmt::Display for Multiaddr {
153    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
154        for protocol in &self.protocols {
155            match protocol {
156                Protocol::Dns(host) => write!(f, "/dns/{}", host)?,
157                Protocol::Ip(addr) => {
158                    if addr.is_ipv4() {
159                        write!(f, "/ip4/{}", addr)?;
160                    } else {
161                        write!(f, "/ip6/{}", addr)?;
162                    }
163                }
164                Protocol::Tcp(port) => write!(f, "/tcp/{}", port)?,
165                Protocol::Udp(port) => write!(f, "/udp/{}", port)?,
166                Protocol::Tls => {
167                    if !self.is_http() {
168                        write!(f, "/tls")?
169                    }
170                }
171                Protocol::Http(path) => {
172                    let path = if path == "/" || path.is_empty() {
173                        ""
174                    } else {
175                        path.as_str()
176                    };
177                    if self.is_tls() {
178                        write!(f, "/https{}", path)?;
179                    } else {
180                        write!(f, "/http{}", path)?;
181                    }
182                }
183            }
184        }
185        Ok(())
186    }
187}
188
189impl serde::Serialize for Multiaddr {
190    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
191        serializer.serialize_str(&self.to_string())
192    }
193}
194
195impl<'de> serde::Deserialize<'de> for Multiaddr {
196    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
197        let s = String::deserialize(deserializer)?;
198        Multiaddr::from_str(&s).map_err(serde::de::Error::custom)
199    }
200}
201
202#[cfg(test)]
203mod test {
204    use super::*;
205
206    #[test]
207    fn test_multiaddr() {
208        let addr = Multiaddr::from_str("/dns/example.com/tcp/8080").unwrap();
209        assert_eq!(addr.to_string(), "/dns/example.com/tcp/8080");
210        assert!(!addr.is_http());
211        assert!(!addr.is_tls());
212
213        let addr = Multiaddr::from_str("/ip4/127.0.0.1/tcp/8080").unwrap();
214        assert_eq!(addr.to_string(), "/ip4/127.0.0.1/tcp/8080");
215        assert!(!addr.is_http());
216        assert!(!addr.is_tls());
217
218        let addr = Multiaddr::from_str("/ip4/127.0.0.1/tcp/8080/tls").unwrap();
219        assert_eq!(addr.to_string(), "/ip4/127.0.0.1/tcp/8080/tls");
220        assert!(!addr.is_http());
221        assert!(addr.is_tls());
222
223        let addr = Multiaddr::from_str("/ip4/127.0.0.1/tcp/8080/http").unwrap();
224        assert_eq!(addr.to_string(), "/ip4/127.0.0.1/tcp/8080/http");
225        assert!(addr.is_http());
226        assert!(!addr.is_tls());
227
228        let addr = Multiaddr::from_str("/ip6/::/tcp/8080/https/example.com/index.html").unwrap();
229        assert_eq!(
230            addr.to_string(),
231            "/ip6/::/tcp/8080/https/example.com/index.html"
232        );
233        assert!(addr.is_http());
234        assert!(addr.is_tls());
235
236        let addr = Multiaddr::from_str("/ip4/127.0.0.1/udp/8080").unwrap();
237        assert_eq!(addr.to_string(), "/ip4/127.0.0.1/udp/8080");
238        assert!(!addr.is_http());
239        assert!(!addr.is_tls());
240        assert!(addr.is_udp());
241    }
242}