From dfdb7f31ada1d4bfb224cb5e1db28fdc5568e1ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20RIBEAU?= Date: Thu, 12 Oct 2023 15:57:12 +0200 Subject: [PATCH 1/3] feat!: add to_p2p_terminated method on multiaddr --- CHANGELOG.md | 6 ++++++ src/lib.rs | 38 ++++++++++++++++++++++++++++++++++++++ tests/lib.rs | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 77 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 26312e3..5b31cb4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +# 0.18.1 - unreleased + +- Add `to_p2p_terminated` on `Multiaddr`. See [PR 102]. + +[PR 102]: https://github.com/multiformats/rust-multiaddr/pull/102 + # 0.18.0 - Add `WebTransport` instance for `Multiaddr`. See [PR 70]. diff --git a/src/lib.rs b/src/lib.rs index 92a31fb..7666c55 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,6 +26,8 @@ use std::{ sync::Arc, }; +use libp2p_identity::PeerId; + #[cfg(feature = "url")] pub use self::from_url::{from_url, from_url_lossy, FromUrlErr}; @@ -127,6 +129,42 @@ impl Multiaddr { self } + /// Ensures the `Multiaddr` is a `/p2p/...` address for the given peer. + /// + /// If the given address is already a `p2p` address for the given peer, + /// i.e. the last encapsulated protocol is `/p2p/`, this is a no-op. + /// + /// If the given address is already a `p2p` address for a different peer + /// than the one given, the peer-id is replaced with the one given. + /// + /// If the given address is not yet a `p2p` address,the `/p2p/` + /// protocol is appended to the returned address. + pub fn to_p2p_terminated(mut self, peer_id: PeerId) -> Self { + let mut last = None; + let mut index = 0; + loop { + let data = &self.bytes.as_ref()[index..]; + if data.is_empty() { + break; + } + + let (p, next_data) = + Protocol::from_bytes(data).expect("`Multiaddr` is known to be valid."); + + last = Some((p, index)); + index += data.len() - next_data.len(); + } + + match last { + Some((Protocol::P2p(p), _)) if p == peer_id => self, + Some((Protocol::P2p(_), index)) => { + Arc::make_mut(&mut self.bytes).truncate(index); + self.with(Protocol::P2p(peer_id)) + } + _ => self.with(Protocol::P2p(peer_id)), + } + } + /// Returns the components of this multiaddress. /// /// # Example diff --git a/tests/lib.rs b/tests/lib.rs index 071a4ce..8e81bd3 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -661,3 +661,36 @@ fn arbitrary_impl_for_all_proto_variants() { let variants = core::mem::variant_count::() as u8; assert_eq!(variants, Proto::IMPL_VARIANT_COUNT); } + +#[test] +fn to_p2p_terminated() { + let peer_id = "QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN" + .parse::() + .unwrap(); + + const TEST_DATA: &[(&str, &str)] = &[ + ( + "/ip4/127.0.0.1", + "/ip4/127.0.0.1/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN", + ), + ( + "/ip4/127.0.0.1/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN", + "/ip4/127.0.0.1/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN", + ), + ( + "/ip4/127.0.0.1/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC", + "/ip4/127.0.0.1/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN", + ), + ( + "/ip4/127.0.0.1/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC/ip4/127.0.0.1/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC/ip4/127.0.0.1/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC", + "/ip4/127.0.0.1/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC/ip4/127.0.0.1/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC/ip4/127.0.0.1/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN", + ), + ]; + + for (input, expected) in TEST_DATA { + let input = input.parse::().unwrap(); + let expected = expected.parse::().unwrap(); + + assert_eq!(expected, input.to_p2p_terminated(peer_id)); + } +} From 089ae924301537f02818b175a34da7f688be2e77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20RIBEAU?= Date: Wed, 18 Oct 2023 09:44:07 +0200 Subject: [PATCH 2/3] feat!: add with_p2p and from_str_with_peer on multiaddr --- CHANGELOG.md | 2 +- src/lib.rs | 47 ++++++++++++++++------------------------------- tests/lib.rs | 48 +++++++++++++++++++++++++++++++----------------- 3 files changed, 48 insertions(+), 49 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b31cb4..ac7157b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # 0.18.1 - unreleased -- Add `to_p2p_terminated` on `Multiaddr`. See [PR 102]. +- Add `with_p2p` and `from_str_and_peer` on `Multiaddr`. See [PR 102]. [PR 102]: https://github.com/multiformats/rust-multiaddr/pull/102 diff --git a/src/lib.rs b/src/lib.rs index 7666c55..3e4771e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -129,39 +129,24 @@ impl Multiaddr { self } - /// Ensures the `Multiaddr` is a `/p2p/...` address for the given peer. + /// Parses a [`Multiaddr`] from the given string, ensuring that it ends with [`Protocol::P2p`] of the provided peer. /// - /// If the given address is already a `p2p` address for the given peer, - /// i.e. the last encapsulated protocol is `/p2p/`, this is a no-op. - /// - /// If the given address is already a `p2p` address for a different peer - /// than the one given, the peer-id is replaced with the one given. - /// - /// If the given address is not yet a `p2p` address,the `/p2p/` - /// protocol is appended to the returned address. - pub fn to_p2p_terminated(mut self, peer_id: PeerId) -> Self { - let mut last = None; - let mut index = 0; - loop { - let data = &self.bytes.as_ref()[index..]; - if data.is_empty() { - break; - } - - let (p, next_data) = - Protocol::from_bytes(data).expect("`Multiaddr` is known to be valid."); - - last = Some((p, index)); - index += data.len() - next_data.len(); - } + /// If the address does not end with `/p2p`, it is simply appended. + /// If the address ends with a _different_ peer ID, parsing will fail. + pub fn from_str_and_peer(addr: &str, peer: PeerId) -> Result { + let addr = addr.parse::()?; + addr.with_p2p(peer).map_err(|_| Error::InvalidMultiaddr) + } - match last { - Some((Protocol::P2p(p), _)) if p == peer_id => self, - Some((Protocol::P2p(_), index)) => { - Arc::make_mut(&mut self.bytes).truncate(index); - self.with(Protocol::P2p(peer_id)) - } - _ => self.with(Protocol::P2p(peer_id)), + /// Appends the given [`PeerId`] if not yet present at the end of this multiaddress. + /// + /// Fails if this address ends in a _different_ [`PeerId`]. + /// In that case, the original, unmodified address is returned. + pub fn with_p2p(self, peer: PeerId) -> std::result::Result { + match self.iter().last() { + Some(Protocol::P2p(p)) if p == peer => Ok(self), + Some(Protocol::P2p(_)) => Err(self), + _ => Ok(self.with(Protocol::P2p(peer))), } } diff --git a/tests/lib.rs b/tests/lib.rs index 8e81bd3..59a8c4c 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -663,34 +663,48 @@ fn arbitrary_impl_for_all_proto_variants() { } #[test] -fn to_p2p_terminated() { - let peer_id = "QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN" - .parse::() - .unwrap(); - - const TEST_DATA: &[(&str, &str)] = &[ +fn multiaddr_with_p2p_and_from_str_and_peer() { + const TEST_DATA: &[(&str, &str, std::result::Result<&str, &str>)] = &[ ( - "/ip4/127.0.0.1", - "/ip4/127.0.0.1/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN", + // Multiaddr is empty -> it should push and return Ok. + "", + "QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN", + Ok("/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN"), ), ( - "/ip4/127.0.0.1/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN", - "/ip4/127.0.0.1/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN", + // Last protocol is not p2p -> it should push and return Ok. + "/ip4/127.0.0.1", + "QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN", + Ok("/ip4/127.0.0.1/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN"), ), ( - "/ip4/127.0.0.1/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC", + // Last protocol is p2p and the contained peer matches the provided one -> it should do nothing and return Ok. "/ip4/127.0.0.1/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN", + "QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN", + Ok("/ip4/127.0.0.1/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN"), ), ( - "/ip4/127.0.0.1/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC/ip4/127.0.0.1/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC/ip4/127.0.0.1/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC", - "/ip4/127.0.0.1/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC/ip4/127.0.0.1/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC/ip4/127.0.0.1/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN", + // Last protocol is p2p but the contained peer does not match the provided one -> it should do nothing and return Err. + "/ip4/127.0.0.1/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN", + "QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN", + Ok("/ip4/127.0.0.1/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN"), ), ]; - for (input, expected) in TEST_DATA { - let input = input.parse::().unwrap(); - let expected = expected.parse::().unwrap(); + for (multiaddr_str, peer, expected) in TEST_DATA { + let peer = peer.parse::().unwrap(); + let expected = expected + .map(|a| a.parse::().unwrap()) + .map_err(|a| a.parse::().unwrap()); + + let mut multiaddr = multiaddr_str.parse::().unwrap(); + for _ in 0..3 { + let result = multiaddr.with_p2p(peer); + assert_eq!(result, expected); + multiaddr = result.unwrap_or_else(|addr| addr); + } - assert_eq!(expected, input.to_p2p_terminated(peer_id)); + let result = Multiaddr::from_str_and_peer(multiaddr_str, peer); + assert_eq!(result.ok(), expected.ok()); } } From 37cc2004381d22ebc844c3c33bc346d520ae44b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20RIBEAU?= Date: Wed, 18 Oct 2023 11:40:02 +0200 Subject: [PATCH 3/3] feat!: add with_p2p on multiaddr --- CHANGELOG.md | 2 +- src/lib.rs | 9 ------ tests/lib.rs | 82 +++++++++++++++++++++++++++++++--------------------- 3 files changed, 50 insertions(+), 43 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ac7157b..d0c5f8a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # 0.18.1 - unreleased -- Add `with_p2p` and `from_str_and_peer` on `Multiaddr`. See [PR 102]. +- Add `with_p2p` on `Multiaddr`. See [PR 102]. [PR 102]: https://github.com/multiformats/rust-multiaddr/pull/102 diff --git a/src/lib.rs b/src/lib.rs index 3e4771e..999738b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -129,15 +129,6 @@ impl Multiaddr { self } - /// Parses a [`Multiaddr`] from the given string, ensuring that it ends with [`Protocol::P2p`] of the provided peer. - /// - /// If the address does not end with `/p2p`, it is simply appended. - /// If the address ends with a _different_ peer ID, parsing will fail. - pub fn from_str_and_peer(addr: &str, peer: PeerId) -> Result { - let addr = addr.parse::()?; - addr.with_p2p(peer).map_err(|_| Error::InvalidMultiaddr) - } - /// Appends the given [`PeerId`] if not yet present at the end of this multiaddress. /// /// Fails if this address ends in a _different_ [`PeerId`]. diff --git a/tests/lib.rs b/tests/lib.rs index 59a8c4c..7c2d659 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -662,49 +662,65 @@ fn arbitrary_impl_for_all_proto_variants() { assert_eq!(variants, Proto::IMPL_VARIANT_COUNT); } -#[test] -fn multiaddr_with_p2p_and_from_str_and_peer() { - const TEST_DATA: &[(&str, &str, std::result::Result<&str, &str>)] = &[ - ( - // Multiaddr is empty -> it should push and return Ok. - "", - "QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN", - Ok("/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN"), - ), - ( - // Last protocol is not p2p -> it should push and return Ok. - "/ip4/127.0.0.1", - "QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN", - Ok("/ip4/127.0.0.1/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN"), - ), - ( - // Last protocol is p2p and the contained peer matches the provided one -> it should do nothing and return Ok. - "/ip4/127.0.0.1/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN", - "QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN", - Ok("/ip4/127.0.0.1/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN"), - ), - ( - // Last protocol is p2p but the contained peer does not match the provided one -> it should do nothing and return Err. - "/ip4/127.0.0.1/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN", - "QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN", - Ok("/ip4/127.0.0.1/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN"), - ), - ]; - - for (multiaddr_str, peer, expected) in TEST_DATA { +mod multiaddr_with_p2p { + use libp2p_identity::PeerId; + use multiaddr::Multiaddr; + + fn test_multiaddr_with_p2p( + multiaddr: &str, + peer: &str, + expected: std::result::Result<&str, &str>, + ) { let peer = peer.parse::().unwrap(); let expected = expected .map(|a| a.parse::().unwrap()) .map_err(|a| a.parse::().unwrap()); - let mut multiaddr = multiaddr_str.parse::().unwrap(); + let mut multiaddr = multiaddr.parse::().unwrap(); + // Testing multiple time to validate idempotence. for _ in 0..3 { let result = multiaddr.with_p2p(peer); assert_eq!(result, expected); multiaddr = result.unwrap_or_else(|addr| addr); } + } - let result = Multiaddr::from_str_and_peer(multiaddr_str, peer); - assert_eq!(result.ok(), expected.ok()); + #[test] + fn empty_multiaddr() { + // Multiaddr is empty -> it should push and return Ok. + test_multiaddr_with_p2p( + "", + "QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN", + Ok("/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN"), + ) + } + #[test] + fn non_p2p_terminated() { + // Last protocol is not p2p -> it should push and return Ok. + test_multiaddr_with_p2p( + "/ip4/127.0.0.1", + "QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN", + Ok("/ip4/127.0.0.1/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN"), + ) + } + + #[test] + fn p2p_terminated_same_peer() { + // Last protocol is p2p and the contained peer matches the provided one -> it should do nothing and return Ok. + test_multiaddr_with_p2p( + "/ip4/127.0.0.1/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN", + "QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN", + Ok("/ip4/127.0.0.1/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN"), + ) + } + + #[test] + fn p2p_terminated_different_peer() { + // Last protocol is p2p but the contained peer does not match the provided one -> it should do nothing and return Err. + test_multiaddr_with_p2p( + "/ip4/127.0.0.1/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN", + "QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC", + Err("/ip4/127.0.0.1/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN"), + ) } }