1use crate::error::{ThreeWordError, Result};
7use serde::{Deserialize, Serialize};
8use std::net::{Ipv4Addr, Ipv6Addr};
9use std::str::FromStr;
10
11#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
13pub enum IpType {
14 IPv4,
15 IPv6,
16 DNS4,
17 DNS6,
18 DNS,
19 Unix,
20 P2P,
21 Onion,
22 Onion3,
23 Garlic64,
24 Garlic32,
25 Memory,
26 CIDv1,
27 SCTP,
28 UTP,
29 Unknown(String),
31}
32
33#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
35pub enum Protocol {
36 TCP,
37 UDP,
38 DCCP,
39 SCTP,
40 UTP,
41 QUIC,
42 QuicV1,
43 WS,
44 WSS,
45 WebSocket,
46 TLS,
47 Noise,
48 Yamux,
49 MPLEX,
50 HTTP,
51 HTTPS,
52 HTTPPath,
53 P2PCircuit,
54 P2PWebSocket,
55 P2PWebSocketStar,
56 P2PStardust,
57 WebRTC,
58 WebRTCDirect,
59 WebTransport,
60 Certhash,
61 Plaintextv2,
62 Unknown(String, bool), }
65
66#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
68pub struct ParsedMultiaddr {
69 pub ip_type: IpType,
70 pub address: String,
71 pub protocol: Protocol,
72 pub port: u16,
73 pub additional_protocols: Vec<Protocol>,
74}
75
76impl ParsedMultiaddr {
77 pub fn parse(multiaddr: &str) -> Result<Self> {
79 if !multiaddr.starts_with('/') {
80 return Err(ThreeWordError::InvalidMultiaddr(
81 format!("Multiaddr must start with '/', got: {}", multiaddr)
82 ));
83 }
84
85 let parts: Vec<&str> = multiaddr.split('/').filter(|s| !s.is_empty()).collect();
86
87 if parts.len() < 3 {
88 return Err(ThreeWordError::InvalidMultiaddr(
89 format!("Multiaddr must have at least 3 parts, got: {}", parts.len())
90 ));
91 }
92
93 let (ip_type, address) = Self::parse_ip_component(&parts[0], &parts[1])?;
95
96 let (protocol, port) = if parts.len() > 3 {
98 Self::parse_protocol_component(&parts[2], &parts[3])?
99 } else {
100 Self::parse_protocol_component(&parts[2], "0")?
102 };
103
104 let additional_protocols = if parts.len() > 4 {
106 Self::parse_additional_protocols(&parts[4..])?
107 } else if parts.len() == 4 && matches!(protocol, Protocol::UDP | Protocol::TCP) {
108 if parts[3].parse::<u16>().is_err() {
110 Self::parse_additional_protocols(&parts[3..])?
111 } else {
112 vec![]
113 }
114 } else {
115 vec![]
116 };
117
118 Ok(ParsedMultiaddr {
119 ip_type,
120 address,
121 protocol,
122 port,
123 additional_protocols,
124 })
125 }
126
127 pub fn to_multiaddr(&self) -> String {
129 let mut result = String::new();
130
131 match self.ip_type {
133 IpType::IPv4 => result.push_str(&format!("/ip4/{}", self.address)),
134 IpType::IPv6 => result.push_str(&format!("/ip6/{}", self.address)),
135 IpType::DNS4 => result.push_str(&format!("/dns4/{}", self.address)),
136 IpType::DNS6 => result.push_str(&format!("/dns6/{}", self.address)),
137 IpType::DNS => result.push_str(&format!("/dns/{}", self.address)),
138 IpType::Unix => result.push_str(&format!("/unix/{}", self.address)),
139 IpType::P2P => result.push_str(&format!("/p2p/{}", self.address)),
140 IpType::Onion => result.push_str(&format!("/onion/{}", self.address)),
141 IpType::Onion3 => result.push_str(&format!("/onion3/{}", self.address)),
142 IpType::Garlic64 => result.push_str(&format!("/garlic64/{}", self.address)),
143 IpType::Garlic32 => result.push_str(&format!("/garlic32/{}", self.address)),
144 IpType::Memory => result.push_str(&format!("/memory/{}", self.address)),
145 IpType::CIDv1 => result.push_str(&format!("/cid/{}", self.address)),
146 IpType::SCTP => result.push_str(&format!("/sctp/{}", self.address)),
147 IpType::UTP => result.push_str(&format!("/utp/{}", self.address)),
148 IpType::Unknown(ref name) => result.push_str(&format!("/{}/{}", name, self.address)),
149 }
150
151 match self.protocol {
153 Protocol::TCP => result.push_str(&format!("/tcp/{}", self.port)),
154 Protocol::UDP => result.push_str(&format!("/udp/{}", self.port)),
155 Protocol::DCCP => result.push_str(&format!("/dccp/{}", self.port)),
156 Protocol::SCTP => result.push_str(&format!("/sctp/{}", self.port)),
157 Protocol::UTP => result.push_str(&format!("/utp/{}", self.port)),
158 Protocol::QUIC => result.push_str("/quic"),
159 Protocol::QuicV1 => result.push_str("/quic-v1"),
160 Protocol::WS => result.push_str("/ws"),
161 Protocol::WSS => result.push_str("/wss"),
162 Protocol::WebSocket => result.push_str("/websocket"),
163 Protocol::TLS => result.push_str("/tls"),
164 Protocol::Noise => result.push_str("/noise"),
165 Protocol::Yamux => result.push_str("/yamux"),
166 Protocol::MPLEX => result.push_str("/mplex"),
167 Protocol::HTTP => result.push_str("/http"),
168 Protocol::HTTPS => result.push_str("/https"),
169 Protocol::HTTPPath => result.push_str("/http-path"),
170 Protocol::P2PCircuit => result.push_str("/p2p-circuit"),
171 Protocol::P2PWebSocket => result.push_str("/p2p-websocket"),
172 Protocol::P2PWebSocketStar => result.push_str("/p2p-websocket-star"),
173 Protocol::P2PStardust => result.push_str("/p2p-stardust"),
174 Protocol::WebRTC => result.push_str("/webrtc"),
175 Protocol::WebRTCDirect => result.push_str("/webrtc-direct"),
176 Protocol::WebTransport => result.push_str("/webtransport"),
177 Protocol::Certhash => result.push_str("/certhash"),
178 Protocol::Plaintextv2 => result.push_str("/plaintextv2"),
179 Protocol::Unknown(ref name, has_port) => {
180 if has_port {
181 result.push_str(&format!("/{}/{}", name, self.port));
182 } else {
183 result.push_str(&format!("/{}", name));
184 }
185 },
186 }
187
188 for protocol in &self.additional_protocols {
190 match protocol {
191 Protocol::QUIC => result.push_str("/quic"),
192 Protocol::WS => result.push_str("/ws"),
193 Protocol::WSS => result.push_str("/wss"),
194 Protocol::TLS => result.push_str("/tls"),
195 Protocol::HTTP => result.push_str("/http"),
196 Protocol::HTTPS => result.push_str("/https"),
197 Protocol::P2PCircuit => result.push_str("/p2p-circuit"),
198 Protocol::WebRTC => result.push_str("/webrtc"),
199 Protocol::WebTransport => result.push_str("/webtransport"),
200 _ => {} }
202 }
203
204 result
205 }
206
207 fn parse_ip_component(ip_type_str: &str, address_str: &str) -> Result<(IpType, String)> {
208 let ip_type = match ip_type_str {
209 "ip4" => {
210 Ipv4Addr::from_str(address_str)
212 .map_err(|e| ThreeWordError::InvalidMultiaddr(
213 format!("Invalid IPv4 address '{}': {}", address_str, e)
214 ))?;
215 IpType::IPv4
216 }
217 "ip6" => {
218 Ipv6Addr::from_str(address_str)
220 .map_err(|e| ThreeWordError::InvalidMultiaddr(
221 format!("Invalid IPv6 address '{}': {}", address_str, e)
222 ))?;
223 IpType::IPv6
224 }
225 "dns4" => IpType::DNS4,
226 "dns6" => IpType::DNS6,
227 "dns" => IpType::DNS,
228 "unix" => IpType::Unix,
229 "p2p" => IpType::P2P,
230 "onion" => IpType::Onion,
231 "onion3" => IpType::Onion3,
232 "garlic64" => IpType::Garlic64,
233 "garlic32" => IpType::Garlic32,
234 "memory" => IpType::Memory,
235 "cid" => IpType::CIDv1,
236 "sctp" => IpType::SCTP,
237 "utp" => IpType::UTP,
238 _ => {
239 IpType::Unknown(ip_type_str.to_string())
241 },
242 };
243
244 Ok((ip_type, address_str.to_string()))
245 }
246
247 fn parse_protocol_component(protocol_str: &str, port_str: &str) -> Result<(Protocol, u16)> {
248 let protocol = match protocol_str {
249 "tcp" => Protocol::TCP,
250 "udp" => Protocol::UDP,
251 "dccp" => Protocol::DCCP,
252 "sctp" => Protocol::SCTP,
253 "utp" => Protocol::UTP,
254 "quic" => return Ok((Protocol::QUIC, 0)),
255 "quic-v1" => return Ok((Protocol::QuicV1, 0)),
256 "ws" => return Ok((Protocol::WS, 0)),
257 "wss" => return Ok((Protocol::WSS, 0)),
258 "websocket" => return Ok((Protocol::WebSocket, 0)),
259 "tls" => return Ok((Protocol::TLS, 0)),
260 "noise" => return Ok((Protocol::Noise, 0)),
261 "yamux" => return Ok((Protocol::Yamux, 0)),
262 "mplex" => return Ok((Protocol::MPLEX, 0)),
263 "http" => return Ok((Protocol::HTTP, 0)),
264 "https" => return Ok((Protocol::HTTPS, 0)),
265 "http-path" => return Ok((Protocol::HTTPPath, 0)),
266 "p2p-circuit" => return Ok((Protocol::P2PCircuit, 0)),
267 "p2p-websocket" => return Ok((Protocol::P2PWebSocket, 0)),
268 "p2p-websocket-star" => return Ok((Protocol::P2PWebSocketStar, 0)),
269 "p2p-stardust" => return Ok((Protocol::P2PStardust, 0)),
270 "webrtc" => return Ok((Protocol::WebRTC, 0)),
271 "webrtc-direct" => return Ok((Protocol::WebRTCDirect, 0)),
272 "webtransport" => return Ok((Protocol::WebTransport, 0)),
273 "certhash" => return Ok((Protocol::Certhash, 0)),
274 "plaintextv2" => return Ok((Protocol::Plaintextv2, 0)),
275 _ => {
276 let has_port = !port_str.is_empty() && port_str != "0";
278 if has_port {
279 let port = port_str.parse::<u16>()
280 .map_err(|e| ThreeWordError::InvalidMultiaddr(
281 format!("Invalid port '{}' for unknown protocol '{}': {}", port_str, protocol_str, e)
282 ))?;
283 return Ok((Protocol::Unknown(protocol_str.to_string(), true), port));
284 } else {
285 return Ok((Protocol::Unknown(protocol_str.to_string(), false), 0));
286 }
287 },
288 };
289
290 let port = port_str.parse::<u16>()
291 .map_err(|e| ThreeWordError::InvalidMultiaddr(
292 format!("Invalid port '{}': {}", port_str, e)
293 ))?;
294
295 Ok((protocol, port))
296 }
297
298 fn parse_additional_protocols(parts: &[&str]) -> Result<Vec<Protocol>> {
299 let mut protocols = Vec::new();
300
301 for part in parts {
302 let protocol = match *part {
303 "quic" => Protocol::QUIC,
304 "ws" => Protocol::WS,
305 "wss" => Protocol::WSS,
306 "tls" => Protocol::TLS,
307 "http" => Protocol::HTTP,
308 "https" => Protocol::HTTPS,
309 "p2p-circuit" => Protocol::P2PCircuit,
310 "webrtc" => Protocol::WebRTC,
311 "webtransport" => Protocol::WebTransport,
312 _ => return Err(ThreeWordError::InvalidMultiaddr(
313 format!("Unknown additional protocol: {}", part)
314 )),
315 };
316 protocols.push(protocol);
317 }
318
319 Ok(protocols)
320 }
321
322 pub fn address_hash(&self) -> u64 {
324 use std::collections::hash_map::DefaultHasher;
325 use std::hash::{Hash, Hasher};
326
327 let mut hasher = DefaultHasher::new();
328 self.address.hash(&mut hasher);
329 hasher.finish()
330 }
331
332 pub fn primary_protocol(&self) -> Protocol {
334 if !self.additional_protocols.is_empty() {
335 self.additional_protocols[0].clone()
336 } else {
337 self.protocol.clone()
338 }
339 }
340}
341
342impl IpType {
343 pub fn to_string(&self) -> String {
345 match self {
346 IpType::IPv4 => "ipv4".to_string(),
347 IpType::IPv6 => "ipv6".to_string(),
348 IpType::DNS4 => "dns4".to_string(),
349 IpType::DNS6 => "dns6".to_string(),
350 IpType::DNS => "dns".to_string(),
351 IpType::Unix => "unix".to_string(),
352 IpType::P2P => "p2p".to_string(),
353 IpType::Onion => "onion".to_string(),
354 IpType::Onion3 => "onion3".to_string(),
355 IpType::Garlic64 => "garlic64".to_string(),
356 IpType::Garlic32 => "garlic32".to_string(),
357 IpType::Memory => "memory".to_string(),
358 IpType::CIDv1 => "cid".to_string(),
359 IpType::SCTP => "sctp".to_string(),
360 IpType::UTP => "utp".to_string(),
361 IpType::Unknown(name) => name.clone(),
362 }
363 }
364
365 pub fn word_index(&self) -> usize {
367 match self {
368 IpType::IPv4 => 0,
369 IpType::IPv6 => 1,
370 IpType::DNS4 => 2,
371 IpType::DNS6 => 3,
372 IpType::DNS => 4,
373 IpType::Unix => 5,
374 IpType::P2P => 6,
375 IpType::Onion => 7,
376 IpType::Onion3 => 8,
377 IpType::Garlic64 => 9,
378 IpType::Garlic32 => 10,
379 IpType::Memory => 11,
380 IpType::CIDv1 => 12,
381 IpType::SCTP => 13,
382 IpType::UTP => 14,
383 IpType::Unknown(ref name) => {
384 15 + (name.len() % 100)
386 },
387 }
388 }
389
390 pub fn from_word_index(index: usize) -> Option<Self> {
392 match index {
393 0 => Some(IpType::IPv4),
394 1 => Some(IpType::IPv6),
395 2 => Some(IpType::DNS4),
396 3 => Some(IpType::DNS6),
397 4 => Some(IpType::DNS),
398 5 => Some(IpType::Unix),
399 6 => Some(IpType::P2P),
400 7 => Some(IpType::Onion),
401 8 => Some(IpType::Onion3),
402 9 => Some(IpType::Garlic64),
403 10 => Some(IpType::Garlic32),
404 11 => Some(IpType::Memory),
405 12 => Some(IpType::CIDv1),
406 13 => Some(IpType::SCTP),
407 14 => Some(IpType::UTP),
408 _ => None,
409 }
410 }
411}
412
413impl Protocol {
414 pub fn word_index(&self) -> usize {
416 match self {
417 Protocol::TCP => 0,
418 Protocol::UDP => 1,
419 Protocol::DCCP => 2,
420 Protocol::SCTP => 3,
421 Protocol::UTP => 4,
422 Protocol::QUIC => 5,
423 Protocol::QuicV1 => 6,
424 Protocol::WS => 7,
425 Protocol::WSS => 8,
426 Protocol::WebSocket => 9,
427 Protocol::TLS => 10,
428 Protocol::Noise => 11,
429 Protocol::Yamux => 12,
430 Protocol::MPLEX => 13,
431 Protocol::HTTP => 14,
432 Protocol::HTTPS => 15,
433 Protocol::HTTPPath => 16,
434 Protocol::P2PCircuit => 17,
435 Protocol::P2PWebSocket => 18,
436 Protocol::P2PWebSocketStar => 19,
437 Protocol::P2PStardust => 20,
438 Protocol::WebRTC => 21,
439 Protocol::WebRTCDirect => 22,
440 Protocol::WebTransport => 23,
441 Protocol::Certhash => 24,
442 Protocol::Plaintextv2 => 25,
443 Protocol::Unknown(ref name, _) => {
444 26 + (name.len() % 100)
446 },
447 }
448 }
449
450 pub fn from_word_index(index: usize) -> Option<Self> {
452 match index {
453 0 => Some(Protocol::TCP),
454 1 => Some(Protocol::UDP),
455 2 => Some(Protocol::DCCP),
456 3 => Some(Protocol::SCTP),
457 4 => Some(Protocol::UTP),
458 5 => Some(Protocol::QUIC),
459 6 => Some(Protocol::QuicV1),
460 7 => Some(Protocol::WS),
461 8 => Some(Protocol::WSS),
462 9 => Some(Protocol::WebSocket),
463 10 => Some(Protocol::TLS),
464 11 => Some(Protocol::Noise),
465 12 => Some(Protocol::Yamux),
466 13 => Some(Protocol::MPLEX),
467 14 => Some(Protocol::HTTP),
468 15 => Some(Protocol::HTTPS),
469 16 => Some(Protocol::HTTPPath),
470 17 => Some(Protocol::P2PCircuit),
471 18 => Some(Protocol::P2PWebSocket),
472 19 => Some(Protocol::P2PWebSocketStar),
473 20 => Some(Protocol::P2PStardust),
474 21 => Some(Protocol::WebRTC),
475 22 => Some(Protocol::WebRTCDirect),
476 23 => Some(Protocol::WebTransport),
477 24 => Some(Protocol::Certhash),
478 25 => Some(Protocol::Plaintextv2),
479 _ => None,
480 }
481 }
482}
483
484#[cfg(test)]
485mod tests {
486 use super::*;
487
488 #[test]
489 fn test_parse_ipv4_tcp() {
490 let multiaddr = "/ip4/192.168.1.1/tcp/8080";
491 let parsed = ParsedMultiaddr::parse(multiaddr).unwrap();
492
493 assert_eq!(parsed.ip_type, IpType::IPv4);
494 assert_eq!(parsed.address, "192.168.1.1");
495 assert_eq!(parsed.protocol, Protocol::TCP);
496 assert_eq!(parsed.port, 8080);
497 assert!(parsed.additional_protocols.is_empty());
498
499 assert_eq!(parsed.to_multiaddr(), multiaddr);
501 }
502
503 #[test]
504 fn test_parse_ipv6_udp_quic() {
505 let multiaddr = "/ip6/2001:db8::1/udp/9000/quic";
506 let parsed = ParsedMultiaddr::parse(multiaddr).unwrap();
507
508 assert_eq!(parsed.ip_type, IpType::IPv6);
509 assert_eq!(parsed.address, "2001:db8::1");
510 assert_eq!(parsed.protocol, Protocol::UDP);
511 assert_eq!(parsed.port, 9000);
512 assert_eq!(parsed.additional_protocols, vec![Protocol::QUIC]);
513
514 assert_eq!(parsed.to_multiaddr(), multiaddr);
516 }
517
518 #[test]
519 fn test_parse_dns4_tcp() {
520 let multiaddr = "/dns4/example.com/tcp/80";
521 let parsed = ParsedMultiaddr::parse(multiaddr).unwrap();
522
523 assert_eq!(parsed.ip_type, IpType::DNS4);
524 assert_eq!(parsed.address, "example.com");
525 assert_eq!(parsed.protocol, Protocol::TCP);
526 assert_eq!(parsed.port, 80);
527
528 assert_eq!(parsed.to_multiaddr(), multiaddr);
530 }
531
532 #[test]
533 fn test_parse_invalid_multiaddr() {
534 assert!(ParsedMultiaddr::parse("ip4/127.0.0.1/tcp/8080").is_err());
536
537 assert!(ParsedMultiaddr::parse("/ip4").is_err());
539
540 assert!(ParsedMultiaddr::parse("/ip4/invalid/tcp/8080").is_err());
542
543 assert!(ParsedMultiaddr::parse("/ip4/127.0.0.1/tcp/invalid").is_err());
545 }
546
547 #[test]
548 fn test_ip_type_word_indices() {
549 assert_eq!(IpType::IPv4.word_index(), 0);
550 assert_eq!(IpType::IPv6.word_index(), 1);
551 assert_eq!(IpType::DNS4.word_index(), 2);
552
553 assert_eq!(IpType::from_word_index(0), Some(IpType::IPv4));
554 assert_eq!(IpType::from_word_index(1), Some(IpType::IPv6));
555 assert_eq!(IpType::from_word_index(999), None);
556 }
557
558 #[test]
559 fn test_protocol_word_indices() {
560 assert_eq!(Protocol::TCP.word_index(), 0);
561 assert_eq!(Protocol::UDP.word_index(), 1);
562 assert_eq!(Protocol::QUIC.word_index(), 5);
563
564 assert_eq!(Protocol::from_word_index(0), Some(Protocol::TCP));
565 assert_eq!(Protocol::from_word_index(1), Some(Protocol::UDP));
566 assert_eq!(Protocol::from_word_index(999), None);
567 }
568
569 #[test]
570 fn test_unknown_protocol_parsing() {
571 let multiaddr = "/ip4/192.168.1.1/future-protocol/1234";
572 let parsed = ParsedMultiaddr::parse(multiaddr).unwrap();
573
574 assert_eq!(parsed.ip_type, IpType::IPv4);
575 assert_eq!(parsed.address, "192.168.1.1");
576 assert_eq!(parsed.protocol, Protocol::Unknown("future-protocol".to_string(), true));
577 assert_eq!(parsed.port, 1234);
578
579 let reconstructed = parsed.to_multiaddr();
581 assert_eq!(reconstructed, multiaddr);
582 }
583
584 #[test]
585 fn test_unknown_ip_type_parsing() {
586 let multiaddr = "/future-ip/test-address/tcp/8080";
587 let parsed = ParsedMultiaddr::parse(multiaddr).unwrap();
588
589 assert_eq!(parsed.ip_type, IpType::Unknown("future-ip".to_string()));
590 assert_eq!(parsed.address, "test-address");
591 assert_eq!(parsed.protocol, Protocol::TCP);
592 assert_eq!(parsed.port, 8080);
593
594 let reconstructed = parsed.to_multiaddr();
596 assert_eq!(reconstructed, multiaddr);
597 }
598
599 #[test]
600 fn test_extended_protocol_support() {
601 let test_multiaddrs = vec![
602 "/onion/example.onion:80/tcp/8080",
603 "/onion3/example.onion/tls",
604 "/garlic64/garlic-address/noise",
605 "/memory/mem-addr/yamux",
606 "/cid/QmHash/webrtc-direct",
607 ];
608
609 for multiaddr in test_multiaddrs {
610 let parsed = ParsedMultiaddr::parse(multiaddr).unwrap();
611 let _reconstructed = parsed.to_multiaddr();
612
613 assert!(parsed.ip_type != IpType::IPv4); println!("✅ Parsed: {} → {:?}", multiaddr, parsed.ip_type);
617 }
618 }
619}