├── 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 | [](http://ipn.io)
4 | [](https://github.com/multiformats/multiformats)
5 | [](https://webchat.freenode.net/?channels=%23ipfs)
6 | [](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 |
--------------------------------------------------------------------------------