├── .gitignore ├── .github ├── codecov.yml ├── dependabot.yml └── workflows │ ├── stale.yml │ ├── generated-pr.yml │ └── build.yml ├── LICENSE ├── Cargo.toml ├── src ├── onion_addr.rs ├── errors.rs ├── from_url.rs ├── lib.rs └── protocol.rs ├── README.md ├── CHANGELOG.md └── tests └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | *.rs.bk 4 | -------------------------------------------------------------------------------- /.github/codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | status: 3 | patch: off 4 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "cargo" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | 8 | - package-ecosystem: "github-actions" 9 | directory: "/" 10 | schedule: 11 | interval: "daily" 12 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: Close Stale Issues 2 | 3 | on: 4 | schedule: 5 | - cron: '0 0 * * *' 6 | workflow_dispatch: 7 | 8 | permissions: 9 | issues: write 10 | pull-requests: write 11 | 12 | jobs: 13 | stale: 14 | uses: ipdxco/unified-github-workflows/.github/workflows/reusable-stale-issue.yml@v1 15 | -------------------------------------------------------------------------------- /.github/workflows/generated-pr.yml: -------------------------------------------------------------------------------- 1 | name: Close Generated PRs 2 | 3 | on: 4 | schedule: 5 | - cron: '0 0 * * *' 6 | workflow_dispatch: 7 | 8 | permissions: 9 | issues: write 10 | pull-requests: write 11 | 12 | jobs: 13 | stale: 14 | uses: ipdxco/unified-github-workflows/.github/workflows/reusable-generated-pr.yml@v1 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (C) 2015-2016 Friedel Ziegelmayer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 9 | of the Software, and to permit persons to whom the Software is furnished to do 10 | so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | Status API Training Shop Blog About Pricing 22 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["dignifiedquire ", "Parity Technologies "] 3 | description = "Implementation of the multiaddr format" 4 | edition = "2021" 5 | rust-version = "1.59.0" 6 | repository = "https://github.com/multiformats/rust-multiaddr" 7 | keywords = ["multiaddr", "ipfs"] 8 | license = "MIT" 9 | name = "multiaddr" 10 | readme = "README.md" 11 | version = "0.18.3" 12 | 13 | [features] 14 | default = ["url"] 15 | 16 | [dependencies] 17 | arrayref = "0.3" 18 | byteorder = "1.5.0" 19 | bytes = "1.7.2" 20 | data-encoding = "2.6.0" 21 | multibase = "0.9.1" 22 | multihash = "0.19" 23 | percent-encoding = "2.3.1" 24 | serde = "1.0.209" 25 | static_assertions = "1.1" 26 | unsigned-varint = "0.8" 27 | url = { version = "2.5.0", optional = true, default-features = false } 28 | libp2p-identity = { version = "0.2.9", features = ["peerid"] } 29 | 30 | [dev-dependencies] 31 | bincode = "1" 32 | quickcheck = { version = "1.0.3", default-features = false } 33 | rand = "0.9.0" 34 | serde_json = "1.0" 35 | 36 | # Passing arguments to the docsrs builder in order to properly document cfg's. 37 | # More information: https://docs.rs/about/builds#cross-compiling 38 | [package.metadata.docs.rs] 39 | all-features = true 40 | 41 | [lints.rust] 42 | unexpected_cfgs = { level = "warn", check-cfg = ['cfg(nightly)'] } 43 | -------------------------------------------------------------------------------- /src/onion_addr.rs: -------------------------------------------------------------------------------- 1 | use std::{borrow::Cow, fmt}; 2 | 3 | /// Represents an Onion v3 address 4 | #[derive(Clone)] 5 | pub struct Onion3Addr<'a>(Cow<'a, [u8; 35]>, u16); 6 | 7 | impl Onion3Addr<'_> { 8 | /// Return the hash of the public key as bytes 9 | pub fn hash(&self) -> &[u8; 35] { 10 | self.0.as_ref() 11 | } 12 | 13 | /// Return the port 14 | pub fn port(&self) -> u16 { 15 | self.1 16 | } 17 | 18 | /// Consume this instance and create an owned version containing the same address 19 | pub fn acquire<'b>(self) -> Onion3Addr<'b> { 20 | Onion3Addr(Cow::Owned(self.0.into_owned()), self.1) 21 | } 22 | } 23 | 24 | impl PartialEq for Onion3Addr<'_> { 25 | fn eq(&self, other: &Self) -> bool { 26 | self.1 == other.1 && self.0[..] == other.0[..] 27 | } 28 | } 29 | 30 | impl Eq for Onion3Addr<'_> {} 31 | 32 | impl From<([u8; 35], u16)> for Onion3Addr<'_> { 33 | fn from(parts: ([u8; 35], u16)) -> Self { 34 | Self(Cow::Owned(parts.0), parts.1) 35 | } 36 | } 37 | 38 | impl<'a> From<(&'a [u8; 35], u16)> for Onion3Addr<'a> { 39 | fn from(parts: (&'a [u8; 35], u16)) -> Self { 40 | Self(Cow::Borrowed(parts.0), parts.1) 41 | } 42 | } 43 | 44 | impl fmt::Debug for Onion3Addr<'_> { 45 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { 46 | f.debug_tuple("Onion3Addr") 47 | .field(&format!("{:02x?}", &self.0[..])) 48 | .field(&self.1) 49 | .finish() 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | name: Build 8 | strategy: 9 | fail-fast: false 10 | matrix: 11 | platform: [ubuntu-latest, macos-latest, windows-latest] 12 | runs-on: ${{ matrix.platform }} 13 | 14 | steps: 15 | - name: Checkout Sources 16 | uses: actions/checkout@v4 17 | 18 | - name: Cache Dependencies & Build Outputs 19 | uses: actions/cache@v4 20 | with: 21 | path: ~/.cargo 22 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 23 | 24 | - name: Install Rust Nightly Toolchain 25 | uses: dtolnay/rust-toolchain@nightly 26 | 27 | - name: Install Rust Stable Toolchain 28 | uses: dtolnay/rust-toolchain@stable 29 | with: 30 | components: rustfmt, clippy 31 | 32 | - name: Check Code Format 33 | run: cargo fmt --all -- --check 34 | shell: bash 35 | 36 | - name: Code Lint 37 | run: cargo clippy --all-targets --all-features --workspace -- -D warnings 38 | shell: bash 39 | 40 | - name: Code Lint Without Default Features 41 | run: cargo clippy --no-default-features --workspace -- -D warnings 42 | shell: bash 43 | 44 | - name: Test 45 | run: cargo test --all-features --workspace 46 | shell: bash 47 | 48 | - name: Nightly Test 49 | run: cargo test 50 | shell: bash 51 | env: 52 | RUSTFLAGS: '--cfg nightly -Zcrate-attr=feature(variant_count)' 53 | RUSTUP_TOOLCHAIN: nightly 54 | 55 | coverage: 56 | name: Code Coverage 57 | runs-on: ubuntu-latest 58 | steps: 59 | - name: Checkout Sources 60 | uses: actions/checkout@v4 61 | 62 | - name: Install Rust Toolchain 63 | uses: dtolnay/rust-toolchain@stable 64 | 65 | - name: Generate Code Coverage 66 | run: | 67 | cargo install --version 0.22.0 cargo-tarpaulin 68 | cargo tarpaulin --all-features --out Xml 69 | shell: bash 70 | 71 | - name: Upload Code Coverage 72 | uses: codecov/codecov-action@v5 73 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rust-multiaddr 2 | 3 | [![](https://img.shields.io/badge/made%20by-Protocol%20Labs-blue.svg?style=flat-square)](http://ipn.io) 4 | [![](https://img.shields.io/badge/project-multiformats-blue.svg?style=flat-square)](https://github.com/multiformats/multiformats) 5 | [![](https://img.shields.io/badge/freenode-%23ipfs-blue.svg?style=flat-square)](https://webchat.freenode.net/?channels=%23ipfs) 6 | [![Travis CI](https://img.shields.io/travis/multiformats/rust-multiaddr.svg?style=flat-square&branch=master)](https://travis-ci.org/multiformats/rust-multiaddr) 7 | [![codecov.io](https://img.shields.io/codecov/c/github/multiformats/rust-multiaddr.svg?style=flat-square&branch=master)](https://codecov.io/github/multiformats/rust-multiaddr?branch=master) 8 | [![](https://img.shields.io/badge/rust-docs-blue.svg?style=flat-square)](https://docs.rs/crate/multiaddr) 9 | [![crates.io](https://img.shields.io/badge/crates.io-v0.2.0-orange.svg?style=flat-square )](https://crates.io/crates/multiaddr) 10 | [![](https://img.shields.io/badge/readme%20style-standard-brightgreen.svg?style=flat-square)](https://github.com/RichardLitt/standard-readme) 11 | 12 | 13 | > [multiaddr](https://github.com/multiformats/multiaddr) implementation in Rust. 14 | 15 | ## Table of Contents 16 | 17 | - [Install](#install) 18 | - [Usage](#usage) 19 | - [Maintainers](#maintainers) 20 | - [Contribute](#contribute) 21 | - [License](#license) 22 | 23 | ## Install 24 | 25 | First add this to your `Cargo.toml` 26 | 27 | ```toml 28 | [dependencies] 29 | multiaddr = "*" 30 | ``` 31 | 32 | then run `cargo build`. 33 | 34 | ## Usage 35 | 36 | ```rust 37 | extern crate multiaddr; 38 | 39 | use multiaddr::{Multiaddr, multiaddr}; 40 | 41 | let address = "/ip4/127.0.0.1/tcp/1234".parse::().unwrap(); 42 | // or with a macro 43 | let other = multiaddr!(Ip4([127, 0, 0, 1]), Udp(10500u16), QuicV1); 44 | 45 | assert_eq!(address.to_string(), "/ip4/127.0.0.1/tcp/1234"); 46 | assert_eq!(other.to_string(), "/ip4/127.0.0.1/udp/10500/quic-v1"); 47 | ``` 48 | 49 | ## Maintainers 50 | 51 | Captain: [@dignifiedquire](https://github.com/dignifiedquire). 52 | 53 | ## Contribute 54 | 55 | Contributions welcome. Please check out [the issues](https://github.com/multiformats/rust-multiaddr/issues). 56 | 57 | Check out our [contributing document](https://github.com/multiformats/multiformats/blob/master/contributing.md) for more information on how we work, and about contributing in general. Please be aware that all interactions related to multiformats are subject to the IPFS [Code of Conduct](https://github.com/ipfs/community/blob/master/code-of-conduct.md). 58 | 59 | Small note: If editing the README, please conform to the [standard-readme](https://github.com/RichardLitt/standard-readme) specification. 60 | 61 | ## License 62 | 63 | [MIT](LICENSE) © 2015-2017 Friedel Ziegelmeyer 64 | -------------------------------------------------------------------------------- /src/errors.rs: -------------------------------------------------------------------------------- 1 | use std::{error, fmt, io, net, num, str, string}; 2 | use unsigned_varint::decode; 3 | 4 | pub type Result = ::std::result::Result; 5 | 6 | /// Error types 7 | #[derive(Debug)] 8 | #[non_exhaustive] 9 | pub enum Error { 10 | DataLessThanLen, 11 | InvalidMultiaddr, 12 | InvalidProtocolString, 13 | InvalidUvar(decode::Error), 14 | ParsingError(Box), 15 | UnknownProtocolId(u32), 16 | UnknownProtocolString(String), 17 | } 18 | 19 | impl fmt::Display for Error { 20 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 21 | match self { 22 | Error::DataLessThanLen => f.write_str("we have less data than indicated by length"), 23 | Error::InvalidMultiaddr => f.write_str("invalid multiaddr"), 24 | Error::InvalidProtocolString => f.write_str("invalid protocol string"), 25 | Error::InvalidUvar(e) => write!(f, "failed to decode unsigned varint: {e}"), 26 | Error::ParsingError(e) => write!(f, "failed to parse: {e}"), 27 | Error::UnknownProtocolId(id) => write!(f, "unknown protocol id: {id}"), 28 | Error::UnknownProtocolString(string) => { 29 | write!(f, "unknown protocol string: {string}") 30 | } 31 | } 32 | } 33 | } 34 | 35 | impl error::Error for Error { 36 | #[inline] 37 | fn source(&self) -> Option<&(dyn error::Error + 'static)> { 38 | if let Error::ParsingError(e) = self { 39 | Some(&**e) 40 | } else { 41 | None 42 | } 43 | } 44 | } 45 | 46 | impl From for Error { 47 | fn from(err: io::Error) -> Error { 48 | Error::ParsingError(err.into()) 49 | } 50 | } 51 | 52 | impl From for Error { 53 | fn from(err: multihash::Error) -> Error { 54 | Error::ParsingError(err.into()) 55 | } 56 | } 57 | 58 | impl From for Error { 59 | fn from(err: multibase::Error) -> Error { 60 | Error::ParsingError(err.into()) 61 | } 62 | } 63 | 64 | impl From for Error { 65 | fn from(err: net::AddrParseError) -> Error { 66 | Error::ParsingError(err.into()) 67 | } 68 | } 69 | 70 | impl From for Error { 71 | fn from(err: num::ParseIntError) -> Error { 72 | Error::ParsingError(err.into()) 73 | } 74 | } 75 | 76 | impl From for Error { 77 | fn from(err: string::FromUtf8Error) -> Error { 78 | Error::ParsingError(err.into()) 79 | } 80 | } 81 | 82 | impl From for Error { 83 | fn from(err: str::Utf8Error) -> Error { 84 | Error::ParsingError(err.into()) 85 | } 86 | } 87 | 88 | impl From for Error { 89 | fn from(e: decode::Error) -> Error { 90 | Error::InvalidUvar(e) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.18.3 2 | 3 | - Add `starts_with` on `Multiaddr`. See [PR 119]. 4 | 5 | [PR 119]: https://github.com/multiformats/rust-multiaddr/pull/119 6 | 7 | # 0.18.2 8 | 9 | - Implement missing protocols. See [PR 110]. 10 | [PR 110]: https://github.com/multiformats/rust-multiaddr/pull/110. 11 | 12 | - Re-export `libp2p_identity::PeerId`. See [PR 108]. 13 | [PR 108]: https://github.com/multiformats/rust-multiaddr/pull/108. 14 | 15 | - Avoid allocations in Display and Debug of Multiaddr. See [PR 106]. 16 | [PR 106]: https://github.com/multiformats/rust-multiaddr/pull/106 17 | 18 | # 0.18.1 19 | 20 | - Add `with_p2p` on `Multiaddr`. See [PR 102]. 21 | 22 | [PR 102]: https://github.com/multiformats/rust-multiaddr/pull/102 23 | 24 | # 0.18.0 25 | 26 | - Add `WebTransport` instance for `Multiaddr`. See [PR 70]. 27 | 28 | - Disable all features of `multihash`. See [PR 77]. 29 | 30 | - Mark `Protocol` as `#[non_exhaustive]`. See [PR 82]. 31 | 32 | - Rename `Protocol::WebRTC` to `Protocol::WebRTCDirect`. 33 | See [multiformats/multiaddr discussion] for context. 34 | Remove deprecated support for `/webrtc` in favor of the existing `/webrtc-direct` string representation. 35 | **Note that this is a breaking change.** 36 | 37 | - Make `/p2p` typesafe, i.e. have `Protocol::P2p` contain a `PeerId` instead of a `Multihash`. 38 | See [PR 83]. 39 | 40 | [multiformats/multiaddr discussion]: https://github.com/multiformats/multiaddr/pull/150#issuecomment-1468791586 41 | [PR 70]: https://github.com/multiformats/rust-multiaddr/pull/70 42 | [PR 77]: https://github.com/multiformats/rust-multiaddr/pull/77 43 | [PR 82]: https://github.com/multiformats/rust-multiaddr/pull/82 44 | [PR 83]: https://github.com/multiformats/rust-multiaddr/pull/83 45 | 46 | # 0.17.1 47 | 48 | - Rename string representation of `WebRTC` protocol from `/webrtc` to `/webrt-direct`. 49 | For backwards compatibility `/webrtc` will still be decoded to `Protocol::WebRTC`, but `Protocol::WebRTC` will from now on always be encoded as `/webrtc-direct`. 50 | See [multiformats/multiaddr discussion] and [PR 84] for context. 51 | ``` rust 52 | assert_eq!( 53 | Multiaddr::empty().with(Protocol::WebRTC), 54 | "/webrtc".parse().unwrap(), 55 | ); 56 | assert_eq!( 57 | Multiaddr::empty().with(Protocol::WebRTC), 58 | "/webrtc-direct".parse().unwrap(), 59 | ); 60 | assert_eq!( 61 | "/webrtc-direct", 62 | Multiaddr::empty().with(Protocol::WebRTC).to_string(), 63 | ); 64 | assert_ne!( 65 | "/webrtc", 66 | Multiaddr::empty().with(Protocol::WebRTC).to_string(), 67 | ); 68 | ``` 69 | 70 | [PR 84]: https://github.com/multiformats/rust-multiaddr/pull/84 71 | 72 | # 0.17.0 73 | 74 | - Update to multihash `v0.17`. See [PR 63]. 75 | 76 | [PR 63]: https://github.com/multiformats/rust-multiaddr/pull/63 77 | 78 | # 0.16.0 [2022-11-04] 79 | 80 | - Create `protocol_stack` for Multiaddr. See [PR 60]. 81 | 82 | - Add `QuicV1` instance for `Multiaddr`. See [PR 64]. 83 | 84 | [PR 60]: https://github.com/multiformats/rust-multiaddr/pull/60 85 | [PR 64]: https://github.com/multiformats/rust-multiaddr/pull/64 86 | 87 | # 0.15.0 [2022-10-20] 88 | 89 | - Add `WebRTC` instance for `Multiaddr`. See [PR 59]. 90 | - Add `Certhash` instance for `Multiaddr`. See [PR 59]. 91 | 92 | - Add support for Noise protocol. See [PR 53]. 93 | 94 | - Use `multibase` instead of `bs58` for base58 encoding. See [PR 56]. 95 | 96 | [PR 53]: https://github.com/multiformats/rust-multiaddr/pull/53 97 | [PR 56]: https://github.com/multiformats/rust-multiaddr/pull/56 98 | [PR 59]: https://github.com/multiformats/rust-multiaddr/pull/59 99 | 100 | # 0.14.0 [2022-02-02] 101 | 102 | - Add support for TLS protocol (see [PR 48]). 103 | 104 | - Update to `multihash` `v0.15` (see [PR 50]). 105 | 106 | - Update to `multihash` `v0.16` (see [PR 51]). 107 | 108 | [PR 48]: https://github.com/multiformats/rust-multiaddr/pull/48 109 | [PR 50]: https://github.com/multiformats/rust-multiaddr/pull/50 110 | [PR 50]: https://github.com/multiformats/rust-multiaddr/pull/51 111 | 112 | # 0.13.0 [2021-07-08] 113 | 114 | - Update to multihash v0.14.0 (see [PR 44]). 115 | 116 | - Update to rand v0.8.4 (see [PR 45]). 117 | 118 | [PR 44]: https://github.com/multiformats/rust-multiaddr/pull/44 119 | [PR 45]: https://github.com/multiformats/rust-multiaddr/pull/45 120 | 121 | # 0.12.0 [2021-05-26] 122 | 123 | - Merge [multiaddr] and [parity-multiaddr] (see [PR 40]). 124 | 125 | - Functionality to go from a `u64` to a `multiaddr::Protocol` and back is 126 | removed. Please open an issue on [multiaddr] in case this is still needed. 127 | 128 | - Given that `multiaddr::Protocol` now represents both the protocol 129 | identifier as well as the protocol data (e.g. protocol identifier `55` 130 | (`dns6`) and protocol data `some-domain.example`) `multiaddr::Protocol` is 131 | no longer `Copy`. 132 | 133 | [multiaddr]: https://github.com/multiformats/rust-multiaddr 134 | [parity-multiaddr]: https://github.com/libp2p/rust-libp2p/blob/master/misc/multiaddr/ 135 | [PR 40]: https://github.com/multiformats/rust-multiaddr/pull/40 136 | 137 | # 0.11.2 [2021-03-17] 138 | 139 | - Add `Multiaddr::ends_with()`. 140 | 141 | # 0.11.1 [2021-02-15] 142 | 143 | - Update dependencies 144 | 145 | # 0.11.0 [2021-01-12] 146 | 147 | - Update dependencies 148 | 149 | # 0.10.1 [2021-01-12] 150 | 151 | - Fix compilation with serde-1.0.119. 152 | [PR 1912](https://github.com/libp2p/rust-libp2p/pull/1912) 153 | 154 | # 0.10.0 [2020-11-25] 155 | 156 | - Upgrade multihash to `0.13`. 157 | 158 | # 0.9.6 [2020-11-17] 159 | 160 | - Move the `from_url` module and functionality behind the `url` feature, 161 | enabled by default. 162 | [PR 1843](https://github.com/libp2p/rust-libp2p/pull/1843). 163 | 164 | # 0.9.5 [2020-11-14] 165 | 166 | - Limit initial memory allocation in `visit_seq`. 167 | [PR 1833](https://github.com/libp2p/rust-libp2p/pull/1833). 168 | 169 | # 0.9.4 [2020-11-09] 170 | 171 | - Update dependencies. 172 | 173 | # 0.9.3 [2020-10-16] 174 | 175 | - Update dependencies. 176 | 177 | # 0.9.2 [2020-08-31] 178 | 179 | - Add `Ord` instance for `Multiaddr`. 180 | 181 | # 0.9.1 [2020-06-22] 182 | 183 | - Updated dependencies. 184 | -------------------------------------------------------------------------------- /src/from_url.rs: -------------------------------------------------------------------------------- 1 | use crate::{Multiaddr, Protocol}; 2 | use std::{error, fmt, iter, net::IpAddr}; 3 | 4 | /// Attempts to parse an URL into a multiaddress. 5 | /// 6 | /// This function will return an error if some information in the URL cannot be retained in the 7 | /// generated multiaddress. This includes a username, password, path (if not supported by the 8 | /// multiaddr), and query string. 9 | /// 10 | /// This function is only present if the `url` feature is enabled, and it is 11 | /// enabled by default. 12 | /// 13 | /// The supported URL schemes are: 14 | /// 15 | /// - `ws://example.com/` 16 | /// - `wss://example.com/` 17 | /// - `http://example.com/` 18 | /// - `https://example.com/` 19 | /// - `unix:/foo/bar` 20 | /// 21 | /// # Example 22 | /// 23 | /// ``` 24 | /// let addr = multiaddr::from_url("ws://127.0.0.1:8080/").unwrap(); 25 | /// assert_eq!(addr, "/ip4/127.0.0.1/tcp/8080/ws".parse().unwrap()); 26 | /// ``` 27 | /// 28 | pub fn from_url(url: &str) -> std::result::Result { 29 | from_url_inner(url, false) 30 | } 31 | 32 | /// Attempts to parse an URL into a multiaddress. Ignores possible loss of information. 33 | /// 34 | /// This function is similar to [`from_url`], except that we don't return an error if some 35 | /// information in the URL cannot be retain in the generated multiaddress. 36 | /// 37 | /// This function is only present if the `url` feature is enabled, and it is 38 | /// enabled by default. 39 | /// 40 | /// # Example 41 | /// 42 | /// ``` 43 | /// let addr = "ws://user:pass@127.0.0.1:8080/"; 44 | /// assert!(multiaddr::from_url(addr).is_err()); 45 | /// assert!(multiaddr::from_url_lossy(addr).is_ok()); 46 | /// ``` 47 | /// 48 | pub fn from_url_lossy(url: &str) -> std::result::Result { 49 | from_url_inner(url, true) 50 | } 51 | 52 | /// Underlying implementation of `from_url` and `from_url_lossy`. 53 | fn from_url_inner(url: &str, lossy: bool) -> std::result::Result { 54 | let url = url::Url::parse(url).map_err(|_| FromUrlErr::BadUrl)?; 55 | 56 | match url.scheme() { 57 | // Note: if you add support for a new scheme, please update the documentation as well. 58 | "ws" | "wss" | "http" | "https" => from_url_inner_http_ws(url, lossy), 59 | "unix" => from_url_inner_path(url, lossy), 60 | _ => Err(FromUrlErr::UnsupportedScheme), 61 | } 62 | } 63 | 64 | /// Called when `url.scheme()` is an Internet-like URL. 65 | fn from_url_inner_http_ws( 66 | url: url::Url, 67 | lossy: bool, 68 | ) -> std::result::Result { 69 | let (protocol, lost_path, default_port) = match url.scheme() { 70 | "ws" => (Protocol::Ws(url.path().to_owned().into()), false, 80), 71 | "wss" => (Protocol::Wss(url.path().to_owned().into()), false, 443), 72 | "http" => (Protocol::Http, true, 80), 73 | "https" => (Protocol::Https, true, 443), 74 | _ => unreachable!("We only call this function for one of the given schemes; qed"), 75 | }; 76 | 77 | let port = Protocol::Tcp(url.port().unwrap_or(default_port)); 78 | let ip = if let Some(hostname) = url.host_str() { 79 | if let Ok(ip) = hostname.parse::() { 80 | Protocol::from(ip) 81 | } else { 82 | Protocol::Dns(hostname.into()) 83 | } 84 | } else { 85 | return Err(FromUrlErr::BadUrl); 86 | }; 87 | 88 | if !lossy 89 | && (!url.username().is_empty() 90 | || url.password().is_some() 91 | || (lost_path && url.path() != "/" && !url.path().is_empty()) 92 | || url.query().is_some() 93 | || url.fragment().is_some()) 94 | { 95 | return Err(FromUrlErr::InformationLoss); 96 | } 97 | 98 | Ok(iter::once(ip) 99 | .chain(iter::once(port)) 100 | .chain(iter::once(protocol)) 101 | .collect()) 102 | } 103 | 104 | /// Called when `url.scheme()` is a path-like URL. 105 | fn from_url_inner_path(url: url::Url, lossy: bool) -> std::result::Result { 106 | let protocol = match url.scheme() { 107 | "unix" => Protocol::Unix(url.path().to_owned().into()), 108 | _ => unreachable!("We only call this function for one of the given schemes; qed"), 109 | }; 110 | 111 | if !lossy 112 | && (!url.username().is_empty() 113 | || url.password().is_some() 114 | || url.query().is_some() 115 | || url.fragment().is_some()) 116 | { 117 | return Err(FromUrlErr::InformationLoss); 118 | } 119 | 120 | Ok(Multiaddr::from(protocol)) 121 | } 122 | 123 | /// Error while parsing an URL. 124 | #[derive(Debug)] 125 | pub enum FromUrlErr { 126 | /// Failed to parse the URL. 127 | BadUrl, 128 | /// The URL scheme was not recognized. 129 | UnsupportedScheme, 130 | /// Some information in the URL would be lost. Never returned by `from_url_lossy`. 131 | InformationLoss, 132 | } 133 | 134 | impl fmt::Display for FromUrlErr { 135 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 136 | match self { 137 | FromUrlErr::BadUrl => write!(f, "Bad URL"), 138 | FromUrlErr::UnsupportedScheme => write!(f, "Unrecognized URL scheme"), 139 | FromUrlErr::InformationLoss => write!(f, "Some information in the URL would be lost"), 140 | } 141 | } 142 | } 143 | 144 | impl error::Error for FromUrlErr {} 145 | 146 | #[cfg(test)] 147 | mod tests { 148 | use super::*; 149 | 150 | #[test] 151 | fn parse_garbage_doesnt_panic() { 152 | for _ in 0..50 { 153 | let url = (0..16).map(|_| rand::random::()).collect::>(); 154 | let url = String::from_utf8_lossy(&url); 155 | assert!(from_url(&url).is_err()); 156 | } 157 | } 158 | 159 | #[test] 160 | fn normal_usage_ws() { 161 | let addr = from_url("ws://127.0.0.1:8000").unwrap(); 162 | assert_eq!(addr, "/ip4/127.0.0.1/tcp/8000/ws".parse().unwrap()); 163 | } 164 | 165 | #[test] 166 | fn normal_usage_wss() { 167 | let addr = from_url("wss://127.0.0.1:8000").unwrap(); 168 | assert_eq!(addr, "/ip4/127.0.0.1/tcp/8000/wss".parse().unwrap()); 169 | } 170 | 171 | #[test] 172 | fn default_ws_port() { 173 | let addr = from_url("ws://127.0.0.1").unwrap(); 174 | assert_eq!(addr, "/ip4/127.0.0.1/tcp/80/ws".parse().unwrap()); 175 | } 176 | 177 | #[test] 178 | fn default_http_port() { 179 | let addr = from_url("http://127.0.0.1").unwrap(); 180 | assert_eq!(addr, "/ip4/127.0.0.1/tcp/80/http".parse().unwrap()); 181 | } 182 | 183 | #[test] 184 | fn default_wss_port() { 185 | let addr = from_url("wss://127.0.0.1").unwrap(); 186 | assert_eq!(addr, "/ip4/127.0.0.1/tcp/443/wss".parse().unwrap()); 187 | } 188 | 189 | #[test] 190 | fn default_https_port() { 191 | let addr = from_url("https://127.0.0.1").unwrap(); 192 | assert_eq!(addr, "/ip4/127.0.0.1/tcp/443/https".parse().unwrap()); 193 | } 194 | 195 | #[test] 196 | fn dns_addr_ws() { 197 | let addr = from_url("ws://example.com").unwrap(); 198 | assert_eq!(addr, "/dns/example.com/tcp/80/ws".parse().unwrap()); 199 | } 200 | 201 | #[test] 202 | fn dns_addr_http() { 203 | let addr = from_url("http://example.com").unwrap(); 204 | assert_eq!(addr, "/dns/example.com/tcp/80/http".parse().unwrap()); 205 | } 206 | 207 | #[test] 208 | fn dns_addr_wss() { 209 | let addr = from_url("wss://example.com").unwrap(); 210 | assert_eq!(addr, "/dns/example.com/tcp/443/wss".parse().unwrap()); 211 | } 212 | 213 | #[test] 214 | fn dns_addr_https() { 215 | let addr = from_url("https://example.com").unwrap(); 216 | assert_eq!(addr, "/dns/example.com/tcp/443/https".parse().unwrap()); 217 | } 218 | 219 | #[test] 220 | fn bad_hostname() { 221 | let addr = from_url("wss://127.0.0.1x").unwrap(); 222 | assert_eq!(addr, "/dns/127.0.0.1x/tcp/443/wss".parse().unwrap()); 223 | } 224 | 225 | #[test] 226 | fn wrong_scheme() { 227 | match from_url("foo://127.0.0.1") { 228 | Err(FromUrlErr::UnsupportedScheme) => {} 229 | _ => panic!(), 230 | } 231 | } 232 | 233 | #[test] 234 | fn dns_and_port() { 235 | let addr = from_url("http://example.com:1000").unwrap(); 236 | assert_eq!(addr, "/dns/example.com/tcp/1000/http".parse().unwrap()); 237 | } 238 | 239 | #[test] 240 | fn username_lossy() { 241 | let addr = "http://foo@example.com:1000/"; 242 | assert!(from_url(addr).is_err()); 243 | assert!(from_url_lossy(addr).is_ok()); 244 | assert!(from_url("http://@example.com:1000/").is_ok()); 245 | } 246 | 247 | #[test] 248 | fn password_lossy() { 249 | let addr = "http://:bar@example.com:1000/"; 250 | assert!(from_url(addr).is_err()); 251 | assert!(from_url_lossy(addr).is_ok()); 252 | } 253 | 254 | #[test] 255 | fn path_lossy() { 256 | let addr = "http://example.com:1000/foo"; 257 | assert!(from_url(addr).is_err()); 258 | assert!(from_url_lossy(addr).is_ok()); 259 | } 260 | 261 | #[test] 262 | fn fragment_lossy() { 263 | let addr = "http://example.com:1000/#foo"; 264 | assert!(from_url(addr).is_err()); 265 | assert!(from_url_lossy(addr).is_ok()); 266 | } 267 | 268 | #[test] 269 | fn unix() { 270 | let addr = from_url("unix:/foo/bar").unwrap(); 271 | assert_eq!(addr, Multiaddr::from(Protocol::Unix("/foo/bar".into()))); 272 | } 273 | 274 | #[test] 275 | fn ws_path() { 276 | let addr = from_url("ws://1.2.3.4:1000/foo/bar").unwrap(); 277 | assert_eq!( 278 | addr, 279 | "/ip4/1.2.3.4/tcp/1000/x-parity-ws/%2ffoo%2fbar" 280 | .parse() 281 | .unwrap() 282 | ); 283 | 284 | let addr = from_url("ws://1.2.3.4:1000/").unwrap(); 285 | assert_eq!(addr, "/ip4/1.2.3.4/tcp/1000/ws".parse().unwrap()); 286 | 287 | let addr = from_url("wss://1.2.3.4:1000/foo/bar").unwrap(); 288 | assert_eq!( 289 | addr, 290 | "/ip4/1.2.3.4/tcp/1000/x-parity-wss/%2ffoo%2fbar" 291 | .parse() 292 | .unwrap() 293 | ); 294 | 295 | let addr = from_url("wss://1.2.3.4:1000").unwrap(); 296 | assert_eq!(addr, "/ip4/1.2.3.4/tcp/1000/wss".parse().unwrap()); 297 | } 298 | } 299 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Implementation of [multiaddr](https://github.com/multiformats/multiaddr) in Rust. 2 | #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] 3 | 4 | pub use multihash; 5 | 6 | mod errors; 7 | mod onion_addr; 8 | mod protocol; 9 | 10 | #[cfg(feature = "url")] 11 | mod from_url; 12 | 13 | pub use self::errors::{Error, Result}; 14 | pub use self::onion_addr::Onion3Addr; 15 | pub use self::protocol::Protocol; 16 | use bytes::{BufMut, Bytes, BytesMut}; 17 | use serde::{ 18 | de::{self, Error as DeserializerError}, 19 | Deserialize, Deserializer, Serialize, Serializer, 20 | }; 21 | use std::{ 22 | convert::TryFrom, 23 | fmt, 24 | iter::FromIterator, 25 | net::{IpAddr, Ipv4Addr, Ipv6Addr}, 26 | result::Result as StdResult, 27 | str::FromStr, 28 | }; 29 | 30 | pub use libp2p_identity::PeerId; 31 | 32 | #[cfg(feature = "url")] 33 | pub use self::from_url::{from_url, from_url_lossy, FromUrlErr}; 34 | 35 | static_assertions::const_assert! { 36 | // This check is most certainly overkill right now, but done here 37 | // anyway to ensure the `as u64` casts in this crate are safe. 38 | std::mem::size_of::() <= std::mem::size_of::() 39 | } 40 | 41 | /// Representation of a Multiaddr. 42 | #[allow(clippy::rc_buffer)] 43 | #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Hash)] 44 | pub struct Multiaddr { 45 | bytes: Bytes, 46 | } 47 | 48 | impl Multiaddr { 49 | /// Create a new, empty multiaddress. 50 | pub fn empty() -> Self { 51 | Self { 52 | bytes: Bytes::new(), 53 | } 54 | } 55 | 56 | /// Create a new, empty multiaddress with the given capacity. 57 | pub fn with_capacity(n: usize) -> Self { 58 | Self { 59 | bytes: BytesMut::with_capacity(n).freeze(), 60 | } 61 | } 62 | 63 | /// Return the length in bytes of this multiaddress. 64 | pub fn len(&self) -> usize { 65 | self.bytes.len() 66 | } 67 | 68 | /// Returns true if the length of this multiaddress is 0. 69 | pub fn is_empty(&self) -> bool { 70 | self.bytes.len() == 0 71 | } 72 | 73 | /// Return a copy of this [`Multiaddr`]'s byte representation. 74 | pub fn to_vec(&self) -> Vec { 75 | Vec::from(&self.bytes[..]) 76 | } 77 | 78 | /// Adds an already-parsed address component to the end of this multiaddr. 79 | /// 80 | /// # Examples 81 | /// 82 | /// ``` 83 | /// use multiaddr::{Multiaddr, Protocol}; 84 | /// 85 | /// let mut address: Multiaddr = "/ip4/127.0.0.1".parse().unwrap(); 86 | /// address.push(Protocol::Tcp(10000)); 87 | /// assert_eq!(address, "/ip4/127.0.0.1/tcp/10000".parse().unwrap()); 88 | /// ``` 89 | /// 90 | pub fn push(&mut self, p: Protocol<'_>) { 91 | let mut bytes = BytesMut::from(std::mem::take(&mut self.bytes)); 92 | p.write_bytes(&mut (&mut bytes).writer()) 93 | .expect("Writing to a `BytesMut` never fails."); 94 | self.bytes = bytes.freeze(); 95 | } 96 | 97 | /// Pops the last `Protocol` of this multiaddr, or `None` if the multiaddr is empty. 98 | /// ``` 99 | /// use multiaddr::{Multiaddr, Protocol}; 100 | /// 101 | /// let mut address: Multiaddr = "/ip4/127.0.0.1/udt/sctp/5678".parse().unwrap(); 102 | /// 103 | /// assert_eq!(address.pop().unwrap(), Protocol::Sctp(5678)); 104 | /// assert_eq!(address.pop().unwrap(), Protocol::Udt); 105 | /// ``` 106 | /// 107 | pub fn pop<'a>(&mut self) -> Option> { 108 | let mut slice = &self.bytes[..]; // the remaining multiaddr slice 109 | if slice.is_empty() { 110 | return None; 111 | } 112 | let protocol = loop { 113 | let (p, s) = Protocol::from_bytes(slice).expect("`slice` is a valid `Protocol`."); 114 | if s.is_empty() { 115 | break p.acquire(); 116 | } 117 | slice = s 118 | }; 119 | let remaining_len = self.len() - slice.len(); 120 | let mut bytes = BytesMut::from(std::mem::take(&mut self.bytes)); 121 | bytes.truncate(remaining_len); 122 | self.bytes = bytes.freeze(); 123 | Some(protocol) 124 | } 125 | 126 | /// Like [`Multiaddr::push`] but consumes `self`. 127 | pub fn with(mut self, p: Protocol<'_>) -> Self { 128 | let mut bytes = BytesMut::from(std::mem::take(&mut self.bytes)); 129 | p.write_bytes(&mut (&mut bytes).writer()) 130 | .expect("Writing to a `BytesMut` never fails."); 131 | self.bytes = bytes.freeze(); 132 | self 133 | } 134 | 135 | /// Appends the given [`PeerId`] if not yet present at the end of this multiaddress. 136 | /// 137 | /// Fails if this address ends in a _different_ [`PeerId`]. 138 | /// In that case, the original, unmodified address is returned. 139 | pub fn with_p2p(self, peer: PeerId) -> std::result::Result { 140 | match self.iter().last() { 141 | Some(Protocol::P2p(p)) if p == peer => Ok(self), 142 | Some(Protocol::P2p(_)) => Err(self), 143 | _ => Ok(self.with(Protocol::P2p(peer))), 144 | } 145 | } 146 | 147 | /// Returns the components of this multiaddress. 148 | /// 149 | /// # Example 150 | /// 151 | /// ```rust 152 | /// use std::net::Ipv4Addr; 153 | /// use multiaddr::{Multiaddr, Protocol}; 154 | /// 155 | /// let address: Multiaddr = "/ip4/127.0.0.1/udt/sctp/5678".parse().unwrap(); 156 | /// 157 | /// let components = address.iter().collect::>(); 158 | /// assert_eq!(components[0], Protocol::Ip4(Ipv4Addr::new(127, 0, 0, 1))); 159 | /// assert_eq!(components[1], Protocol::Udt); 160 | /// assert_eq!(components[2], Protocol::Sctp(5678)); 161 | /// ``` 162 | /// 163 | pub fn iter(&self) -> Iter<'_> { 164 | Iter(&self.bytes) 165 | } 166 | 167 | /// Replace a [`Protocol`] at some position in this `Multiaddr`. 168 | /// 169 | /// The parameter `at` denotes the index of the protocol at which the function 170 | /// `by` will be applied to the current protocol, returning an optional replacement. 171 | /// 172 | /// If `at` is out of bounds or `by` does not yield a replacement value, 173 | /// `None` will be returned. Otherwise a copy of this `Multiaddr` with the 174 | /// updated `Protocol` at position `at` will be returned. 175 | pub fn replace<'a, F>(&self, at: usize, by: F) -> Option 176 | where 177 | F: FnOnce(&Protocol<'_>) -> Option>, 178 | { 179 | let mut address = Multiaddr::with_capacity(self.len()); 180 | let mut fun = Some(by); 181 | let mut replaced = false; 182 | 183 | for (i, p) in self.iter().enumerate() { 184 | if i == at { 185 | let f = fun.take().expect("i == at only happens once"); 186 | if let Some(q) = f(&p) { 187 | address = address.with(q); 188 | replaced = true; 189 | continue; 190 | } 191 | return None; 192 | } 193 | address = address.with(p) 194 | } 195 | 196 | if replaced { 197 | Some(address) 198 | } else { 199 | None 200 | } 201 | } 202 | 203 | /// Checks whether the given `Multiaddr` is a suffix of this `Multiaddr`. 204 | pub fn ends_with(&self, other: &Multiaddr) -> bool { 205 | let n = self.bytes.len(); 206 | let m = other.bytes.len(); 207 | if n < m { 208 | return false; 209 | } 210 | self.bytes[(n - m)..] == other.bytes[..] 211 | } 212 | 213 | /// Checks whether the given `Multiaddr` is a prefix of this `Multiaddr`. 214 | pub fn starts_with(&self, other: &Multiaddr) -> bool { 215 | let n = self.bytes.len(); 216 | let m = other.bytes.len(); 217 | if n < m { 218 | return false; 219 | } 220 | self.bytes[..m] == other.bytes[..] 221 | } 222 | 223 | /// Returns &str identifiers for the protocol names themselves. 224 | /// This omits specific info like addresses, ports, peer IDs, and the like. 225 | /// Example: `"/ip4/127.0.0.1/tcp/5001"` would return `["ip4", "tcp"]` 226 | pub fn protocol_stack(&self) -> ProtoStackIter { 227 | ProtoStackIter { parts: self.iter() } 228 | } 229 | } 230 | 231 | impl fmt::Debug for Multiaddr { 232 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 233 | fmt::Display::fmt(self, f) 234 | } 235 | } 236 | 237 | impl fmt::Display for Multiaddr { 238 | /// Convert a Multiaddr to a string 239 | /// 240 | /// # Example 241 | /// 242 | /// ``` 243 | /// use multiaddr::Multiaddr; 244 | /// 245 | /// let address: Multiaddr = "/ip4/127.0.0.1/udt".parse().unwrap(); 246 | /// assert_eq!(address.to_string(), "/ip4/127.0.0.1/udt"); 247 | /// ``` 248 | /// 249 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 250 | for s in self.iter() { 251 | s.fmt(f)?; 252 | } 253 | Ok(()) 254 | } 255 | } 256 | 257 | impl AsRef<[u8]> for Multiaddr { 258 | fn as_ref(&self) -> &[u8] { 259 | self.bytes.as_ref() 260 | } 261 | } 262 | 263 | impl<'a> IntoIterator for &'a Multiaddr { 264 | type Item = Protocol<'a>; 265 | type IntoIter = Iter<'a>; 266 | 267 | fn into_iter(self) -> Iter<'a> { 268 | Iter(&self.bytes) 269 | } 270 | } 271 | 272 | impl<'a> FromIterator> for Multiaddr { 273 | fn from_iter(iter: T) -> Self 274 | where 275 | T: IntoIterator>, 276 | { 277 | let mut bytes = BytesMut::new(); 278 | for cmp in iter { 279 | cmp.write_bytes(&mut (&mut bytes).writer()) 280 | .expect("Writing to a `BytesMut` never fails."); 281 | } 282 | Multiaddr { 283 | bytes: bytes.freeze(), 284 | } 285 | } 286 | } 287 | 288 | impl FromStr for Multiaddr { 289 | type Err = Error; 290 | 291 | fn from_str(input: &str) -> Result { 292 | let mut bytes = BytesMut::new(); 293 | let mut parts = input.split('/').peekable(); 294 | 295 | if Some("") != parts.next() { 296 | // A multiaddr must start with `/` 297 | return Err(Error::InvalidMultiaddr); 298 | } 299 | 300 | while parts.peek().is_some() { 301 | let p = Protocol::from_str_parts(&mut parts)?; 302 | p.write_bytes(&mut (&mut bytes).writer()) 303 | .expect("Writing to a `BytesMut` never fails."); 304 | } 305 | 306 | Ok(Multiaddr { 307 | bytes: bytes.freeze(), 308 | }) 309 | } 310 | } 311 | 312 | /// Iterator over `Multiaddr` [`Protocol`]s. 313 | pub struct Iter<'a>(&'a [u8]); 314 | 315 | impl<'a> Iterator for Iter<'a> { 316 | type Item = Protocol<'a>; 317 | 318 | fn next(&mut self) -> Option { 319 | if self.0.is_empty() { 320 | return None; 321 | } 322 | 323 | let (p, next_data) = 324 | Protocol::from_bytes(self.0).expect("`Multiaddr` is known to be valid."); 325 | 326 | self.0 = next_data; 327 | Some(p) 328 | } 329 | } 330 | 331 | /// Iterator over the string identifiers of the protocols (not addrs) in a multiaddr 332 | pub struct ProtoStackIter<'a> { 333 | parts: Iter<'a>, 334 | } 335 | 336 | impl Iterator for ProtoStackIter<'_> { 337 | type Item = &'static str; 338 | fn next(&mut self) -> Option { 339 | self.parts.next().as_ref().map(Protocol::tag) 340 | } 341 | } 342 | 343 | impl<'a> From> for Multiaddr { 344 | fn from(p: Protocol<'a>) -> Multiaddr { 345 | let mut bytes = BytesMut::new(); 346 | p.write_bytes(&mut (&mut bytes).writer()) 347 | .expect("Writing to a `BytesMut` never fails."); 348 | Multiaddr { 349 | bytes: bytes.freeze(), 350 | } 351 | } 352 | } 353 | 354 | impl From for Multiaddr { 355 | fn from(v: IpAddr) -> Multiaddr { 356 | match v { 357 | IpAddr::V4(a) => a.into(), 358 | IpAddr::V6(a) => a.into(), 359 | } 360 | } 361 | } 362 | 363 | impl From for Multiaddr { 364 | fn from(v: Ipv4Addr) -> Multiaddr { 365 | Protocol::Ip4(v).into() 366 | } 367 | } 368 | 369 | impl From for Multiaddr { 370 | fn from(v: Ipv6Addr) -> Multiaddr { 371 | Protocol::Ip6(v).into() 372 | } 373 | } 374 | 375 | impl TryFrom> for Multiaddr { 376 | type Error = Error; 377 | 378 | fn try_from(v: Vec) -> Result { 379 | // Check if the argument is a valid `Multiaddr` by reading its protocols. 380 | let mut slice = &v[..]; 381 | while !slice.is_empty() { 382 | let (_, s) = Protocol::from_bytes(slice)?; 383 | slice = s 384 | } 385 | Ok(Multiaddr { 386 | bytes: Bytes::from(v), 387 | }) 388 | } 389 | } 390 | 391 | impl TryFrom for Multiaddr { 392 | type Error = Error; 393 | 394 | fn try_from(s: String) -> Result { 395 | s.parse() 396 | } 397 | } 398 | 399 | impl<'a> TryFrom<&'a str> for Multiaddr { 400 | type Error = Error; 401 | 402 | fn try_from(s: &'a str) -> Result { 403 | s.parse() 404 | } 405 | } 406 | 407 | impl Serialize for Multiaddr { 408 | fn serialize(&self, serializer: S) -> StdResult 409 | where 410 | S: Serializer, 411 | { 412 | if serializer.is_human_readable() { 413 | serializer.serialize_str(&self.to_string()) 414 | } else { 415 | serializer.serialize_bytes(self.as_ref()) 416 | } 417 | } 418 | } 419 | 420 | impl<'de> Deserialize<'de> for Multiaddr { 421 | fn deserialize(deserializer: D) -> StdResult 422 | where 423 | D: Deserializer<'de>, 424 | { 425 | struct Visitor { 426 | is_human_readable: bool, 427 | } 428 | 429 | impl<'de> de::Visitor<'de> for Visitor { 430 | type Value = Multiaddr; 431 | 432 | fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 433 | formatter.write_str("multiaddress") 434 | } 435 | fn visit_seq>( 436 | self, 437 | mut seq: A, 438 | ) -> StdResult { 439 | let mut buf: Vec = 440 | Vec::with_capacity(std::cmp::min(seq.size_hint().unwrap_or(0), 4096)); 441 | while let Some(e) = seq.next_element()? { 442 | buf.push(e); 443 | } 444 | if self.is_human_readable { 445 | let s = String::from_utf8(buf).map_err(DeserializerError::custom)?; 446 | s.parse().map_err(DeserializerError::custom) 447 | } else { 448 | Multiaddr::try_from(buf).map_err(DeserializerError::custom) 449 | } 450 | } 451 | fn visit_str(self, v: &str) -> StdResult { 452 | v.parse().map_err(DeserializerError::custom) 453 | } 454 | fn visit_borrowed_str(self, v: &'de str) -> StdResult { 455 | self.visit_str(v) 456 | } 457 | fn visit_string(self, v: String) -> StdResult { 458 | self.visit_str(&v) 459 | } 460 | fn visit_bytes(self, v: &[u8]) -> StdResult { 461 | self.visit_byte_buf(v.into()) 462 | } 463 | fn visit_borrowed_bytes(self, v: &'de [u8]) -> StdResult { 464 | self.visit_byte_buf(v.into()) 465 | } 466 | fn visit_byte_buf(self, v: Vec) -> StdResult { 467 | Multiaddr::try_from(v).map_err(DeserializerError::custom) 468 | } 469 | } 470 | 471 | if deserializer.is_human_readable() { 472 | deserializer.deserialize_str(Visitor { 473 | is_human_readable: true, 474 | }) 475 | } else { 476 | deserializer.deserialize_bytes(Visitor { 477 | is_human_readable: false, 478 | }) 479 | } 480 | } 481 | } 482 | 483 | /// Easy way for a user to create a `Multiaddr`. 484 | /// 485 | /// Example: 486 | /// 487 | /// ```rust 488 | /// # use multiaddr::multiaddr; 489 | /// let addr = multiaddr!(Ip4([127, 0, 0, 1]), Tcp(10500u16)); 490 | /// ``` 491 | /// 492 | /// Each element passed to `multiaddr!` should be a variant of the `Protocol` enum. The 493 | /// optional parameter is turned into the proper type with the `Into` trait. 494 | /// 495 | /// For example, `Ip4([127, 0, 0, 1])` works because `Ipv4Addr` implements `From<[u8; 4]>`. 496 | #[macro_export] 497 | macro_rules! multiaddr { 498 | ($($comp:ident $(($param:expr))*),+) => { 499 | { 500 | use std::iter; 501 | let elem = iter::empty::<$crate::Protocol>(); 502 | $( 503 | let elem = { 504 | let cmp = $crate::Protocol::$comp $(( $param.into() ))*; 505 | elem.chain(iter::once(cmp)) 506 | }; 507 | )+ 508 | elem.collect::<$crate::Multiaddr>() 509 | } 510 | } 511 | } 512 | -------------------------------------------------------------------------------- /tests/lib.rs: -------------------------------------------------------------------------------- 1 | use data_encoding::HEXUPPER; 2 | use multiaddr::*; 3 | use multihash::Multihash; 4 | use quickcheck::{Arbitrary, Gen, QuickCheck}; 5 | use std::{ 6 | borrow::Cow, 7 | convert::{TryFrom, TryInto}, 8 | iter::{self, FromIterator}, 9 | net::{Ipv4Addr, Ipv6Addr}, 10 | str::FromStr, 11 | }; 12 | 13 | // Property tests 14 | 15 | #[test] 16 | fn to_from_bytes_identity() { 17 | fn prop(a: Ma) -> bool { 18 | let b = a.0.to_vec(); 19 | Some(a) == Multiaddr::try_from(b).ok().map(Ma) 20 | } 21 | QuickCheck::new().quickcheck(prop as fn(Ma) -> bool) 22 | } 23 | 24 | #[test] 25 | fn to_from_str_identity() { 26 | fn prop(a: Ma) -> bool { 27 | let b = a.0.to_string(); 28 | Some(a) == Multiaddr::from_str(&b).ok().map(Ma) 29 | } 30 | QuickCheck::new().quickcheck(prop as fn(Ma) -> bool) 31 | } 32 | 33 | #[test] 34 | fn byteswriter() { 35 | fn prop(a: Ma, b: Ma) -> bool { 36 | let mut x = a.0.clone(); 37 | for p in b.0.iter() { 38 | x = x.with(p) 39 | } 40 | x.iter() 41 | .zip(a.0.iter().chain(b.0.iter())) 42 | .all(|(x, y)| x == y) 43 | } 44 | QuickCheck::new().quickcheck(prop as fn(Ma, Ma) -> bool) 45 | } 46 | 47 | #[test] 48 | fn push_pop_identity() { 49 | fn prop(a: Ma, p: Proto) -> bool { 50 | let mut b = a.clone(); 51 | let q = p.clone(); 52 | b.0.push(q.0); 53 | assert_ne!(a.0, b.0); 54 | Some(p.0) == b.0.pop() && a.0 == b.0 55 | } 56 | QuickCheck::new().quickcheck(prop as fn(Ma, Proto) -> bool) 57 | } 58 | 59 | #[test] 60 | fn ends_with() { 61 | fn prop(Ma(m): Ma) { 62 | let n = m.iter().count(); 63 | for i in 0..n { 64 | let suffix = m.iter().skip(i).collect::(); 65 | assert!(m.ends_with(&suffix)); 66 | } 67 | } 68 | QuickCheck::new().quickcheck(prop as fn(_)) 69 | } 70 | 71 | #[test] 72 | fn starts_with() { 73 | fn prop(Ma(m): Ma) { 74 | let n = m.iter().count(); 75 | for i in 0..n { 76 | let prefix = m.iter().take(i + 1).collect::(); 77 | assert!(m.starts_with(&prefix)); 78 | } 79 | } 80 | QuickCheck::new().quickcheck(prop as fn(_)) 81 | } 82 | 83 | // Arbitrary impls 84 | 85 | #[derive(PartialEq, Eq, Clone, Hash, Debug)] 86 | struct Ma(Multiaddr); 87 | 88 | impl Arbitrary for Ma { 89 | fn arbitrary(g: &mut Gen) -> Self { 90 | let iter = (0..u8::arbitrary(g) % 128).map(|_| Proto::arbitrary(g).0); 91 | Ma(Multiaddr::from_iter(iter)) 92 | } 93 | } 94 | 95 | #[derive(PartialEq, Eq, Clone, Debug)] 96 | struct Proto(Protocol<'static>); 97 | 98 | impl Proto { 99 | const IMPL_VARIANT_COUNT: u8 = 40; 100 | } 101 | 102 | impl Arbitrary for Proto { 103 | fn arbitrary(g: &mut Gen) -> Self { 104 | use Protocol::*; 105 | match u8::arbitrary(g) % Proto::IMPL_VARIANT_COUNT { 106 | 0 => Proto(Dccp(Arbitrary::arbitrary(g))), 107 | 1 => Proto(Dns(Cow::Owned(SubString::arbitrary(g).0))), 108 | 2 => Proto(Dns4(Cow::Owned(SubString::arbitrary(g).0))), 109 | 3 => Proto(Dns6(Cow::Owned(SubString::arbitrary(g).0))), 110 | 4 => Proto(Dnsaddr(Cow::Owned(SubString::arbitrary(g).0))), 111 | 5 => Proto(Http), 112 | 6 => Proto(Https), 113 | 7 => Proto(Ip4(Ipv4Addr::arbitrary(g))), 114 | 8 => Proto(Ip6(Ipv6Addr::arbitrary(g))), 115 | 9 => Proto(P2pWebRtcDirect), 116 | 10 => Proto(P2pWebRtcStar), 117 | 11 => Proto(WebRTCDirect), 118 | 12 => Proto(Certhash(Mh::arbitrary(g).0)), 119 | 13 => Proto(P2pWebSocketStar), 120 | 14 => Proto(Memory(Arbitrary::arbitrary(g))), 121 | 15 => { 122 | let a = iter::repeat_with(|| u8::arbitrary(g)) 123 | .take(10) 124 | .collect::>() 125 | .try_into() 126 | .unwrap(); 127 | Proto(Onion(Cow::Owned(a), std::cmp::max(1, u16::arbitrary(g)))) 128 | } 129 | 16 => { 130 | let a: [u8; 35] = iter::repeat_with(|| u8::arbitrary(g)) 131 | .take(35) 132 | .collect::>() 133 | .try_into() 134 | .unwrap(); 135 | Proto(Onion3((a, std::cmp::max(1, u16::arbitrary(g))).into())) 136 | } 137 | 17 => Proto(P2p(PId::arbitrary(g).0)), 138 | 18 => Proto(P2pCircuit), 139 | 19 => Proto(Quic), 140 | 20 => Proto(QuicV1), 141 | 21 => Proto(Sctp(Arbitrary::arbitrary(g))), 142 | 22 => Proto(Tcp(Arbitrary::arbitrary(g))), 143 | 23 => Proto(Tls), 144 | 24 => Proto(Noise), 145 | 25 => Proto(Udp(Arbitrary::arbitrary(g))), 146 | 26 => Proto(Udt), 147 | 27 => Proto(Unix(Cow::Owned(SubString::arbitrary(g).0))), 148 | 28 => Proto(Utp), 149 | 29 => Proto(WebTransport), 150 | 30 => Proto(Ws("/".into())), 151 | 31 => Proto(Wss("/".into())), 152 | 32 => Proto(Ip6zone(Cow::Owned(SubString::arbitrary(g).0))), 153 | 33 => Proto(Ipcidr(Arbitrary::arbitrary(g))), 154 | 34 => { 155 | let len = usize::arbitrary(g) % (462 - 387) + 387; 156 | let a = iter::repeat_with(|| u8::arbitrary(g)) 157 | .take(len) 158 | .collect::>(); 159 | Proto(Garlic64(Cow::Owned(a))) 160 | } 161 | 35 => { 162 | let len = if bool::arbitrary(g) { 163 | 32 164 | } else { 165 | usize::arbitrary(g) % 128 + 35 166 | }; 167 | let a = iter::repeat_with(|| u8::arbitrary(g)) 168 | .take(len) 169 | .collect::>(); 170 | Proto(Garlic32(Cow::Owned(a))) 171 | } 172 | 36 => Proto(Sni(Cow::Owned(SubString::arbitrary(g).0))), 173 | 37 => Proto(P2pStardust), 174 | 38 => Proto(WebRTC), 175 | 39 => Proto(HttpPath(Cow::Owned(SubString::arbitrary(g).0))), 176 | _ => panic!("outside range"), 177 | } 178 | } 179 | } 180 | 181 | #[derive(Clone, Debug)] 182 | struct Mh(Multihash<64>); 183 | 184 | impl Arbitrary for Mh { 185 | fn arbitrary(g: &mut Gen) -> Self { 186 | let mut hash: [u8; 32] = [0; 32]; 187 | hash.fill_with(|| u8::arbitrary(g)); 188 | Mh(Multihash::wrap(0x0, &hash).expect("The digest size is never too large")) 189 | } 190 | } 191 | 192 | #[derive(Clone, Debug)] 193 | struct PId(PeerId); 194 | 195 | impl Arbitrary for PId { 196 | fn arbitrary(g: &mut Gen) -> Self { 197 | let mh = Mh::arbitrary(g); 198 | 199 | PId(PeerId::from_multihash(mh.0).expect("identity multihash works if digest size < 64")) 200 | } 201 | } 202 | 203 | #[derive(PartialEq, Eq, Clone, Debug)] 204 | struct SubString(String); // ASCII string without '/' 205 | 206 | impl Arbitrary for SubString { 207 | fn arbitrary(g: &mut Gen) -> Self { 208 | let mut s = String::arbitrary(g); 209 | s.retain(|c| c.is_ascii() && c != '/'); 210 | SubString(s) 211 | } 212 | } 213 | 214 | // other unit tests 215 | 216 | fn ma_valid(source: &str, target: &str, protocols: Vec>) { 217 | let parsed = source.parse::().unwrap(); 218 | assert_eq!(HEXUPPER.encode(&parsed.to_vec()[..]), target); 219 | assert_eq!(parsed.iter().collect::>(), protocols); 220 | assert_eq!(source.parse::().unwrap().to_string(), source); 221 | assert_eq!( 222 | Multiaddr::try_from(HEXUPPER.decode(target.as_bytes()).unwrap()).unwrap(), 223 | parsed 224 | ); 225 | } 226 | 227 | fn peer_id(s: &str) -> PeerId { 228 | s.parse().unwrap() 229 | } 230 | 231 | #[test] 232 | fn multiaddr_eq() { 233 | let m1 = "/ip4/127.0.0.1/udp/1234".parse::().unwrap(); 234 | let m2 = "/ip4/127.0.0.1/tcp/1234".parse::().unwrap(); 235 | let m3 = "/ip4/127.0.0.1/tcp/1234".parse::().unwrap(); 236 | 237 | assert_ne!(m1, m2); 238 | assert_ne!(m2, m1); 239 | assert_eq!(m2, m3); 240 | assert_eq!(m1, m1); 241 | } 242 | 243 | #[test] 244 | fn construct_success() { 245 | use Protocol::*; 246 | 247 | let local: Ipv4Addr = "127.0.0.1".parse().unwrap(); 248 | let addr6: Ipv6Addr = "2001:8a0:7ac5:4201:3ac9:86ff:fe31:7095".parse().unwrap(); 249 | 250 | ma_valid( 251 | "/ip4/1.2.3.4", 252 | "0401020304", 253 | vec![Ip4("1.2.3.4".parse().unwrap())], 254 | ); 255 | ma_valid( 256 | "/ip4/0.0.0.0", 257 | "0400000000", 258 | vec![Ip4("0.0.0.0".parse().unwrap())], 259 | ); 260 | ma_valid( 261 | "/ip6/::1", 262 | "2900000000000000000000000000000001", 263 | vec![Ip6("::1".parse().unwrap())], 264 | ); 265 | ma_valid( 266 | "/ip6/2601:9:4f81:9700:803e:ca65:66e8:c21", 267 | "29260100094F819700803ECA6566E80C21", 268 | vec![Ip6("2601:9:4f81:9700:803e:ca65:66e8:c21".parse().unwrap())], 269 | ); 270 | ma_valid( 271 | "/ip6/fe80::9700:803e:ca65:66e8:c21/ip6zone/wlan0", 272 | "29FE80000000009700803ECA6566E80C212A05776C616E30", 273 | vec![ 274 | Ip6("fe80::9700:803e:ca65:66e8:c21".parse().unwrap()), 275 | Ip6zone(Cow::Borrowed("wlan0")), 276 | ], 277 | ); 278 | ma_valid("/udp/0", "91020000", vec![Udp(0)]); 279 | ma_valid("/tcp/0", "060000", vec![Tcp(0)]); 280 | ma_valid("/sctp/0", "84010000", vec![Sctp(0)]); 281 | ma_valid("/udp/1234", "910204D2", vec![Udp(1234)]); 282 | ma_valid("/tcp/1234", "0604D2", vec![Tcp(1234)]); 283 | ma_valid("/sctp/1234", "840104D2", vec![Sctp(1234)]); 284 | ma_valid("/udp/65535", "9102FFFF", vec![Udp(65535)]); 285 | ma_valid("/tcp/65535", "06FFFF", vec![Tcp(65535)]); 286 | ma_valid( 287 | "/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC", 288 | "A503221220D52EBB89D85B02A284948203A62FF28389C57C9F42BEEC4EC20DB76A68911C0B", 289 | vec![P2p(peer_id( 290 | "QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC", 291 | ))], 292 | ); 293 | ma_valid( 294 | "/udp/1234/sctp/1234", 295 | "910204D2840104D2", 296 | vec![Udp(1234), Sctp(1234)], 297 | ); 298 | ma_valid("/udp/1234/udt", "910204D2AD02", vec![Udp(1234), Udt]); 299 | ma_valid("/udp/1234/utp", "910204D2AE02", vec![Udp(1234), Utp]); 300 | ma_valid("/tcp/1234/http", "0604D2E003", vec![Tcp(1234), Http]); 301 | ma_valid( 302 | "/tcp/1234/tls/http", 303 | "0604D2C003E003", 304 | vec![Tcp(1234), Tls, Http], 305 | ); 306 | ma_valid( 307 | "/tcp/1234/http/http-path/user", 308 | "0604D2E003E1030475736572", 309 | vec![Tcp(1234), Http, HttpPath(Cow::Borrowed("user"))], 310 | ); 311 | ma_valid( 312 | "/tcp/1234/http/http-path/api%2Fv0%2Flogin", 313 | "0604D2E003E1030C6170692F76302F6C6F67696E", 314 | vec![Tcp(1234), Http, HttpPath(Cow::Borrowed("api/v0/login"))], 315 | ); 316 | ma_valid( 317 | "/tcp/1234/http/http-path/a%2520space", 318 | "0604D2E003E10309612532307370616365", 319 | vec![Tcp(1234), Http, HttpPath(Cow::Borrowed("a%20space"))], 320 | ); 321 | ma_valid("/tcp/1234/https", "0604D2BB03", vec![Tcp(1234), Https]); 322 | ma_valid( 323 | "/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC/tcp/1234", 324 | "A503221220D52EBB89D85B02A284948203A62FF28389C57C9F42BEEC4EC20DB76A68911C0B0604D2", 325 | vec![ 326 | P2p(peer_id("QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC")), 327 | Tcp(1234), 328 | ], 329 | ); 330 | ma_valid( 331 | "/ip4/127.0.0.1/udp/1234", 332 | "047F000001910204D2", 333 | vec![Ip4(local), Udp(1234)], 334 | ); 335 | ma_valid( 336 | "/ip4/127.0.0.1/udp/0", 337 | "047F00000191020000", 338 | vec![Ip4(local), Udp(0)], 339 | ); 340 | ma_valid( 341 | "/ip4/127.0.0.1/tcp/1234", 342 | "047F0000010604D2", 343 | vec![Ip4(local), Tcp(1234)], 344 | ); 345 | ma_valid( 346 | "/ip4/127.0.0.1/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC", 347 | "047F000001A503221220D52EBB89D85B02A284948203A62FF28389C57C9F42BEEC4EC20DB76A68911C0B", 348 | vec![ 349 | Ip4(local), 350 | P2p(peer_id("QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC")), 351 | ], 352 | ); 353 | ma_valid("/ip4/127.0.0.1/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC/tcp/1234", 354 | "047F000001A503221220D52EBB89D85B02A284948203A62FF28389C57C9F42BEEC4EC20DB76A68911C0B0604D2", 355 | vec![Ip4(local), P2p(peer_id("QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC")), Tcp(1234)]); 356 | // /unix/a/b/c/d/e, 357 | // /unix/stdio, 358 | // /ip4/1.2.3.4/tcp/80/unix/a/b/c/d/e/f, 359 | // /ip4/127.0.0.1/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC/tcp/1234/unix/stdio 360 | ma_valid("/ip6/2001:8a0:7ac5:4201:3ac9:86ff:fe31:7095/tcp/8000/ws/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC", 361 | "29200108A07AC542013AC986FFFE317095061F40DD03A503221220D52EBB89D85B02A284948203A62FF28389C57C9F42BEEC4EC20DB76A68911C0B", 362 | vec![Ip6(addr6), Tcp(8000), Ws("/".into()), P2p(peer_id("QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC")) 363 | ]); 364 | ma_valid("/p2p-webrtc-star/ip4/127.0.0.1/tcp/9090/ws/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC", 365 | "9302047F000001062382DD03A503221220D52EBB89D85B02A284948203A62FF28389C57C9F42BEEC4EC20DB76A68911C0B", 366 | vec![P2pWebRtcStar, Ip4(local), Tcp(9090), Ws("/".into()), P2p(peer_id("QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC")) 367 | ]); 368 | ma_valid("/ip6/2001:8a0:7ac5:4201:3ac9:86ff:fe31:7095/tcp/8000/wss/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC", 369 | "29200108A07AC542013AC986FFFE317095061F40DE03A503221220D52EBB89D85B02A284948203A62FF28389C57C9F42BEEC4EC20DB76A68911C0B", 370 | vec![Ip6(addr6), Tcp(8000), Wss("/".into()), P2p(peer_id("QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC"))]); 371 | ma_valid("/ip4/127.0.0.1/tcp/9090/p2p-circuit/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC", 372 | "047F000001062382A202A503221220D52EBB89D85B02A284948203A62FF28389C57C9F42BEEC4EC20DB76A68911C0B", 373 | vec![Ip4(local), Tcp(9090), P2pCircuit, P2p(peer_id("QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC"))]); 374 | 375 | ma_valid( 376 | "/onion/aaimaq4ygg2iegci:80", 377 | "BC030010C0439831B48218480050", 378 | vec![Onion( 379 | Cow::Owned([0, 16, 192, 67, 152, 49, 180, 130, 24, 72]), 380 | 80, 381 | )], 382 | ); 383 | ma_valid( 384 | "/onion3/vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd:1234", 385 | "BD03ADADEC040BE047F9658668B11A504F3155001F231A37F54C4476C07FB4CC139ED7E30304D2", 386 | vec![Onion3( 387 | ( 388 | [ 389 | 173, 173, 236, 4, 11, 224, 71, 249, 101, 134, 104, 177, 26, 80, 79, 49, 85, 0, 390 | 31, 35, 26, 55, 245, 76, 68, 118, 192, 127, 180, 204, 19, 158, 215, 227, 3, 391 | ], 392 | 1234, 393 | ) 394 | .into(), 395 | )], 396 | ); 397 | ma_valid( 398 | "/garlic64/jT~IyXaoauTni6N4517EG8mrFUKpy0IlgZh-EY9csMAk82Odatmzr~YTZy8Hv7u~wvkg75EFNOyqb~nAPg-khyp2TS~ObUz8WlqYAM2VlEzJ7wJB91P-cUlKF\ 399 | 18zSzVoJFmsrcQHZCirSbWoOknS6iNmsGRh5KVZsBEfp1Dg3gwTipTRIx7Vl5Vy~1OSKQVjYiGZS9q8RL0MF~7xFiKxZDLbPxk0AK9TzGGqm~wMTI2HS0Gm4Ycy8LYPVmLvG\ 400 | onIBYndg2bJC7WLuF6tVjVquiokSVDKFwq70BCUU5AU-EvdOD5KEOAM7mPfw-gJUG4tm1TtvcobrObqoRnmhXPTBTN5H7qDD12AvlwFGnfAlBXjuP4xOUAISL5SRLiulrsMS\ 401 | iT4GcugSI80mF6sdB0zWRgL1yyvoVWeTBn1TqjO27alr95DGTluuSqrNAxgpQzCKEWAyzrQkBfo2avGAmmz2NaHaAvYbOg0QSJz1PLjv2jdPW~ofiQmrGWM1cd~1cCqAAAA", 402 | "BE0383038D3FC8C976A86AE4E78BA378E75EC41BC9AB1542A9CB422581987E118F5CB0C024F3639D6AD9B3AFF613672F07BFBBBFC2F920EF910534ECAA6FF9C03E\ 403 | 0FA4872A764D2FCE6D4CFC5A5A9800CD95944CC9EF0241F753FE71494A175F334B35682459ACADC4076428AB49B5A83A49D2EA2366B06461E4A559B0111FA750E0D\ 404 | E0C138A94D1231ED5979572FF53922905636221994BDABC44BD0C17FEF11622B16432DB3F193400AF53CC61AA9BFC0C4C8D874B41A6E18732F0B60F5662EF1A89C8\ 405 | 0589DD8366C90BB58BB85EAD56356ABA2A244950CA170ABBD01094539014F84BDD383E4A10E00CEE63DFC3E809506E2D9B54EDBDCA1BACE6EAA119E68573D305337\ 406 | 91FBA830F5D80BE5C051A77C09415E3B8FE3139400848BE5244B8AE96BB0C4A24F819CBA0488F34985EAC741D3359180BD72CAFA1559E4C19F54EA8CEDBB6A5AFDE\ 407 | 4319396EB92AAB340C60A50CC2284580CB3AD09017E8D9ABC60269B3D8D687680BD86CE834412273D4F2E3BF68DD3D6FE87E2426AC658CD5C77FD5C0AA000000", 408 | vec![Garlic64( 409 | ( 410 | &[ 411 | 141, 63, 200, 201, 118, 168, 106, 228, 231, 139, 163, 120, 231, 94, 196, 27, 201, 171, 21, 66, 412 | 169, 203, 66, 37, 129, 152, 126, 17, 143, 92, 176, 192, 36, 243, 99, 157, 106, 217, 179, 175, 413 | 246, 19, 103, 47, 7, 191, 187, 191, 194, 249, 32, 239, 145, 5, 52, 236, 170, 111, 249, 192, 414 | 62, 15, 164, 135, 42, 118, 77, 47, 206, 109, 76, 252, 90, 90, 152, 0, 205, 149, 148, 76, 415 | 201, 239, 2, 65, 247, 83, 254, 113, 73, 74, 23, 95, 51, 75, 53, 104, 36, 89, 172, 173, 416 | 196, 7, 100, 40, 171, 73, 181, 168, 58, 73, 210, 234, 35, 102, 176, 100, 97, 228, 165, 89, 417 | 176, 17, 31, 167, 80, 224, 222, 12, 19, 138, 148, 209, 35, 30, 213, 151, 149, 114, 255, 83, 418 | 146, 41, 5, 99, 98, 33, 153, 75, 218, 188, 68, 189, 12, 23, 254, 241, 22, 34, 177, 100, 419 | 50, 219, 63, 25, 52, 0, 175, 83, 204, 97, 170, 155, 252, 12, 76, 141, 135, 75, 65, 166, 420 | 225, 135, 50, 240, 182, 15, 86, 98, 239, 26, 137, 200, 5, 137, 221, 131, 102, 201, 11, 181, 421 | 139, 184, 94, 173, 86, 53, 106, 186, 42, 36, 73, 80, 202, 23, 10, 187, 208, 16, 148, 83, 422 | 144, 20, 248, 75, 221, 56, 62, 74, 16, 224, 12, 238, 99, 223, 195, 232, 9, 80, 110, 45, 423 | 155, 84, 237, 189, 202, 27, 172, 230, 234, 161, 25, 230, 133, 115, 211, 5, 51, 121, 31, 186, 424 | 131, 15, 93, 128, 190, 92, 5, 26, 119, 192, 148, 21, 227, 184, 254, 49, 57, 64, 8, 72, 425 | 190, 82, 68, 184, 174, 150, 187, 12, 74, 36, 248, 25, 203, 160, 72, 143, 52, 152, 94, 172, 426 | 116, 29, 51, 89, 24, 11, 215, 44, 175, 161, 85, 158, 76, 25, 245, 78, 168, 206, 219, 182, 427 | 165, 175, 222, 67, 25, 57, 110, 185, 42, 171, 52, 12, 96, 165, 12, 194, 40, 69, 128, 203, 428 | 58, 208, 144, 23, 232, 217, 171, 198, 2, 105, 179, 216, 214, 135, 104, 11, 216, 108, 232, 52, 429 | 65, 34, 115, 212, 242, 227, 191, 104, 221, 61, 111, 232, 126, 36, 38, 172, 101, 140, 213, 199, 430 | 127, 213, 192, 170, 0, 0, 0, 431 | ] 432 | ).into() 433 | )], 434 | ); 435 | ma_valid( 436 | "/dnsaddr/sjc-1.bootstrap.libp2p.io", 437 | "3819736A632D312E626F6F7473747261702E6C69627032702E696F", 438 | vec![Dnsaddr(Cow::Borrowed("sjc-1.bootstrap.libp2p.io"))], 439 | ); 440 | ma_valid( 441 | "/dnsaddr/sjc-1.bootstrap.libp2p.io/tcp/1234/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN", 442 | "3819736A632D312E626F6F7473747261702E6C69627032702E696F0604D2A50322122006B3608AA000274049EB28AD8E793A26FF6FAB281A7D3BD77CD18EB745DFAABB", 443 | vec![Dnsaddr(Cow::Borrowed("sjc-1.bootstrap.libp2p.io")), Tcp(1234), P2p(peer_id("QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN"))] 444 | ); 445 | ma_valid( 446 | "/ip4/127.0.0.1/tcp/127/ws", 447 | "047F00000106007FDD03", 448 | vec![Ip4(local), Tcp(127), Ws("/".into())], 449 | ); 450 | ma_valid( 451 | "/ip4/127.0.0.1/tcp/127/tls", 452 | "047F00000106007FC003", 453 | vec![Ip4(local), Tcp(127), Tls], 454 | ); 455 | ma_valid( 456 | "/ip4/127.0.0.1/tcp/127/tls/ws", 457 | "047F00000106007FC003DD03", 458 | vec![Ip4(local), Tcp(127), Tls, Ws("/".into())], 459 | ); 460 | 461 | ma_valid( 462 | "/ip4/127.0.0.1/tcp/127/noise", 463 | "047F00000106007FC603", 464 | vec![Ip4(local), Tcp(127), Noise], 465 | ); 466 | 467 | ma_valid( 468 | "/ip4/127.0.0.1/udp/1234/webrtc-direct", 469 | "047F000001910204D29802", 470 | vec![Ip4(local), Udp(1234), WebRTCDirect], 471 | ); 472 | 473 | let (_base, decoded) = 474 | multibase::decode("uEiDDq4_xNyDorZBH3TlGazyJdOWSwvo4PUo5YHFMrvDE8g").unwrap(); 475 | ma_valid( 476 | "/ip4/127.0.0.1/udp/1234/webrtc-direct/certhash/uEiDDq4_xNyDorZBH3TlGazyJdOWSwvo4PUo5YHFMrvDE8g", 477 | "047F000001910204D29802D203221220C3AB8FF13720E8AD9047DD39466B3C8974E592C2FA383D4A3960714CAEF0C4F2", 478 | vec![ 479 | Ip4(local), 480 | Udp(1234), 481 | WebRTCDirect, 482 | Certhash(Multihash::from_bytes(&decoded).unwrap()), 483 | ], 484 | ); 485 | 486 | ma_valid( 487 | "/ip4/127.0.0.1/udp/1234/quic/webtransport", 488 | "047F000001910204D2CC03D103", 489 | vec![Ip4(local), Udp(1234), Quic, WebTransport], 490 | ); 491 | 492 | let (_base, decoded) = 493 | multibase::decode("uEiDDq4_xNyDorZBH3TlGazyJdOWSwvo4PUo5YHFMrvDE8g").unwrap(); 494 | ma_valid( 495 | "/ip4/127.0.0.1/udp/1234/webtransport/certhash/uEiDDq4_xNyDorZBH3TlGazyJdOWSwvo4PUo5YHFMrvDE8g", 496 | "047F000001910204D2D103D203221220C3AB8FF13720E8AD9047DD39466B3C8974E592C2FA383D4A3960714CAEF0C4F2", 497 | vec![ 498 | Ip4(local), 499 | Udp(1234), 500 | WebTransport, 501 | Certhash(Multihash::from_bytes(&decoded).unwrap()), 502 | ], 503 | ); 504 | } 505 | 506 | #[test] 507 | fn construct_fail() { 508 | let addresses = [ 509 | "/ip4", 510 | "/ip4/::1", 511 | "/ip4/fdpsofodsajfdoisa", 512 | "/ip6", 513 | "/ip6/fe80::9700:803e:ca65:66e8:c21/ip6zone", 514 | "/udp", 515 | "/tcp", 516 | "/sctp", 517 | "/udp/65536", 518 | "/tcp/65536", 519 | "/onion/9imaq4ygg2iegci7:80", 520 | "/onion/aaimaq4ygg2iegci7:80", 521 | "/onion/timaq4ygg2iegci7:0", 522 | "/onion/timaq4ygg2iegci7:-1", 523 | "/onion/timaq4ygg2iegci7", 524 | "/onion/timaq4ygg2iegci@:666", 525 | "/onion3/9ww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd:80", 526 | "/onion3/vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd7:80", 527 | "/onion3/vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd:0", 528 | "/onion3/vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd:-1", 529 | "/onion3/vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd", 530 | "/onion3/vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyy@:666", 531 | "/garlic64/jT~", 532 | "/garlic32/566niximlxdzpanmn4qouucvua3k7neniwss47li5r6ugoertzu", 533 | "/garlic32/566niximlxdzpanmn4qouucvua3k7neniwss47li5r6ugoertzu77", 534 | "/garlic32/566niximlxdzpanmn4qouucvua3k7neniwss47li5r6ugoertzu:80", 535 | "/garlic32/566niximlxdzpanmn4qouucvua3k7neniwss47li5r6ugoertzuq:-1", 536 | "/garlic32/566niximlxdzpanmn4qouucvua3k7neniwss47li5r6ugoertzu@", 537 | "/udp/1234/sctp", 538 | "/udp/1234/udt/1234", 539 | "/udp/1234/utp/1234", 540 | "/ip4/127.0.0.1/udp/jfodsajfidosajfoidsa", 541 | "/ip4/127.0.0.1/udp", 542 | "/ip4/127.0.0.1/tcp/jfodsajfidosajfoidsa", 543 | "/ip4/127.0.0.1/tcp", 544 | "/ip4/127.0.0.1/p2p", 545 | "/ip4/127.0.0.1/p2p/tcp", 546 | "/p2p-circuit/50", 547 | "/ip4/127.0.0.1/udp/1234/webrtc-direct/certhash", 548 | "/ip4/127.0.0.1/udp/1234/webrtc-direct/certhash/b2uaraocy6yrdblb4sfptaddgimjmmp", // 1 character missing from certhash 549 | "/tcp/1234/http/http-path/a/b", 550 | ]; 551 | 552 | for address in &addresses { 553 | assert!( 554 | address.parse::().is_err(), 555 | "{}", 556 | address.to_string() 557 | ); 558 | } 559 | } 560 | 561 | #[test] 562 | fn to_multiaddr() { 563 | assert_eq!( 564 | Multiaddr::from(Ipv4Addr::new(127, 0, 0, 1)), 565 | "/ip4/127.0.0.1".parse().unwrap() 566 | ); 567 | assert_eq!( 568 | Multiaddr::from(Ipv6Addr::new( 569 | 0x2601, 0x9, 0x4f81, 0x9700, 0x803e, 0xca65, 0x66e8, 0xc21 570 | )), 571 | "/ip6/2601:9:4f81:9700:803e:ca65:66e8:c21".parse().unwrap() 572 | ); 573 | assert_eq!( 574 | Multiaddr::try_from("/ip4/127.0.0.1/tcp/1234".to_string()).unwrap(), 575 | "/ip4/127.0.0.1/tcp/1234".parse::().unwrap() 576 | ); 577 | assert_eq!( 578 | Multiaddr::try_from("/ip6/2601:9:4f81:9700:803e:ca65:66e8:c21").unwrap(), 579 | "/ip6/2601:9:4f81:9700:803e:ca65:66e8:c21" 580 | .parse::() 581 | .unwrap() 582 | ); 583 | assert_eq!( 584 | Multiaddr::from(Ipv4Addr::new(127, 0, 0, 1)).with(Protocol::Tcp(1234)), 585 | "/ip4/127.0.0.1/tcp/1234".parse::().unwrap() 586 | ); 587 | assert_eq!( 588 | Multiaddr::from(Ipv6Addr::new( 589 | 0x2601, 0x9, 0x4f81, 0x9700, 0x803e, 0xca65, 0x66e8, 0xc21 590 | )) 591 | .with(Protocol::Tcp(1234)), 592 | "/ip6/2601:9:4f81:9700:803e:ca65:66e8:c21/tcp/1234" 593 | .parse::() 594 | .unwrap() 595 | ); 596 | } 597 | 598 | #[test] 599 | fn from_bytes_fail() { 600 | let bytes = vec![1, 2, 3, 4]; 601 | assert!(Multiaddr::try_from(bytes).is_err()); 602 | } 603 | 604 | #[test] 605 | fn ser_and_deser_json() { 606 | let addr: Multiaddr = "/ip4/0.0.0.0/tcp/0/tls".parse::().unwrap(); 607 | let serialized = serde_json::to_string(&addr).unwrap(); 608 | assert_eq!(serialized, "\"/ip4/0.0.0.0/tcp/0/tls\""); 609 | let deserialized: Multiaddr = serde_json::from_str(&serialized).unwrap(); 610 | assert_eq!(addr, deserialized); 611 | } 612 | 613 | #[test] 614 | fn ser_and_deser_bincode() { 615 | let addr: Multiaddr = "/ip4/0.0.0.0/tcp/0/tls".parse::().unwrap(); 616 | let serialized = bincode::serialize(&addr).unwrap(); 617 | // compact addressing 618 | assert_eq!( 619 | serialized, 620 | vec![10, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 6, 0, 0, 192, 3] 621 | ); 622 | let deserialized: Multiaddr = bincode::deserialize(&serialized).unwrap(); 623 | assert_eq!(addr, deserialized); 624 | } 625 | 626 | #[test] 627 | fn append() { 628 | let mut a: Multiaddr = Protocol::Ip4(Ipv4Addr::new(1, 2, 3, 4)).into(); 629 | a.push(Protocol::Tcp(80)); 630 | a.push(Protocol::Http); 631 | 632 | let mut i = a.iter(); 633 | assert_eq!(Some(Protocol::Ip4(Ipv4Addr::new(1, 2, 3, 4))), i.next()); 634 | assert_eq!(Some(Protocol::Tcp(80)), i.next()); 635 | assert_eq!(Some(Protocol::Http), i.next()); 636 | assert_eq!(None, i.next()) 637 | } 638 | 639 | fn replace_ip_addr(a: &Multiaddr, p: Protocol<'_>) -> Option { 640 | a.replace(0, move |x| match x { 641 | Protocol::Ip4(_) | Protocol::Ip6(_) => Some(p), 642 | _ => None, 643 | }) 644 | } 645 | 646 | #[test] 647 | fn replace_ip4_with_ip4() { 648 | let server = multiaddr!(Ip4(Ipv4Addr::LOCALHOST), Tcp(10000u16)); 649 | let result = replace_ip_addr(&server, Protocol::Ip4([80, 81, 82, 83].into())).unwrap(); 650 | assert_eq!(result, multiaddr!(Ip4([80, 81, 82, 83]), Tcp(10000u16))) 651 | } 652 | 653 | #[test] 654 | fn replace_ip6_with_ip4() { 655 | let server = multiaddr!(Ip6(Ipv6Addr::LOCALHOST), Tcp(10000u16)); 656 | let result = replace_ip_addr(&server, Protocol::Ip4([80, 81, 82, 83].into())).unwrap(); 657 | assert_eq!(result, multiaddr!(Ip4([80, 81, 82, 83]), Tcp(10000u16))) 658 | } 659 | 660 | #[test] 661 | fn replace_ip4_with_ip6() { 662 | let server = multiaddr!(Ip4(Ipv4Addr::LOCALHOST), Tcp(10000u16)); 663 | let result = replace_ip_addr(&server, "2001:db8::1".parse::().unwrap().into()); 664 | assert_eq!( 665 | result.unwrap(), 666 | "/ip6/2001:db8::1/tcp/10000".parse::().unwrap() 667 | ) 668 | } 669 | 670 | #[test] 671 | fn unknown_protocol_string() { 672 | match "/unknown/1.2.3.4".parse::() { 673 | Ok(_) => panic!("The UnknownProtocolString error should be caused"), 674 | Err(e) => match e { 675 | crate::Error::UnknownProtocolString(protocol) => { 676 | assert_eq!(protocol, "unknown") 677 | } 678 | _ => panic!("The UnknownProtocolString error should be caused"), 679 | }, 680 | } 681 | } 682 | 683 | #[test] 684 | fn protocol_stack() { 685 | let addresses = [ 686 | "/ip4/0.0.0.0", 687 | "/ip6/::1", 688 | "/ip6/2601:9:4f81:9700:803e:ca65:66e8:c21", 689 | "/ip6/fe80::9700:803e:ca65:66e8:c21/ip6zone/wlan0", 690 | "/udp/0", 691 | "/tcp/0", 692 | "/sctp/0", 693 | "/udp/1234", 694 | "/tcp/1234", 695 | "/sctp/1234", 696 | "/udp/65535", 697 | "/tcp/65535", 698 | "/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC", 699 | "/udp/1234/sctp/1234", 700 | "/udp/1234/udt", 701 | "/udp/1234/utp", 702 | "/tcp/1234/http", 703 | "/tcp/1234/tls/http", 704 | "/tcp/1234/http/http-path/user", 705 | "/tcp/1234/http/http-path/api%2Fv1%2Flogin", 706 | "/tcp/1234/http/http-path/a%20space", 707 | "/tcp/1234/https", 708 | "/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC/tcp/1234", 709 | "/ip4/127.0.0.1/udp/1234", 710 | "/ip4/127.0.0.1/udp/0", 711 | "/ip4/127.0.0.1/tcp/1234", 712 | "/ip4/127.0.0.1/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC", 713 | "/ip4/127.0.0.1/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC/tcp/1234", 714 | "/ip6/2001:8a0:7ac5:4201:3ac9:86ff:fe31:7095/tcp/8000/ws/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC", 715 | "/p2p-webrtc-star/ip4/127.0.0.1/tcp/9090/ws/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC", 716 | "/ip6/2001:8a0:7ac5:4201:3ac9:86ff:fe31:7095/tcp/8000/wss/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC", 717 | "/ip4/127.0.0.1/tcp/9090/p2p-circuit/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC", 718 | "/onion/aaimaq4ygg2iegci:80", 719 | "/dnsaddr/sjc-1.bootstrap.libp2p.io", 720 | "/dnsaddr/sjc-1.bootstrap.libp2p.io/tcp/1234/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN", 721 | "/ip4/127.0.0.1/tcp/127/ws", 722 | "/ip4/127.0.0.1/tcp/127/tls", 723 | "/ip4/127.0.0.1/tcp/127/tls/ws", 724 | "/ip4/127.0.0.1/tcp/127/noise", 725 | "/ip4/127.0.0.1/udp/1234/webrtc-direct", 726 | ]; 727 | let argless = std::collections::HashSet::from([ 728 | "http", 729 | "https", 730 | "noise", 731 | "p2p-circuit", 732 | "p2p-webrtc-direct", 733 | "p2p-webrtc-star", 734 | "p2p-websocket-star", 735 | "quic", 736 | "quic-v1", 737 | "tls", 738 | "udt", 739 | "utp", 740 | "webrtc-direct", 741 | "ws", 742 | "wss", 743 | ]); 744 | for addr_str in addresses { 745 | let ma = Multiaddr::from_str(addr_str).expect("These are supposed to be valid multiaddrs"); 746 | let ps: Vec<&str> = ma.protocol_stack().collect(); 747 | let mut toks: Vec<&str> = addr_str.split('/').collect(); 748 | assert_eq!("", toks[0]); 749 | toks.remove(0); 750 | let mut i = 0; 751 | while i < toks.len() { 752 | let proto_tag = toks[i]; 753 | i += 1; 754 | if argless.contains(proto_tag) { 755 | //skip 756 | } else { 757 | toks.remove(i); 758 | } 759 | } 760 | assert_eq!(ps, toks); 761 | } 762 | } 763 | 764 | // Assert all `Protocol` variants are covered 765 | // in its `Arbitrary` impl. 766 | #[cfg(nightly)] 767 | #[test] 768 | fn arbitrary_impl_for_all_proto_variants() { 769 | let variants = core::mem::variant_count::() as u8; 770 | assert_eq!(variants, Proto::IMPL_VARIANT_COUNT); 771 | } 772 | 773 | mod multiaddr_with_p2p { 774 | use multiaddr::{Multiaddr, PeerId}; 775 | 776 | fn test_multiaddr_with_p2p( 777 | multiaddr: &str, 778 | peer: &str, 779 | expected: std::result::Result<&str, &str>, 780 | ) { 781 | let peer = peer.parse::().unwrap(); 782 | let expected = expected 783 | .map(|a| a.parse::().unwrap()) 784 | .map_err(|a| a.parse::().unwrap()); 785 | 786 | let mut multiaddr = multiaddr.parse::().unwrap(); 787 | // Testing multiple time to validate idempotence. 788 | for _ in 0..3 { 789 | let result = multiaddr.with_p2p(peer); 790 | assert_eq!(result, expected); 791 | multiaddr = result.unwrap_or_else(|addr| addr); 792 | } 793 | } 794 | 795 | #[test] 796 | fn empty_multiaddr() { 797 | // Multiaddr is empty -> it should push and return Ok. 798 | test_multiaddr_with_p2p( 799 | "", 800 | "QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN", 801 | Ok("/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN"), 802 | ) 803 | } 804 | #[test] 805 | fn non_p2p_terminated() { 806 | // Last protocol is not p2p -> it should push and return Ok. 807 | test_multiaddr_with_p2p( 808 | "/ip4/127.0.0.1", 809 | "QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN", 810 | Ok("/ip4/127.0.0.1/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN"), 811 | ) 812 | } 813 | 814 | #[test] 815 | fn p2p_terminated_same_peer() { 816 | // Last protocol is p2p and the contained peer matches the provided one -> it should do nothing and return Ok. 817 | test_multiaddr_with_p2p( 818 | "/ip4/127.0.0.1/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN", 819 | "QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN", 820 | Ok("/ip4/127.0.0.1/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN"), 821 | ) 822 | } 823 | 824 | #[test] 825 | fn p2p_terminated_different_peer() { 826 | // Last protocol is p2p but the contained peer does not match the provided one -> it should do nothing and return Err. 827 | test_multiaddr_with_p2p( 828 | "/ip4/127.0.0.1/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN", 829 | "QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC", 830 | Err("/ip4/127.0.0.1/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN"), 831 | ) 832 | } 833 | } 834 | -------------------------------------------------------------------------------- /src/protocol.rs: -------------------------------------------------------------------------------- 1 | use crate::onion_addr::Onion3Addr; 2 | use crate::{Error, PeerId, Result}; 3 | use arrayref::array_ref; 4 | use byteorder::{BigEndian, ByteOrder, ReadBytesExt, WriteBytesExt}; 5 | use data_encoding::BASE32; 6 | use std::{ 7 | borrow::Cow, 8 | convert::From, 9 | fmt, 10 | io::{Cursor, Write}, 11 | net::{IpAddr, Ipv4Addr, Ipv6Addr}, 12 | str::{self, FromStr}, 13 | }; 14 | use unsigned_varint::{decode, encode}; 15 | 16 | // All the values are obtained by converting hexadecimal protocol codes to u32. 17 | // Protocols as well as their corresponding codes are defined in 18 | // https://github.com/multiformats/multiaddr/blob/master/protocols.csv . 19 | const DCCP: u32 = 33; 20 | const DNS: u32 = 53; 21 | const DNS4: u32 = 54; 22 | const DNS6: u32 = 55; 23 | const DNSADDR: u32 = 56; 24 | const HTTP: u32 = 480; 25 | const HTTPS: u32 = 443; // Deprecated - alias for /tls/http 26 | const IP4: u32 = 4; 27 | const IP6: u32 = 41; 28 | const P2P_WEBRTC_DIRECT: u32 = 276; // Deprecated 29 | const P2P_WEBRTC_STAR: u32 = 275; // Deprecated 30 | const WEBRTC_DIRECT: u32 = 280; 31 | const CERTHASH: u32 = 466; 32 | const P2P_WEBSOCKET_STAR: u32 = 479; // Deprecated 33 | const MEMORY: u32 = 777; 34 | const ONION: u32 = 444; 35 | const ONION3: u32 = 445; 36 | const P2P: u32 = 421; 37 | const P2P_CIRCUIT: u32 = 290; 38 | const QUIC: u32 = 460; 39 | const QUIC_V1: u32 = 461; 40 | const SCTP: u32 = 132; 41 | const TCP: u32 = 6; 42 | const TLS: u32 = 448; 43 | const NOISE: u32 = 454; 44 | const UDP: u32 = 273; 45 | const UDT: u32 = 301; 46 | const UNIX: u32 = 400; 47 | const UTP: u32 = 302; 48 | const WEBTRANSPORT: u32 = 465; 49 | const WS: u32 = 477; 50 | const WS_WITH_PATH: u32 = 4770; // Note: not standard 51 | const WSS: u32 = 478; // Deprecated - alias for /tls/ws 52 | const WSS_WITH_PATH: u32 = 4780; // Note: not standard 53 | const IP6ZONE: u32 = 42; 54 | const IPCIDR: u32 = 43; 55 | // const IPFS: u32 = 421; // Deprecated 56 | const GARLIC64: u32 = 446; 57 | const GARLIC32: u32 = 447; 58 | const SNI: u32 = 449; 59 | const P2P_STARDUST: u32 = 277; // Deprecated 60 | const WEBRTC: u32 = 281; 61 | const HTTP_PATH: u32 = 481; 62 | 63 | /// Type-alias for how multi-addresses use `Multihash`. 64 | /// 65 | /// The `64` defines the allocation size for the digest within the `Multihash`. 66 | /// This allows us to use hashes such as SHA512. 67 | /// In case protocols like `/certhash` ever support hashes larger than that, we will need to update this size here (which will be a breaking change!). 68 | type Multihash = multihash::Multihash<64>; 69 | 70 | const PATH_SEGMENT_ENCODE_SET: &percent_encoding::AsciiSet = &percent_encoding::CONTROLS 71 | .add(b'%') 72 | .add(b'/') 73 | .add(b'`') 74 | .add(b'?') 75 | .add(b'{') 76 | .add(b'}') 77 | .add(b' ') 78 | .add(b'"') 79 | .add(b'#') 80 | .add(b'<') 81 | .add(b'>'); 82 | 83 | /// `Protocol` describes all possible multiaddress protocols. 84 | /// 85 | /// For `Unix`, `Ws` and `Wss` we use `&str` instead of `Path` to allow 86 | /// cross-platform usage of `Protocol` since encoding `Paths` to bytes is 87 | /// platform-specific. This means that the actual validation of paths needs to 88 | /// happen separately. 89 | #[derive(PartialEq, Eq, Clone, Debug)] 90 | #[non_exhaustive] 91 | pub enum Protocol<'a> { 92 | Dccp(u16), 93 | Dns(Cow<'a, str>), 94 | Dns4(Cow<'a, str>), 95 | Dns6(Cow<'a, str>), 96 | Dnsaddr(Cow<'a, str>), 97 | Http, 98 | Https, 99 | Ip4(Ipv4Addr), 100 | Ip6(Ipv6Addr), 101 | P2pWebRtcDirect, 102 | P2pWebRtcStar, 103 | WebRTCDirect, 104 | Certhash(Multihash), 105 | P2pWebSocketStar, 106 | /// Contains the "port" to contact. Similar to TCP or UDP, 0 means "assign me a port". 107 | Memory(u64), 108 | Onion(Cow<'a, [u8; 10]>, u16), 109 | Onion3(Onion3Addr<'a>), 110 | P2p(PeerId), 111 | P2pCircuit, 112 | Quic, 113 | QuicV1, 114 | Sctp(u16), 115 | Tcp(u16), 116 | Tls, 117 | Noise, 118 | Udp(u16), 119 | Udt, 120 | Unix(Cow<'a, str>), 121 | Utp, 122 | WebTransport, 123 | Ws(Cow<'a, str>), 124 | Wss(Cow<'a, str>), 125 | Ip6zone(Cow<'a, str>), 126 | Ipcidr(u8), 127 | Garlic64(Cow<'a, [u8]>), 128 | Garlic32(Cow<'a, [u8]>), 129 | Sni(Cow<'a, str>), 130 | P2pStardust, 131 | WebRTC, 132 | HttpPath(Cow<'a, str>), 133 | } 134 | 135 | impl<'a> Protocol<'a> { 136 | /// Parse a protocol value from the given iterator of string slices. 137 | /// 138 | /// The parsing only consumes the minimum amount of string slices necessary to 139 | /// produce a well-formed protocol. The same iterator can thus be used to parse 140 | /// a sequence of protocols in succession. It is up to client code to check 141 | /// that iteration has finished whenever appropriate. 142 | pub fn from_str_parts(mut iter: I) -> Result 143 | where 144 | I: Iterator, 145 | { 146 | match iter.next().ok_or(Error::InvalidProtocolString)? { 147 | "ip4" => { 148 | let s = iter.next().ok_or(Error::InvalidProtocolString)?; 149 | Ok(Protocol::Ip4(Ipv4Addr::from_str(s)?)) 150 | } 151 | "tcp" => { 152 | let s = iter.next().ok_or(Error::InvalidProtocolString)?; 153 | Ok(Protocol::Tcp(s.parse()?)) 154 | } 155 | "tls" => Ok(Protocol::Tls), 156 | "noise" => Ok(Protocol::Noise), 157 | "udp" => { 158 | let s = iter.next().ok_or(Error::InvalidProtocolString)?; 159 | Ok(Protocol::Udp(s.parse()?)) 160 | } 161 | "dccp" => { 162 | let s = iter.next().ok_or(Error::InvalidProtocolString)?; 163 | Ok(Protocol::Dccp(s.parse()?)) 164 | } 165 | "ip6" => { 166 | let s = iter.next().ok_or(Error::InvalidProtocolString)?; 167 | Ok(Protocol::Ip6(Ipv6Addr::from_str(s)?)) 168 | } 169 | "dns" => { 170 | let s = iter.next().ok_or(Error::InvalidProtocolString)?; 171 | Ok(Protocol::Dns(Cow::Borrowed(s))) 172 | } 173 | "dns4" => { 174 | let s = iter.next().ok_or(Error::InvalidProtocolString)?; 175 | Ok(Protocol::Dns4(Cow::Borrowed(s))) 176 | } 177 | "dns6" => { 178 | let s = iter.next().ok_or(Error::InvalidProtocolString)?; 179 | Ok(Protocol::Dns6(Cow::Borrowed(s))) 180 | } 181 | "dnsaddr" => { 182 | let s = iter.next().ok_or(Error::InvalidProtocolString)?; 183 | Ok(Protocol::Dnsaddr(Cow::Borrowed(s))) 184 | } 185 | "sctp" => { 186 | let s = iter.next().ok_or(Error::InvalidProtocolString)?; 187 | Ok(Protocol::Sctp(s.parse()?)) 188 | } 189 | "udt" => Ok(Protocol::Udt), 190 | "utp" => Ok(Protocol::Utp), 191 | "unix" => { 192 | let s = iter.next().ok_or(Error::InvalidProtocolString)?; 193 | Ok(Protocol::Unix(Cow::Borrowed(s))) 194 | } 195 | "p2p" | "ipfs" => { 196 | let s = iter.next().ok_or(Error::InvalidProtocolString)?; 197 | let decoded = multibase::Base::Base58Btc.decode(s)?; 198 | let peer_id = 199 | PeerId::from_bytes(&decoded).map_err(|e| Error::ParsingError(Box::new(e)))?; 200 | Ok(Protocol::P2p(peer_id)) 201 | } 202 | "http" => Ok(Protocol::Http), 203 | "https" => Ok(Protocol::Https), 204 | "onion" => iter 205 | .next() 206 | .ok_or(Error::InvalidProtocolString) 207 | .and_then(|s| read_onion(&s.to_uppercase())) 208 | .map(|(a, p)| Protocol::Onion(Cow::Owned(a), p)), 209 | "onion3" => iter 210 | .next() 211 | .ok_or(Error::InvalidProtocolString) 212 | .and_then(|s| read_onion3(&s.to_uppercase())) 213 | .map(|(a, p)| Protocol::Onion3((a, p).into())), 214 | "quic" => Ok(Protocol::Quic), 215 | "quic-v1" => Ok(Protocol::QuicV1), 216 | "webtransport" => Ok(Protocol::WebTransport), 217 | "ws" => Ok(Protocol::Ws(Cow::Borrowed("/"))), 218 | "wss" => Ok(Protocol::Wss(Cow::Borrowed("/"))), 219 | "x-parity-ws" => { 220 | let s = iter.next().ok_or(Error::InvalidProtocolString)?; 221 | let decoded = percent_encoding::percent_decode(s.as_bytes()).decode_utf8()?; 222 | Ok(Protocol::Ws(decoded)) 223 | } 224 | "x-parity-wss" => { 225 | let s = iter.next().ok_or(Error::InvalidProtocolString)?; 226 | let decoded = percent_encoding::percent_decode(s.as_bytes()).decode_utf8()?; 227 | Ok(Protocol::Wss(decoded)) 228 | } 229 | "p2p-websocket-star" => Ok(Protocol::P2pWebSocketStar), 230 | "p2p-webrtc-star" => Ok(Protocol::P2pWebRtcStar), 231 | "webrtc-direct" => Ok(Protocol::WebRTCDirect), 232 | "certhash" => { 233 | let s = iter.next().ok_or(Error::InvalidProtocolString)?; 234 | let (_base, decoded) = multibase::decode(s)?; 235 | Ok(Protocol::Certhash(Multihash::from_bytes(&decoded)?)) 236 | } 237 | "p2p-webrtc-direct" => Ok(Protocol::P2pWebRtcDirect), 238 | "p2p-circuit" => Ok(Protocol::P2pCircuit), 239 | "memory" => { 240 | let s = iter.next().ok_or(Error::InvalidProtocolString)?; 241 | Ok(Protocol::Memory(s.parse()?)) 242 | } 243 | "ip6zone" => { 244 | let s = iter.next().ok_or(Error::InvalidProtocolString)?; 245 | Ok(Protocol::Ip6zone(Cow::Borrowed(s))) 246 | } 247 | "ipcidr" => { 248 | let s = iter.next().ok_or(Error::InvalidProtocolString)?; 249 | Ok(Protocol::Ipcidr(s.parse()?)) 250 | } 251 | "garlic64" => { 252 | let s = iter 253 | .next() 254 | .ok_or(Error::InvalidProtocolString)? 255 | .replace('-', "+") 256 | .replace('~', "/"); 257 | 258 | if s.len() < 516 || s.len() > 616 { 259 | return Err(Error::InvalidProtocolString); 260 | } 261 | 262 | let decoded = multibase::Base::Base64.decode(s)?; 263 | Ok(Protocol::Garlic64(Cow::from(decoded))) 264 | } 265 | "garlic32" => { 266 | let s = iter.next().ok_or(Error::InvalidProtocolString)?; 267 | 268 | if s.len() < 55 && s.len() != 52 { 269 | return Err(Error::InvalidProtocolString); 270 | } 271 | 272 | let decoded = multibase::Base::Base32Lower.decode(s)?; 273 | Ok(Protocol::Garlic32(Cow::from(decoded))) 274 | } 275 | "sni" => { 276 | let s = iter.next().ok_or(Error::InvalidProtocolString)?; 277 | Ok(Protocol::Sni(Cow::Borrowed(s))) 278 | } 279 | "p2p-stardust" => Ok(Protocol::P2pStardust), 280 | "webrtc" => Ok(Protocol::WebRTC), 281 | "http-path" => { 282 | let s = iter.next().ok_or(Error::InvalidProtocolString)?; 283 | let decoded = percent_encoding::percent_decode(s.as_bytes()).decode_utf8()?; 284 | Ok(Protocol::HttpPath(decoded)) 285 | } 286 | unknown => Err(Error::UnknownProtocolString(unknown.to_string())), 287 | } 288 | } 289 | 290 | /// Parse a single `Protocol` value from its byte slice representation, 291 | /// returning the protocol as well as the remaining byte slice. 292 | pub fn from_bytes(input: &'a [u8]) -> Result<(Self, &'a [u8])> { 293 | fn split_at(n: usize, input: &[u8]) -> Result<(&[u8], &[u8])> { 294 | if input.len() < n { 295 | return Err(Error::DataLessThanLen); 296 | } 297 | Ok(input.split_at(n)) 298 | } 299 | let (id, input) = decode::u32(input)?; 300 | match id { 301 | DCCP => { 302 | let (data, rest) = split_at(2, input)?; 303 | let mut rdr = Cursor::new(data); 304 | let num = rdr.read_u16::()?; 305 | Ok((Protocol::Dccp(num), rest)) 306 | } 307 | DNS => { 308 | let (n, input) = decode::usize(input)?; 309 | let (data, rest) = split_at(n, input)?; 310 | Ok((Protocol::Dns(Cow::Borrowed(str::from_utf8(data)?)), rest)) 311 | } 312 | DNS4 => { 313 | let (n, input) = decode::usize(input)?; 314 | let (data, rest) = split_at(n, input)?; 315 | Ok((Protocol::Dns4(Cow::Borrowed(str::from_utf8(data)?)), rest)) 316 | } 317 | DNS6 => { 318 | let (n, input) = decode::usize(input)?; 319 | let (data, rest) = split_at(n, input)?; 320 | Ok((Protocol::Dns6(Cow::Borrowed(str::from_utf8(data)?)), rest)) 321 | } 322 | DNSADDR => { 323 | let (n, input) = decode::usize(input)?; 324 | let (data, rest) = split_at(n, input)?; 325 | Ok(( 326 | Protocol::Dnsaddr(Cow::Borrowed(str::from_utf8(data)?)), 327 | rest, 328 | )) 329 | } 330 | HTTP => Ok((Protocol::Http, input)), 331 | HTTPS => Ok((Protocol::Https, input)), 332 | IP4 => { 333 | let (data, rest) = split_at(4, input)?; 334 | Ok(( 335 | Protocol::Ip4(Ipv4Addr::new(data[0], data[1], data[2], data[3])), 336 | rest, 337 | )) 338 | } 339 | IP6 => { 340 | let (data, rest) = split_at(16, input)?; 341 | let mut rdr = Cursor::new(data); 342 | let mut seg = [0_u16; 8]; 343 | 344 | for x in seg.iter_mut() { 345 | *x = rdr.read_u16::()?; 346 | } 347 | 348 | let addr = Ipv6Addr::new( 349 | seg[0], seg[1], seg[2], seg[3], seg[4], seg[5], seg[6], seg[7], 350 | ); 351 | 352 | Ok((Protocol::Ip6(addr), rest)) 353 | } 354 | P2P_WEBRTC_DIRECT => Ok((Protocol::P2pWebRtcDirect, input)), 355 | P2P_WEBRTC_STAR => Ok((Protocol::P2pWebRtcStar, input)), 356 | WEBRTC_DIRECT => Ok((Protocol::WebRTCDirect, input)), 357 | CERTHASH => { 358 | let (n, input) = decode::usize(input)?; 359 | let (data, rest) = split_at(n, input)?; 360 | Ok((Protocol::Certhash(Multihash::from_bytes(data)?), rest)) 361 | } 362 | P2P_WEBSOCKET_STAR => Ok((Protocol::P2pWebSocketStar, input)), 363 | MEMORY => { 364 | let (data, rest) = split_at(8, input)?; 365 | let mut rdr = Cursor::new(data); 366 | let num = rdr.read_u64::()?; 367 | Ok((Protocol::Memory(num), rest)) 368 | } 369 | ONION => { 370 | let (data, rest) = split_at(12, input)?; 371 | let port = BigEndian::read_u16(&data[10..]); 372 | Ok(( 373 | Protocol::Onion(Cow::Borrowed(array_ref!(data, 0, 10)), port), 374 | rest, 375 | )) 376 | } 377 | ONION3 => { 378 | let (data, rest) = split_at(37, input)?; 379 | let port = BigEndian::read_u16(&data[35..]); 380 | Ok(( 381 | Protocol::Onion3((array_ref!(data, 0, 35), port).into()), 382 | rest, 383 | )) 384 | } 385 | P2P => { 386 | let (n, input) = decode::usize(input)?; 387 | let (data, rest) = split_at(n, input)?; 388 | Ok(( 389 | Protocol::P2p( 390 | PeerId::from_bytes(data).map_err(|e| Error::ParsingError(Box::new(e)))?, 391 | ), 392 | rest, 393 | )) 394 | } 395 | P2P_CIRCUIT => Ok((Protocol::P2pCircuit, input)), 396 | QUIC => Ok((Protocol::Quic, input)), 397 | QUIC_V1 => Ok((Protocol::QuicV1, input)), 398 | SCTP => { 399 | let (data, rest) = split_at(2, input)?; 400 | let mut rdr = Cursor::new(data); 401 | let num = rdr.read_u16::()?; 402 | Ok((Protocol::Sctp(num), rest)) 403 | } 404 | TCP => { 405 | let (data, rest) = split_at(2, input)?; 406 | let mut rdr = Cursor::new(data); 407 | let num = rdr.read_u16::()?; 408 | Ok((Protocol::Tcp(num), rest)) 409 | } 410 | TLS => Ok((Protocol::Tls, input)), 411 | NOISE => Ok((Protocol::Noise, input)), 412 | UDP => { 413 | let (data, rest) = split_at(2, input)?; 414 | let mut rdr = Cursor::new(data); 415 | let num = rdr.read_u16::()?; 416 | Ok((Protocol::Udp(num), rest)) 417 | } 418 | UDT => Ok((Protocol::Udt, input)), 419 | UNIX => { 420 | let (n, input) = decode::usize(input)?; 421 | let (data, rest) = split_at(n, input)?; 422 | Ok((Protocol::Unix(Cow::Borrowed(str::from_utf8(data)?)), rest)) 423 | } 424 | UTP => Ok((Protocol::Utp, input)), 425 | WEBTRANSPORT => Ok((Protocol::WebTransport, input)), 426 | WS => Ok((Protocol::Ws(Cow::Borrowed("/")), input)), 427 | WS_WITH_PATH => { 428 | let (n, input) = decode::usize(input)?; 429 | let (data, rest) = split_at(n, input)?; 430 | Ok((Protocol::Ws(Cow::Borrowed(str::from_utf8(data)?)), rest)) 431 | } 432 | WSS => Ok((Protocol::Wss(Cow::Borrowed("/")), input)), 433 | WSS_WITH_PATH => { 434 | let (n, input) = decode::usize(input)?; 435 | let (data, rest) = split_at(n, input)?; 436 | Ok((Protocol::Wss(Cow::Borrowed(str::from_utf8(data)?)), rest)) 437 | } 438 | IP6ZONE => { 439 | let (n, input) = decode::usize(input)?; 440 | let (data, rest) = split_at(n, input)?; 441 | Ok(( 442 | Protocol::Ip6zone(Cow::Borrowed(str::from_utf8(data)?)), 443 | rest, 444 | )) 445 | } 446 | IPCIDR => { 447 | let (data, rest) = split_at(1, input)?; 448 | Ok((Protocol::Ipcidr(data[0]), rest)) 449 | } 450 | GARLIC64 => { 451 | let (n, input) = decode::usize(input)?; 452 | let (data, rest) = split_at(n, input)?; 453 | Ok((Protocol::Garlic64(Cow::Borrowed(data)), rest)) 454 | } 455 | GARLIC32 => { 456 | let (n, input) = decode::usize(input)?; 457 | let (data, rest) = split_at(n, input)?; 458 | Ok((Protocol::Garlic32(Cow::Borrowed(data)), rest)) 459 | } 460 | SNI => { 461 | let (n, input) = decode::usize(input)?; 462 | let (data, rest) = split_at(n, input)?; 463 | Ok((Protocol::Sni(Cow::Borrowed(str::from_utf8(data)?)), rest)) 464 | } 465 | P2P_STARDUST => Ok((Protocol::P2pStardust, input)), 466 | WEBRTC => Ok((Protocol::WebRTC, input)), 467 | HTTP_PATH => { 468 | let (n, input) = decode::usize(input)?; 469 | let (data, rest) = split_at(n, input)?; 470 | Ok(( 471 | Protocol::HttpPath(Cow::Borrowed(str::from_utf8(data)?)), 472 | rest, 473 | )) 474 | } 475 | _ => Err(Error::UnknownProtocolId(id)), 476 | } 477 | } 478 | 479 | /// Encode this protocol by writing its binary representation into 480 | /// the given `Write` impl. 481 | pub fn write_bytes(&self, w: &mut W) -> Result<()> { 482 | let mut buf = encode::u32_buffer(); 483 | match self { 484 | Protocol::Ip4(addr) => { 485 | w.write_all(encode::u32(IP4, &mut buf))?; 486 | w.write_all(&addr.octets())? 487 | } 488 | Protocol::Ip6(addr) => { 489 | w.write_all(encode::u32(IP6, &mut buf))?; 490 | for &segment in &addr.segments() { 491 | w.write_u16::(segment)? 492 | } 493 | } 494 | Protocol::Tcp(port) => { 495 | w.write_all(encode::u32(TCP, &mut buf))?; 496 | w.write_u16::(*port)? 497 | } 498 | Protocol::Tls => w.write_all(encode::u32(TLS, &mut buf))?, 499 | Protocol::Noise => w.write_all(encode::u32(NOISE, &mut buf))?, 500 | Protocol::Udp(port) => { 501 | w.write_all(encode::u32(UDP, &mut buf))?; 502 | w.write_u16::(*port)? 503 | } 504 | Protocol::Dccp(port) => { 505 | w.write_all(encode::u32(DCCP, &mut buf))?; 506 | w.write_u16::(*port)? 507 | } 508 | Protocol::Sctp(port) => { 509 | w.write_all(encode::u32(SCTP, &mut buf))?; 510 | w.write_u16::(*port)? 511 | } 512 | Protocol::Dns(s) => { 513 | w.write_all(encode::u32(DNS, &mut buf))?; 514 | let bytes = s.as_bytes(); 515 | w.write_all(encode::usize(bytes.len(), &mut encode::usize_buffer()))?; 516 | w.write_all(bytes)? 517 | } 518 | Protocol::Dns4(s) => { 519 | w.write_all(encode::u32(DNS4, &mut buf))?; 520 | let bytes = s.as_bytes(); 521 | w.write_all(encode::usize(bytes.len(), &mut encode::usize_buffer()))?; 522 | w.write_all(bytes)? 523 | } 524 | Protocol::Dns6(s) => { 525 | w.write_all(encode::u32(DNS6, &mut buf))?; 526 | let bytes = s.as_bytes(); 527 | w.write_all(encode::usize(bytes.len(), &mut encode::usize_buffer()))?; 528 | w.write_all(bytes)? 529 | } 530 | Protocol::Dnsaddr(s) => { 531 | w.write_all(encode::u32(DNSADDR, &mut buf))?; 532 | let bytes = s.as_bytes(); 533 | w.write_all(encode::usize(bytes.len(), &mut encode::usize_buffer()))?; 534 | w.write_all(bytes)? 535 | } 536 | Protocol::Unix(s) => { 537 | w.write_all(encode::u32(UNIX, &mut buf))?; 538 | let bytes = s.as_bytes(); 539 | w.write_all(encode::usize(bytes.len(), &mut encode::usize_buffer()))?; 540 | w.write_all(bytes)? 541 | } 542 | Protocol::P2p(peer_id) => { 543 | w.write_all(encode::u32(P2P, &mut buf))?; 544 | let bytes = peer_id.to_bytes(); 545 | w.write_all(encode::usize(bytes.len(), &mut encode::usize_buffer()))?; 546 | w.write_all(&bytes)? 547 | } 548 | Protocol::Onion(addr, port) => { 549 | w.write_all(encode::u32(ONION, &mut buf))?; 550 | w.write_all(addr.as_ref())?; 551 | w.write_u16::(*port)? 552 | } 553 | Protocol::Onion3(addr) => { 554 | w.write_all(encode::u32(ONION3, &mut buf))?; 555 | w.write_all(addr.hash().as_ref())?; 556 | w.write_u16::(addr.port())? 557 | } 558 | Protocol::Quic => w.write_all(encode::u32(QUIC, &mut buf))?, 559 | Protocol::QuicV1 => w.write_all(encode::u32(QUIC_V1, &mut buf))?, 560 | Protocol::Utp => w.write_all(encode::u32(UTP, &mut buf))?, 561 | Protocol::Udt => w.write_all(encode::u32(UDT, &mut buf))?, 562 | Protocol::Http => w.write_all(encode::u32(HTTP, &mut buf))?, 563 | Protocol::Https => w.write_all(encode::u32(HTTPS, &mut buf))?, 564 | Protocol::WebTransport => w.write_all(encode::u32(WEBTRANSPORT, &mut buf))?, 565 | Protocol::Ws(ref s) if s == "/" => w.write_all(encode::u32(WS, &mut buf))?, 566 | Protocol::Ws(s) => { 567 | w.write_all(encode::u32(WS_WITH_PATH, &mut buf))?; 568 | let bytes = s.as_bytes(); 569 | w.write_all(encode::usize(bytes.len(), &mut encode::usize_buffer()))?; 570 | w.write_all(bytes)? 571 | } 572 | Protocol::Wss(ref s) if s == "/" => w.write_all(encode::u32(WSS, &mut buf))?, 573 | Protocol::Wss(s) => { 574 | w.write_all(encode::u32(WSS_WITH_PATH, &mut buf))?; 575 | let bytes = s.as_bytes(); 576 | w.write_all(encode::usize(bytes.len(), &mut encode::usize_buffer()))?; 577 | w.write_all(bytes)? 578 | } 579 | Protocol::P2pWebSocketStar => w.write_all(encode::u32(P2P_WEBSOCKET_STAR, &mut buf))?, 580 | Protocol::P2pWebRtcStar => w.write_all(encode::u32(P2P_WEBRTC_STAR, &mut buf))?, 581 | Protocol::WebRTCDirect => w.write_all(encode::u32(WEBRTC_DIRECT, &mut buf))?, 582 | Protocol::Certhash(hash) => { 583 | w.write_all(encode::u32(CERTHASH, &mut buf))?; 584 | let bytes = hash.to_bytes(); 585 | w.write_all(encode::usize(bytes.len(), &mut encode::usize_buffer()))?; 586 | w.write_all(&bytes)? 587 | } 588 | Protocol::P2pWebRtcDirect => w.write_all(encode::u32(P2P_WEBRTC_DIRECT, &mut buf))?, 589 | Protocol::P2pCircuit => w.write_all(encode::u32(P2P_CIRCUIT, &mut buf))?, 590 | Protocol::Memory(port) => { 591 | w.write_all(encode::u32(MEMORY, &mut buf))?; 592 | w.write_u64::(*port)? 593 | } 594 | Protocol::Ip6zone(zone_id) => { 595 | w.write_all(encode::u32(IP6ZONE, &mut buf))?; 596 | let bytes = zone_id.as_bytes(); 597 | w.write_all(encode::usize(bytes.len(), &mut encode::usize_buffer()))?; 598 | w.write_all(bytes)? 599 | } 600 | Protocol::Ipcidr(mask) => { 601 | w.write_all(encode::u32(IPCIDR, &mut buf))?; 602 | w.write_u8(*mask)? 603 | } 604 | Protocol::Garlic64(addr) => { 605 | w.write_all(encode::u32(GARLIC64, &mut buf))?; 606 | w.write_all(encode::usize(addr.len(), &mut encode::usize_buffer()))?; 607 | w.write_all(addr)? 608 | } 609 | Protocol::Garlic32(addr) => { 610 | w.write_all(encode::u32(GARLIC32, &mut buf))?; 611 | w.write_all(encode::usize(addr.len(), &mut encode::usize_buffer()))?; 612 | w.write_all(addr)? 613 | } 614 | Protocol::Sni(s) => { 615 | w.write_all(encode::u32(SNI, &mut buf))?; 616 | let bytes = s.as_bytes(); 617 | w.write_all(encode::usize(bytes.len(), &mut encode::usize_buffer()))?; 618 | w.write_all(bytes)? 619 | } 620 | Protocol::P2pStardust => w.write_all(encode::u32(P2P_STARDUST, &mut buf))?, 621 | Protocol::WebRTC => w.write_all(encode::u32(WEBRTC, &mut buf))?, 622 | Protocol::HttpPath(s) => { 623 | w.write_all(encode::u32(HTTP_PATH, &mut buf))?; 624 | let bytes = s.as_bytes(); 625 | w.write_all(encode::usize(bytes.len(), &mut encode::usize_buffer()))?; 626 | w.write_all(bytes)? 627 | } 628 | } 629 | Ok(()) 630 | } 631 | 632 | /// Turn this `Protocol` into one that owns its data, thus being valid for any lifetime. 633 | pub fn acquire<'b>(self) -> Protocol<'b> { 634 | use self::Protocol::*; 635 | match self { 636 | Dccp(a) => Dccp(a), 637 | Dns(cow) => Dns(Cow::Owned(cow.into_owned())), 638 | Dns4(cow) => Dns4(Cow::Owned(cow.into_owned())), 639 | Dns6(cow) => Dns6(Cow::Owned(cow.into_owned())), 640 | Dnsaddr(cow) => Dnsaddr(Cow::Owned(cow.into_owned())), 641 | Http => Http, 642 | Https => Https, 643 | Ip4(a) => Ip4(a), 644 | Ip6(a) => Ip6(a), 645 | P2pWebRtcDirect => P2pWebRtcDirect, 646 | P2pWebRtcStar => P2pWebRtcStar, 647 | WebRTCDirect => WebRTCDirect, 648 | Certhash(hash) => Certhash(hash), 649 | P2pWebSocketStar => P2pWebSocketStar, 650 | Memory(a) => Memory(a), 651 | Onion(addr, port) => Onion(Cow::Owned(addr.into_owned()), port), 652 | Onion3(addr) => Onion3(addr.acquire()), 653 | P2p(a) => P2p(a), 654 | P2pCircuit => P2pCircuit, 655 | Quic => Quic, 656 | QuicV1 => QuicV1, 657 | Sctp(a) => Sctp(a), 658 | Tcp(a) => Tcp(a), 659 | Tls => Tls, 660 | Noise => Noise, 661 | Udp(a) => Udp(a), 662 | Udt => Udt, 663 | Unix(cow) => Unix(Cow::Owned(cow.into_owned())), 664 | Utp => Utp, 665 | WebTransport => WebTransport, 666 | Ws(cow) => Ws(Cow::Owned(cow.into_owned())), 667 | Wss(cow) => Wss(Cow::Owned(cow.into_owned())), 668 | Ip6zone(cow) => Ip6zone(Cow::Owned(cow.into_owned())), 669 | Ipcidr(mask) => Ipcidr(mask), 670 | Garlic64(addr) => Garlic64(Cow::Owned(addr.into_owned())), 671 | Garlic32(addr) => Garlic32(Cow::Owned(addr.into_owned())), 672 | Sni(cow) => Sni(Cow::Owned(cow.into_owned())), 673 | P2pStardust => P2pStardust, 674 | WebRTC => WebRTC, 675 | HttpPath(cow) => HttpPath(Cow::Owned(cow.into_owned())), 676 | } 677 | } 678 | 679 | pub fn tag(&self) -> &'static str { 680 | use self::Protocol::*; 681 | match self { 682 | Dccp(_) => "dccp", 683 | Dns(_) => "dns", 684 | Dns4(_) => "dns4", 685 | Dns6(_) => "dns6", 686 | Dnsaddr(_) => "dnsaddr", 687 | Http => "http", 688 | Https => "https", 689 | Ip4(_) => "ip4", 690 | Ip6(_) => "ip6", 691 | P2pWebRtcDirect => "p2p-webrtc-direct", 692 | P2pWebRtcStar => "p2p-webrtc-star", 693 | WebRTCDirect => "webrtc-direct", 694 | Certhash(_) => "certhash", 695 | P2pWebSocketStar => "p2p-websocket-star", 696 | Memory(_) => "memory", 697 | Onion(_, _) => "onion", 698 | Onion3(_) => "onion3", 699 | P2p(_) => "p2p", 700 | P2pCircuit => "p2p-circuit", 701 | Quic => "quic", 702 | QuicV1 => "quic-v1", 703 | Sctp(_) => "sctp", 704 | Tcp(_) => "tcp", 705 | Tls => "tls", 706 | Noise => "noise", 707 | Udp(_) => "udp", 708 | Udt => "udt", 709 | Unix(_) => "unix", 710 | Utp => "utp", 711 | WebTransport => "webtransport", 712 | Ws(ref s) if s == "/" => "ws", 713 | Ws(_) => "x-parity-ws", 714 | Wss(ref s) if s == "/" => "wss", 715 | Wss(_) => "x-parity-wss", 716 | Ip6zone(_) => "ip6zone", 717 | Ipcidr(_) => "ipcidr", 718 | Garlic64(_) => "garlic64", 719 | Garlic32(_) => "garlic32", 720 | Sni(_) => "sni", 721 | P2pStardust => "p2p-stardust", 722 | WebRTC => "webrtc", 723 | HttpPath(_) => "http-path", 724 | } 725 | } 726 | } 727 | 728 | impl fmt::Display for Protocol<'_> { 729 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 730 | use self::Protocol::*; 731 | write!(f, "/{}", self.tag())?; 732 | match self { 733 | Dccp(port) => write!(f, "/{port}"), 734 | Dns(s) => write!(f, "/{s}"), 735 | Dns4(s) => write!(f, "/{s}"), 736 | Dns6(s) => write!(f, "/{s}"), 737 | Dnsaddr(s) => write!(f, "/{s}"), 738 | Ip4(addr) => write!(f, "/{addr}"), 739 | Ip6(addr) => write!(f, "/{addr}"), 740 | Certhash(hash) => write!( 741 | f, 742 | "/{}", 743 | multibase::encode(multibase::Base::Base64Url, hash.to_bytes()) 744 | ), 745 | Memory(port) => write!(f, "/{port}"), 746 | Onion(addr, port) => { 747 | let s = BASE32.encode(addr.as_ref()); 748 | write!(f, "/{}:{}", s.to_lowercase(), port) 749 | } 750 | Onion3(addr) => { 751 | let s = BASE32.encode(addr.hash()); 752 | write!(f, "/{}:{}", s.to_lowercase(), addr.port()) 753 | } 754 | P2p(c) => write!(f, "/{}", multibase::Base::Base58Btc.encode(c.to_bytes())), 755 | Sctp(port) => write!(f, "/{port}"), 756 | Tcp(port) => write!(f, "/{port}"), 757 | Udp(port) => write!(f, "/{port}"), 758 | Unix(s) => write!(f, "/{s}"), 759 | Ws(s) if s != "/" => { 760 | let encoded = 761 | percent_encoding::percent_encode(s.as_bytes(), PATH_SEGMENT_ENCODE_SET); 762 | write!(f, "/{encoded}") 763 | } 764 | Wss(s) if s != "/" => { 765 | let encoded = 766 | percent_encoding::percent_encode(s.as_bytes(), PATH_SEGMENT_ENCODE_SET); 767 | write!(f, "/{encoded}") 768 | } 769 | Ip6zone(zone) => write!(f, "/{zone}"), 770 | Ipcidr(mask) => write!(f, "/{mask}"), 771 | Garlic64(addr) => write!( 772 | f, 773 | "/{}", 774 | multibase::Base::Base64 775 | .encode(addr) 776 | .replace('+', "-") 777 | .replace('/', "~") 778 | ), 779 | Garlic32(addr) => write!(f, "/{}", multibase::Base::Base32Lower.encode(addr)), 780 | Sni(s) => write!(f, "/{s}"), 781 | HttpPath(s) => { 782 | let encoded = 783 | percent_encoding::percent_encode(s.as_bytes(), PATH_SEGMENT_ENCODE_SET); 784 | write!(f, "/{encoded}") 785 | } 786 | _ => Ok(()), 787 | } 788 | } 789 | } 790 | 791 | impl From for Protocol<'_> { 792 | #[inline] 793 | fn from(addr: IpAddr) -> Self { 794 | match addr { 795 | IpAddr::V4(addr) => Protocol::Ip4(addr), 796 | IpAddr::V6(addr) => Protocol::Ip6(addr), 797 | } 798 | } 799 | } 800 | 801 | impl From for Protocol<'_> { 802 | #[inline] 803 | fn from(addr: Ipv4Addr) -> Self { 804 | Protocol::Ip4(addr) 805 | } 806 | } 807 | 808 | impl From for Protocol<'_> { 809 | #[inline] 810 | fn from(addr: Ipv6Addr) -> Self { 811 | Protocol::Ip6(addr) 812 | } 813 | } 814 | 815 | macro_rules! read_onion_impl { 816 | ($name:ident, $len:expr, $encoded_len:expr) => { 817 | fn $name(s: &str) -> Result<([u8; $len], u16)> { 818 | let mut parts = s.split(':'); 819 | 820 | // address part (without ".onion") 821 | let b32 = parts.next().ok_or(Error::InvalidMultiaddr)?; 822 | if b32.len() != $encoded_len { 823 | return Err(Error::InvalidMultiaddr); 824 | } 825 | 826 | // port number 827 | let port = parts 828 | .next() 829 | .ok_or(Error::InvalidMultiaddr) 830 | .and_then(|p| str::parse(p).map_err(From::from))?; 831 | 832 | // port == 0 is not valid for onion 833 | if port == 0 { 834 | return Err(Error::InvalidMultiaddr); 835 | } 836 | 837 | // nothing else expected 838 | if parts.next().is_some() { 839 | return Err(Error::InvalidMultiaddr); 840 | } 841 | 842 | if $len 843 | != BASE32 844 | .decode_len(b32.len()) 845 | .map_err(|_| Error::InvalidMultiaddr)? 846 | { 847 | return Err(Error::InvalidMultiaddr); 848 | } 849 | 850 | let mut buf = [0u8; $len]; 851 | BASE32 852 | .decode_mut(b32.as_bytes(), &mut buf) 853 | .map_err(|_| Error::InvalidMultiaddr)?; 854 | 855 | Ok((buf, port)) 856 | } 857 | }; 858 | } 859 | 860 | // Parse a version 2 onion address and return its binary representation. 861 | // 862 | // Format: ":" 863 | read_onion_impl!(read_onion, 10, 16); 864 | // Parse a version 3 onion address and return its binary representation. 865 | // 866 | // Format: ":" 867 | read_onion_impl!(read_onion3, 35, 56); 868 | --------------------------------------------------------------------------------