Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

src/lib: Add Multiaddr::protocol_stack #60

Merged
merged 10 commits into from
Oct 24, 2022
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
target
Cargo.lock
*.rs.bk
*.rs.bk
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# 0.16.0 [unreleased]

- Create `protocol_stack` for Multiaddr. See [PR 60]

# 0.15.0 [2022-10-20]

- Add `WebRTC` instance for `Multiaddr`. See [PR 59].
Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ keywords = ["multiaddr", "ipfs"]
license = "MIT"
name = "multiaddr"
readme = "README.md"
version = "0.15.0"
version = "0.16.0"

[features]
default = ["url"]
Expand Down
19 changes: 19 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,13 @@ impl Multiaddr {
}
self.bytes[(n - m)..] == other.bytes[..]
}

/// Returns &str identifiers for the protocol names themselves.
/// This omits specific info like addresses, ports, peer IDs, and the like.
/// Example: `"/ip4/127.0.0.1/tcp/5001"` would return `["ip4", "tcp"]`
pub fn protocol_stack(&self) -> ProtoStackIter {
ProtoStackIter { parts: self.iter() }
}
}

impl fmt::Debug for Multiaddr {
Expand Down Expand Up @@ -293,6 +300,18 @@ impl<'a> Iterator for Iter<'a> {
}
}

/// Iterator over the string idtenfiers of the protocols (not addrs) in a multiaddr
pub struct ProtoStackIter<'a> {
parts: Iter<'a>,
John-LittleBearLabs marked this conversation as resolved.
Show resolved Hide resolved
}

impl<'a> Iterator for ProtoStackIter<'a> {
type Item = &'static str;
fn next(&mut self) -> Option<Self::Item> {
self.parts.next().as_ref().map(Protocol::tag)
}
}

impl<'a> From<Protocol<'a>> for Multiaddr {
fn from(p: Protocol<'a>) -> Multiaddr {
let mut w = Vec::new();
Expand Down
100 changes: 61 additions & 39 deletions src/protocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -510,66 +510,88 @@ impl<'a> Protocol<'a> {
Wss(cow) => Wss(Cow::Owned(cow.into_owned())),
}
}

pub fn tag(&self) -> &'static str {
John-LittleBearLabs marked this conversation as resolved.
Show resolved Hide resolved
John-LittleBearLabs marked this conversation as resolved.
Show resolved Hide resolved
use self::Protocol::*;
match self {
Dccp(_) => "dccp",
Dns(_) => "dns",
Dns4(_) => "dns4",
Dns6(_) => "dns6",
Dnsaddr(_) => "dnsaddr",
Http => "http",
Https => "https",
Ip4(_) => "ip4",
Ip6(_) => "ip6",
P2pWebRtcDirect => "p2p-webrtc-direct",
P2pWebRtcStar => "p2p-webrtc-star",
WebRTC => "webrtc",
Certhash(_) => "certhash",
P2pWebSocketStar => "p2p-websocket-star",
Memory(_) => "memory",
Onion(_, _) => "onion",
Onion3(_) => "onion3",
P2p(_) => "p2p",
P2pCircuit => "p2p-circuit",
Quic => "quic",
Sctp(_) => "sctp",
Tcp(_) => "tcp",
Tls => "tls",
Noise => "noise",
Udp(_) => "udp",
Udt => "udt",
Unix(_) => "unix",
Utp => "utp",
Ws(ref s) if s == "/" => "ws",
Ws(_) => "x-parity-ws",
Wss(ref s) if s == "/" => "wss",
Wss(_) => "x-parity-wss",
}
}
}

impl<'a> fmt::Display for Protocol<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use self::Protocol::*;
write!(f, "/{}", self.tag())?;
match self {
Dccp(port) => write!(f, "/dccp/{}", port),
Dns(s) => write!(f, "/dns/{}", s),
Dns4(s) => write!(f, "/dns4/{}", s),
Dns6(s) => write!(f, "/dns6/{}", s),
Dnsaddr(s) => write!(f, "/dnsaddr/{}", s),
Http => f.write_str("/http"),
Https => f.write_str("/https"),
Ip4(addr) => write!(f, "/ip4/{}", addr),
Ip6(addr) => write!(f, "/ip6/{}", addr),
P2pWebRtcDirect => f.write_str("/p2p-webrtc-direct"),
P2pWebRtcStar => f.write_str("/p2p-webrtc-star"),
WebRTC => f.write_str("/webrtc"),
Dccp(port) => write!(f, "/{}", port),
Dns(s) => write!(f, "/{}", s),
Dns4(s) => write!(f, "/{}", s),
Dns6(s) => write!(f, "/{}", s),
Dnsaddr(s) => write!(f, "/{}", s),
Ip4(addr) => write!(f, "/{}", addr),
Ip6(addr) => write!(f, "/{}", addr),
Certhash(hash) => write!(
f,
"/certhash/{}",
"/{}",
multibase::encode(multibase::Base::Base64Url, hash.to_bytes())
),
P2pWebSocketStar => f.write_str("/p2p-websocket-star"),
Memory(port) => write!(f, "/memory/{}", port),
Memory(port) => write!(f, "/{}", port),
Onion(addr, port) => {
let s = BASE32.encode(addr.as_ref());
write!(f, "/onion/{}:{}", s.to_lowercase(), port)
write!(f, "/{}:{}", s.to_lowercase(), port)
}
Onion3(addr) => {
let s = BASE32.encode(addr.hash());
write!(f, "/onion3/{}:{}", s.to_lowercase(), addr.port())
}
P2p(c) => write!(
f,
"/p2p/{}",
multibase::Base::Base58Btc.encode(c.to_bytes())
),
P2pCircuit => f.write_str("/p2p-circuit"),
Quic => f.write_str("/quic"),
Sctp(port) => write!(f, "/sctp/{}", port),
Tcp(port) => write!(f, "/tcp/{}", port),
Tls => write!(f, "/tls"),
Noise => write!(f, "/noise"),
Udp(port) => write!(f, "/udp/{}", port),
Udt => f.write_str("/udt"),
Unix(s) => write!(f, "/unix/{}", s),
Utp => f.write_str("/utp"),
Ws(ref s) if s == "/" => f.write_str("/ws"),
Ws(s) => {
write!(f, "/{}:{}", s.to_lowercase(), addr.port())
}
P2p(c) => write!(f, "/{}", multibase::Base::Base58Btc.encode(c.to_bytes())),
Sctp(port) => write!(f, "/{}", port),
Tcp(port) => write!(f, "/{}", port),
Udp(port) => write!(f, "/{}", port),
Unix(s) => write!(f, "/{}", s),
Ws(s) if s != "/" => {
let encoded =
percent_encoding::percent_encode(s.as_bytes(), PATH_SEGMENT_ENCODE_SET);
write!(f, "/x-parity-ws/{}", encoded)
write!(f, "/{}", encoded)
}
Wss(ref s) if s == "/" => f.write_str("/wss"),
Wss(s) => {
Wss(s) if s != "/" => {
let encoded =
percent_encoding::percent_encode(s.as_bytes(), PATH_SEGMENT_ENCODE_SET);
write!(f, "/x-parity-wss/{}", encoded)
write!(f, "/{}", encoded)
}
_ => Ok(()),
}
}
}
Expand Down
78 changes: 78 additions & 0 deletions tests/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
extern crate core;

use data_encoding::HEXUPPER;
use multiaddr::*;
use multihash::Multihash;
Expand Down Expand Up @@ -527,3 +529,79 @@ fn unknown_protocol_string() {
},
}
}

#[test]
fn protocol_stack() {
let addresses = [
"/ip4/0.0.0.0",
"/ip6/::1",
"/ip6/2601:9:4f81:9700:803e:ca65:66e8:c21",
"/udp/0",
"/tcp/0",
"/sctp/0",
"/udp/1234",
"/tcp/1234",
"/sctp/1234",
"/udp/65535",
"/tcp/65535",
"/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC",
"/udp/1234/sctp/1234",
"/udp/1234/udt",
"/udp/1234/utp",
"/tcp/1234/http",
"/tcp/1234/tls/http",
"/tcp/1234/https",
"/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC/tcp/1234",
"/ip4/127.0.0.1/udp/1234",
"/ip4/127.0.0.1/udp/0",
"/ip4/127.0.0.1/tcp/1234",
"/ip4/127.0.0.1/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC",
"/ip4/127.0.0.1/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC/tcp/1234",
"/ip6/2001:8a0:7ac5:4201:3ac9:86ff:fe31:7095/tcp/8000/ws/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC",
"/p2p-webrtc-star/ip4/127.0.0.1/tcp/9090/ws/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC",
"/ip6/2001:8a0:7ac5:4201:3ac9:86ff:fe31:7095/tcp/8000/wss/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC",
"/ip4/127.0.0.1/tcp/9090/p2p-circuit/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC",
"/onion/aaimaq4ygg2iegci:80",
"/dnsaddr/sjc-1.bootstrap.libp2p.io",
"/dnsaddr/sjc-1.bootstrap.libp2p.io/tcp/1234/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN",
"/ip4/127.0.0.1/tcp/127/ws",
"/ip4/127.0.0.1/tcp/127/tls",
"/ip4/127.0.0.1/tcp/127/tls/ws",
"/ip4/127.0.0.1/tcp/127/noise",
"/ip4/127.0.0.1/udp/1234/webrtc",
];
let argless = std::collections::HashSet::from([
"http",
"https",
"noise",
"p2p-circuit",
"p2p-webrtc-direct",
"p2p-webrtc-star",
"p2p-websocket-star",
"quic",
"tls",
"udt",
"utp",
"webrtc",
"ws",
"wss",
]);
for addr_str in addresses {
let ma = Multiaddr::from_str(addr_str).expect("These are supposed to be valid multiaddrs");
let ps: Vec<&str> = ma.protocol_stack().collect();
let mut toks: Vec<&str> = addr_str.split('/').collect();
assert_eq!("", toks[0]);
toks.remove(0);
let mut i = 0;
while i < toks.len() {
let proto_tag = toks[i];
i += 1;
if argless.contains(proto_tag) {
//skip
} else {
toks.remove(i);
}
}
assert_eq!(ps, toks);
}
}