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}