Skip to content

Commit 1cfb923

Browse files
src/lib: Add Multiaddr::protocol_stack (#60)
A convenience for cases where _which_ protocols is of concern but the specific addresses are not, for example libp2p/rust-libp2p#2758
1 parent 82a171e commit 1cfb923

File tree

6 files changed

+164
-41
lines changed

6 files changed

+164
-41
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
target
22
Cargo.lock
3-
*.rs.bk
3+
*.rs.bk

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
# 0.16.0 [unreleased]
2+
3+
- Create `protocol_stack` for Multiaddr. See [PR 60]
4+
15
# 0.15.0 [2022-10-20]
26

37
- Add `WebRTC` instance for `Multiaddr`. See [PR 59].

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ keywords = ["multiaddr", "ipfs"]
77
license = "MIT"
88
name = "multiaddr"
99
readme = "README.md"
10-
version = "0.15.0"
10+
version = "0.16.0"
1111

1212
[features]
1313
default = ["url"]

src/lib.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,13 @@ impl Multiaddr {
191191
}
192192
self.bytes[(n - m)..] == other.bytes[..]
193193
}
194+
195+
/// Returns &str identifiers for the protocol names themselves.
196+
/// This omits specific info like addresses, ports, peer IDs, and the like.
197+
/// Example: `"/ip4/127.0.0.1/tcp/5001"` would return `["ip4", "tcp"]`
198+
pub fn protocol_stack(&self) -> ProtoStackIter {
199+
ProtoStackIter { parts: self.iter() }
200+
}
194201
}
195202

196203
impl fmt::Debug for Multiaddr {
@@ -293,6 +300,18 @@ impl<'a> Iterator for Iter<'a> {
293300
}
294301
}
295302

303+
/// Iterator over the string idtenfiers of the protocols (not addrs) in a multiaddr
304+
pub struct ProtoStackIter<'a> {
305+
parts: Iter<'a>,
306+
}
307+
308+
impl<'a> Iterator for ProtoStackIter<'a> {
309+
type Item = &'static str;
310+
fn next(&mut self) -> Option<Self::Item> {
311+
self.parts.next().as_ref().map(Protocol::tag)
312+
}
313+
}
314+
296315
impl<'a> From<Protocol<'a>> for Multiaddr {
297316
fn from(p: Protocol<'a>) -> Multiaddr {
298317
let mut w = Vec::new();

src/protocol.rs

Lines changed: 61 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -510,66 +510,88 @@ impl<'a> Protocol<'a> {
510510
Wss(cow) => Wss(Cow::Owned(cow.into_owned())),
511511
}
512512
}
513+
514+
pub fn tag(&self) -> &'static str {
515+
use self::Protocol::*;
516+
match self {
517+
Dccp(_) => "dccp",
518+
Dns(_) => "dns",
519+
Dns4(_) => "dns4",
520+
Dns6(_) => "dns6",
521+
Dnsaddr(_) => "dnsaddr",
522+
Http => "http",
523+
Https => "https",
524+
Ip4(_) => "ip4",
525+
Ip6(_) => "ip6",
526+
P2pWebRtcDirect => "p2p-webrtc-direct",
527+
P2pWebRtcStar => "p2p-webrtc-star",
528+
WebRTC => "webrtc",
529+
Certhash(_) => "certhash",
530+
P2pWebSocketStar => "p2p-websocket-star",
531+
Memory(_) => "memory",
532+
Onion(_, _) => "onion",
533+
Onion3(_) => "onion3",
534+
P2p(_) => "p2p",
535+
P2pCircuit => "p2p-circuit",
536+
Quic => "quic",
537+
Sctp(_) => "sctp",
538+
Tcp(_) => "tcp",
539+
Tls => "tls",
540+
Noise => "noise",
541+
Udp(_) => "udp",
542+
Udt => "udt",
543+
Unix(_) => "unix",
544+
Utp => "utp",
545+
Ws(ref s) if s == "/" => "ws",
546+
Ws(_) => "x-parity-ws",
547+
Wss(ref s) if s == "/" => "wss",
548+
Wss(_) => "x-parity-wss",
549+
}
550+
}
513551
}
514552

515553
impl<'a> fmt::Display for Protocol<'a> {
516554
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
517555
use self::Protocol::*;
556+
write!(f, "/{}", self.tag())?;
518557
match self {
519-
Dccp(port) => write!(f, "/dccp/{}", port),
520-
Dns(s) => write!(f, "/dns/{}", s),
521-
Dns4(s) => write!(f, "/dns4/{}", s),
522-
Dns6(s) => write!(f, "/dns6/{}", s),
523-
Dnsaddr(s) => write!(f, "/dnsaddr/{}", s),
524-
Http => f.write_str("/http"),
525-
Https => f.write_str("/https"),
526-
Ip4(addr) => write!(f, "/ip4/{}", addr),
527-
Ip6(addr) => write!(f, "/ip6/{}", addr),
528-
P2pWebRtcDirect => f.write_str("/p2p-webrtc-direct"),
529-
P2pWebRtcStar => f.write_str("/p2p-webrtc-star"),
530-
WebRTC => f.write_str("/webrtc"),
558+
Dccp(port) => write!(f, "/{}", port),
559+
Dns(s) => write!(f, "/{}", s),
560+
Dns4(s) => write!(f, "/{}", s),
561+
Dns6(s) => write!(f, "/{}", s),
562+
Dnsaddr(s) => write!(f, "/{}", s),
563+
Ip4(addr) => write!(f, "/{}", addr),
564+
Ip6(addr) => write!(f, "/{}", addr),
531565
Certhash(hash) => write!(
532566
f,
533-
"/certhash/{}",
567+
"/{}",
534568
multibase::encode(multibase::Base::Base64Url, hash.to_bytes())
535569
),
536-
P2pWebSocketStar => f.write_str("/p2p-websocket-star"),
537-
Memory(port) => write!(f, "/memory/{}", port),
570+
Memory(port) => write!(f, "/{}", port),
538571
Onion(addr, port) => {
539572
let s = BASE32.encode(addr.as_ref());
540-
write!(f, "/onion/{}:{}", s.to_lowercase(), port)
573+
write!(f, "/{}:{}", s.to_lowercase(), port)
541574
}
542575
Onion3(addr) => {
543576
let s = BASE32.encode(addr.hash());
544-
write!(f, "/onion3/{}:{}", s.to_lowercase(), addr.port())
545-
}
546-
P2p(c) => write!(
547-
f,
548-
"/p2p/{}",
549-
multibase::Base::Base58Btc.encode(c.to_bytes())
550-
),
551-
P2pCircuit => f.write_str("/p2p-circuit"),
552-
Quic => f.write_str("/quic"),
553-
Sctp(port) => write!(f, "/sctp/{}", port),
554-
Tcp(port) => write!(f, "/tcp/{}", port),
555-
Tls => write!(f, "/tls"),
556-
Noise => write!(f, "/noise"),
557-
Udp(port) => write!(f, "/udp/{}", port),
558-
Udt => f.write_str("/udt"),
559-
Unix(s) => write!(f, "/unix/{}", s),
560-
Utp => f.write_str("/utp"),
561-
Ws(ref s) if s == "/" => f.write_str("/ws"),
562-
Ws(s) => {
577+
write!(f, "/{}:{}", s.to_lowercase(), addr.port())
578+
}
579+
P2p(c) => write!(f, "/{}", multibase::Base::Base58Btc.encode(c.to_bytes())),
580+
Sctp(port) => write!(f, "/{}", port),
581+
Tcp(port) => write!(f, "/{}", port),
582+
Udp(port) => write!(f, "/{}", port),
583+
Unix(s) => write!(f, "/{}", s),
584+
Ws(s) if s != "/" => {
563585
let encoded =
564586
percent_encoding::percent_encode(s.as_bytes(), PATH_SEGMENT_ENCODE_SET);
565-
write!(f, "/x-parity-ws/{}", encoded)
587+
write!(f, "/{}", encoded)
566588
}
567-
Wss(ref s) if s == "/" => f.write_str("/wss"),
568-
Wss(s) => {
589+
Wss(s) if s != "/" => {
569590
let encoded =
570591
percent_encoding::percent_encode(s.as_bytes(), PATH_SEGMENT_ENCODE_SET);
571-
write!(f, "/x-parity-wss/{}", encoded)
592+
write!(f, "/{}", encoded)
572593
}
594+
_ => Ok(()),
573595
}
574596
}
575597
}

tests/lib.rs

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
extern crate core;
2+
13
use data_encoding::HEXUPPER;
24
use multiaddr::*;
35
use multihash::Multihash;
@@ -527,3 +529,79 @@ fn unknown_protocol_string() {
527529
},
528530
}
529531
}
532+
533+
#[test]
534+
fn protocol_stack() {
535+
let addresses = [
536+
"/ip4/0.0.0.0",
537+
"/ip6/::1",
538+
"/ip6/2601:9:4f81:9700:803e:ca65:66e8:c21",
539+
"/udp/0",
540+
"/tcp/0",
541+
"/sctp/0",
542+
"/udp/1234",
543+
"/tcp/1234",
544+
"/sctp/1234",
545+
"/udp/65535",
546+
"/tcp/65535",
547+
"/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC",
548+
"/udp/1234/sctp/1234",
549+
"/udp/1234/udt",
550+
"/udp/1234/utp",
551+
"/tcp/1234/http",
552+
"/tcp/1234/tls/http",
553+
"/tcp/1234/https",
554+
"/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC/tcp/1234",
555+
"/ip4/127.0.0.1/udp/1234",
556+
"/ip4/127.0.0.1/udp/0",
557+
"/ip4/127.0.0.1/tcp/1234",
558+
"/ip4/127.0.0.1/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC",
559+
"/ip4/127.0.0.1/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC/tcp/1234",
560+
"/ip6/2001:8a0:7ac5:4201:3ac9:86ff:fe31:7095/tcp/8000/ws/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC",
561+
"/p2p-webrtc-star/ip4/127.0.0.1/tcp/9090/ws/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC",
562+
"/ip6/2001:8a0:7ac5:4201:3ac9:86ff:fe31:7095/tcp/8000/wss/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC",
563+
"/ip4/127.0.0.1/tcp/9090/p2p-circuit/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC",
564+
"/onion/aaimaq4ygg2iegci:80",
565+
"/dnsaddr/sjc-1.bootstrap.libp2p.io",
566+
"/dnsaddr/sjc-1.bootstrap.libp2p.io/tcp/1234/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN",
567+
"/ip4/127.0.0.1/tcp/127/ws",
568+
"/ip4/127.0.0.1/tcp/127/tls",
569+
"/ip4/127.0.0.1/tcp/127/tls/ws",
570+
"/ip4/127.0.0.1/tcp/127/noise",
571+
"/ip4/127.0.0.1/udp/1234/webrtc",
572+
];
573+
let argless = std::collections::HashSet::from([
574+
"http",
575+
"https",
576+
"noise",
577+
"p2p-circuit",
578+
"p2p-webrtc-direct",
579+
"p2p-webrtc-star",
580+
"p2p-websocket-star",
581+
"quic",
582+
"tls",
583+
"udt",
584+
"utp",
585+
"webrtc",
586+
"ws",
587+
"wss",
588+
]);
589+
for addr_str in addresses {
590+
let ma = Multiaddr::from_str(addr_str).expect("These are supposed to be valid multiaddrs");
591+
let ps: Vec<&str> = ma.protocol_stack().collect();
592+
let mut toks: Vec<&str> = addr_str.split('/').collect();
593+
assert_eq!("", toks[0]);
594+
toks.remove(0);
595+
let mut i = 0;
596+
while i < toks.len() {
597+
let proto_tag = toks[i];
598+
i += 1;
599+
if argless.contains(proto_tag) {
600+
//skip
601+
} else {
602+
toks.remove(i);
603+
}
604+
}
605+
assert_eq!(ps, toks);
606+
}
607+
}

0 commit comments

Comments
 (0)