├── jitpack.yml ├── .github ├── dependabot.yml └── workflows │ ├── stale.yml │ ├── generated-pr.yml │ └── maven.yml ├── .gitignore ├── LICENSE ├── README.md ├── pom.xml └── src ├── main └── java │ └── io │ └── ipfs │ └── multiaddr │ ├── MultiAddress.java │ └── Protocol.java └── test └── java └── io └── ipfs └── api └── MultiAddressTest.java /jitpack.yml: -------------------------------------------------------------------------------- 1 | before_install: 2 | - sdk install java 21-tem 3 | - sdk use java 21-tem 4 | - sdk install maven 5 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .classpath 2 | .project 3 | .settings 4 | *.class 5 | 6 | target/** 7 | build/** 8 | dist/** 9 | .idea/** 10 | 11 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 12 | hs_err_pid* 13 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.github/workflows/maven.yml: -------------------------------------------------------------------------------- 1 | name: Java Maven CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ${{ matrix.os }} 9 | strategy: 10 | matrix: 11 | os: [ubuntu-latest] 12 | 13 | steps: 14 | - uses: actions/checkout@v6 15 | - name: Set up JDK 11 16 | uses: actions/setup-java@v4 17 | with: 18 | distribution: temurin 19 | java-version: 11 20 | - name: Build with Maven 21 | run: mvn -B test-compile 22 | - name: Run tests with Maven 23 | timeout-minutes: 10 24 | run: mvn -B test 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2015 Ian Preston 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do 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, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # java-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 | [![](https://img.shields.io/badge/readme%20style-standard-brightgreen.svg?style=flat-square)](https://github.com/RichardLitt/standard-readme) 7 | 8 | > Java implementation of [multiaddr](https://github.com/multiformats/multiaddr). 9 | 10 | ## Install 11 | 12 | Simply clone this repo. 13 | 14 | ## Usage 15 | 16 | ```java 17 | MultiAddress m = new MultiAddress("/ip4/127.0.0.1/tcp/1234"); 18 | ``` 19 | or 20 | 21 | ```java 22 | MultiAddress m = new MultiAddress("/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC"); 23 | ``` 24 | 25 | ## Dependency 26 | You can use this project by building the JAR file as specified below, or by using [JitPack](https://jitpack.io/#multiformats/java-multiaddr/) (also supporting Gradle, SBT, etc). 27 | 28 | for Maven, you can add the follwing sections to your POM.XML: 29 | ```xml 30 | 31 | 32 | jitpack.io 33 | https://jitpack.io 34 | 35 | 36 | 37 | 38 | 39 | com.github.multiformats 40 | java-multiaddr 41 | $latest_version 42 | 43 | 44 | ``` 45 | 46 | ## Testing 47 | 48 | `mvn test` 49 | 50 | ## Building 51 | 52 | `mvn package` will build a JAR file with Maven dependency information. 53 | 54 | ## Releasing 55 | 56 | The version number is specified in the `pom.xml` file and must be changed in order to be accurately reflected in the JAR file manifest. A git tag must be added in the format "vx.x.x" for JitPack to work. 57 | 58 | ## Maintainers 59 | 60 | Captain: [@ianopolous](https://github.com/ianopolous). 61 | 62 | ## Contribute 63 | 64 | Contributions welcome. Please check out [the issues](https://github.com/multiformats/java-multiaddr/issues). 65 | 66 | 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). 67 | 68 | Small note: If editing the README, please conform to the [standard-readme](https://github.com/RichardLitt/standard-readme) specification. 69 | 70 | ## License 71 | 72 | [MIT](LICENSE) © 2015 Ian Preston 73 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | com.github.multiformats 6 | java-multiaddr 7 | v1.4.13 8 | 9 | multiaddr 10 | https://github.com/multiformats/java-multiaddr 11 | 12 | 13 | https://github.com/multiformats/java-multiaddr/issues 14 | GitHub Issues 15 | 16 | 17 | 18 | https://github.com/multiformats/java-multiaddr 19 | scm:git:git://github.com/multiformats/java-multiaddr.git 20 | scm:git:git@github.com:multiformats/java-multiaddr.git 21 | 22 | 23 | 24 | 25 | MIT License 26 | https://github.com/multiformats/java-multiaddr/blob/master/LICENSE 27 | repo 28 | 29 | 30 | 31 | 32 | UTF-8 33 | UTF-8 34 | 5.11.0 35 | 3.0 36 | v1.3.10 37 | 38 | 39 | 40 | 41 | jitpack.io 42 | https://jitpack.io 43 | 44 | 45 | 46 | 47 | 48 | com.github.ipld 49 | java-cid 50 | ${version.cid} 51 | 52 | 53 | org.junit.jupiter 54 | junit-jupiter 55 | ${version.junit} 56 | test 57 | 58 | 59 | org.hamcrest 60 | hamcrest 61 | ${version.hamcrest} 62 | test 63 | 64 | 65 | 66 | 67 | 68 | 69 | org.apache.maven.plugins 70 | maven-compiler-plugin 71 | 3.1 72 | 73 | 11 74 | 11 75 | 76 | 77 | 78 | org.apache.maven.plugins 79 | maven-surefire-plugin 80 | 3.3.1 81 | 82 | 83 | org.apache.maven.plugins 84 | maven-jar-plugin 85 | 3.0.2 86 | 87 | 88 | 89 | true 90 | 91 | 92 | io.ipfs.multiaddr 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /src/main/java/io/ipfs/multiaddr/MultiAddress.java: -------------------------------------------------------------------------------- 1 | package io.ipfs.multiaddr; 2 | 3 | import io.ipfs.multihash.*; 4 | 5 | import java.io.*; 6 | import java.net.*; 7 | import java.util.*; 8 | import java.util.stream.*; 9 | 10 | public class MultiAddress 11 | { 12 | private final byte[] raw; 13 | 14 | public MultiAddress(Multihash hash) { 15 | this("/ipfs/" + hash.toBase58()); 16 | } 17 | 18 | public MultiAddress(String address) { 19 | this(decodeFromString(address)); 20 | } 21 | 22 | public MultiAddress(byte[] raw) { 23 | encodeToString(raw); // check validity 24 | this.raw = raw; 25 | } 26 | 27 | public byte[] getBytes() { 28 | return Arrays.copyOfRange(raw, 0, raw.length); 29 | } 30 | 31 | public boolean isRelayed() { 32 | return has(Protocol.get("p2p-circuit")); 33 | } 34 | 35 | public boolean has(Protocol p) { 36 | String[] parts = toString().substring(1).split("/"); 37 | return Arrays.asList(parts).contains(p.name()); 38 | } 39 | 40 | public boolean isPublic(boolean testReachable) { 41 | String[] parts = toString().substring(1).split("/"); 42 | try { 43 | for (int i = 0; i < parts.length; i++) { 44 | if (parts[i].equals(Protocol.Type.IP6ZONE.name)) 45 | return true; 46 | if (parts[i].equals(Protocol.Type.IP4.name) || parts[i].equals(Protocol.Type.IP6.name)) { 47 | InetAddress ip = InetAddress.getByName(parts[i + 1]); 48 | if (ip.isLoopbackAddress() || ip.isSiteLocalAddress() || ip.isLinkLocalAddress() || ip.isAnyLocalAddress()) 49 | return false; 50 | return !testReachable || ip.isReachable(1000); 51 | } 52 | } 53 | } catch (UnknownHostException e) {} 54 | catch (IOException e) {} 55 | return false; 56 | } 57 | 58 | public boolean isTCPIP() { 59 | String[] parts = toString().substring(1).split("/"); 60 | if (parts.length != 4) 61 | return false; 62 | if (!parts[0].startsWith("ip")) 63 | return false; 64 | if (!parts[2].equals("tcp")) 65 | return false; 66 | return true; 67 | } 68 | 69 | public String getHost() { 70 | String[] parts = toString().substring(1).split("/"); 71 | if (parts[0].startsWith("ip") || parts[0].startsWith("dns")) 72 | return parts[1]; 73 | throw new IllegalStateException("This multiaddress doesn't have a host: "+toString()); 74 | } 75 | 76 | public int getPort() { 77 | String[] parts = toString().substring(1).split("/"); 78 | if (parts[2].startsWith("tcp") || parts[2].startsWith("udp")) 79 | return Integer.parseInt(parts[3]); 80 | throw new IllegalStateException("This multiaddress doesn't have a port: "+toString()); 81 | } 82 | 83 | private static byte[] decodeFromString(String addr) { 84 | while (addr.endsWith("/")) 85 | addr = addr.substring(0, addr.length()-1); 86 | String[] parts = addr.split("/"); 87 | if (parts[0].length() != 0) 88 | throw new IllegalStateException("MultiAddress must start with a /"); 89 | 90 | ByteArrayOutputStream bout = new ByteArrayOutputStream(); 91 | try { 92 | for (int i = 1; i < parts.length;) { 93 | String part = parts[i++]; 94 | Protocol p = Protocol.get(part); 95 | p.appendCode(bout); 96 | if (p.size() == 0) 97 | continue; 98 | 99 | String component = p.isTerminal() ? 100 | Stream.of(Arrays.copyOfRange(parts, i, parts.length)).reduce("", (a, b) -> a + "/" + b) : 101 | parts[i++]; 102 | if (component.length() == 0) 103 | throw new IllegalStateException("Protocol requires address, but none provided!"); 104 | 105 | bout.write(p.addressToBytes(component)); 106 | if (p.isTerminal()) 107 | break; 108 | } 109 | return bout.toByteArray(); 110 | } catch (IOException e) { 111 | throw new IllegalStateException("Error decoding multiaddress: "+addr); 112 | } 113 | } 114 | 115 | private static String encodeToString(byte[] raw) { 116 | StringBuilder b = new StringBuilder(); 117 | InputStream in = new ByteArrayInputStream(raw); 118 | try { 119 | while (true) { 120 | int code = (int)Protocol.readVarint(in); 121 | Protocol p = Protocol.get(code); 122 | b.append("/" + p.name()); 123 | if (p.size() == 0) 124 | continue; 125 | 126 | String addr = p.readAddress(in); 127 | if (addr.length() > 0) 128 | b.append("/" +addr); 129 | } 130 | } 131 | catch (EOFException eof) {} 132 | catch (IOException e) { 133 | throw new RuntimeException(e); 134 | } 135 | 136 | return b.toString(); 137 | } 138 | 139 | @Override 140 | public String toString() { 141 | return encodeToString(raw); 142 | } 143 | 144 | @Override 145 | public boolean equals(Object other) { 146 | if (!(other instanceof MultiAddress)) 147 | return false; 148 | return Arrays.equals(raw, ((MultiAddress) other).raw); 149 | } 150 | 151 | @Override 152 | public int hashCode() { 153 | return Arrays.hashCode(raw); 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/main/java/io/ipfs/multiaddr/Protocol.java: -------------------------------------------------------------------------------- 1 | package io.ipfs.multiaddr; 2 | 3 | import io.ipfs.multibase.*; 4 | import io.ipfs.multihash.Multihash; 5 | import io.ipfs.cid.Cid; 6 | import java.io.*; 7 | import java.net.*; 8 | import java.util.*; 9 | 10 | public class Protocol { 11 | public static int LENGTH_PREFIXED_VAR_SIZE = -1; 12 | private static final String IPV4_REGEX = "\\A(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z"; 13 | 14 | enum Type { 15 | IP4(4, 32, "ip4"), 16 | TCP(6, 16, "tcp"), 17 | DCCP(33, 16, "dccp"), 18 | IP6(41, 128, "ip6"), 19 | IP6ZONE(42, LENGTH_PREFIXED_VAR_SIZE, "ip6zone"), 20 | IPCIDR(43, 8, "ipcidr"), 21 | DNS(53, LENGTH_PREFIXED_VAR_SIZE, "dns"), 22 | DNS4(54, LENGTH_PREFIXED_VAR_SIZE, "dns4"), 23 | DNS6(55, LENGTH_PREFIXED_VAR_SIZE, "dns6"), 24 | DNSADDR(56, LENGTH_PREFIXED_VAR_SIZE, "dnsaddr"), 25 | SCTP(132, 16, "sctp"), 26 | UDP(273, 16, "udp"), 27 | P2PWEBRTC(276, 0, "p2p-webrtc-direct"), 28 | WEBRTC(280, 0, "webrtc"), 29 | UTP(301, 0, "utp"), 30 | UDT(302, 0, "udt"), 31 | UNIX(400, LENGTH_PREFIXED_VAR_SIZE, "unix"), 32 | P2P(421, LENGTH_PREFIXED_VAR_SIZE, "p2p"), 33 | IPFS(421, LENGTH_PREFIXED_VAR_SIZE, "ipfs"), 34 | HTTPS(443, 0, "https"), 35 | ONION(444, 80, "onion"), 36 | ONION3(445, 296, "onion3"), 37 | GARLIC64(446, LENGTH_PREFIXED_VAR_SIZE, "garlic64"), 38 | GARLIC32(447, LENGTH_PREFIXED_VAR_SIZE, "garlic32"), 39 | TLS(448, 0, "tls"), 40 | SNI(449, LENGTH_PREFIXED_VAR_SIZE, "sni"), 41 | NOISE(454, 0, "noise"), 42 | QUIC(460, 0, "quic"), 43 | QUIC_V1(461, 0, "quic-v1"), 44 | WEBTRANSPORT(465, 0, "webtransport"), 45 | CERTHASH(466, LENGTH_PREFIXED_VAR_SIZE, "certhash"), 46 | WS(477, 0, "ws"), 47 | WSS(478, 0, "wss"), 48 | P2PCIRCUIT(290, 0, "p2p-circuit"), 49 | HTTP(480, 0, "http"); 50 | 51 | public final int code, size; 52 | public final String name; 53 | private final byte[] encoded; 54 | 55 | Type(int code, int size, String name) { 56 | this.code = code; 57 | this.size = size; 58 | this.name = name; 59 | this.encoded = encode(code); 60 | } 61 | 62 | static byte[] encode(int code) { 63 | byte[] varint = new byte[(32 - Integer.numberOfLeadingZeros(code)+6)/7]; 64 | putUvarint(varint, code); 65 | return varint; 66 | } 67 | } 68 | 69 | public final Type type; 70 | 71 | public Protocol(Type type) { 72 | this.type = type; 73 | } 74 | 75 | public void appendCode(OutputStream out) throws IOException { 76 | out.write(type.encoded); 77 | } 78 | 79 | public boolean isTerminal() { 80 | return type == Type.UNIX; 81 | } 82 | 83 | public int size() { 84 | return type.size; 85 | } 86 | 87 | public String name() { 88 | return type.name; 89 | } 90 | 91 | public int code() { 92 | return type.code; 93 | } 94 | 95 | @Override 96 | public String toString() { 97 | return name(); 98 | } 99 | 100 | public byte[] addressToBytes(String addr) { 101 | try { 102 | switch (type) { 103 | case IP4: 104 | if (! addr.matches(IPV4_REGEX)) 105 | throw new IllegalStateException("Invalid IPv4 address: " + addr); 106 | return Inet4Address.getByName(addr).getAddress(); 107 | case IP6: 108 | return Inet6Address.getByName(addr).getAddress(); 109 | case IPCIDR: 110 | return new byte[] {Byte.parseByte(addr)}; 111 | case IP6ZONE: 112 | if (addr.isEmpty()) 113 | throw new IllegalStateException("Empty IPv6 zone!"); 114 | if (addr.contains("/")) 115 | throw new IllegalStateException("IPv6 zone ID contains '/'"); 116 | return addr.getBytes(); 117 | case TCP: 118 | case UDP: 119 | case DCCP: 120 | case SCTP: 121 | int x = Integer.parseInt(addr); 122 | if (x > 65535) 123 | throw new IllegalStateException("Failed to parse "+type.name+" address "+addr + " (> 65535"); 124 | return new byte[]{(byte)(x >>8), (byte)x}; 125 | case P2P: 126 | case IPFS: { 127 | Multihash hash; 128 | if (addr.startsWith("Qm") || addr.startsWith("1")) 129 | hash = Multihash.fromBase58(addr); 130 | else { 131 | Cid cid = Cid.decode(addr); 132 | if (cid.codec != Cid.Codec.Libp2pKey) 133 | throw new IllegalStateException("failed to parse p2p addr: " + addr + " has the invalid codec " + cid.codec); 134 | hash = new Multihash(cid.getType(), cid.getHash()); 135 | } 136 | ByteArrayOutputStream bout = new ByteArrayOutputStream(); 137 | byte[] hashBytes = hash.toBytes(); 138 | byte[] varint = new byte[(32 - Integer.numberOfLeadingZeros(hashBytes.length) + 6) / 7]; 139 | putUvarint(varint, hashBytes.length); 140 | bout.write(varint); 141 | bout.write(hashBytes); 142 | return bout.toByteArray(); 143 | } 144 | case CERTHASH: { 145 | byte[] raw = Multibase.decode(addr); 146 | Multihash.deserialize(raw); 147 | ByteArrayOutputStream bout = new ByteArrayOutputStream(); 148 | byte[] varint = new byte[(32 - Integer.numberOfLeadingZeros(raw.length) + 6) / 7]; 149 | putUvarint(varint, raw.length); 150 | bout.write(varint); 151 | bout.write(raw); 152 | return bout.toByteArray(); 153 | } 154 | case ONION: { 155 | String[] split = addr.split(":"); 156 | if (split.length != 2) 157 | throw new IllegalStateException("Onion address needs a port: " + addr); 158 | 159 | // onion address without the ".onion" substring 160 | if (split[0].length() != 16) 161 | throw new IllegalStateException("failed to parse " + name() + " addr: " + addr + " not a Tor onion address."); 162 | 163 | byte[] onionHostBytes = Multibase.decode(Multibase.Base.Base32.prefix + split[0]); 164 | if (onionHostBytes.length != 10) 165 | throw new IllegalStateException("Invalid onion address host: " + split[0]); 166 | int port = Integer.parseInt(split[1]); 167 | if (port > 65535) 168 | throw new IllegalStateException("Port is > 65535: " + port); 169 | 170 | if (port < 1) 171 | throw new IllegalStateException("Port is < 1: " + port); 172 | 173 | ByteArrayOutputStream b = new ByteArrayOutputStream(); 174 | DataOutputStream dout = new DataOutputStream(b); 175 | dout.write(onionHostBytes); 176 | dout.writeShort(port); 177 | dout.flush(); 178 | return b.toByteArray(); 179 | } 180 | case ONION3: { 181 | String[] split = addr.split(":"); 182 | if (split.length != 2) 183 | throw new IllegalStateException("Onion3 address needs a port: " + addr); 184 | 185 | // onion3 address without the ".onion" substring 186 | if (split[0].length() != 56) 187 | throw new IllegalStateException("failed to parse " + name() + " addr: " + addr + " not a Tor onion3 address."); 188 | 189 | byte[] onionHostBytes = Multibase.decode(Multibase.Base.Base32.prefix + split[0]); 190 | if (onionHostBytes.length != 35) 191 | throw new IllegalStateException("Invalid onion3 address host: " + split[0]); 192 | int port = Integer.parseInt(split[1]); 193 | if (port > 65535) 194 | throw new IllegalStateException("Port is > 65535: " + port); 195 | 196 | if (port < 1) 197 | throw new IllegalStateException("Port is < 1: " + port); 198 | 199 | ByteArrayOutputStream b = new ByteArrayOutputStream(); 200 | DataOutputStream dout = new DataOutputStream(b); 201 | dout.write(onionHostBytes); 202 | dout.writeShort(port); 203 | dout.flush(); 204 | return b.toByteArray(); 205 | } case GARLIC32: { 206 | // an i2p base32 address with a length of greater than 55 characters is 207 | // using an Encrypted Leaseset v2. all other base32 addresses will always be 208 | // exactly 52 characters 209 | if (addr.length() < 55 && addr.length() != 52 || addr.contains(":")) { 210 | throw new IllegalStateException(String.format("Invalid garlic addr: %s not a i2p base32 address. len: %d", addr, addr.length())); 211 | } 212 | 213 | while (addr.length() % 8 != 0) { 214 | addr += "="; 215 | } 216 | 217 | ByteArrayOutputStream bout = new ByteArrayOutputStream(); 218 | byte[] hashBytes = Multibase.decode(Multibase.Base.Base32.prefix + addr); 219 | byte[] varint = new byte[(32 - Integer.numberOfLeadingZeros(hashBytes.length) + 6) / 7]; 220 | putUvarint(varint, hashBytes.length); 221 | bout.write(varint); 222 | bout.write(hashBytes); 223 | return bout.toByteArray(); 224 | } case GARLIC64: { 225 | // i2p base64 address will be between 516 and 616 characters long, depending on certificate type 226 | if (addr.length() < 516 || addr.length() > 616 || addr.contains(":")) { 227 | throw new IllegalStateException(String.format("Invalid garlic addr: %s not a i2p base64 address. len: %d", addr, addr.length())); 228 | } 229 | 230 | ByteArrayOutputStream bout = new ByteArrayOutputStream(); 231 | byte[] hashBytes = Multibase.decode(Multibase.Base.Base64.prefix + addr.replaceAll("-", "+").replaceAll("~", "/")); 232 | byte[] varint = new byte[(32 - Integer.numberOfLeadingZeros(hashBytes.length) + 6) / 7]; 233 | putUvarint(varint, hashBytes.length); 234 | bout.write(varint); 235 | bout.write(hashBytes); 236 | return bout.toByteArray(); 237 | } case UNIX: { 238 | if (addr.startsWith("/")) 239 | addr = addr.substring(1); 240 | byte[] path = addr.getBytes(); 241 | ByteArrayOutputStream b = new ByteArrayOutputStream(); 242 | DataOutputStream dout = new DataOutputStream(b); 243 | byte[] length = new byte[(32 - Integer.numberOfLeadingZeros(path.length)+6)/7]; 244 | putUvarint(length, path.length); 245 | dout.write(length); 246 | dout.write(path); 247 | dout.flush(); 248 | return b.toByteArray(); 249 | } 250 | case DNS: 251 | case DNS4: 252 | case DNS6: 253 | case DNSADDR: { 254 | ByteArrayOutputStream bout = new ByteArrayOutputStream(); 255 | byte[] hashBytes = addr.getBytes(); 256 | byte[] varint = new byte[(32 - Integer.numberOfLeadingZeros(hashBytes.length) + 6) / 7]; 257 | putUvarint(varint, hashBytes.length); 258 | bout.write(varint); 259 | bout.write(hashBytes); 260 | return bout.toByteArray(); 261 | } 262 | default: 263 | throw new IllegalStateException("Unknown multiaddr type: " + type); 264 | } 265 | } catch (IOException e) { 266 | throw new RuntimeException(e); 267 | } 268 | } 269 | 270 | public String readAddress(InputStream in) throws IOException { 271 | int sizeForAddress = sizeForAddress(in); 272 | byte[] buf; 273 | switch (type) { 274 | case IP4: 275 | buf = new byte[sizeForAddress]; 276 | read(in, buf); 277 | return Inet4Address.getByAddress(buf).toString().substring(1); 278 | case IP6: 279 | buf = new byte[sizeForAddress]; 280 | read(in, buf); 281 | return Inet6Address.getByAddress(buf).toString().substring(1); 282 | case IPCIDR: 283 | return Integer.toString(in.read()); 284 | case IP6ZONE: 285 | buf = new byte[sizeForAddress]; 286 | read(in, buf); 287 | return new String(buf); 288 | case TCP: 289 | case UDP: 290 | case DCCP: 291 | case SCTP: 292 | return Integer.toString((in.read() << 8) | (in.read())); 293 | case IPFS: 294 | buf = new byte[sizeForAddress]; 295 | read(in, buf); 296 | return Multihash.deserialize(buf).toString(); 297 | case CERTHASH: 298 | buf = new byte[sizeForAddress]; 299 | read(in, buf); 300 | return Multibase.encode(Multibase.Base.Base64Url, buf); 301 | case ONION: { 302 | byte[] host = new byte[10]; 303 | read(in, host); 304 | String port = Integer.toString((in.read() << 8) | (in.read())); 305 | return Multibase.encode(Multibase.Base.Base32, host).substring(1) + ":" + port; 306 | } case ONION3: { 307 | byte[] host = new byte[35]; 308 | read(in, host); 309 | String port = Integer.toString((in.read() << 8) | (in.read())); 310 | return Multibase.encode(Multibase.Base.Base32, host).substring(1) + ":" + port; 311 | } case GARLIC32: { 312 | buf = new byte[sizeForAddress]; 313 | read(in, buf); 314 | // an i2p base32 for an Encrypted Leaseset v2 will be at least 35 bytes 315 | // long other than that, they will be exactly 32 bytes 316 | if (buf.length < 35 && buf.length != 32) { 317 | throw new IllegalStateException("Invalid garlic addr length: " + buf.length); 318 | } 319 | return Multibase.encode(Multibase.Base.Base32, buf).substring(1); 320 | } case GARLIC64: { 321 | buf = new byte[sizeForAddress]; 322 | read(in, buf); 323 | // A garlic64 address will always be greater than 386 bytes 324 | if (buf.length < 386) { 325 | throw new IllegalStateException("Invalid garlic64 addr length: " + buf.length); 326 | } 327 | return Multibase.encode(Multibase.Base.Base64, buf).substring(1).replaceAll("\\+", "-").replaceAll("/", "~"); 328 | } case UNIX: 329 | buf = new byte[sizeForAddress]; 330 | read(in, buf); 331 | return new String(buf); 332 | case DNS: 333 | case DNS4: 334 | case DNS6: 335 | case DNSADDR: 336 | buf = new byte[sizeForAddress]; 337 | read(in, buf); 338 | return new String(buf); 339 | } 340 | throw new IllegalStateException("Unimplemented protocol type: "+type.name); 341 | } 342 | 343 | private static void read(InputStream in, byte[] b) throws IOException { 344 | read(in, b, 0, b.length); 345 | } 346 | 347 | private static void read(InputStream in, byte[] b, int offset, int len) throws IOException { 348 | int total=0, r=0; 349 | while (total < len && r != -1) { 350 | r = in.read(b, offset + total, len - total); 351 | if (r >=0) 352 | total += r; 353 | } 354 | } 355 | 356 | public int sizeForAddress(InputStream in) throws IOException { 357 | if (type.size > 0) 358 | return type.size/8; 359 | if (type.size == 0) 360 | return 0; 361 | return (int)readVarint(in); 362 | } 363 | 364 | static int putUvarint(byte[] buf, long x) { 365 | int i = 0; 366 | while (x >= 0x80) { 367 | buf[i] = (byte)(x | 0x80); 368 | x >>= 7; 369 | i++; 370 | } 371 | buf[i] = (byte)x; 372 | return i + 1; 373 | } 374 | 375 | static long readVarint(InputStream in) throws IOException { 376 | long x = 0; 377 | int s=0; 378 | for (int i=0; i < 10; i++) { 379 | int b = in.read(); 380 | if (b == -1) 381 | throw new EOFException(); 382 | if (b < 0x80) { 383 | if (i > 9 || i == 9 && b > 1) { 384 | throw new IllegalStateException("Overflow reading varint" +(-(i + 1))); 385 | } 386 | return x | (((long)b) << s); 387 | } 388 | x |= ((long)b & 0x7f) << s; 389 | s += 7; 390 | } 391 | throw new IllegalStateException("Varint too long!"); 392 | } 393 | 394 | private static Map byName = new HashMap<>(); 395 | private static Map byCode = new HashMap<>(); 396 | 397 | static { 398 | for (Protocol.Type t: Protocol.Type.values()) { 399 | Protocol p = new Protocol(t); 400 | byName.put(p.name(), p); 401 | byCode.put(p.code(), p); 402 | } 403 | 404 | } 405 | 406 | public static Protocol get(String name) { 407 | if (byName.containsKey(name)) 408 | return byName.get(name); 409 | throw new IllegalStateException("No protocol with name: "+name); 410 | } 411 | 412 | public static Protocol get(int code) { 413 | if (byCode.containsKey(code)) 414 | return byCode.get(code); 415 | throw new IllegalStateException("No protocol with code: "+code); 416 | } 417 | } 418 | -------------------------------------------------------------------------------- /src/test/java/io/ipfs/api/MultiAddressTest.java: -------------------------------------------------------------------------------- 1 | package io.ipfs.api; 2 | 3 | import io.ipfs.multiaddr.*; 4 | import io.ipfs.multihash.*; 5 | import org.junit.jupiter.api.Test; 6 | 7 | import java.io.*; 8 | import java.util.*; 9 | import java.util.function.*; 10 | import java.util.stream.*; 11 | 12 | import static org.junit.jupiter.api.Assertions.assertEquals; 13 | import static org.junit.jupiter.api.Assertions.assertFalse; 14 | import static org.junit.jupiter.api.Assertions.assertTrue; 15 | 16 | public class MultiAddressTest { 17 | 18 | @Test 19 | void fails() { 20 | List parsed = Stream.of( 21 | "/ip4", 22 | "/ip4/::1", 23 | "/ip4/fdpsofodsajfdoisa", 24 | "/ip6", 25 | "/udp", 26 | "/tcp", 27 | "/sctp", 28 | "/udp/65536", 29 | "/tcp/65536", 30 | "/quic/65536", 31 | "/onion/9imaq4ygg2iegci7:80", 32 | "/onion/aaimaq4ygg2iegci7:80", 33 | "/onion/timaq4ygg2iegci7:0", 34 | "/onion/timaq4ygg2iegci7:-1", 35 | "/onion/timaq4ygg2iegci7", 36 | "/onion/timaq4ygg2iegci@:666", 37 | "/onion3/9ww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd:80", 38 | "/onion3/vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd7:80", 39 | "/onion3/vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd:0", 40 | "/onion3/vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd:-1", 41 | "/onion3/vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd", 42 | "/onion3/vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyy@:666", 43 | "/garlic64/jT~IyXaoauTni6N4517EG8mrFUKpy0IlgZh-EY9csMAk82Odatmzr~YTZy8Hv7u~wvkg75EFNOyqb~nAPg-khyp2TS~ObUz8WlqYAM2VlEzJ7wJB91P-cUlKF18zSzVoJFmsrcQHZCirSbWoOknS6iNmsGRh5KVZsBEfp1Dg3gwTipTRIx7Vl5Vy~1OSKQVjYiGZS9q8RL0MF~7xFiKxZDLbPxk0AK9TzGGqm~wMTI2HS0Gm4Ycy8LYPVmLvGonIBYndg2bJC7WLuF6tVjVquiokSVDKFwq70BCUU5AU-EvdOD5KEOAM7mPfw-gJUG4tm1TtvcobrObqoRnmhXPTBTN5H7qDD12AvlwFGnfAlBXjuP4xOUAISL5SRLiulrsMSiT4GcugSI80mF6sdB0zWRgL1yyvoVWeTBn1TqjO27alr95DGTluuSqrNAxgpQzCKEWAyzrQkBfo2avGAmmz2NaHaAvYbOg0QSJz1PLjv2jdPW~ofiQmrGWM1cd~1cCqAAAA7:80", 44 | "/garlic64/jT~IyXaoauTni6N4517EG8mrFUKpy0IlgZh-EY9csMAk82Odatmzr~YTZy8Hv7u~wvkg75EFNOyqb~nAPg-khyp2TS~ObUz8WlqYAM2VlEzJ7wJB91P-cUlKF18zSzVoJFmsrcQHZCirSbWoOknS6iNmsGRh5KVZsBEfp1Dg3gwTipTRIx7Vl5Vy~1OSKQVjYiGZS9q8RL0MF~7xFiKxZDLbPxk0AK9TzGGqm~wMTI2HS0Gm4Ycy8LYPVmLvGonIBYndg2bJC7WLuF6tVjVquiokSVDKFwq70BCUU5AU-EvdOD5KEOAM7mPfw-gJUG4tm1TtvcobrObqoRnmhXPTBTN5H7qDD12AvlwFGnfAlBXjuP4xOUAISL5SRLiulrsMSiT4GcugSI80mF6sdB0zWRgL1yyvoVWeTBn1TqjO27alr95DGTluuSqrNAxgpQzCKEWAyzrQkBfo2avGAmmz2NaHaAvYbOg0QSJz1PLjv2jdPW~ofiQmrGWM1cd~1cCqAAAA:0", 45 | "/garlic64/jT~IyXaoauTni6N4517EG8mrFUKpy0IlgZh-EY9csMAk82Odatmzr~YTZy8Hv7u~wvkg75EFNOyqb~nAPg-khyp2TS~ObUz8WlqYAM2VlEzJ7wJB91P-cUlKF18zSzVoJFmsrcQHZCirSbWoOknS6iNmsGRh5KVZsBEfp1Dg3gwTipTRIx7Vl5Vy~1OSKQVjYiGZS9q8RL0MF~7xFiKxZDLbPxk0AK9TzGGqm~wMTI2HS0Gm4Ycy8LYPVmLvGonIBYndg2bJC7WLuF6tVjVquiokSVDKFwq70BCUU5AU-EvdOD5KEOAM7mPfw-gJUG4tm1TtvcobrObqoRnmhXPTBTN5H7qDD12AvlwFGnfAlBXjuP4xOUAISL5SRLiulrsMSiT4GcugSI80mF6sdB0zWRgL1yyvoVWeTBn1TqjO27alr95DGTluuSqrNAxgpQzCKEWAyzrQkBfo2avGAmmz2NaHaAvYbOg0QSJz1PLjv2jdPW~ofiQmrGWM1cd~1cCqAAAA:0", 46 | "/garlic64/jT~IyXaoauTni6N4517EG8mrFUKpy0IlgZh-EY9csMAk82Odatmzr~YTZy8Hv7u~wvkg75EFNOyqb~nAPg-khyp2TS~ObUz8WlqYAM2VlEzJ7wJB91P-cUlKF18zSzVoJFmsrcQHZCirSbWoOknS6iNmsGRh5KVZsBEfp1Dg3gwTipTRIx7Vl5Vy~1OSKQVjYiGZS9q8RL0MF~7xFiKxZDLbPxk0AK9TzGGqm~wMTI2HS0Gm4Ycy8LYPVmLvGonIBYndg2bJC7WLuF6tVjVquiokSVDKFwq70BCUU5AU-EvdOD5KEOAM7mPfw-gJUG4tm1TtvcobrObqoRnmhXPTBTN5H7qDD12AvlwFGnfAlBXjuP4xOUAISL5SRLiulrsMSiT4GcugSI80mF6sdB0zWRgL1yyvoVWeTBn1TqjO27alr95DGTluuSqrNAxgpQzCKEWAyzrQkBfo2avGAmmz2NaHaAvYbOg0QSJz1PLjv2jdPW~ofiQmrGWM1cd~1cCqAAAA:-1", 47 | "/garlic64/jT~IyXaoauTni6N4517EG8mrFUKpy0IlgZh-EY9csMAk82Odatmzr~YTZy8Hv7u~wvkg75EFNOyqb~nAPg-khyp2TS~ObUz8WlqYAM2VlEzJ7wJB91P-cUlKF18zSzVoJFmsrcQHZCirSbWoOknS6iNmsGRh5KVZsBEfp1Dg3gwTipTRIx7Vl5Vy~1OSKQVjYiGZS9q8RL0MF~7xFiKxZDLbPxk0AK9TzGGqm~wMTI2HS0Gm4Ycy8LYPVmLvGonIBYndg2bJC7WLuF6tVjVquiokSVDKFwq70BCUU5AU-EvdOD5KEOAM7mPfw-gJUG4tm1TtvcobrObqoRnmhXPTBTN5H7qDD12AvlwFGnfAlBXjuP4xOUAISL5SRLiulrsMSiT4GcugSI80mF6sdB0zWRgL1yyvoVWeTBn1TqjO27alr95DGTluuSqrNAxgpQzCKEWAyzrQkBfo2avGAmmz2NaHaAvYbOg0QSJz1PLjv2jdPW~ofiQmrGWM1cd~1cCqAAAA@:666", 48 | "/garlic64/jT~IyXaoauTni6N4517EG8mrFUKpy0IlgZh-EY9csMAk82Odatmzr~YTZy8Hv7u~wvkg75EFNOyqb~nAPg-khyp2TS~ObUz8WlqYAM2VlEzJ7wJB91P-cUlKF18zSzVoJFmsrcQHZCirSbWoOknS6iNmsGRh5KVZsBEfp1Dg3gwTipTRIx7Vl5Vy~1OSKQVjYiGZS9q8RL0MF~7xFiKxZDLbPxk0AK9TzGGqm~wMTI2HS0Gm4Ycy8LYPVmLvGonIBYndg2bJC7WLuF6tVjVquiokSVDKFwq70BCUU5AU-EvdOD5KEOAM7mPfw-gJUG4tm1TtvcobrObqoRnmhXPTBTN5H7qDD12AvlwFGnfAlBXjuP4xOUAISL5SRLiulrsMSiT4GcugSI80mF6sdB0zWRgL1yyvoVWeTBn1TqjO27alr95DGTluuSqrNAxgpQzCKEWAyzrQkBfo2avGAmmz2NaHaAvYbOg0QSJz1PLjv2jdPW~ofiQmrGWM1cd~1cCqAAAA7:80", 49 | "/garlic64/jT~IyXaoauTni6N4517EG8mrFUKpy0IlgZh-EY9csMAk82Odatmzr~YTZy8Hv7u~wvkg75EFNOyqb~nAPg-khyp2TS~ObUz8WlqYAM2VlEzJ7wJB91P-cUlKF18zSzVoJFmsrcQHZCirSbWoOknS6iNmsGRh5KVZsBEfp1Dg3gwTipTRIx7Vl5Vy~1OSKQVjYiGZS9q8RL0MF~7xFiKxZDLbPxk0AK9TzGGqm~wMTI2HS0Gm4Ycy8LYPVmLvGonIBYndg2bJC7WLuF6tVjVquiokSVDKFwq70BCUU5AU-EvdOD5KEOAM7mPfw-gJUG4tm1TtvcobrObqoRnmhXPTBTN5H7qDD12AvlwFGnfAlBXjuP4xOUAISL5SRLiulrsMSiT4GcugSI80mF6sdB0zWRgL1yyvoVWeTBn1TqjO27alr95DGTluuSqrNAxgpQzCKEWAyzrQkBfo2avGAmmz2NaHaAvYbOg0QSJz1PLjv2jdPW~ofiQmrGWM1cd~1cCqAAAA:0", 50 | "/garlic64/jT~IyXaoauTni6N4517EG8mrFUKpy0IlgZh-EY9csMAk82Odatmzr~YTZy8Hv7u~wvkg75EFNOyqb~nAPg-khyp2TS~ObUz8WlqYAM2VlEzJ7wJB91P-cUlKF18zSzVoJFmsrcQHZCirSbWoOknS6iNmsGRh5KVZsBEfp1Dg3gwTipTRIx7Vl5Vy~1OSKQVjYiGZS9q8RL0MF~7xFiKxZDLbPxk0AK9TzGGqm~wMTI2HS0Gm4Ycy8LYPVmLvGonIBYndg2bJC7WLuF6tVjVquiokSVDKFwq70BCUU5AU-EvdOD5KEOAM7mPfw-gJUG4tm1TtvcobrObqoRnmhXPTBTN5H7qDD12AvlwFGnfAlBXjuP4xOUAISL5SRLiulrsMSiT4GcugSI80mF6sdB0zWRgL1yyvoVWeTBn1TqjO27alr95DGTluuSqrNAxgpQzCKEWAyzrQkBfo2avGAmmz2NaHaAvYbOg0QSJz1PLjv2jdPW~ofiQmrGWM1cd~1cCqAAAA:0", 51 | "/garlic64/jT~IyXaoauTni6N4517EG8mrFUKpy0IlgZh-EY9csMAk82Odatmzr~YTZy8Hv7u~wvkg75EFNOyqb~nAPg-khyp2TS~ObUz8WlqYAM2VlEzJ7wJB91P-cUlKF18zSzVoJFmsrcQHZCirSbWoOknS6iNmsGRh5KVZsBEfp1Dg3gwTipTRIx7Vl5Vy~1OSKQVjYiGZS9q8RL0MF~7xFiKxZDLbPxk0AK9TzGGqm~wMTI2HS0Gm4Ycy8LYPVmLvGonIBYndg2bJC7WLuF6tVjVquiokSVDKFwq70BCUU5AU-EvdOD5KEOAM7mPfw-gJUG4tm1TtvcobrObqoRnmhXPTBTN5H7qDD12AvlwFGnfAlBXjuP4xOUAISL5SRLiulrsMSiT4GcugSI80mF6sdB0zWRgL1yyvoVWeTBn1TqjO27alr95DGTluuSqrNAxgpQzCKEWAyzrQkBfo2avGAmmz2NaHaAvYbOg0QSJz1PLjv2jdPW~ofiQmrGWM1cd~1cCqAAAA:-1", 52 | "/garlic64/jT~IyXaoauTni6N4517EG8mrFUKpy0IlgZh-EY9csMAk82Odatmzr~YTZy8Hv7u~wvkg75EFNOyqb~nAPg-khyp2TS~ObUz8WlqYAM2VlEzJ7wJB91P-cUlKF18zSzVoJFmsrcQHZCirSbWoOknS6iNmsGRh5KVZsBEfp1Dg3gwTipTRIx7Vl5Vy~1OSKQVjYiGZS9q8RL0MF~7xFiKxZDLbPxk0AK9TzGGqm~wMTI2HS0Gm4Ycy8LYPVmLvGonIBYndg2bJC7WLuF6tVjVquiokSVDKFwq70BCUU5AU-EvdOD5KEOAM7mPfw-gJUG4tm1TtvcobrObqoRnmhXPTBTN5H7qDD12AvlwFGnfAlBXjuP4xOUAISL5SRLiulrsMSiT4GcugSI80mF6sdB0zWRgL1yyvoVWeTBn1TqjO27alr95DGTluuSqrNAxgpQzCKEWAyzrQkBfo2avGAmmz2NaHaAvYbOg0QSJz1PLjv2jdPW~ofiQmrGWM1cd~1cCqAAAA@:666", 53 | "/garlic32/566niximlxdzpanmn4qouucvua3k7neniwss47li5r6ugoertzu", 54 | "/garlic32/566niximlxdzpanmn4qouucvua3k7neniwss47li5r6ugoertzu77", 55 | "/garlic32/566niximlxdzpanmn4qouucvua3k7neniwss47li5r6ugoertzu:80", 56 | "/garlic32/566niximlxdzpanmn4qouucvua3k7neniwss47li5r6ugoertzuq:-1", 57 | "/garlic32/566niximlxdzpanmn4qouucvua3k7neniwss47li5r6ugoertzu@", 58 | "/udp/1234/sctp", 59 | "/udp/1234/udt/1234", 60 | "/udp/1234/utp/1234", 61 | "/ip4/127.0.0.1/udp/jfodsajfidosajfoidsa", 62 | "/ip4/127.0.0.1/udp", 63 | "/ip4/127.0.0.1/tcp/jfodsajfidosajfoidsa", 64 | "/ip4/127.0.0.1/tcp", 65 | "/ip4/127.0.0.1/ipfs", 66 | "/ip4/127.0.0.1/ipfs/tcp", 67 | "/unix" 68 | ).flatMap(s -> { 69 | try { 70 | new MultiAddress(s); 71 | return Stream.of(s); 72 | } catch (Exception e) { 73 | return Stream.empty(); 74 | } 75 | }).collect(Collectors.toList()); 76 | if (parsed.size() > 0) 77 | throw new IllegalStateException("Parsed invalid MultiAddresses: "+parsed); 78 | } 79 | 80 | @Test 81 | void publicIps() { 82 | List pub = Stream.of("/ip4/8.8.8.8") 83 | .map(MultiAddress::new) 84 | .collect(Collectors.toList()); 85 | for (MultiAddress addr : pub) { 86 | assertTrue(addr.isPublic(false), addr.toString() + " is public"); 87 | } 88 | List priv = Stream.of( 89 | // "/ip4/100.64.0.0", 90 | "/ip4/172.16.0.0", 91 | "/ip4/10.0.0.0", 92 | "/ip4/169.254.0.0", 93 | "/ip4/192.168.0.0", 94 | "/ip4/127.0.0.1", 95 | "/ip4/0.0.0.0", 96 | // "/ip4/192.0.0.0", 97 | // "/ip4/192.0.2.0", 98 | // "/ip4/192.88.99.0", 99 | // "/ip4/198.18.0.0", 100 | // "/ip4/198.51.100.0", 101 | // "/ip4/203.0.113.0", 102 | // "/ip4/224.0.0.0", 103 | // "/ip4/240.0.0.0", 104 | // "/ip4/255.255.255.255", 105 | "/ip6/::1", 106 | // "/ip6/fc00::", 107 | // "/ip6/ff00::", 108 | "/ip6/fe80::" 109 | ).map(MultiAddress::new) 110 | .collect(Collectors.toList()); 111 | for (MultiAddress addr : priv) { 112 | assertFalse(addr.isPublic(false), addr.toString() + " is private"); 113 | } 114 | } 115 | 116 | @Test 117 | void succeeds() { 118 | List failed = Stream.of( 119 | "/ip4/1.2.3.4", 120 | "/ip4/0.0.0.0", 121 | "/ip4/192.0.2.0/ipcidr/24", 122 | "/ip6/::1", 123 | "/ip6/2601:9:4f81:9700:803e:ca65:66e8:c21", 124 | "/ip6/2601:9:4f81:9700:803e:ca65:66e8:c21/udp/1234/quic", 125 | "/ip6/2001:db8::/ipcidr/32", 126 | "/ip6zone/x/ip6/fe80::1", 127 | "/ip6zone/x%y/ip6/fe80::1", 128 | "/ip6zone/x%y/ip6/::", 129 | "/ip6zone/x/ip6/fe80::1/udp/1234/quic", 130 | "/ip6/::1/udp/4001/quic-v1", 131 | "/dnsaddr/bootstrap.libp2p.io/ipfs/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb", 132 | "/ip4/127.0.0.1/udp/4001/quic-v1/webtransport/certhash/uEiBwuhj7sQdRxdMIppgEp5kx2XTfYtSUE_8ukOpi8TRPRw/certhash/uEiD8fBEvx4OOkDOdeknjNkpiFWBOEqJYOjwsFdBJobIJNA", 133 | "/onion/timaq4ygg2iegci7:1234", 134 | "/onion/timaq4ygg2iegci7:80/http", 135 | "/onion3/vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd:1234", 136 | "/onion3/vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd:80/http", 137 | "/garlic64/jT~IyXaoauTni6N4517EG8mrFUKpy0IlgZh-EY9csMAk82Odatmzr~YTZy8Hv7u~wvkg75EFNOyqb~nAPg-khyp2TS~ObUz8WlqYAM2VlEzJ7wJB91P-cUlKF18zSzVoJFmsrcQHZCirSbWoOknS6iNmsGRh5KVZsBEfp1Dg3gwTipTRIx7Vl5Vy~1OSKQVjYiGZS9q8RL0MF~7xFiKxZDLbPxk0AK9TzGGqm~wMTI2HS0Gm4Ycy8LYPVmLvGonIBYndg2bJC7WLuF6tVjVquiokSVDKFwq70BCUU5AU-EvdOD5KEOAM7mPfw-gJUG4tm1TtvcobrObqoRnmhXPTBTN5H7qDD12AvlwFGnfAlBXjuP4xOUAISL5SRLiulrsMSiT4GcugSI80mF6sdB0zWRgL1yyvoVWeTBn1TqjO27alr95DGTluuSqrNAxgpQzCKEWAyzrQkBfo2avGAmmz2NaHaAvYbOg0QSJz1PLjv2jdPW~ofiQmrGWM1cd~1cCqAAAA", 138 | "/garlic64/jT~IyXaoauTni6N4517EG8mrFUKpy0IlgZh-EY9csMAk82Odatmzr~YTZy8Hv7u~wvkg75EFNOyqb~nAPg-khyp2TS~ObUz8WlqYAM2VlEzJ7wJB91P-cUlKF18zSzVoJFmsrcQHZCirSbWoOknS6iNmsGRh5KVZsBEfp1Dg3gwTipTRIx7Vl5Vy~1OSKQVjYiGZS9q8RL0MF~7xFiKxZDLbPxk0AK9TzGGqm~wMTI2HS0Gm4Ycy8LYPVmLvGonIBYndg2bJC7WLuF6tVjVquiokSVDKFwq70BCUU5AU-EvdOD5KEOAM7mPfw-gJUG4tm1TtvcobrObqoRnmhXPTBTN5H7qDD12AvlwFGnfAlBXjuP4xOUAISL5SRLiulrsMSiT4GcugSI80mF6sdB0zWRgL1yyvoVWeTBn1TqjO27alr95DGTluuSqrNAxgpQzCKEWAyzrQkBfo2avGAmmz2NaHaAvYbOg0QSJz1PLjv2jdPW~ofiQmrGWM1cd~1cCqAAAA/http", 139 | "/garlic64/jT~IyXaoauTni6N4517EG8mrFUKpy0IlgZh-EY9csMAk82Odatmzr~YTZy8Hv7u~wvkg75EFNOyqb~nAPg-khyp2TS~ObUz8WlqYAM2VlEzJ7wJB91P-cUlKF18zSzVoJFmsrcQHZCirSbWoOknS6iNmsGRh5KVZsBEfp1Dg3gwTipTRIx7Vl5Vy~1OSKQVjYiGZS9q8RL0MF~7xFiKxZDLbPxk0AK9TzGGqm~wMTI2HS0Gm4Ycy8LYPVmLvGonIBYndg2bJC7WLuF6tVjVquiokSVDKFwq70BCUU5AU-EvdOD5KEOAM7mPfw-gJUG4tm1TtvcobrObqoRnmhXPTBTN5H7qDD12AvlwFGnfAlBXjuP4xOUAISL5SRLiulrsMSiT4GcugSI80mF6sdB0zWRgL1yyvoVWeTBn1TqjO27alr95DGTluuSqrNAxgpQzCKEWAyzrQkBfo2avGAmmz2NaHaAvYbOg0QSJz1PLjv2jdPW~ofiQmrGWM1cd~1cCqAAAA/udp/8080", 140 | "/garlic64/jT~IyXaoauTni6N4517EG8mrFUKpy0IlgZh-EY9csMAk82Odatmzr~YTZy8Hv7u~wvkg75EFNOyqb~nAPg-khyp2TS~ObUz8WlqYAM2VlEzJ7wJB91P-cUlKF18zSzVoJFmsrcQHZCirSbWoOknS6iNmsGRh5KVZsBEfp1Dg3gwTipTRIx7Vl5Vy~1OSKQVjYiGZS9q8RL0MF~7xFiKxZDLbPxk0AK9TzGGqm~wMTI2HS0Gm4Ycy8LYPVmLvGonIBYndg2bJC7WLuF6tVjVquiokSVDKFwq70BCUU5AU-EvdOD5KEOAM7mPfw-gJUG4tm1TtvcobrObqoRnmhXPTBTN5H7qDD12AvlwFGnfAlBXjuP4xOUAISL5SRLiulrsMSiT4GcugSI80mF6sdB0zWRgL1yyvoVWeTBn1TqjO27alr95DGTluuSqrNAxgpQzCKEWAyzrQkBfo2avGAmmz2NaHaAvYbOg0QSJz1PLjv2jdPW~ofiQmrGWM1cd~1cCqAAAA/tcp/8080", 141 | "/garlic32/566niximlxdzpanmn4qouucvua3k7neniwss47li5r6ugoertzuq", 142 | "/garlic32/566niximlxdzpanmn4qouucvua3k7neniwss47li5r6ugoertzuqzwas", 143 | "/garlic32/566niximlxdzpanmn4qouucvua3k7neniwss47li5r6ugoertzuqzwassw", 144 | "/garlic32/566niximlxdzpanmn4qouucvua3k7neniwss47li5r6ugoertzuq/http", 145 | "/garlic32/566niximlxdzpanmn4qouucvua3k7neniwss47li5r6ugoertzuq/tcp/8080", 146 | "/garlic32/566niximlxdzpanmn4qouucvua3k7neniwss47li5r6ugoertzuq/udp/8080", 147 | "/udp/0", 148 | "/tcp/0", 149 | "/sctp/0", 150 | "/udp/1234", 151 | "/tcp/1234", 152 | "/sctp/1234", 153 | "/udp/65535", 154 | "/tcp/65535", 155 | "/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC", 156 | "/ipfs/k2k4r8oqamigqdo6o7hsbfwd45y70oyynp98usk7zmyfrzpqxh1pohl7", 157 | "/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC", 158 | "/p2p/k2k4r8oqamigqdo6o7hsbfwd45y70oyynp98usk7zmyfrzpqxh1pohl7", 159 | "/p2p/bafzbeigvf25ytwc3akrijfecaotc74udrhcxzh2cx3we5qqnw5vgrei4bm", 160 | "/p2p/12D3KooWCryG7Mon9orvQxcS1rYZjotPgpwoJNHHKcLLfE4Hf5mV", 161 | "/p2p/k51qzi5uqu5dhb6l8spkdx7yxafegfkee5by8h7lmjh2ehc2sgg34z7c15vzqs", 162 | "/p2p/bafzaajaiaejcalj543iwv2d7pkjt7ykvefrkfu7qjfi6sduakhso4lay6abn2d5u", 163 | "/udp/1234/sctp/1234", 164 | "/udp/1234/udt", 165 | "/udp/1234/utp", 166 | "/tcp/1234/http", 167 | "/tcp/1234/tls/http", 168 | "/tcp/1234/https", 169 | "/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC/tcp/1234", 170 | "/ipfs/k2k4r8oqamigqdo6o7hsbfwd45y70oyynp98usk7zmyfrzpqxh1pohl7/tcp/1234", 171 | "/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC/tcp/1234", 172 | "/p2p/k2k4r8oqamigqdo6o7hsbfwd45y70oyynp98usk7zmyfrzpqxh1pohl7/tcp/1234", 173 | "/ip4/127.0.0.1/udp/1234", 174 | "/ip4/127.0.0.1/udp/0", 175 | "/ip4/127.0.0.1/tcp/1234", 176 | "/ip4/127.0.0.1/tcp/1234/", 177 | "/ip4/127.0.0.1/udp/1234/quic", 178 | "/ip4/127.0.0.1/udp/1234/quic/webtransport", 179 | "/ip4/127.0.0.1/udp/1234/quic/webtransport/certhash/b2uaraocy6yrdblb4sfptaddgimjmmpy", 180 | "/ip4/127.0.0.1/udp/1234/quic/webtransport/certhash/b2uaraocy6yrdblb4sfptaddgimjmmpy/certhash/zQmbWTwYGcmdyK9CYfNBcfs9nhZs17a6FQ4Y8oea278xx41", 181 | "/ip4/127.0.0.1/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC", 182 | "/ip4/127.0.0.1/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC/tcp/1234", 183 | "/ip4/127.0.0.1/ipfs/k2k4r8oqamigqdo6o7hsbfwd45y70oyynp98usk7zmyfrzpqxh1pohl7", 184 | "/ip4/127.0.0.1/ipfs/k2k4r8oqamigqdo6o7hsbfwd45y70oyynp98usk7zmyfrzpqxh1pohl7/tcp/1234", 185 | "/ip4/127.0.0.1/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC", 186 | "/ip4/127.0.0.1/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC/tcp/1234", 187 | "/ip4/127.0.0.1/p2p/k2k4r8oqamigqdo6o7hsbfwd45y70oyynp98usk7zmyfrzpqxh1pohl7", 188 | "/ip4/127.0.0.1/p2p/k2k4r8oqamigqdo6o7hsbfwd45y70oyynp98usk7zmyfrzpqxh1pohl7/tcp/1234", 189 | "/unix/a/b/c/d/e", 190 | "/unix/stdio", 191 | "/ip4/1.2.3.4/tcp/80/unix/a/b/c/d/e/f", 192 | "/ip4/127.0.0.1/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC/tcp/1234/unix/stdio", 193 | "/ip4/127.0.0.1/ipfs/k2k4r8oqamigqdo6o7hsbfwd45y70oyynp98usk7zmyfrzpqxh1pohl7/tcp/1234/unix/stdio", 194 | "/ip4/127.0.0.1/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC/tcp/1234/unix/stdio", 195 | "/ip4/127.0.0.1/p2p/k2k4r8oqamigqdo6o7hsbfwd45y70oyynp98usk7zmyfrzpqxh1pohl7/tcp/1234/unix/stdio", 196 | "/ip4/127.0.0.1/tcp/9090/http/p2p-webrtc-direct", 197 | "/ip4/127.0.0.1/tcp/127/ws", 198 | "/ip4/127.0.0.1/tcp/127/ws", 199 | "/ip4/127.0.0.1/tcp/127/tls", 200 | "/ip4/127.0.0.1/tcp/127/tls/ws", 201 | "/ip4/127.0.0.1/tcp/127/noise", 202 | "/ip4/127.0.0.1/tcp/127/wss", 203 | "/ip4/127.0.0.1/tcp/127/wss" 204 | ).flatMap(s -> { 205 | try { 206 | new MultiAddress(s); 207 | return Stream.empty(); 208 | } catch (Exception e) { 209 | e.printStackTrace(); 210 | return Stream.of(s); 211 | } 212 | }).collect(Collectors.toList()); 213 | if (failed.size() > 0) 214 | throw new IllegalStateException("Failed to construct MultiAddresses: "+failed); 215 | } 216 | 217 | @Test 218 | void equalsTests() { 219 | MultiAddress m1 = new MultiAddress("/ip4/127.0.0.1/udp/1234"); 220 | MultiAddress m2 = new MultiAddress("/ip4/127.0.0.1/tcp/1234"); 221 | MultiAddress m3 = new MultiAddress("/ip4/127.0.0.1/tcp/1234"); 222 | MultiAddress m4 = new MultiAddress("/ip4/127.0.0.1/tcp/1234/"); 223 | 224 | if (m1.equals(m2)) 225 | throw new IllegalStateException("Should be unequal!"); 226 | 227 | if (m2.equals(m1)) 228 | throw new IllegalStateException("Should be unequal!"); 229 | 230 | if (!m2.equals(m3)) 231 | throw new IllegalStateException("Should be equal!"); 232 | 233 | if (!m3.equals(m2)) 234 | throw new IllegalStateException("Should be equal!"); 235 | 236 | if (!m1.equals(m1)) 237 | throw new IllegalStateException("Should be equal!"); 238 | 239 | if (!m2.equals(m4)) 240 | throw new IllegalStateException("Should be equal!"); 241 | 242 | if (!m4.equals(m3)) 243 | throw new IllegalStateException("Should be equal!"); 244 | } 245 | 246 | @Test 247 | void conversion() { 248 | BiConsumer test = (s, h) -> { 249 | if (!s.equals(new MultiAddress(fromHex(h)).toString())) 250 | throw new IllegalStateException(s + " != " + new MultiAddress(fromHex(h))); 251 | if (! Arrays.equals(new MultiAddress(s).getBytes(), fromHex(h))) 252 | throw new IllegalStateException("bytes for " + s + " != " + fromHex(h)); 253 | }; 254 | 255 | test.accept("/ip4/159.89.141.29/udp/5491/quic", "049f598d1d91021573cc03"); 256 | test.accept("/ip4/127.0.0.1/udp/1234", "047f000001910204d2"); 257 | test.accept("/ip4/127.0.0.1/tcp/4321", "047f0000010610e1"); 258 | test.accept("/ip4/127.0.0.1/udp/1234/ip4/127.0.0.1/tcp/4321", "047f000001910204d2047f0000010610e1"); 259 | test.accept("/onion/aaimaq4ygg2iegci:80", "bc030010c0439831b48218480050"); 260 | test.accept("/onion3/vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd:1234", "bd03adadec040be047f9658668b11a504f3155001f231a37f54c4476c07fb4cc139ed7e30304d2"); 261 | test.accept("/garlic64/jT~IyXaoauTni6N4517EG8mrFUKpy0IlgZh-EY9csMAk82Odatmzr~YTZy8Hv7u~wvkg75EFNOyqb~nAPg-" + 262 | "khyp2TS~ObUz8WlqYAM2VlEzJ7wJB91P-cUlKF18zSzVoJFmsrcQHZCirSbWoOknS6iNmsGRh5KVZsBEfp1Dg3gwTipTRIx7Vl5V" + 263 | "y~1OSKQVjYiGZS9q8RL0MF~7xFiKxZDLbPxk0AK9TzGGqm~wMTI2HS0Gm4Ycy8LYPVmLvGonIBYndg2bJC7WLuF6tVjVquiokSVD" + 264 | "KFwq70BCUU5AU-EvdOD5KEOAM7mPfw-gJUG4tm1TtvcobrObqoRnmhXPTBTN5H7qDD12AvlwFGnfAlBXjuP4xOUAISL5SRLiulrs" + 265 | "MSiT4GcugSI80mF6sdB0zWRgL1yyvoVWeTBn1TqjO27alr95DGTluuSqrNAxgpQzCKEWAyzrQkBfo2avGAmmz2NaHaAvYbOg0QSJ" + 266 | "z1PLjv2jdPW~ofiQmrGWM1cd~1cCqAAAA", 267 | "be0383038d3fc8c976a86ae4e78ba378e75ec41bc9ab1542a9cb422581987e118f5cb0c024f3639d6ad9b3aff613672f0" + 268 | "7bfbbbfc2f920ef910534ecaa6ff9c03e0fa4872a764d2fce6d4cfc5a5a9800cd95944cc9ef0241f753fe71494a1" + 269 | "75f334b35682459acadc4076428ab49b5a83a49d2ea2366b06461e4a559b0111fa750e0de0c138a94d1231ed5979" + 270 | "572ff53922905636221994bdabc44bd0c17fef11622b16432db3f193400af53cc61aa9bfc0c4c8d874b41a6e1873" + 271 | "2f0b60f5662ef1a89c80589dd8366c90bb58bb85ead56356aba2a244950ca170abbd01094539014f84bdd383e4a1" + 272 | "0e00cee63dfc3e809506e2d9b54edbdca1bace6eaa119e68573d30533791fba830f5d80be5c051a77c09415e3b8f" + 273 | "e3139400848be5244b8ae96bb0c4a24f819cba0488f34985eac741d3359180bd72cafa1559e4c19f54ea8cedbb6a" + 274 | "5afde4319396eb92aab340c60a50cc2284580cb3ad09017e8d9abc60269b3d8d687680bd86ce834412273d4f2e3b" + 275 | "f68dd3d6fe87e2426ac658cd5c77fd5c0aa000000"); 276 | test.accept("/garlic32/566niximlxdzpanmn4qouucvua3k7neniwss47li5r6ugoertzuq", 277 | "bf0320efbcd45d0c5dc79781ac6f20ea5055a036afb48d45a52e7d68ec7d4338919e69"); 278 | } 279 | 280 | @Test 281 | void dns() { 282 | String dns = "mydomain.com"; 283 | int port = 5001; 284 | String address = "/dns6/"+dns+"/tcp/"+port+"/https"; 285 | MultiAddress multiAddress = new MultiAddress(address); 286 | 287 | assertEquals(dns, multiAddress.getHost(), "host should be equal to " + dns); 288 | assertEquals(port, multiAddress.getPort(), "port should be equal to " + port); 289 | } 290 | 291 | public static byte[] fromHex(String hex) { 292 | if (hex.length() % 2 != 0) 293 | throw new IllegalStateException("Uneven number of hex digits!"); 294 | ByteArrayOutputStream bout = new ByteArrayOutputStream(); 295 | for (int i=0; i < hex.length()-1; i+= 2) 296 | bout.write(Integer.valueOf(hex.substring(i, i+2), 16)); 297 | return bout.toByteArray(); 298 | } 299 | } 300 | --------------------------------------------------------------------------------