├── .github └── workflows │ └── ant.yml ├── .gitignore ├── LICENSE ├── README.md ├── build.xml ├── docker-compose.yml ├── install-run-ipfs.sh ├── lib ├── cid.jar ├── hamcrest-core-1.3.jar ├── junit-4.12.jar ├── multiaddr.jar ├── multibase.jar └── multihash.jar ├── pom.xml ├── print_test_errors.sh └── src ├── main └── java │ └── io │ └── ipfs │ └── api │ ├── IPFS.java │ ├── IpldNode.java │ ├── JSONParser.java │ ├── KeyInfo.java │ ├── MerkleNode.java │ ├── Multipart.java │ ├── NamedStreamable.java │ ├── Pair.java │ ├── Peer.java │ ├── Version.java │ └── cbor │ ├── CborConstants.java │ ├── CborDecoder.java │ ├── CborEncoder.java │ ├── CborObject.java │ ├── CborType.java │ └── Cborable.java └── test ├── java └── io │ └── ipfs │ └── api │ ├── APITest.java │ ├── AddTest.java │ ├── RecursiveAddTest.java │ ├── SimpleAddTest.java │ └── VersionsTest.java └── resources └── html ├── chap └── ch01.html ├── css └── default.css ├── img └── logo.png └── index.html /.github/workflows/ant.yml: -------------------------------------------------------------------------------- 1 | name: Java CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v1 12 | - name: Set up JDK 11 13 | uses: actions/setup-java@v1 14 | with: 15 | java-version: 11 16 | - name: Install and run ipfs 17 | run: ./install-run-ipfs.sh 18 | - name: Build with Ant 19 | run: ant -noinput -buildfile build.xml dist 20 | - name: Run tests 21 | timeout-minutes: 10 22 | run: ant -noinput -buildfile build.xml test 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .classpath 2 | .project 3 | .settings 4 | .DS_Store 5 | *.class 6 | *.log 7 | dist/* 8 | target/ 9 | dependency-reduced-pom.xml 10 | TEST-io.ipfs.api.APITest.* 11 | 12 | # Scala-IDE specific 13 | .idea/* 14 | api.iml 15 | 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 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 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # java-ipfs-http-client 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-IPFS-blue.svg?style=flat-square)](http://ipfs.io/) 5 | [![](https://img.shields.io/badge/freenode-%23ipfs-blue.svg?style=flat-square)](http://webchat.freenode.net/?channels=%23ipfs) 6 | [![standard-readme compliant](https://img.shields.io/badge/standard--readme-OK-green.svg?style=flat-square)](https://github.com/RichardLitt/standard-readme) 7 | 8 | ![](https://ipfs.io/ipfs/QmQJ68PFMDdAsgCZvA1UVzzn18asVcf7HVvCDgpjiSCAse) 9 | 10 | > A Java client for the IPFS http api 11 | 12 | ## Table of Contents 13 | 14 | - [Install](#install) 15 | - [Usage](#usage) 16 | - [Building](#building) 17 | - [Contribute](#contribute) 18 | - [License](#license) 19 | 20 | ## Install 21 | 22 | ### Official releases 23 | 24 | You can use this project by including `ipfs.jar` from one of the [releases](https://github.com/ipfs/java-ipfs-api/releases) along with the dependencies. 25 | 26 | ### Maven, Gradle, SBT 27 | 28 | Package managers are supported through [JitPack](https://jitpack.io/#ipfs/java-ipfs-http-client/) which supports Maven, Gradle, SBT, etc. 29 | 30 | for Maven, add the following sections to your pom.xml (replacing $LATEST_VERSION): 31 | ``` 32 | 33 | 34 | jitpack.io 35 | https://jitpack.io 36 | 37 | 38 | 39 | 40 | 41 | com.github.ipfs 42 | java-ipfs-http-client 43 | $LATEST_VERSION 44 | 45 | 46 | ``` 47 | 48 | ### Building 49 | 50 | * Clone this repository 51 | * Run `ant dist` 52 | * Copy `dist/ipfs.jar` into your project. Appropriate versions of other [dependencies](#dependencies) are also included in `dist/lib/`. 53 | * Run tests using `ant test`. 54 | 55 | ### Running tests 56 | 57 | To run tests, IPFS daemon must be running on `127.0.0.1` interface. 58 | 59 | ### IPFS installation 60 | 61 | #### Command line 62 | 63 | Download ipfs from https://dist.ipfs.io/#go-ipfs and run with `ipfs daemon --enable-pubsub-experiment` 64 | 65 | #### Docker Compose 66 | 67 | Run `docker-compose up` from the project's root directory. Check [docker-compose.yml](docker-compose.yml) for more details. 68 | 69 | ## Usage 70 | 71 | Create an IPFS instance with: 72 | ```Java 73 | IPFS ipfs = new IPFS("/ip4/127.0.0.1/tcp/5001"); 74 | ``` 75 | 76 | Then run commands like: 77 | ```Java 78 | ipfs.refs.local(); 79 | ``` 80 | 81 | To add a file use (the add method returns a list of merklenodes, in this case there is only one element): 82 | ```Java 83 | NamedStreamable.FileWrapper file = new NamedStreamable.FileWrapper(new File("hello.txt")); 84 | MerkleNode addResult = ipfs.add(file).get(0); 85 | ``` 86 | 87 | To add a byte[] use: 88 | ```Java 89 | NamedStreamable.ByteArrayWrapper file = new NamedStreamable.ByteArrayWrapper("hello.txt", "G'day world! IPFS rocks!".getBytes()); 90 | MerkleNode addResult = ipfs.add(file).get(0); 91 | ``` 92 | 93 | To get a file use: 94 | ```Java 95 | Multihash filePointer = Multihash.fromBase58("QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB"); 96 | byte[] fileContents = ipfs.cat(filePointer); 97 | ``` 98 | 99 | ## Dependencies 100 | 101 | Current versions of dependencies are included in the `./lib` directory. 102 | 103 | * [multibase](https://github.com/multiformats/java-multibase) 104 | * [multiaddr](https://github.com/multiformats/java-multiaddr) 105 | * [multihash](https://github.com/multiformats/java-multihash) 106 | * [cid](https://github.com/ipld/java-cid) 107 | 108 | ## Releasing 109 | The version number is specified in `build.xml` and `pom.xml` and must be changed in both places 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](https://jitpack.io/#ipfs/java-ipfs-http-client/) to work. 110 | 111 | ## Contribute 112 | 113 | Feel free to join in. All welcome. Open an [issue](https://github.com/ipfs/java-ipfs-api/issues)! 114 | 115 | This repository falls under the IPFS [Code of Conduct](https://github.com/ipfs/community/blob/master/code-of-conduct.md). 116 | 117 | [![](https://cdn.rawgit.com/jbenet/contribute-ipfs-gif/master/img/contribute.gif)](https://github.com/ipfs/community/blob/master/contributing.md) 118 | 119 | ## License 120 | 121 | [MIT](LICENSE) 122 | -------------------------------------------------------------------------------- /build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Java IPFS api 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | services: 3 | ipfs-daemon: 4 | image: 'ipfs/go-ipfs:v0.6.0' 5 | ports: 6 | - "4001:4001" 7 | - "5001:5001" 8 | user: "ipfs" 9 | command: [ "daemon", "--enable-pubsub-experiment" ] 10 | -------------------------------------------------------------------------------- /install-run-ipfs.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | wget https://dist.ipfs.io/go-ipfs/v0.6.0/go-ipfs_v0.6.0_linux-amd64.tar.gz -O /tmp/go-ipfs_linux-amd64.tar.gz 3 | tar -xvf /tmp/go-ipfs_linux-amd64.tar.gz 4 | export PATH=$PATH:$PWD/go-ipfs/ 5 | ipfs init 6 | ipfs daemon --enable-pubsub-experiment --routing=dhtclient & 7 | -------------------------------------------------------------------------------- /lib/cid.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Openfabric/java-ipfs-http-client/ed9609ce8db035ca9517bae941757188669c2847/lib/cid.jar -------------------------------------------------------------------------------- /lib/hamcrest-core-1.3.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Openfabric/java-ipfs-http-client/ed9609ce8db035ca9517bae941757188669c2847/lib/hamcrest-core-1.3.jar -------------------------------------------------------------------------------- /lib/junit-4.12.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Openfabric/java-ipfs-http-client/ed9609ce8db035ca9517bae941757188669c2847/lib/junit-4.12.jar -------------------------------------------------------------------------------- /lib/multiaddr.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Openfabric/java-ipfs-http-client/ed9609ce8db035ca9517bae941757188669c2847/lib/multiaddr.jar -------------------------------------------------------------------------------- /lib/multibase.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Openfabric/java-ipfs-http-client/ed9609ce8db035ca9517bae941757188669c2847/lib/multibase.jar -------------------------------------------------------------------------------- /lib/multihash.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Openfabric/java-ipfs-http-client/ed9609ce8db035ca9517bae941757188669c2847/lib/multihash.jar -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | com.github.ipfs 6 | java-ipfs-http-client 7 | v1.3.3 8 | jar 9 | 10 | java-ipfs-http-client 11 | https://github.com/ipfs/java-ipfs-http-client 12 | 13 | 14 | https://github.com/ipfs/java-ipfs-http-client/issues 15 | GitHub Issues 16 | 17 | 18 | 19 | https://github.com/ipfs/java-ipfs-http-client 20 | scm:git:git://github.com/ipfs/java-ipfs-http-client.git 21 | scm:git:git@github.com:ipfs/java-ipfs-http-client.git 22 | 23 | 24 | 25 | 26 | MIT License 27 | https://github.com/ipfs/java-ipfs-http-client/blob/master/LICENSE 28 | repo 29 | 30 | 31 | 32 | 33 | UTF-8 34 | UTF-8 35 | 4.13.1 36 | 1.3 37 | v1.4.2 38 | 2.7.4 39 | 3.13.6 40 | 41 | 42 | 43 | 44 | jitpack.io 45 | https://jitpack.io 46 | 47 | 48 | 49 | 50 | 51 | com.github.Openfabric 52 | java-multiaddr 53 | ${version.multiaddr} 54 | 55 | 56 | junit 57 | junit 58 | ${version.junit} 59 | test 60 | 61 | 62 | org.hamcrest 63 | hamcrest-core 64 | ${version.hamcrest} 65 | test 66 | 67 | 68 | com.fasterxml.jackson.core 69 | jackson-annotations 70 | ${version.jaxon} 71 | 72 | 73 | com.fasterxml.jackson.core 74 | jackson-core 75 | ${version.jaxon} 76 | 77 | 78 | com.fasterxml.jackson.core 79 | jackson-databind 80 | ${version.jaxon} 81 | 82 | 83 | com.konghq 84 | unirest-java 85 | ${version.unirest} 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | org.apache.maven.plugins 94 | maven-compiler-plugin 95 | 3.8.0 96 | 97 | 1.8 98 | 1.8 99 | 100 | 101 | 102 | org.apache.maven.plugins 103 | maven-surefire-plugin 104 | 2.19.1 105 | 106 | 107 | org.apache.maven.plugins 108 | maven-jar-plugin 109 | 3.0.2 110 | 111 | 112 | 113 | true 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | -------------------------------------------------------------------------------- /print_test_errors.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Read junit test-reports and print a summary of the error-cases, including the stack trace. 4 | # Will exit with status 1 if there are any errors, otherwise exit status 0. 5 | # 6 | # By default will scan all files in "./test.reports". 7 | # 8 | # Usage "./print_test_errors.sh 9 | # 10 | awk '/<(failure|error)/,/\/(failure|error)/ {print prev; has_err=1} {prev=$0} END {exit has_err}' ${1:-test.reports/*} 11 | -------------------------------------------------------------------------------- /src/main/java/io/ipfs/api/IPFS.java: -------------------------------------------------------------------------------- 1 | package io.ipfs.api; 2 | 3 | import io.ipfs.cid.Cid; 4 | import io.ipfs.multiaddr.MultiAddress; 5 | import io.ipfs.multihash.Multihash; 6 | import kong.unirest.RawResponse; 7 | import kong.unirest.Unirest; 8 | 9 | import java.io.BufferedReader; 10 | import java.io.IOException; 11 | import java.io.InputStream; 12 | import java.io.InputStreamReader; 13 | import java.net.URL; 14 | import java.net.URLEncoder; 15 | import java.nio.file.Paths; 16 | import java.util.*; 17 | import java.util.concurrent.*; 18 | import java.util.function.Consumer; 19 | import java.util.stream.Collectors; 20 | import java.util.stream.Stream; 21 | 22 | public class IPFS { 23 | 24 | public static final Version MIN_VERSION = Version.parse("0.4.11"); 25 | 26 | public enum PinType {all, direct, indirect, recursive} 27 | 28 | public List ObjectTemplates = Arrays.asList("unixfs-dir"); 29 | public List ObjectPatchTypes = Arrays.asList("add-link", "rm-link", "set-data", "append-data"); 30 | private static final int DEFAULT_CONNECT_TIMEOUT_MILLIS = 10_000; 31 | private static final int DEFAULT_READ_TIMEOUT_MILLIS = 60_000; 32 | 33 | public final String host; 34 | public final int port; 35 | public final String protocol; 36 | private final String version; 37 | private final int connectTimeoutMillis; 38 | private final int readTimeoutMillis; 39 | public final Key key = new Key(); 40 | public final Pin pin = new Pin(); 41 | public final Repo repo = new Repo(); 42 | public final IPFSObject object = new IPFSObject(); 43 | public final Swarm swarm = new Swarm(); 44 | public final Bootstrap bootstrap = new Bootstrap(); 45 | public final Block block = new Block(); 46 | public final Dag dag = new Dag(); 47 | public final Diag diag = new Diag(); 48 | public final Config config = new Config(); 49 | public final Refs refs = new Refs(); 50 | public final Update update = new Update(); 51 | public final DHT dht = new DHT(); 52 | public final File file = new File(); 53 | public final Stats stats = new Stats(); 54 | public final Name name = new Name(); 55 | public final Pubsub pubsub = new Pubsub(); 56 | 57 | public IPFS(String host, int port) { 58 | this(host, port, "/api/v0/", false); 59 | } 60 | 61 | public IPFS(String multiaddr) { 62 | this(new MultiAddress(multiaddr)); 63 | } 64 | 65 | public IPFS(MultiAddress addr) { 66 | this(addr.getHost(), addr.getPort(), "/api/v0/", detectSSL(addr)); 67 | } 68 | 69 | public IPFS(String host, int port, String version, boolean ssl) { 70 | this(host, port, version, DEFAULT_CONNECT_TIMEOUT_MILLIS, DEFAULT_READ_TIMEOUT_MILLIS, ssl); 71 | } 72 | 73 | public IPFS(String host, int port, String version, int connectTimeoutMillis, int readTimeoutMillis, boolean ssl) { 74 | if (connectTimeoutMillis < 0) throw new IllegalArgumentException("connect timeout must be zero or positive"); 75 | if (readTimeoutMillis < 0) throw new IllegalArgumentException("read timeout must be zero or positive"); 76 | this.host = host; 77 | this.port = port; 78 | this.connectTimeoutMillis = connectTimeoutMillis; 79 | this.readTimeoutMillis = readTimeoutMillis; 80 | 81 | if (ssl) { 82 | this.protocol = "https"; 83 | } else { 84 | this.protocol = "http"; 85 | } 86 | 87 | this.version = version; 88 | // Check IPFS is sufficiently recent 89 | try { 90 | Version detected = Version.parse(version()); 91 | if (detected.isBefore(MIN_VERSION)) 92 | throw new IllegalStateException("You need to use a more recent version of IPFS! >= " + MIN_VERSION); 93 | } catch (IOException e) { 94 | throw new RuntimeException(e); 95 | } 96 | } 97 | 98 | /** 99 | * Configure a HTTP client timeout 100 | * 101 | * @param timeout (default 0: infinite timeout) 102 | * @return current IPFS object with configured timeout 103 | */ 104 | public IPFS timeout(int timeout) { 105 | return new IPFS(host, port, version, connectTimeoutMillis, readTimeoutMillis, protocol.equals("https")); 106 | } 107 | 108 | public List add(NamedStreamable file) throws IOException { 109 | return add(file, false); 110 | } 111 | 112 | public List add(NamedStreamable file, boolean wrap) throws IOException { 113 | return add(file, wrap, false); 114 | } 115 | 116 | public List add(NamedStreamable file, boolean wrap, boolean hashOnly) throws IOException { 117 | return add(Collections.singletonList(file), wrap, hashOnly); 118 | } 119 | 120 | public List add(List files, boolean wrap, boolean hashOnly) throws IOException { 121 | Multipart multipart = new Multipart(protocol + "://" + host + ":" + port + version + "add?stream-channels=true&w=" + wrap + "&n=" + hashOnly, "UTF-8"); 122 | for (NamedStreamable file : files) { 123 | if (file.isDirectory()) { 124 | multipart.addSubtree(Paths.get(""), file); 125 | } else 126 | multipart.addFilePart("file", Paths.get(""), file); 127 | } 128 | 129 | String res = multipart.finish(); 130 | return JSONParser.parseStream(res).stream() 131 | .map(x -> MerkleNode.fromJSON((Map) x)) 132 | .collect(Collectors.toList()); 133 | } 134 | 135 | public List ls(Multihash hash) throws IOException { 136 | Map reply = retrieveMap("ls?arg=" + hash); 137 | return ((List) reply.get("Objects")) 138 | .stream() 139 | .flatMap(x -> ((List) ((Map) x).get("Links")) 140 | .stream() 141 | .map(MerkleNode::fromJSON)) 142 | .collect(Collectors.toList()); 143 | } 144 | 145 | public byte[] cat(Multihash hash) throws IOException { 146 | return retrieve("cat?arg=" + hash); 147 | } 148 | 149 | public byte[] cat(Multihash hash, String subPath) throws IOException { 150 | return retrieve("cat?arg=" + hash + URLEncoder.encode(subPath, "UTF-8")); 151 | } 152 | 153 | public byte[] get(Multihash hash) throws IOException { 154 | return retrieve("get?arg=" + hash); 155 | } 156 | 157 | public InputStream catStream(Multihash hash) throws IOException { 158 | return retrieveStream("cat?arg=" + hash); 159 | } 160 | 161 | public List refs(Multihash hash, boolean recursive) throws IOException { 162 | String jsonStream = new String(retrieve("refs?arg=" + hash + "&r=" + recursive)); 163 | return JSONParser.parseStream(jsonStream).stream() 164 | .map(m -> (String) (((Map) m).get("Ref"))) 165 | .map(Cid::decode) 166 | .collect(Collectors.toList()); 167 | } 168 | 169 | public Map resolve(String scheme, Multihash hash, boolean recursive) throws IOException { 170 | return retrieveMap("resolve?arg=/" + scheme + "/" + hash + "&r=" + recursive); 171 | } 172 | 173 | 174 | public String dns(String domain, boolean recursive) throws IOException { 175 | Map res = retrieveMap("dns?arg=" + domain + "&r=" + recursive); 176 | return (String) res.get("Path"); 177 | } 178 | 179 | public Map mount(java.io.File ipfsRoot, java.io.File ipnsRoot) throws IOException { 180 | if (ipfsRoot != null && !ipfsRoot.exists()) 181 | ipfsRoot.mkdirs(); 182 | if (ipnsRoot != null && !ipnsRoot.exists()) 183 | ipnsRoot.mkdirs(); 184 | return (Map) retrieveAndParse("mount?arg=" + (ipfsRoot != null ? ipfsRoot.getPath() : "/ipfs") + "&arg=" + 185 | (ipnsRoot != null ? ipnsRoot.getPath() : "/ipns")); 186 | } 187 | 188 | // level 2 commands 189 | public class Refs { 190 | public List local() throws IOException { 191 | String jsonStream = new String(retrieve("refs/local")); 192 | return JSONParser.parseStream(jsonStream).stream() 193 | .map(m -> (String) (((Map) m).get("Ref"))) 194 | .map(Cid::decode) 195 | .collect(Collectors.toList()); 196 | } 197 | } 198 | 199 | /* Pinning an object ensures a local copy of it is kept. 200 | */ 201 | public class Pin { 202 | public List add(Multihash hash) throws IOException { 203 | return ((List) ((Map) retrieveAndParse("pin/add?stream-channels=true&arg=" + hash)).get("Pins")) 204 | .stream() 205 | .map(x -> Cid.decode((String) x)) 206 | .collect(Collectors.toList()); 207 | } 208 | 209 | public Map ls() throws IOException { 210 | return ls(PinType.direct); 211 | } 212 | 213 | public Map ls(PinType type) throws IOException { 214 | return ((Map) (((Map) retrieveAndParse("pin/ls?stream-channels=true&t=" + type.name())).get("Keys"))).entrySet() 215 | .stream() 216 | .collect(Collectors.toMap(x -> Cid.decode(x.getKey()), x -> x.getValue())); 217 | } 218 | 219 | public List rm(Multihash hash) throws IOException { 220 | return rm(hash, true); 221 | } 222 | 223 | public List rm(Multihash hash, boolean recursive) throws IOException { 224 | Map json = retrieveMap("pin/rm?stream-channels=true&r=" + recursive + "&arg=" + hash); 225 | return ((List) json.get("Pins")).stream().map(x -> Cid.decode((String) x)).collect(Collectors.toList()); 226 | } 227 | 228 | public List update(Multihash existing, Multihash modified, boolean unpin) throws IOException { 229 | return ((List) ((Map) retrieveAndParse( 230 | "pin/update?stream-channels=true&arg=" + existing + "&arg=" + modified + "&unpin=" + unpin)) 231 | .getOrDefault("Pins", new ArrayList<>())) 232 | .stream() 233 | .map(x -> Cid.decode((String) x)) 234 | .collect(Collectors.toList()); 235 | } 236 | } 237 | 238 | /* 'ipfs repo' is a plumbing command used to manipulate the repo. 239 | */ 240 | public class Key { 241 | public KeyInfo gen(String name, Optional type, Optional size) throws IOException { 242 | return KeyInfo.fromJson(retrieveAndParse("key/gen?arg=" + name + type.map(t -> "&type=" + t).orElse("") + size.map(s -> "&size=" + s).orElse(""))); 243 | } 244 | 245 | public List list() throws IOException { 246 | return ((List) ((Map) retrieveAndParse("key/list")).get("Keys")) 247 | .stream() 248 | .map(KeyInfo::fromJson) 249 | .collect(Collectors.toList()); 250 | } 251 | 252 | public Object rename(String name, String newName) throws IOException { 253 | return retrieveAndParse("key/rename?arg=" + name + "&arg=" + newName); 254 | } 255 | 256 | public List rm(String name) throws IOException { 257 | return ((List) ((Map) retrieveAndParse("key/rm?arg=" + name)).get("Keys")) 258 | .stream() 259 | .map(KeyInfo::fromJson) 260 | .collect(Collectors.toList()); 261 | } 262 | } 263 | 264 | /* 'ipfs repo' is a plumbing command used to manipulate the repo. 265 | */ 266 | public class Repo { 267 | public Object gc() throws IOException { 268 | return retrieveAndParse("repo/gc"); 269 | } 270 | } 271 | 272 | public class Pubsub { 273 | public Object ls() throws IOException { 274 | return retrieveAndParse("pubsub/ls"); 275 | } 276 | 277 | public Object peers() throws IOException { 278 | return retrieveAndParse("pubsub/peers"); 279 | } 280 | 281 | public Object peers(String topic) throws IOException { 282 | return retrieveAndParse("pubsub/peers?arg=" + topic); 283 | } 284 | 285 | /** 286 | * @param topic 287 | * @param data url encoded data to be published 288 | * @return 289 | * @throws IOException 290 | */ 291 | public Object pub(String topic, String data) throws Exception { 292 | return retrieveAndParse("pubsub/pub?arg=" + topic + "&arg=" + data); 293 | } 294 | 295 | public Stream> sub(String topic) throws Exception { 296 | return sub(topic, ForkJoinPool.commonPool()); 297 | } 298 | 299 | public Stream> sub(String topic, ForkJoinPool threadSupplier) throws Exception { 300 | return retrieveAndParseStream("pubsub/sub?arg=" + topic, threadSupplier).map(obj -> (Map) obj); 301 | } 302 | 303 | /** 304 | * A synchronous method to subscribe which consumes the calling thread 305 | * 306 | * @param topic 307 | * @param results 308 | * @throws IOException 309 | */ 310 | public void sub(String topic, Consumer> results, Consumer error) throws IOException { 311 | retrieveAndParseStream("pubsub/sub?arg=" + topic, res -> results.accept((Map) res), error); 312 | } 313 | 314 | 315 | } 316 | 317 | /* 'ipfs block' is a plumbing command used to manipulate raw ipfs blocks. 318 | */ 319 | public class Block { 320 | public byte[] get(Multihash hash) throws IOException { 321 | return retrieve("block/get?stream-channels=true&arg=" + hash); 322 | } 323 | 324 | public byte[] rm(Multihash hash) throws IOException { 325 | return retrieve("block/rm?stream-channels=true&arg=" + hash); 326 | } 327 | 328 | public List put(List data) throws IOException { 329 | return put(data, Optional.empty()); 330 | } 331 | 332 | public List put(List data, Optional format) throws IOException { 333 | // N.B. Once IPFS implements a bulk put this can become a single multipart call with multiple 'files' 334 | List res = new ArrayList<>(); 335 | for (byte[] value : data) { 336 | res.add(put(value, format)); 337 | } 338 | return res; 339 | } 340 | 341 | public MerkleNode put(byte[] data, Optional format) throws IOException { 342 | String fmt = format.map(f -> "&format=" + f).orElse(""); 343 | Multipart m = new Multipart(protocol + "://" + host + ":" + port + version + "block/put?stream-channels=true" + fmt, "UTF-8"); 344 | try { 345 | m.addFilePart("file", Paths.get(""), new NamedStreamable.ByteArrayWrapper(data)); 346 | String res = m.finish(); 347 | return JSONParser.parseStream(res).stream().map(x -> MerkleNode.fromJSON((Map) x)).findFirst().get(); 348 | } catch (IOException e) { 349 | throw new RuntimeException(e.getMessage(), e); 350 | } 351 | } 352 | 353 | public Map stat(Multihash hash) throws IOException { 354 | return retrieveMap("block/stat?stream-channels=true&arg=" + hash); 355 | } 356 | } 357 | 358 | /* 'ipfs object' is a plumbing command used to manipulate DAG objects directly. {Object} is a subset of {Block} 359 | */ 360 | public class IPFSObject { 361 | public List put(List data) throws IOException { 362 | Multipart m = new Multipart(protocol + "://" + host + ":" + port + version + "object/put?stream-channels=true", "UTF-8"); 363 | for (byte[] f : data) 364 | m.addFilePart("file", Paths.get(""), new NamedStreamable.ByteArrayWrapper(f)); 365 | String res = m.finish(); 366 | return JSONParser.parseStream(res).stream().map(x -> MerkleNode.fromJSON((Map) x)).collect(Collectors.toList()); 367 | } 368 | 369 | public List put(String encoding, List data) throws IOException { 370 | if (!"json".equals(encoding) && !"protobuf".equals(encoding)) 371 | throw new IllegalArgumentException("Encoding must be json or protobuf"); 372 | Multipart m = new Multipart(protocol + "://" + host + ":" + port + version + "object/put?stream-channels=true&encoding=" + encoding, "UTF-8"); 373 | for (byte[] f : data) 374 | m.addFilePart("file", Paths.get(""), new NamedStreamable.ByteArrayWrapper(f)); 375 | String res = m.finish(); 376 | return JSONParser.parseStream(res).stream().map(x -> MerkleNode.fromJSON((Map) x)).collect(Collectors.toList()); 377 | } 378 | 379 | public MerkleNode get(Multihash hash) throws IOException { 380 | Map json = retrieveMap("object/get?stream-channels=true&arg=" + hash); 381 | json.put("Hash", hash.toBase58()); 382 | return MerkleNode.fromJSON(json); 383 | } 384 | 385 | public MerkleNode links(Multihash hash) throws IOException { 386 | Map json = retrieveMap("object/links?stream-channels=true&arg=" + hash); 387 | return MerkleNode.fromJSON(json); 388 | } 389 | 390 | public Map stat(Multihash hash) throws IOException { 391 | return retrieveMap("object/stat?stream-channels=true&arg=" + hash); 392 | } 393 | 394 | public byte[] data(Multihash hash) throws IOException { 395 | return retrieve("object/data?stream-channels=true&arg=" + hash); 396 | } 397 | 398 | public MerkleNode _new(Optional template) throws IOException { 399 | if (template.isPresent() && !ObjectTemplates.contains(template.get())) 400 | throw new IllegalStateException("Unrecognised template: " + template.get()); 401 | Map json = retrieveMap("object/new?stream-channels=true" + (template.isPresent() ? "&arg=" + template.get() : "")); 402 | return MerkleNode.fromJSON(json); 403 | } 404 | 405 | public MerkleNode patch(Multihash base, String command, Optional data, Optional name, Optional target) throws IOException { 406 | if (!ObjectPatchTypes.contains(command)) 407 | throw new IllegalStateException("Illegal Object.patch command type: " + command); 408 | String targetPath = "object/patch/" + command + "?arg=" + base.toBase58(); 409 | if (name.isPresent()) 410 | targetPath += "&arg=" + name.get(); 411 | if (target.isPresent()) 412 | targetPath += "&arg=" + target.get().toBase58(); 413 | 414 | switch (command) { 415 | case "add-link": 416 | if (!target.isPresent()) 417 | throw new IllegalStateException("add-link requires name and target!"); 418 | case "rm-link": 419 | if (!name.isPresent()) 420 | throw new IllegalStateException("link name is required!"); 421 | return MerkleNode.fromJSON(retrieveMap(targetPath)); 422 | case "set-data": 423 | case "append-data": 424 | if (!data.isPresent()) 425 | throw new IllegalStateException("set-data requires data!"); 426 | Multipart m = new Multipart(protocol + "://" + host + ":" + port + version + "object/patch/" + command + "?arg=" + base.toBase58() + "&stream-channels=true", "UTF-8"); 427 | m.addFilePart("file", Paths.get(""), new NamedStreamable.ByteArrayWrapper(data.get())); 428 | String res = m.finish(); 429 | return MerkleNode.fromJSON(JSONParser.parse(res)); 430 | 431 | default: 432 | throw new IllegalStateException("Unimplemented"); 433 | } 434 | } 435 | } 436 | 437 | public class Name { 438 | public Map publish(Multihash hash) throws IOException { 439 | return publish(hash, Optional.empty()); 440 | } 441 | 442 | public Map publish(Multihash hash, Optional id) throws IOException { 443 | return retrieveMap("name/publish?arg=/ipfs/" + hash + id.map(name -> "&key=" + name).orElse("")); 444 | } 445 | 446 | public String resolve(String ipns) throws IOException { 447 | Map res = (Map) retrieveAndParse("name/resolve?arg=" + ipns); 448 | return (String) res.get("Path"); 449 | } 450 | 451 | } 452 | 453 | public class DHT { 454 | public List> findprovs(Multihash hash) throws IOException { 455 | return getAndParseStream("dht/findprovs?arg=" + hash).stream() 456 | .map(x -> (Map) x) 457 | .collect(Collectors.toList()); 458 | } 459 | 460 | public Map query(Multihash peerId) throws IOException { 461 | return retrieveMap("dht/query?arg=" + peerId.toString()); 462 | } 463 | 464 | public Map findpeer(Multihash id) throws IOException { 465 | return retrieveMap("dht/findpeer?arg=" + id.toString()); 466 | } 467 | 468 | public Map get(Multihash hash) throws IOException { 469 | return retrieveMap("dht/get?arg=" + hash); 470 | } 471 | 472 | public Map put(String key, String value) throws IOException { 473 | return retrieveMap("dht/put?arg=" + key + "&arg=" + value); 474 | } 475 | } 476 | 477 | public class File { 478 | public Map ls(Multihash path) throws IOException { 479 | return retrieveMap("file/ls?arg=" + path); 480 | } 481 | } 482 | 483 | // Network commands 484 | 485 | public List bootstrap() throws IOException { 486 | return ((List) retrieveMap("bootstrap/").get("Peers")) 487 | .stream() 488 | .flatMap(x -> { 489 | try { 490 | return Stream.of(new MultiAddress(x)); 491 | } catch (Exception e) { 492 | return Stream.empty(); 493 | } 494 | }).collect(Collectors.toList()); 495 | } 496 | 497 | public class Bootstrap { 498 | public List list() throws IOException { 499 | return bootstrap(); 500 | } 501 | 502 | public List add(MultiAddress addr) throws IOException { 503 | return ((List) retrieveMap("bootstrap/add?arg=" + addr).get("Peers")).stream().map(x -> new MultiAddress(x)).collect(Collectors.toList()); 504 | } 505 | 506 | public List rm(MultiAddress addr) throws IOException { 507 | return rm(addr, false); 508 | } 509 | 510 | public List rm(MultiAddress addr, boolean all) throws IOException { 511 | return ((List) retrieveMap("bootstrap/rm?" + (all ? "all=true&" : "") + "arg=" + addr).get("Peers")).stream().map(x -> new MultiAddress(x)).collect(Collectors.toList()); 512 | } 513 | } 514 | 515 | /* ipfs swarm is a tool to manipulate the network swarm. The swarm is the 516 | component that opens, listens for, and maintains connections to other 517 | ipfs peers in the internet. 518 | */ 519 | public class Swarm { 520 | public List peers() throws IOException { 521 | Map m = retrieveMap("swarm/peers?stream-channels=true"); 522 | return ((List) m.get("Peers")).stream() 523 | .flatMap(json -> { 524 | try { 525 | return Stream.of(Peer.fromJSON(json)); 526 | } catch (Exception e) { 527 | return Stream.empty(); 528 | } 529 | }).collect(Collectors.toList()); 530 | } 531 | 532 | public Map> addrs() throws IOException { 533 | Map m = retrieveMap("swarm/addrs?stream-channels=true"); 534 | return ((Map) m.get("Addrs")).entrySet() 535 | .stream() 536 | .collect(Collectors.toMap( 537 | e -> Multihash.fromBase58(e.getKey()), 538 | e -> ((List) e.getValue()) 539 | .stream() 540 | .map(MultiAddress::new) 541 | .collect(Collectors.toList()))); 542 | } 543 | 544 | public Map connect(MultiAddress multiAddr) throws IOException { 545 | Map m = retrieveMap("swarm/connect?arg=" + multiAddr); 546 | return m; 547 | } 548 | 549 | public Map disconnect(MultiAddress multiAddr) throws IOException { 550 | Map m = retrieveMap("swarm/disconnect?arg=" + multiAddr); 551 | return m; 552 | } 553 | } 554 | 555 | public class Dag { 556 | public byte[] get(Cid cid) throws IOException { 557 | return retrieve("dag/get?stream-channels=true&arg=" + cid); 558 | } 559 | 560 | public MerkleNode put(byte[] object) throws IOException { 561 | return put("json", object, "cbor"); 562 | } 563 | 564 | public MerkleNode put(String inputFormat, byte[] object) throws IOException { 565 | return put(inputFormat, object, "cbor"); 566 | } 567 | 568 | public MerkleNode put(byte[] object, String outputFormat) throws IOException { 569 | return put("json", object, outputFormat); 570 | } 571 | 572 | public MerkleNode put(String inputFormat, byte[] object, String outputFormat) throws IOException { 573 | String prefix = protocol + "://" + host + ":" + port + version; 574 | Multipart m = new Multipart(prefix + "dag/put/?stream-channels=true&input-enc=" + inputFormat + "&f=" + outputFormat, "UTF-8"); 575 | m.addFilePart("file", Paths.get(""), new NamedStreamable.ByteArrayWrapper(object)); 576 | String res = m.finish(); 577 | return MerkleNode.fromJSON(JSONParser.parse(res)); 578 | } 579 | } 580 | 581 | public class Diag { 582 | public String cmds() throws IOException { 583 | return new String(retrieve("diag/cmds?stream-channels=true")); 584 | } 585 | 586 | public String sys() throws IOException { 587 | return new String(retrieve("diag/sys?stream-channels=true")); 588 | } 589 | } 590 | 591 | public Map ping(Multihash target) throws IOException { 592 | return retrieveMap("ping/" + target.toBase58()); 593 | } 594 | 595 | public Map id(Multihash target) throws IOException { 596 | return retrieveMap("id/" + target.toBase58()); 597 | } 598 | 599 | public Map id() throws IOException { 600 | return retrieveMap("id"); 601 | } 602 | 603 | public class Stats { 604 | public Map bw() throws IOException { 605 | return retrieveMap("stats/bw"); 606 | } 607 | } 608 | 609 | // Tools 610 | public String version() throws IOException { 611 | Map m = (Map) retrieveAndParse("version"); 612 | return (String) m.get("Version"); 613 | } 614 | 615 | public Map commands() throws IOException { 616 | return retrieveMap("commands"); 617 | } 618 | 619 | public Map log() throws IOException { 620 | return retrieveMap("log/tail"); 621 | } 622 | 623 | public class Config { 624 | public Map show() throws IOException { 625 | return (Map) retrieveAndParse("config/show"); 626 | } 627 | 628 | public void replace(NamedStreamable file) throws IOException { 629 | Multipart m = new Multipart(protocol + "://" + host + ":" + port + version + "config/replace?stream-channels=true", "UTF-8"); 630 | m.addFilePart("file", Paths.get(""), file); 631 | String res = m.finish(); 632 | } 633 | 634 | public Object get(String key) throws IOException { 635 | Map m = (Map) retrieveAndParse("config?arg=" + key); 636 | return m.get("Value"); 637 | } 638 | 639 | public Map set(String key, Object value) throws IOException { 640 | return retrieveMap("config?arg=" + key + "&arg=" + value); 641 | } 642 | } 643 | 644 | public Object update() throws IOException { 645 | return retrieveAndParse("update"); 646 | } 647 | 648 | public class Update { 649 | public Object check() throws IOException { 650 | return retrieveAndParse("update/check"); 651 | } 652 | 653 | public Object log() throws IOException { 654 | return retrieveAndParse("update/log"); 655 | } 656 | } 657 | 658 | private Map retrieveMap(String path) throws IOException { 659 | return (Map) retrieveAndParse(path); 660 | } 661 | 662 | private Object retrieveAndParse(String path) throws IOException { 663 | byte[] res = retrieve(path); 664 | return JSONParser.parse(new String(res)); 665 | } 666 | 667 | private Stream retrieveAndParseStream(String path, ForkJoinPool executor) throws IOException { 668 | // BlockingQueue> results = new LinkedBlockingQueue<>(); 669 | // InputStream stream = 670 | // executor.submit(() -> getObjectStream(stream, 671 | // res -> results.add(CompletableFuture.completedFuture(res)), 672 | // err -> { 673 | // CompletableFuture fut = new CompletableFuture<>(); 674 | // fut.completeExceptionally(err); 675 | // results.add(fut); 676 | // }) 677 | // ); 678 | return Stream.generate(() -> { 679 | try { 680 | String json = new String(retrieve(path)); 681 | return JSONParser.parse(json); 682 | } catch (Exception e) { 683 | throw new RuntimeException(e); 684 | } 685 | }); 686 | } 687 | 688 | /** 689 | * A synchronous stream retriever that consumes the calling thread 690 | * 691 | * @param path 692 | * @param results 693 | * @throws IOException 694 | */ 695 | private void retrieveAndParseStream(String path, Consumer results, Consumer err) throws IOException { 696 | getObjectStream(retrieveStream(path), d -> results.accept(JSONParser.parse(new String(d))), err); 697 | } 698 | 699 | private byte[] retrieve(String path) throws IOException { 700 | URL target = new URL(protocol, host, port, version + path); 701 | return IPFS.get(target, connectTimeoutMillis, readTimeoutMillis); 702 | } 703 | 704 | private static byte[] get(URL target, int connectTimeoutMillis, int readTimeoutMillis) throws IOException { 705 | 706 | return Unirest.post(target.toString()) 707 | .header("accept", "application/json") 708 | .contentType("application/json") 709 | .connectTimeout(connectTimeoutMillis) 710 | .socketTimeout(readTimeoutMillis) 711 | .asBytes() 712 | .ifFailure(response -> { 713 | response.getParsingError().ifPresent(e -> { 714 | throw new RuntimeException("IO Exception:" + e + " " + e.getOriginalBody()); 715 | }); 716 | }) 717 | .getBody(); 718 | 719 | } 720 | 721 | private void getObjectStream(InputStream in, Consumer processor, Consumer error) { 722 | 723 | BufferedReader reader = new BufferedReader(new InputStreamReader(in)); 724 | try { 725 | while (reader.ready()) { 726 | String line = reader.readLine(); 727 | processor.accept(line.getBytes()); 728 | } 729 | } catch (IOException ex) { 730 | error.accept(ex); 731 | } 732 | 733 | } 734 | 735 | private List getAndParseStream(String path) throws IOException { 736 | 737 | List res = new ArrayList<>(); 738 | InputStream in = retrieveStream(path); 739 | BufferedReader reader = new BufferedReader(new InputStreamReader(in)); 740 | 741 | while (reader.ready()) { 742 | String line = reader.readLine(); 743 | res.add(JSONParser.parse(line)); 744 | } 745 | return res; 746 | 747 | } 748 | 749 | private InputStream retrieveStream(String path) throws IOException { 750 | URL target = new URL(protocol, host, port, version + path); 751 | return IPFS.getStream(target, connectTimeoutMillis, readTimeoutMillis); 752 | } 753 | 754 | private static InputStream getStream(URL target, int connectTimeoutMillis, int readTimeoutMillis) throws IOException { 755 | try { 756 | return Unirest.post(target.toString()) 757 | .header("accept", "application/json") 758 | .contentType("application/json") 759 | .connectTimeout(connectTimeoutMillis) 760 | .socketTimeout(readTimeoutMillis) 761 | .asObjectAsync(RawResponse::getContent) 762 | .get() 763 | .getBody(); 764 | } catch (InterruptedException | ExecutionException e) { 765 | throw new RuntimeException(e); 766 | } 767 | } 768 | 769 | private Map postMap(String path, byte[] body, Map headers) throws IOException { 770 | URL target = new URL(protocol, host, port, version + path); 771 | return (Map) JSONParser.parse(new String(post(target, body, headers, connectTimeoutMillis, readTimeoutMillis))); 772 | } 773 | 774 | private static byte[] post(URL target, byte[] body, Map headers, int connectTimeoutMillis, int readTimeoutMillis) throws IOException { 775 | return Unirest.post(target.toString()) 776 | .header("accept", "application/json") 777 | .headers(headers) 778 | .body(body) 779 | .contentType("application/json") 780 | .connectTimeout(connectTimeoutMillis) 781 | .socketTimeout(readTimeoutMillis) 782 | .asBytes() 783 | .ifFailure(response -> { 784 | response.getParsingError().ifPresent(e -> { 785 | throw new RuntimeException("IO Exception:" + e + " " + e.getOriginalBody()); 786 | }); 787 | }) 788 | .getBody(); 789 | } 790 | 791 | private static boolean detectSSL(MultiAddress multiaddress) { 792 | return multiaddress.toString().contains("/https"); 793 | } 794 | 795 | } 796 | -------------------------------------------------------------------------------- /src/main/java/io/ipfs/api/IpldNode.java: -------------------------------------------------------------------------------- 1 | package io.ipfs.api; 2 | 3 | import io.ipfs.api.cbor.*; 4 | import io.ipfs.cid.*; 5 | import io.ipfs.multihash.*; 6 | 7 | import java.security.*; 8 | import java.util.*; 9 | import java.util.stream.*; 10 | 11 | public interface IpldNode extends Cborable { 12 | 13 | Pair> resolve(List path); 14 | 15 | /** Lists all paths within the object under 'path', and up to the given depth. 16 | * To list the entire object (similar to `find .`) pass "" and -1 17 | * @param path 18 | * @param depth 19 | * @return 20 | */ 21 | List tree(String path, int depth); 22 | 23 | /** 24 | * 25 | * @return calculate this objects Cid 26 | */ 27 | default Cid cid() { 28 | byte[] raw = rawData(); 29 | try { 30 | MessageDigest md = MessageDigest.getInstance("SHA-256"); 31 | md.update(raw); 32 | byte[] digest = md.digest(); 33 | return new Cid(1, Cid.Codec.DagCbor, Multihash.Type.sha2_256, digest); 34 | } catch (NoSuchAlgorithmException e) { 35 | throw new RuntimeException(e.getMessage(), e); 36 | } 37 | } 38 | 39 | /** 40 | * 41 | * @return size of this object when encoded 42 | */ 43 | long size(); 44 | 45 | /** 46 | * 47 | * @return this object's serialization 48 | */ 49 | byte[] rawData(); 50 | 51 | /** 52 | * 53 | * @return the merkle links from this object 54 | */ 55 | List getLinks(); 56 | 57 | static IpldNode fromCBOR(CborObject cbor) { 58 | return new CborIpldNode(cbor); 59 | } 60 | 61 | static IpldNode fromJSON(Object json) { 62 | return new JsonIpldNode(json); 63 | } 64 | 65 | class CborIpldNode implements IpldNode { 66 | private final CborObject base; 67 | 68 | public CborIpldNode(CborObject base) { 69 | this.base = base; 70 | } 71 | 72 | @Override 73 | public CborObject toCbor() { 74 | return base; 75 | } 76 | 77 | @Override 78 | public Pair> resolve(List path) { 79 | throw new IllegalStateException("Unimplemented!"); 80 | } 81 | 82 | @Override 83 | public List tree(String path, int depth) { 84 | return tree(base, path, depth); 85 | } 86 | 87 | private List tree(CborObject base, String rawPath, int depth) { 88 | String path = rawPath.startsWith("/") ? rawPath.substring(1) : rawPath; 89 | 90 | if (depth == 0 || (path.equals("") && depth != -1)) 91 | return Collections.singletonList(""); 92 | 93 | if (base instanceof CborObject.CborMap) { 94 | return ((CborObject.CborMap) base).values.entrySet() 95 | .stream() 96 | .flatMap(e -> { 97 | String name = ((CborObject.CborString) e.getKey()).value; 98 | if (path.startsWith(name) || depth == -1) 99 | return tree(e.getValue(), path.length() > 0 ? path.substring(name.length()) : path, 100 | depth == -1 ? -1 : depth - 1) 101 | .stream() 102 | .map(p -> "/" + name + p); 103 | return Stream.empty(); 104 | }).collect(Collectors.toList()); 105 | } 106 | if (depth == -1) 107 | return Collections.singletonList(""); 108 | return Collections.emptyList(); 109 | } 110 | 111 | @Override 112 | public long size() { 113 | return rawData().length; 114 | } 115 | 116 | @Override 117 | public byte[] rawData() { 118 | return base.toByteArray(); 119 | } 120 | 121 | @Override 122 | public List getLinks() { 123 | return getLinks(base); 124 | } 125 | 126 | private static List getLinks(CborObject base) { 127 | if (base instanceof CborObject.CborMerkleLink) 128 | return Collections.singletonList(new Link("", 0, ((CborObject.CborMerkleLink) base).target)); 129 | if (base instanceof CborObject.CborMap) { 130 | return ((CborObject.CborMap) base).values.values() 131 | .stream() 132 | .flatMap(cbor -> getLinks(cbor).stream()) 133 | .collect(Collectors.toList()); 134 | } 135 | if (base instanceof CborObject.CborList) { 136 | return ((CborObject.CborList) base).value 137 | .stream() 138 | .flatMap(cbor -> getLinks(cbor).stream()) 139 | .collect(Collectors.toList()); 140 | } 141 | return Collections.emptyList(); 142 | } 143 | } 144 | 145 | class JsonIpldNode implements IpldNode { 146 | private final Object json; 147 | 148 | public JsonIpldNode(Object json) { 149 | this.json = json; 150 | } 151 | 152 | @Override 153 | public CborObject toCbor() { 154 | throw new IllegalStateException("Unimplemented!"); 155 | } 156 | 157 | @Override 158 | public Pair> resolve(List path) { 159 | throw new IllegalStateException("Unimplemented!"); 160 | } 161 | 162 | @Override 163 | public List tree(String path, int depth) { 164 | throw new IllegalStateException("Unimplemented!"); 165 | } 166 | 167 | @Override 168 | public long size() { 169 | return rawData().length; 170 | } 171 | 172 | @Override 173 | public byte[] rawData() { 174 | return JSONParser.toString(json).getBytes(); 175 | } 176 | 177 | @Override 178 | public List getLinks() { 179 | throw new IllegalStateException("Unimplemented!"); 180 | } 181 | } 182 | 183 | class Link { 184 | public final String name; 185 | // Cumulative size of target 186 | public final long size; 187 | public final Multihash target; 188 | 189 | public Link(String name, long size, Multihash target) { 190 | this.name = name; 191 | this.size = size; 192 | this.target = target; 193 | } 194 | 195 | @Override 196 | public boolean equals(Object o) { 197 | if (this == o) return true; 198 | if (o == null || getClass() != o.getClass()) return false; 199 | 200 | Link link = (Link) o; 201 | 202 | if (size != link.size) return false; 203 | if (name != null ? !name.equals(link.name) : link.name != null) return false; 204 | return target != null ? target.equals(link.target) : link.target == null; 205 | 206 | } 207 | 208 | @Override 209 | public int hashCode() { 210 | int result = name != null ? name.hashCode() : 0; 211 | result = 31 * result + (int) (size ^ (size >>> 32)); 212 | result = 31 * result + (target != null ? target.hashCode() : 0); 213 | return result; 214 | } 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /src/main/java/io/ipfs/api/JSONParser.java: -------------------------------------------------------------------------------- 1 | package io.ipfs.api; 2 | 3 | import com.fasterxml.jackson.core.JsonProcessingException; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import com.fasterxml.jackson.databind.ObjectWriter; 6 | 7 | import java.io.IOException; 8 | import java.util.*; 9 | import java.util.stream.Collectors; 10 | 11 | public class JSONParser { 12 | 13 | private static ObjectWriter printer; 14 | private static final ObjectMapper mapper = new ObjectMapper(); 15 | 16 | public static Map parse(String json) { 17 | return parse(json, HashMap.class); 18 | } 19 | 20 | public static T parse(String json, Class clazz) { 21 | if (json == null || "".equals(json.trim())) { 22 | return null; 23 | } 24 | 25 | try { 26 | return mapper.readValue(json, clazz); 27 | } catch (IOException e) { 28 | e.printStackTrace(); 29 | throw new RuntimeException(e); 30 | } 31 | } 32 | 33 | public static List parseStream(String jsonStream) { 34 | if ("".equals(jsonStream.trim())) { 35 | return new ArrayList<>(); 36 | } 37 | 38 | return Arrays.stream(jsonStream.split("\n")) 39 | .map(e -> parse(e, HashMap.class)) 40 | .collect(Collectors.toList()); 41 | } 42 | 43 | public static String toString(Object obj) { 44 | try { 45 | if (printer == null) { 46 | printer = mapper.writer(); 47 | } 48 | return printer.writeValueAsString(obj); 49 | } catch (JsonProcessingException e) { 50 | e.printStackTrace(); 51 | throw new RuntimeException(e); 52 | } 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/io/ipfs/api/KeyInfo.java: -------------------------------------------------------------------------------- 1 | package io.ipfs.api; 2 | 3 | import io.ipfs.cid.*; 4 | import io.ipfs.multihash.*; 5 | 6 | import java.util.*; 7 | 8 | public class KeyInfo { 9 | 10 | public final String name; 11 | public final Multihash id; 12 | 13 | public KeyInfo(String name, Multihash id) { 14 | this.name = name; 15 | this.id = id; 16 | } 17 | 18 | public String toString() { 19 | return name + ": " + id; 20 | } 21 | 22 | @Override 23 | public boolean equals(Object o) { 24 | if (this == o) return true; 25 | if (o == null || getClass() != o.getClass()) return false; 26 | 27 | KeyInfo keyInfo = (KeyInfo) o; 28 | 29 | if (name != null ? !name.equals(keyInfo.name) : keyInfo.name != null) return false; 30 | return id != null ? id.equals(keyInfo.id) : keyInfo.id == null; 31 | } 32 | 33 | @Override 34 | public int hashCode() { 35 | int result = name != null ? name.hashCode() : 0; 36 | result = 31 * result + (id != null ? id.hashCode() : 0); 37 | return result; 38 | } 39 | 40 | public static KeyInfo fromJson(Object json) { 41 | Map m = (Map) json; 42 | return new KeyInfo(m.get("Name"), Cid.decode(m.get("Id"))); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/io/ipfs/api/MerkleNode.java: -------------------------------------------------------------------------------- 1 | package io.ipfs.api; 2 | 3 | import io.ipfs.cid.*; 4 | import io.ipfs.multihash.Multihash; 5 | 6 | import java.util.*; 7 | import java.util.stream.*; 8 | 9 | public class MerkleNode { 10 | 11 | public final Multihash hash; 12 | public final Optional name; 13 | public final Optional size; 14 | public final Optional largeSize; 15 | public final Optional type; 16 | public final List links; 17 | public final Optional data; 18 | 19 | public MerkleNode(String hash, 20 | Optional name, 21 | Optional size, 22 | Optional largeSize, 23 | Optional type, 24 | List links, 25 | Optional data) { 26 | this.name = name; 27 | this.hash = Cid.decode(hash); 28 | this.size = size; 29 | this.largeSize = largeSize; 30 | this.type = type; 31 | this.links = links; 32 | this.data = data; 33 | } 34 | 35 | public MerkleNode(String hash) { 36 | this(hash, Optional.empty()); 37 | } 38 | 39 | public MerkleNode(String hash, Optional name) { 40 | this(hash, name, Optional.empty(), Optional.empty(), Optional.empty(), Arrays.asList(), Optional.empty()); 41 | } 42 | 43 | @Override 44 | public boolean equals(Object b) { 45 | if (!(b instanceof MerkleNode)) 46 | return false; 47 | MerkleNode other = (MerkleNode) b; 48 | return hash.equals(other.hash); // ignore name hash says it all 49 | } 50 | 51 | @Override 52 | public int hashCode() { 53 | return hash.hashCode(); 54 | } 55 | 56 | public static MerkleNode fromJSON(Object rawjson) { 57 | if (rawjson instanceof String) 58 | return new MerkleNode((String)rawjson); 59 | Map json = (Map)rawjson; 60 | if ("error".equals(json.get("Type"))) 61 | throw new IllegalStateException("Remote IPFS error: " + json.get("Message")); 62 | String hash = (String)json.get("Hash"); 63 | if (hash == null) 64 | hash = (String)json.get("Key"); 65 | if (hash == null && json.containsKey("Cid")) 66 | hash = (String) (((Map) json.get("Cid")).get("/")); 67 | Optional name = json.containsKey("Name") ? 68 | Optional.of((String) json.get("Name")) : 69 | Optional.empty(); 70 | Object rawSize = json.get("Size"); 71 | Optional size = rawSize instanceof Integer ? 72 | Optional.of((Integer) rawSize) : 73 | Optional.empty(); 74 | Optional largeSize = rawSize instanceof String ? 75 | Optional.of((String) json.get("Size")) : 76 | Optional.empty(); 77 | Optional type = json.containsKey("Type") ? 78 | Optional.of((Integer) json.get("Type")) : 79 | Optional.empty(); 80 | List linksRaw = (List) json.get("Links"); 81 | List links = linksRaw == null ? 82 | Collections.emptyList() : 83 | linksRaw.stream().map(x -> MerkleNode.fromJSON(x)).collect(Collectors.toList()); 84 | Optional data = json.containsKey("Data") ? Optional.of(((String)json.get("Data")).getBytes()): Optional.empty(); 85 | return new MerkleNode(hash, name, size, largeSize, type, links, data); 86 | } 87 | 88 | public Object toJSON() { 89 | Map res = new TreeMap<>(); 90 | res.put("Links", links.stream().map(x -> x.hash).collect(Collectors.toList())); 91 | data.ifPresent(bytes -> res.put("Data", bytes)); 92 | return res; 93 | } 94 | 95 | public String toJSONString() { 96 | return JSONParser.toString(toJSON()); 97 | } 98 | 99 | @Override 100 | public String toString() { 101 | return hash + "-" + name.orElse(""); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/main/java/io/ipfs/api/Multipart.java: -------------------------------------------------------------------------------- 1 | package io.ipfs.api; 2 | 3 | import java.io.*; 4 | import java.net.*; 5 | import java.nio.file.*; 6 | import java.util.*; 7 | 8 | public class Multipart { 9 | private final String boundary; 10 | private static final String LINE_FEED = "\r\n"; 11 | private HttpURLConnection httpConn; 12 | private String charset; 13 | private OutputStream out; 14 | 15 | public Multipart(String requestURL, String charset) { 16 | this.charset = charset; 17 | 18 | boundary = createBoundary(); 19 | 20 | try { 21 | URL url = new URL(requestURL); 22 | httpConn = (HttpURLConnection) url.openConnection(); 23 | httpConn.setUseCaches(false); 24 | httpConn.setDoOutput(true); 25 | httpConn.setDoInput(true); 26 | httpConn.setRequestProperty("Expect", "100-continue"); 27 | httpConn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary); 28 | httpConn.setRequestProperty("User-Agent", "Java IPFS CLient"); 29 | out = httpConn.getOutputStream(); 30 | } catch (IOException e) { 31 | throw new RuntimeException(e.getMessage(), e); 32 | } 33 | } 34 | 35 | public static String createBoundary() { 36 | Random r = new Random(); 37 | String allowed = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; 38 | StringBuilder b = new StringBuilder(); 39 | for (int i = 0; i < 32; i++) 40 | b.append(allowed.charAt(r.nextInt(allowed.length()))); 41 | return b.toString(); 42 | } 43 | 44 | private Multipart append(String value) throws IOException { 45 | out.write(value.getBytes(charset)); 46 | return this; 47 | } 48 | 49 | public void addFormField(String name, String value) throws IOException { 50 | append("--").append(boundary).append(LINE_FEED); 51 | append("Content-Disposition: form-data; name=\"").append(name).append("\"") 52 | .append(LINE_FEED); 53 | append("Content-Type: text/plain; charset=").append(charset).append(LINE_FEED); 54 | append(LINE_FEED); 55 | append(value).append(LINE_FEED); 56 | out.flush(); 57 | } 58 | 59 | public void addSubtree(Path parentPath, NamedStreamable dir) throws IOException { 60 | Path dirPath = parentPath.resolve(dir.getName().get()); 61 | addDirectoryPart(dirPath); 62 | for (NamedStreamable f : dir.getChildren()) { 63 | if (f.isDirectory()) 64 | addSubtree(dirPath, f); 65 | else 66 | addFilePart("file", dirPath, f); 67 | } 68 | } 69 | 70 | public void addDirectoryPart(Path path) throws IOException { 71 | append("--").append(boundary).append(LINE_FEED); 72 | append("Content-Disposition: file; filename=\"").append(encode(path.toString())).append("\"").append(LINE_FEED); 73 | append("Content-Type: application/x-directory").append(LINE_FEED); 74 | append("Content-Transfer-Encoding: binary").append(LINE_FEED); 75 | append(LINE_FEED); 76 | append(LINE_FEED); 77 | out.flush(); 78 | } 79 | 80 | private static String encode(String in) { 81 | try { 82 | return URLEncoder.encode(in, "UTF-8"); 83 | } catch (UnsupportedEncodingException e) { 84 | throw new RuntimeException(e); 85 | } 86 | } 87 | 88 | public void addFilePart(String fieldName, Path parent, NamedStreamable uploadFile) throws IOException { 89 | Optional fileName = uploadFile.getName().map(n -> encode(parent.resolve(n).toString().replace('\\', '/'))); 90 | append("--").append(boundary).append(LINE_FEED); 91 | if (!fileName.isPresent()) 92 | append("Content-Disposition: file; name=\"").append(fieldName).append("\";").append(LINE_FEED); 93 | else 94 | append("Content-Disposition: file; filename=\"").append(fileName.get()).append("\";").append(LINE_FEED); 95 | append("Content-Type: application/octet-stream").append(LINE_FEED); 96 | append("Content-Transfer-Encoding: binary").append(LINE_FEED); 97 | append(LINE_FEED); 98 | out.flush(); 99 | 100 | try { 101 | InputStream inputStream = uploadFile.getInputStream(); 102 | byte[] buffer = new byte[4096]; 103 | int r; 104 | while ((r = inputStream.read(buffer)) != -1) 105 | out.write(buffer, 0, r); 106 | out.flush(); 107 | inputStream.close(); 108 | } catch (IOException e) { 109 | throw new RuntimeException(e.getMessage(), e); 110 | } 111 | 112 | append(LINE_FEED); 113 | out.flush(); 114 | } 115 | 116 | public void addHeaderField(String name, String value) throws IOException { 117 | append(name + ": " + value).append(LINE_FEED); 118 | out.flush(); 119 | } 120 | 121 | public String finish() throws IOException { 122 | StringBuilder b = new StringBuilder(); 123 | 124 | append("--" + boundary + "--").append(LINE_FEED); 125 | out.flush(); 126 | out.close(); 127 | 128 | try { 129 | int status = httpConn.getResponseCode(); 130 | if (status == HttpURLConnection.HTTP_OK) { 131 | BufferedReader reader = new BufferedReader(new InputStreamReader( 132 | httpConn.getInputStream())); 133 | String line; 134 | while ((line = reader.readLine()) != null) { 135 | b.append(line).append("\n"); 136 | } 137 | reader.close(); 138 | httpConn.disconnect(); 139 | } else { 140 | try { 141 | BufferedReader reader = new BufferedReader(new InputStreamReader( 142 | httpConn.getInputStream())); 143 | String line; 144 | while ((line = reader.readLine()) != null) { 145 | b.append(line).append("\n"); 146 | } 147 | reader.close(); 148 | } catch (Throwable t) { 149 | } 150 | throw new IOException("Server returned status: " + status + " with body: " + b.toString() + " and Trailer header: " + httpConn.getHeaderFields().get("Trailer")); 151 | } 152 | 153 | return b.toString(); 154 | } catch (IOException e) { 155 | throw new RuntimeException(e.getMessage(), e); 156 | } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/main/java/io/ipfs/api/NamedStreamable.java: -------------------------------------------------------------------------------- 1 | package io.ipfs.api; 2 | 3 | import java.io.*; 4 | import java.net.*; 5 | import java.util.*; 6 | import java.util.stream.*; 7 | 8 | public interface NamedStreamable 9 | { 10 | InputStream getInputStream() throws IOException; 11 | 12 | Optional getName(); 13 | 14 | List getChildren(); 15 | 16 | boolean isDirectory(); 17 | 18 | default byte[] getContents() throws IOException { 19 | InputStream in = getInputStream(); 20 | ByteArrayOutputStream bout = new ByteArrayOutputStream(); 21 | byte[] tmp = new byte[4096]; 22 | int r; 23 | while ((r=in.read(tmp))>= 0) 24 | bout.write(tmp, 0, r); 25 | return bout.toByteArray(); 26 | } 27 | 28 | class FileWrapper implements NamedStreamable { 29 | private final File source; 30 | 31 | public FileWrapper(File source) { 32 | if (! source.exists()) 33 | throw new IllegalStateException("File does not exist: " + source); 34 | this.source = source; 35 | } 36 | 37 | public InputStream getInputStream() throws IOException { 38 | return new FileInputStream(source); 39 | } 40 | 41 | public boolean isDirectory() { 42 | return source.isDirectory(); 43 | } 44 | 45 | @Override 46 | public List getChildren() { 47 | return isDirectory() ? 48 | Stream.of(source.listFiles()) 49 | .map(NamedStreamable.FileWrapper::new) 50 | .collect(Collectors.toList()) : 51 | Collections.emptyList(); 52 | } 53 | 54 | public Optional getName() { 55 | try { 56 | return Optional.of(URLEncoder.encode(source.getName(), "UTF-8")); 57 | } catch (UnsupportedEncodingException e) { 58 | throw new RuntimeException(e); 59 | } 60 | } 61 | } 62 | 63 | class InputStreamWrapper implements NamedStreamable { 64 | private final Optional name; 65 | private final InputStream data; 66 | 67 | public InputStreamWrapper(InputStream data) { 68 | this(Optional.empty(), data); 69 | } 70 | 71 | public InputStreamWrapper(String name, InputStream data) { 72 | this(Optional.of(name), data); 73 | } 74 | 75 | public InputStreamWrapper(Optional name, InputStream data) { 76 | this.name = name; 77 | this.data = data; 78 | } 79 | 80 | public boolean isDirectory() { 81 | return false; 82 | } 83 | 84 | public InputStream getInputStream() { 85 | return data; 86 | } 87 | 88 | @Override 89 | public List getChildren() { 90 | return Collections.emptyList(); 91 | } 92 | 93 | public Optional getName() { 94 | return name; 95 | } 96 | } 97 | 98 | class ByteArrayWrapper implements NamedStreamable { 99 | private final Optional name; 100 | private final byte[] data; 101 | 102 | public ByteArrayWrapper(byte[] data) { 103 | this(Optional.empty(), data); 104 | } 105 | 106 | public ByteArrayWrapper(String name, byte[] data) { 107 | this(Optional.of(name), data); 108 | } 109 | 110 | public ByteArrayWrapper(Optional name, byte[] data) { 111 | this.name = name; 112 | this.data = data; 113 | } 114 | 115 | public boolean isDirectory() { 116 | return false; 117 | } 118 | 119 | public InputStream getInputStream() throws IOException { 120 | return new ByteArrayInputStream(data); 121 | } 122 | 123 | @Override 124 | public List getChildren() { 125 | return Collections.emptyList(); 126 | } 127 | 128 | public Optional getName() { 129 | return name; 130 | } 131 | } 132 | 133 | class DirWrapper implements NamedStreamable { 134 | 135 | private final String name; 136 | private final List children; 137 | 138 | public DirWrapper(String name, List children) { 139 | this.name = name; 140 | this.children = children; 141 | } 142 | 143 | @Override 144 | public InputStream getInputStream() throws IOException { 145 | throw new IllegalStateException("Cannot get an input stream for a directory!"); 146 | } 147 | 148 | @Override 149 | public Optional getName() { 150 | return Optional.of(name); 151 | } 152 | 153 | @Override 154 | public List getChildren() { 155 | return children; 156 | } 157 | 158 | @Override 159 | public boolean isDirectory() { 160 | return true; 161 | } 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/main/java/io/ipfs/api/Pair.java: -------------------------------------------------------------------------------- 1 | package io.ipfs.api; 2 | 3 | import java.util.function.Function; 4 | 5 | public class Pair { 6 | public final L left; 7 | public final R right; 8 | 9 | public Pair(L left, R right) { 10 | this.left = left; 11 | this.right = right; 12 | } 13 | 14 | public Pair swapped() { 15 | return new Pair<>(right, left); 16 | } 17 | 18 | public Pair apply(Function applyLeft, Function applyRight) { 19 | return new Pair<>( 20 | applyLeft.apply(left), 21 | applyRight.apply(right)); 22 | } 23 | 24 | @Override 25 | public boolean equals(Object o) { 26 | if (this == o) return true; 27 | if (o == null || getClass() != o.getClass()) return false; 28 | 29 | Pair pair = (Pair) o; 30 | 31 | if (left != null ? !left.equals(pair.left) : pair.left != null) return false; 32 | return right != null ? right.equals(pair.right) : pair.right == null; 33 | 34 | } 35 | 36 | @Override 37 | public int hashCode() { 38 | int result = left != null ? left.hashCode() : 0; 39 | result = 31 * result + (right != null ? right.hashCode() : 0); 40 | return result; 41 | } 42 | 43 | @Override 44 | public String toString() { 45 | return String.format("(%s, %s)", left.toString(), right.toString()); 46 | } 47 | } -------------------------------------------------------------------------------- /src/main/java/io/ipfs/api/Peer.java: -------------------------------------------------------------------------------- 1 | package io.ipfs.api; 2 | 3 | import io.ipfs.cid.Cid; 4 | import io.ipfs.multiaddr.MultiAddress; 5 | import io.ipfs.multihash.Multihash; 6 | 7 | import java.util.Map; 8 | import java.util.function.Function; 9 | 10 | public class Peer { 11 | public final MultiAddress address; 12 | public final Multihash id; 13 | public final long latency; 14 | public final String muxer; 15 | public final Object streams; 16 | 17 | public Peer(MultiAddress address, Multihash id, long latency, String muxer, Object streams) { 18 | this.address = address; 19 | this.id = id; 20 | this.latency = latency; 21 | this.muxer = muxer; 22 | this.streams = streams; 23 | } 24 | 25 | public static Peer fromJSON(Object json) { 26 | if (! (json instanceof Map)) 27 | throw new IllegalStateException("Incorrect json for Peer: " + JSONParser.toString(json)); 28 | Map m = (Map) json; 29 | Function val = key -> (String) m.get(key); 30 | long latency = val.apply("Latency").length() > 0 ? Long.parseLong(val.apply("Latency")) : -1; 31 | return new Peer(new MultiAddress(val.apply("Addr")), Cid.decode(val.apply("Peer")), latency, val.apply("Muxer"), val.apply("Streams")); 32 | } 33 | 34 | @Override 35 | public String toString() { 36 | return id + "@" + address; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/io/ipfs/api/Version.java: -------------------------------------------------------------------------------- 1 | package io.ipfs.api; 2 | 3 | public class Version implements Comparable { 4 | 5 | public final int major, minor, patch; 6 | public final String suffix; 7 | 8 | public Version(int major, int minor, int patch, String suffix) { 9 | this.major = major; 10 | this.minor = minor; 11 | this.patch = patch; 12 | this.suffix = suffix; 13 | } 14 | 15 | public String toString() { 16 | return major + "." + minor + "." + patch + (suffix.length() > 0 ? "-" + suffix : ""); 17 | } 18 | 19 | public boolean isBefore(Version other) { 20 | return this.compareTo(other) < 0; 21 | } 22 | 23 | @Override 24 | public int compareTo(Version other) { 25 | int major = Integer.compare(this.major, other.major); 26 | if (major != 0) 27 | return major; 28 | int minor = Integer.compare(this.minor, other.minor); 29 | if (minor != 0) 30 | return minor; 31 | int patch = Integer.compare(this.patch, other.patch); 32 | if (patch != 0) 33 | return patch; 34 | if (suffix.length() == 0) 35 | return 1; 36 | if (other.suffix.length() == 0) 37 | return -1; 38 | return suffix.compareTo(other.suffix); 39 | } 40 | 41 | public static Version parse(String version) { 42 | int first = version.indexOf("."); 43 | int second = version.indexOf(".", first + 1); 44 | int third = version.contains("-") ? version.indexOf("-") : version.length(); 45 | 46 | int major = Integer.parseInt(version.substring(0, first)); 47 | int minor = Integer.parseInt(version.substring(first + 1, second)); 48 | int patch = Integer.parseInt(version.substring(second + 1, third)); 49 | String suffix = third < version.length() ? version.substring(third + 1) : ""; 50 | return new Version(major, minor, patch, suffix); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/io/ipfs/api/cbor/CborConstants.java: -------------------------------------------------------------------------------- 1 | package io.ipfs.api.cbor; 2 | 3 | /* 4 | * JACOB - CBOR implementation in Java. 5 | * 6 | * (C) Copyright - 2013 - J.W. Janssen 7 | * 8 | * Licensed under Apache License v2.0. 9 | */ 10 | 11 | /** 12 | * Constant values used by the CBOR format. 13 | */ 14 | public interface CborConstants { 15 | /** Major type 0: unsigned integers. */ 16 | int TYPE_UNSIGNED_INTEGER = 0x00; 17 | /** Major type 1: negative integers. */ 18 | int TYPE_NEGATIVE_INTEGER = 0x01; 19 | /** Major type 2: byte string. */ 20 | int TYPE_BYTE_STRING = 0x02; 21 | /** Major type 3: text/UTF8 string. */ 22 | int TYPE_TEXT_STRING = 0x03; 23 | /** Major type 4: array of items. */ 24 | int TYPE_ARRAY = 0x04; 25 | /** Major type 5: map of pairs. */ 26 | int TYPE_MAP = 0x05; 27 | /** Major type 6: semantic tags. */ 28 | int TYPE_TAG = 0x06; 29 | /** Major type 7: floating point, simple data types. */ 30 | int TYPE_FLOAT_SIMPLE = 0x07; 31 | 32 | /** Denotes a one-byte value (uint8). */ 33 | int ONE_BYTE = 0x18; 34 | /** Denotes a two-byte value (uint16). */ 35 | int TWO_BYTES = 0x19; 36 | /** Denotes a four-byte value (uint32). */ 37 | int FOUR_BYTES = 0x1a; 38 | /** Denotes a eight-byte value (uint64). */ 39 | int EIGHT_BYTES = 0x1b; 40 | 41 | /** The CBOR-encoded boolean false value (encoded as "simple value": {@link #MT_SIMPLE}). */ 42 | int FALSE = 0x14; 43 | /** The CBOR-encoded boolean true value (encoded as "simple value": {@link #MT_SIMPLE}). */ 44 | int TRUE = 0x15; 45 | /** The CBOR-encoded null value (encoded as "simple value": {@link #MT_SIMPLE}). */ 46 | int NULL = 0x16; 47 | /** The CBOR-encoded "undefined" value (encoded as "simple value": {@link #MT_SIMPLE}). */ 48 | int UNDEFINED = 0x17; 49 | /** Denotes a half-precision float (two-byte IEEE 754, see {@link #MT_FLOAT}). */ 50 | int HALF_PRECISION_FLOAT = 0x19; 51 | /** Denotes a single-precision float (four-byte IEEE 754, see {@link #MT_FLOAT}). */ 52 | int SINGLE_PRECISION_FLOAT = 0x1a; 53 | /** Denotes a double-precision float (eight-byte IEEE 754, see {@link #MT_FLOAT}). */ 54 | int DOUBLE_PRECISION_FLOAT = 0x1b; 55 | /** The CBOR-encoded "break" stop code for unlimited arrays/maps. */ 56 | int BREAK = 0x1f; 57 | 58 | /** Semantic tag value describing date/time values in the standard format (UTF8 string, RFC3339). */ 59 | int TAG_STANDARD_DATE_TIME = 0; 60 | /** Semantic tag value describing date/time values as Epoch timestamp (numeric, RFC3339). */ 61 | int TAG_EPOCH_DATE_TIME = 1; 62 | /** Semantic tag value describing a positive big integer value (byte string). */ 63 | int TAG_POSITIVE_BIGINT = 2; 64 | /** Semantic tag value describing a negative big integer value (byte string). */ 65 | int TAG_NEGATIVE_BIGINT = 3; 66 | /** Semantic tag value describing a decimal fraction value (two-element array, base 10). */ 67 | int TAG_DECIMAL_FRACTION = 4; 68 | /** Semantic tag value describing a big decimal value (two-element array, base 2). */ 69 | int TAG_BIGDECIMAL = 5; 70 | /** Semantic tag value describing an expected conversion to base64url encoding. */ 71 | int TAG_EXPECTED_BASE64_URL_ENCODED = 21; 72 | /** Semantic tag value describing an expected conversion to base64 encoding. */ 73 | int TAG_EXPECTED_BASE64_ENCODED = 22; 74 | /** Semantic tag value describing an expected conversion to base16 encoding. */ 75 | int TAG_EXPECTED_BASE16_ENCODED = 23; 76 | /** Semantic tag value describing an encoded CBOR data item (byte string). */ 77 | int TAG_CBOR_ENCODED = 24; 78 | /** Semantic tag value describing an URL (UTF8 string). */ 79 | int TAG_URI = 32; 80 | /** Semantic tag value describing a base64url encoded string (UTF8 string). */ 81 | int TAG_BASE64_URL_ENCODED = 33; 82 | /** Semantic tag value describing a base64 encoded string (UTF8 string). */ 83 | int TAG_BASE64_ENCODED = 34; 84 | /** Semantic tag value describing a regular expression string (UTF8 string, PCRE). */ 85 | int TAG_REGEXP = 35; 86 | /** Semantic tag value describing a MIME message (UTF8 string, RFC2045). */ 87 | int TAG_MIME_MESSAGE = 36; 88 | /** Semantic tag value describing CBOR content. */ 89 | int TAG_CBOR_MARKER = 55799; 90 | } 91 | -------------------------------------------------------------------------------- /src/main/java/io/ipfs/api/cbor/CborDecoder.java: -------------------------------------------------------------------------------- 1 | package io.ipfs.api.cbor; 2 | 3 | /* 4 | * JACOB - CBOR implementation in Java. 5 | * 6 | * (C) Copyright - 2013 - J.W. Janssen 7 | */ 8 | 9 | import java.io.*; 10 | 11 | import static io.ipfs.api.cbor.CborConstants.*; 12 | import static io.ipfs.api.cbor.CborType.*; 13 | 14 | /** 15 | * Provides a decoder capable of handling CBOR encoded data from a {@link InputStream}. 16 | */ 17 | public class CborDecoder { 18 | protected final PushbackInputStream m_is; 19 | 20 | /** 21 | * Creates a new {@link CborDecoder} instance. 22 | * 23 | * @param is the actual input stream to read the CBOR-encoded data from, cannot be null. 24 | */ 25 | public CborDecoder(InputStream is) { 26 | if (is == null) { 27 | throw new IllegalArgumentException("InputStream cannot be null!"); 28 | } 29 | m_is = (is instanceof PushbackInputStream) ? (PushbackInputStream) is : new PushbackInputStream(is); 30 | } 31 | 32 | private static void fail(String msg, Object... args) throws IOException { 33 | throw new IOException(msg + args); 34 | } 35 | 36 | private static String lengthToString(int len) { 37 | return (len < 0) ? "no payload" : (len == ONE_BYTE) ? "one byte" : (len == TWO_BYTES) ? "two bytes" 38 | : (len == FOUR_BYTES) ? "four bytes" : (len == EIGHT_BYTES) ? "eight bytes" : "(unknown)"; 39 | } 40 | 41 | /** 42 | * Peeks in the input stream for the upcoming type. 43 | * 44 | * @return the upcoming type in the stream, or null in case of an end-of-stream. 45 | * @throws IOException in case of I/O problems reading the CBOR-type from the underlying input stream. 46 | */ 47 | public CborType peekType() throws IOException { 48 | int p = m_is.read(); 49 | if (p < 0) { 50 | // EOF, nothing to peek at... 51 | return null; 52 | } 53 | m_is.unread(p); 54 | return valueOf(p); 55 | } 56 | 57 | /** 58 | * Prolog to reading an array value in CBOR format. 59 | * 60 | * @return the number of elements in the array to read, or -1 in case of infinite-length arrays. 61 | * @throws IOException in case of I/O problems reading the CBOR-encoded value from the underlying input stream. 62 | */ 63 | public long readArrayLength() throws IOException { 64 | return readMajorTypeWithSize(TYPE_ARRAY); 65 | } 66 | 67 | /** 68 | * Reads a boolean value in CBOR format. 69 | * 70 | * @return the read boolean. 71 | * @throws IOException in case of I/O problems reading the CBOR-encoded value from the underlying input stream. 72 | */ 73 | public boolean readBoolean() throws IOException { 74 | int b = readMajorType(TYPE_FLOAT_SIMPLE); 75 | if (b != FALSE && b != TRUE) { 76 | fail("Unexpected boolean value: %d!", b); 77 | } 78 | return b == TRUE; 79 | } 80 | 81 | /** 82 | * Reads a "break"/stop value in CBOR format. 83 | * 84 | * @return always null. 85 | * @throws IOException in case of I/O problems reading the CBOR-encoded value from the underlying input stream. 86 | */ 87 | public Object readBreak() throws IOException { 88 | readMajorTypeExact(TYPE_FLOAT_SIMPLE, BREAK); 89 | 90 | return null; 91 | } 92 | 93 | /** 94 | * Reads a byte string value in CBOR format. 95 | * 96 | * @return the read byte string, never null. In case the encoded string has a length of 0, an empty string is returned. 97 | * @throws IOException in case of I/O problems reading the CBOR-encoded value from the underlying input stream. 98 | */ 99 | public byte[] readByteString() throws IOException { 100 | long len = readMajorTypeWithSize(TYPE_BYTE_STRING); 101 | if (len < 0) { 102 | fail("Infinite-length byte strings not supported!"); 103 | } 104 | if (len > Integer.MAX_VALUE) { 105 | fail("String length too long!"); 106 | } 107 | return readFully(new byte[(int) len]); 108 | } 109 | 110 | /** 111 | * Prolog to reading a byte string value in CBOR format. 112 | * 113 | * @return the number of bytes in the string to read, or -1 in case of infinite-length strings. 114 | * @throws IOException in case of I/O problems reading the CBOR-encoded value from the underlying input stream. 115 | */ 116 | public long readByteStringLength() throws IOException { 117 | return readMajorTypeWithSize(TYPE_BYTE_STRING); 118 | } 119 | 120 | /** 121 | * Reads a double-precision float value in CBOR format. 122 | * 123 | * @return the read double value, values from {@link Float#MIN_VALUE} to {@link Float#MAX_VALUE} are supported. 124 | * @throws IOException in case of I/O problems reading the CBOR-encoded value from the underlying input stream. 125 | */ 126 | public double readDouble() throws IOException { 127 | readMajorTypeExact(TYPE_FLOAT_SIMPLE, DOUBLE_PRECISION_FLOAT); 128 | 129 | return Double.longBitsToDouble(readUInt64()); 130 | } 131 | 132 | /** 133 | * Reads a single-precision float value in CBOR format. 134 | * 135 | * @return the read float value, values from {@link Float#MIN_VALUE} to {@link Float#MAX_VALUE} are supported. 136 | * @throws IOException in case of I/O problems reading the CBOR-encoded value from the underlying input stream. 137 | */ 138 | public float readFloat() throws IOException { 139 | readMajorTypeExact(TYPE_FLOAT_SIMPLE, SINGLE_PRECISION_FLOAT); 140 | 141 | return Float.intBitsToFloat((int) readUInt32()); 142 | } 143 | 144 | /** 145 | * Reads a half-precision float value in CBOR format. 146 | * 147 | * @return the read half-precision float value, values from {@link Float#MIN_VALUE} to {@link Float#MAX_VALUE} are supported. 148 | * @throws IOException in case of I/O problems reading the CBOR-encoded value from the underlying input stream. 149 | */ 150 | public double readHalfPrecisionFloat() throws IOException { 151 | readMajorTypeExact(TYPE_FLOAT_SIMPLE, HALF_PRECISION_FLOAT); 152 | 153 | int half = readUInt16(); 154 | int exp = (half >> 10) & 0x1f; 155 | int mant = half & 0x3ff; 156 | 157 | double val; 158 | if (exp == 0) { 159 | val = mant * Math.pow(2, -24); 160 | } else if (exp != 31) { 161 | val = (mant + 1024) * Math.pow(2, exp - 25); 162 | } else if (mant != 0) { 163 | val = Double.NaN; 164 | } else { 165 | val = Double.POSITIVE_INFINITY; 166 | } 167 | 168 | return ((half & 0x8000) == 0) ? val : -val; 169 | } 170 | 171 | /** 172 | * Reads a signed or unsigned integer value in CBOR format. 173 | * 174 | * @return the read integer value, values from {@link Long#MIN_VALUE} to {@link Long#MAX_VALUE} are supported. 175 | * @throws IOException in case of I/O problems reading the CBOR-encoded value from the underlying input stream. 176 | */ 177 | public long readInt() throws IOException { 178 | int ib = m_is.read(); 179 | 180 | // in case of negative integers, extends the sign to all bits; otherwise zero... 181 | long ui = expectIntegerType(ib); 182 | // in case of negative integers does a ones complement 183 | return ui ^ readUInt(ib & 0x1f, false /* breakAllowed */); 184 | } 185 | 186 | /** 187 | * Reads a signed or unsigned 16-bit integer value in CBOR format. 188 | * 189 | * @read the small integer value, values from [-65536..65535] are supported. 190 | * @throws IOException in case of I/O problems reading the CBOR-encoded value from the underlying output stream. 191 | */ 192 | public int readInt16() throws IOException { 193 | int ib = m_is.read(); 194 | 195 | // in case of negative integers, extends the sign to all bits; otherwise zero... 196 | long ui = expectIntegerType(ib); 197 | // in case of negative integers does a ones complement 198 | return (int) (ui ^ readUIntExact(TWO_BYTES, ib & 0x1f)); 199 | } 200 | 201 | /** 202 | * Reads a signed or unsigned 32-bit integer value in CBOR format. 203 | * 204 | * @read the small integer value, values in the range [-4294967296..4294967295] are supported. 205 | * @throws IOException in case of I/O problems reading the CBOR-encoded value from the underlying output stream. 206 | */ 207 | public long readInt32() throws IOException { 208 | int ib = m_is.read(); 209 | 210 | // in case of negative integers, extends the sign to all bits; otherwise zero... 211 | long ui = expectIntegerType(ib); 212 | // in case of negative integers does a ones complement 213 | return ui ^ readUIntExact(FOUR_BYTES, ib & 0x1f); 214 | } 215 | 216 | /** 217 | * Reads a signed or unsigned 64-bit integer value in CBOR format. 218 | * 219 | * @read the small integer value, values from {@link Long#MIN_VALUE} to {@link Long#MAX_VALUE} are supported. 220 | * @throws IOException in case of I/O problems reading the CBOR-encoded value from the underlying output stream. 221 | */ 222 | public long readInt64() throws IOException { 223 | int ib = m_is.read(); 224 | 225 | // in case of negative integers, extends the sign to all bits; otherwise zero... 226 | long ui = expectIntegerType(ib); 227 | // in case of negative integers does a ones complement 228 | return ui ^ readUIntExact(EIGHT_BYTES, ib & 0x1f); 229 | } 230 | 231 | /** 232 | * Reads a signed or unsigned 8-bit integer value in CBOR format. 233 | * 234 | * @read the small integer value, values in the range [-256..255] are supported. 235 | * @throws IOException in case of I/O problems reading the CBOR-encoded value from the underlying output stream. 236 | */ 237 | public int readInt8() throws IOException { 238 | int ib = m_is.read(); 239 | 240 | // in case of negative integers, extends the sign to all bits; otherwise zero... 241 | long ui = expectIntegerType(ib); 242 | // in case of negative integers does a ones complement 243 | return (int) (ui ^ readUIntExact(ONE_BYTE, ib & 0x1f)); 244 | } 245 | 246 | /** 247 | * Prolog to reading a map of key-value pairs in CBOR format. 248 | * 249 | * @return the number of entries in the map, >= 0. 250 | * @throws IOException in case of I/O problems reading the CBOR-encoded value from the underlying input stream. 251 | */ 252 | public long readMapLength() throws IOException { 253 | return readMajorTypeWithSize(TYPE_MAP); 254 | } 255 | 256 | /** 257 | * Reads a null-value in CBOR format. 258 | * 259 | * @return always null. 260 | * @throws IOException in case of I/O problems reading the CBOR-encoded value from the underlying input stream. 261 | */ 262 | public Object readNull() throws IOException { 263 | readMajorTypeExact(TYPE_FLOAT_SIMPLE, NULL); 264 | return null; 265 | } 266 | 267 | /** 268 | * Reads a single byte value in CBOR format. 269 | * 270 | * @return the read byte value. 271 | * @throws IOException in case of I/O problems reading the CBOR-encoded value from the underlying input stream. 272 | */ 273 | public byte readSimpleValue() throws IOException { 274 | readMajorTypeExact(TYPE_FLOAT_SIMPLE, ONE_BYTE); 275 | return (byte) readUInt8(); 276 | } 277 | 278 | /** 279 | * Reads a signed or unsigned small (<= 23) integer value in CBOR format. 280 | * 281 | * @read the small integer value, values in the range [-24..23] are supported. 282 | * @throws IOException in case of I/O problems reading the CBOR-encoded value from the underlying output stream. 283 | */ 284 | public int readSmallInt() throws IOException { 285 | int ib = m_is.read(); 286 | 287 | // in case of negative integers, extends the sign to all bits; otherwise zero... 288 | long ui = expectIntegerType(ib); 289 | // in case of negative integers does a ones complement 290 | return (int) (ui ^ readUIntExact(-1, ib & 0x1f)); 291 | } 292 | 293 | /** 294 | * Reads a semantic tag value in CBOR format. 295 | * 296 | * @return the read tag value. 297 | * @throws IOException in case of I/O problems reading the CBOR-encoded value from the underlying input stream. 298 | */ 299 | public long readTag() throws IOException { 300 | return readUInt(readMajorType(TYPE_TAG), false /* breakAllowed */); 301 | } 302 | 303 | /** 304 | * Reads an UTF-8 encoded string value in CBOR format. 305 | * 306 | * @return the read UTF-8 encoded string, never null. In case the encoded string has a length of 0, an empty string is returned. 307 | * @throws IOException in case of I/O problems reading the CBOR-encoded value from the underlying input stream. 308 | */ 309 | public String readTextString() throws IOException { 310 | long len = readMajorTypeWithSize(TYPE_TEXT_STRING); 311 | if (len < 0) { 312 | fail("Infinite-length text strings not supported!"); 313 | } 314 | if (len > Integer.MAX_VALUE) { 315 | fail("String length too long!"); 316 | } 317 | return new String(readFully(new byte[(int) len]), "UTF-8"); 318 | } 319 | 320 | /** 321 | * Prolog to reading an UTF-8 encoded string value in CBOR format. 322 | * 323 | * @return the length of the string to read, or -1 in case of infinite-length strings. 324 | * @throws IOException in case of I/O problems reading the CBOR-encoded value from the underlying input stream. 325 | */ 326 | public long readTextStringLength() throws IOException { 327 | return readMajorTypeWithSize(TYPE_TEXT_STRING); 328 | } 329 | 330 | /** 331 | * Reads an undefined value in CBOR format. 332 | * 333 | * @return always null. 334 | * @throws IOException in case of I/O problems reading the CBOR-encoded value from the underlying input stream. 335 | */ 336 | public Object readUndefined() throws IOException { 337 | readMajorTypeExact(TYPE_FLOAT_SIMPLE, UNDEFINED); 338 | return null; 339 | } 340 | 341 | /** 342 | * Reads the next major type from the underlying input stream, and verifies whether it matches the given expectation. 343 | * 344 | * @param majorType the expected major type, cannot be null (unchecked). 345 | * @return either -1 if the major type was an signed integer, or 0 otherwise. 346 | * @throws IOException in case of I/O problems reading the CBOR-encoded value from the underlying input stream. 347 | */ 348 | protected long expectIntegerType(int ib) throws IOException { 349 | int majorType = ((ib & 0xFF) >>> 5); 350 | if ((majorType != TYPE_UNSIGNED_INTEGER) && (majorType != TYPE_NEGATIVE_INTEGER)) { 351 | fail("Unexpected type: %s, expected type %s or %s!", getName(majorType), getName(TYPE_UNSIGNED_INTEGER), 352 | getName(TYPE_NEGATIVE_INTEGER)); 353 | } 354 | return -majorType; 355 | } 356 | 357 | /** 358 | * Reads the next major type from the underlying input stream, and verifies whether it matches the given expectation. 359 | * 360 | * @param majorType the expected major type, cannot be null (unchecked). 361 | * @return the read subtype, or payload, of the read major type. 362 | * @throws IOException in case of I/O problems reading the CBOR-encoded value from the underlying input stream. 363 | */ 364 | protected int readMajorType(int majorType) throws IOException { 365 | int ib = m_is.read(); 366 | if (majorType != ((ib >>> 5) & 0x07)) { 367 | fail("Unexpected type: %s, expected: %s!", getName(ib), getName(majorType)); 368 | } 369 | return ib & 0x1F; 370 | } 371 | 372 | /** 373 | * Reads the next major type from the underlying input stream, and verifies whether it matches the given expectations. 374 | * 375 | * @param majorType the expected major type, cannot be null (unchecked); 376 | * @param subtype the expected subtype. 377 | * @throws IOException in case of I/O problems reading the CBOR-encoded value from the underlying input stream. 378 | */ 379 | protected void readMajorTypeExact(int majorType, int subtype) throws IOException { 380 | int st = readMajorType(majorType); 381 | if ((st ^ subtype) != 0) { 382 | fail("Unexpected subtype: %d, expected: %d!", st, subtype); 383 | } 384 | } 385 | 386 | /** 387 | * Reads the next major type from the underlying input stream, verifies whether it matches the given expectation, and decodes the payload into a size. 388 | * 389 | * @param majorType the expected major type, cannot be null (unchecked). 390 | * @return the number of succeeding bytes, >= 0, or -1 if an infinite-length type is read. 391 | * @throws IOException in case of I/O problems reading the CBOR-encoded value from the underlying input stream. 392 | */ 393 | protected long readMajorTypeWithSize(int majorType) throws IOException { 394 | return readUInt(readMajorType(majorType), true /* breakAllowed */); 395 | } 396 | 397 | /** 398 | * Reads an unsigned integer with a given length-indicator. 399 | * 400 | * @param length the length indicator to use; 401 | * @return the read unsigned integer, as long value. 402 | * @throws IOException in case of I/O problems reading the unsigned integer from the underlying input stream. 403 | */ 404 | protected long readUInt(int length, boolean breakAllowed) throws IOException { 405 | long result = -1; 406 | if (length < ONE_BYTE) { 407 | result = length; 408 | } else if (length == ONE_BYTE) { 409 | result = readUInt8(); 410 | } else if (length == TWO_BYTES) { 411 | result = readUInt16(); 412 | } else if (length == FOUR_BYTES) { 413 | result = readUInt32(); 414 | } else if (length == EIGHT_BYTES) { 415 | result = readUInt64(); 416 | } else if (breakAllowed && length == BREAK) { 417 | return -1; 418 | } 419 | if (result < 0) { 420 | fail("Not well-formed CBOR integer found, invalid length: %d!", result); 421 | } 422 | return result; 423 | } 424 | 425 | /** 426 | * Reads an unsigned 16-bit integer value 427 | * 428 | * @return value the read value, values from {@link Long#MIN_VALUE} to {@link Long#MAX_VALUE} are supported. 429 | * @throws IOException in case of I/O problems writing the CBOR-encoded value to the underlying output stream. 430 | */ 431 | protected int readUInt16() throws IOException { 432 | byte[] buf = readFully(new byte[2]); 433 | return (buf[0] & 0xFF) << 8 | (buf[1] & 0xFF); 434 | } 435 | 436 | /** 437 | * Reads an unsigned 32-bit integer value 438 | * 439 | * @return value the read value, values from {@link Long#MIN_VALUE} to {@link Long#MAX_VALUE} are supported. 440 | * @throws IOException in case of I/O problems writing the CBOR-encoded value to the underlying output stream. 441 | */ 442 | protected long readUInt32() throws IOException { 443 | byte[] buf = readFully(new byte[4]); 444 | return ((buf[0] & 0xFF) << 24 | (buf[1] & 0xFF) << 16 | (buf[2] & 0xFF) << 8 | (buf[3] & 0xFF)) & 0xffffffffL; 445 | } 446 | 447 | /** 448 | * Reads an unsigned 64-bit integer value 449 | * 450 | * @return value the read value, values from {@link Long#MIN_VALUE} to {@link Long#MAX_VALUE} are supported. 451 | * @throws IOException in case of I/O problems writing the CBOR-encoded value to the underlying output stream. 452 | */ 453 | protected long readUInt64() throws IOException { 454 | byte[] buf = readFully(new byte[8]); 455 | return (buf[0] & 0xFFL) << 56 | (buf[1] & 0xFFL) << 48 | (buf[2] & 0xFFL) << 40 | (buf[3] & 0xFFL) << 32 | // 456 | (buf[4] & 0xFFL) << 24 | (buf[5] & 0xFFL) << 16 | (buf[6] & 0xFFL) << 8 | (buf[7] & 0xFFL); 457 | } 458 | 459 | /** 460 | * Reads an unsigned 8-bit integer value 461 | * 462 | * @return value the read value, values from {@link Long#MIN_VALUE} to {@link Long#MAX_VALUE} are supported. 463 | * @throws IOException in case of I/O problems writing the CBOR-encoded value to the underlying output stream. 464 | */ 465 | protected int readUInt8() throws IOException { 466 | return m_is.read() & 0xff; 467 | } 468 | 469 | /** 470 | * Reads an unsigned integer with a given length-indicator. 471 | * 472 | * @param length the length indicator to use; 473 | * @return the read unsigned integer, as long value. 474 | * @throws IOException in case of I/O problems reading the unsigned integer from the underlying input stream. 475 | */ 476 | protected long readUIntExact(int expectedLength, int length) throws IOException { 477 | if (((expectedLength == -1) && (length >= ONE_BYTE)) || ((expectedLength >= 0) && (length != expectedLength))) { 478 | fail("Unexpected payload/length! Expected %s, but got %s.", lengthToString(expectedLength), 479 | lengthToString(length)); 480 | } 481 | return readUInt(length, false /* breakAllowed */); 482 | } 483 | 484 | private byte[] readFully(byte[] buf) throws IOException { 485 | int len = buf.length; 486 | int n = 0, off = 0; 487 | while (n < len) { 488 | int count = m_is.read(buf, off + n, len - n); 489 | if (count < 0) { 490 | throw new EOFException(); 491 | } 492 | n += count; 493 | } 494 | return buf; 495 | } 496 | } -------------------------------------------------------------------------------- /src/main/java/io/ipfs/api/cbor/CborEncoder.java: -------------------------------------------------------------------------------- 1 | package io.ipfs.api.cbor; 2 | 3 | /* 4 | * JACOB - CBOR implementation in Java. 5 | * 6 | * (C) Copyright - 2013 - J.W. Janssen 7 | * 8 | * Licensed under Apache License v2.0. 9 | */ 10 | 11 | import java.io.*; 12 | 13 | import static io.ipfs.api.cbor.CborConstants.*; 14 | 15 | /** 16 | * Provides an encoder capable of encoding data into CBOR format to a given {@link OutputStream}. 17 | */ 18 | public class CborEncoder { 19 | private static final int NEG_INT_MASK = TYPE_NEGATIVE_INTEGER << 5; 20 | 21 | private final OutputStream m_os; 22 | 23 | /** 24 | * Creates a new {@link CborEncoder} instance. 25 | * 26 | * @param os the actual output stream to write the CBOR-encoded data to, cannot be null. 27 | */ 28 | public CborEncoder(OutputStream os) { 29 | if (os == null) { 30 | throw new IllegalArgumentException("OutputStream cannot be null!"); 31 | } 32 | m_os = os; 33 | } 34 | 35 | /** 36 | * Interprets a given float-value as a half-precision float value and 37 | * converts it to its raw integer form, as defined in IEEE 754. 38 | *

39 | * Taken from: this Stack Overflow answer. 40 | *

41 | * 42 | * @param fval the value to convert. 43 | * @return the raw integer representation of the given float value. 44 | */ 45 | static int halfPrecisionToRawIntBits(float fval) { 46 | int fbits = Float.floatToIntBits(fval); 47 | int sign = (fbits >>> 16) & 0x8000; 48 | int val = (fbits & 0x7fffffff) + 0x1000; 49 | 50 | // might be or become NaN/Inf 51 | if (val >= 0x47800000) { 52 | if ((fbits & 0x7fffffff) >= 0x47800000) { // is or must become NaN/Inf 53 | if (val < 0x7f800000) { 54 | // was value but too large, make it +/-Inf 55 | return sign | 0x7c00; 56 | } 57 | return sign | 0x7c00 | (fbits & 0x007fffff) >>> 13; // keep NaN (and Inf) bits 58 | } 59 | return sign | 0x7bff; // unrounded not quite Inf 60 | } 61 | if (val >= 0x38800000) { 62 | // remains normalized value 63 | return sign | val - 0x38000000 >>> 13; // exp - 127 + 15 64 | } 65 | if (val < 0x33000000) { 66 | // too small for subnormal 67 | return sign; // becomes +/-0 68 | } 69 | 70 | val = (fbits & 0x7fffffff) >>> 23; 71 | // add subnormal bit, round depending on cut off and div by 2^(1-(exp-127+15)) and >> 13 | exp=0 72 | return sign | ((fbits & 0x7fffff | 0x800000) + (0x800000 >>> val - 102) >>> 126 - val); 73 | } 74 | 75 | /** 76 | * Writes the start of an indefinite-length array. 77 | *

78 | * After calling this method, one is expected to write the given number of array elements, which can be of any type. No length checks are performed.
79 | * After all array elements are written, one should write a single break value to end the array, see {@link #writeBreak()}. 80 | *

81 | * 82 | * @throws IOException in case of I/O problems writing the CBOR-encoded value to the underlying output stream. 83 | */ 84 | public void writeArrayStart() throws IOException { 85 | writeSimpleType(TYPE_ARRAY, BREAK); 86 | } 87 | 88 | /** 89 | * Writes the start of a definite-length array. 90 | *

91 | * After calling this method, one is expected to write the given number of array elements, which can be of any type. No length checks are performed. 92 | *

93 | * 94 | * @param length the number of array elements to write, should >= 0. 95 | * @throws IllegalArgumentException in case the given length was negative; 96 | * @throws IOException in case of I/O problems writing the CBOR-encoded value to the underlying output stream. 97 | */ 98 | public void writeArrayStart(int length) throws IOException { 99 | if (length < 0) { 100 | throw new IllegalArgumentException("Invalid array-length!"); 101 | } 102 | writeType(TYPE_ARRAY, length); 103 | } 104 | 105 | /** 106 | * Writes a boolean value in canonical CBOR format. 107 | * 108 | * @param value the boolean to write. 109 | * @throws IOException in case of I/O problems writing the CBOR-encoded value to the underlying output stream. 110 | */ 111 | public void writeBoolean(boolean value) throws IOException { 112 | writeSimpleType(TYPE_FLOAT_SIMPLE, value ? TRUE : FALSE); 113 | } 114 | 115 | /** 116 | * Writes a "break" stop-value in canonical CBOR format. 117 | * 118 | * @throws IOException in case of I/O problems writing the CBOR-encoded value to the underlying output stream. 119 | */ 120 | public void writeBreak() throws IOException { 121 | writeSimpleType(TYPE_FLOAT_SIMPLE, BREAK); 122 | } 123 | 124 | /** 125 | * Writes a byte string in canonical CBOR-format. 126 | * 127 | * @param bytes the byte string to write, can be null in which case a byte-string of length 0 is written. 128 | * @throws IOException in case of I/O problems writing the CBOR-encoded value to the underlying output stream. 129 | */ 130 | public void writeByteString(byte[] bytes) throws IOException { 131 | writeString(TYPE_BYTE_STRING, bytes); 132 | } 133 | 134 | /** 135 | * Writes the start of an indefinite-length byte string. 136 | *

137 | * After calling this method, one is expected to write the given number of string parts. No length checks are performed.
138 | * After all string parts are written, one should write a single break value to end the string, see {@link #writeBreak()}. 139 | *

140 | * 141 | * @throws IOException in case of I/O problems writing the CBOR-encoded value to the underlying output stream. 142 | */ 143 | public void writeByteStringStart() throws IOException { 144 | writeSimpleType(TYPE_BYTE_STRING, BREAK); 145 | } 146 | 147 | /** 148 | * Writes a double-precision float value in canonical CBOR format. 149 | * 150 | * @param value the value to write, values from {@link Double#MIN_VALUE} to {@link Double#MAX_VALUE} are supported. 151 | * @throws IOException in case of I/O problems writing the CBOR-encoded value to the underlying output stream. 152 | */ 153 | public void writeDouble(double value) throws IOException { 154 | writeUInt64(TYPE_FLOAT_SIMPLE << 5, Double.doubleToRawLongBits(value)); 155 | } 156 | 157 | /** 158 | * Writes a single-precision float value in canonical CBOR format. 159 | * 160 | * @param value the value to write, values from {@link Float#MIN_VALUE} to {@link Float#MAX_VALUE} are supported. 161 | * @throws IOException in case of I/O problems writing the CBOR-encoded value to the underlying output stream. 162 | */ 163 | public void writeFloat(float value) throws IOException { 164 | writeUInt32(TYPE_FLOAT_SIMPLE << 5, Float.floatToRawIntBits(value)); 165 | } 166 | 167 | /** 168 | * Writes a half-precision float value in canonical CBOR format. 169 | * 170 | * @param value the value to write, values from {@link Float#MIN_VALUE} to {@link Float#MAX_VALUE} are supported. 171 | * @throws IOException in case of I/O problems writing the CBOR-encoded value to the underlying output stream. 172 | */ 173 | public void writeHalfPrecisionFloat(float value) throws IOException { 174 | writeUInt16(TYPE_FLOAT_SIMPLE << 5, halfPrecisionToRawIntBits(value)); 175 | } 176 | 177 | /** 178 | * Writes a signed or unsigned integer value in canonical CBOR format, that is, tries to encode it in a little bytes as possible.. 179 | * 180 | * @param value the value to write, values from {@link Long#MIN_VALUE} to {@link Long#MAX_VALUE} are supported. 181 | * @throws IOException in case of I/O problems writing the CBOR-encoded value to the underlying output stream. 182 | */ 183 | public void writeInt(long value) throws IOException { 184 | // extends the sign over all bits... 185 | long sign = value >> 63; 186 | // in case value is negative, this bit should be set... 187 | int mt = (int) (sign & NEG_INT_MASK); 188 | // complement negative value... 189 | value = (sign ^ value); 190 | 191 | writeUInt(mt, value); 192 | } 193 | 194 | /** 195 | * Writes a signed or unsigned 16-bit integer value in CBOR format. 196 | * 197 | * @param value the value to write, values from [-65536..65535] are supported. 198 | * @throws IOException in case of I/O problems writing the CBOR-encoded value to the underlying output stream. 199 | */ 200 | public void writeInt16(int value) throws IOException { 201 | // extends the sign over all bits... 202 | int sign = value >> 31; 203 | // in case value is negative, this bit should be set... 204 | int mt = (int) (sign & NEG_INT_MASK); 205 | // complement negative value... 206 | writeUInt16(mt, (sign ^ value) & 0xffff); 207 | } 208 | 209 | /** 210 | * Writes a signed or unsigned 32-bit integer value in CBOR format. 211 | * 212 | * @param value the value to write, values in the range [-4294967296..4294967295] are supported. 213 | * @throws IOException in case of I/O problems writing the CBOR-encoded value to the underlying output stream. 214 | */ 215 | public void writeInt32(long value) throws IOException { 216 | // extends the sign over all bits... 217 | long sign = value >> 63; 218 | // in case value is negative, this bit should be set... 219 | int mt = (int) (sign & NEG_INT_MASK); 220 | // complement negative value... 221 | writeUInt32(mt, (int) ((sign ^ value) & 0xffffffffL)); 222 | } 223 | 224 | /** 225 | * Writes a signed or unsigned 64-bit integer value in CBOR format. 226 | * 227 | * @param value the value to write, values from {@link Long#MIN_VALUE} to {@link Long#MAX_VALUE} are supported. 228 | * @throws IOException in case of I/O problems writing the CBOR-encoded value to the underlying output stream. 229 | */ 230 | public void writeInt64(long value) throws IOException { 231 | // extends the sign over all bits... 232 | long sign = value >> 63; 233 | // in case value is negative, this bit should be set... 234 | int mt = (int) (sign & NEG_INT_MASK); 235 | // complement negative value... 236 | writeUInt64(mt, sign ^ value); 237 | } 238 | 239 | /** 240 | * Writes a signed or unsigned 8-bit integer value in CBOR format. 241 | * 242 | * @param value the value to write, values in the range [-256..255] are supported. 243 | * @throws IOException in case of I/O problems writing the CBOR-encoded value to the underlying output stream. 244 | */ 245 | public void writeInt8(int value) throws IOException { 246 | // extends the sign over all bits... 247 | int sign = value >> 31; 248 | // in case value is negative, this bit should be set... 249 | int mt = (int) (sign & NEG_INT_MASK); 250 | // complement negative value... 251 | writeUInt8(mt, (sign ^ value) & 0xff); 252 | } 253 | 254 | /** 255 | * Writes the start of an indefinite-length map. 256 | *

257 | * After calling this method, one is expected to write any number of map entries, as separate key and value. Keys and values can both be of any type. No length checks are performed.
258 | * After all map entries are written, one should write a single break value to end the map, see {@link #writeBreak()}. 259 | *

260 | * 261 | * @throws IOException in case of I/O problems writing the CBOR-encoded value to the underlying output stream. 262 | */ 263 | public void writeMapStart() throws IOException { 264 | writeSimpleType(TYPE_MAP, BREAK); 265 | } 266 | 267 | /** 268 | * Writes the start of a finite-length map. 269 | *

270 | * After calling this method, one is expected to write any number of map entries, as separate key and value. Keys and values can both be of any type. No length checks are performed. 271 | *

272 | * 273 | * @param length the number of map entries to write, should >= 0. 274 | * @throws IllegalArgumentException in case the given length was negative; 275 | * @throws IOException in case of I/O problems writing the CBOR-encoded value to the underlying output stream. 276 | */ 277 | public void writeMapStart(int length) throws IOException { 278 | if (length < 0) { 279 | throw new IllegalArgumentException("Invalid length of map!"); 280 | } 281 | writeType(TYPE_MAP, length); 282 | } 283 | 284 | /** 285 | * Writes a null value in canonical CBOR format. 286 | * 287 | * @throws IOException in case of I/O problems writing the CBOR-encoded value to the underlying output stream. 288 | */ 289 | public void writeNull() throws IOException { 290 | writeSimpleType(TYPE_FLOAT_SIMPLE, NULL); 291 | } 292 | 293 | /** 294 | * Writes a simple value, i.e., an "atom" or "constant" value in canonical CBOR format. 295 | * 296 | * @param simpleValue the (unsigned byte) value to write, values from 32 to 255 are supported (though not enforced). 297 | * @throws IOException in case of I/O problems writing the CBOR-encoded value to the underlying output stream. 298 | */ 299 | public void writeSimpleValue(byte simpleValue) throws IOException { 300 | // convert to unsigned value... 301 | int value = (simpleValue & 0xff); 302 | writeType(TYPE_FLOAT_SIMPLE, value); 303 | } 304 | 305 | /** 306 | * Writes a signed or unsigned small (<= 23) integer value in CBOR format. 307 | * 308 | * @param value the value to write, values in the range [-24..23] are supported. 309 | * @throws IOException in case of I/O problems writing the CBOR-encoded value to the underlying output stream. 310 | */ 311 | public void writeSmallInt(int value) throws IOException { 312 | // extends the sign over all bits... 313 | int sign = value >> 31; 314 | // in case value is negative, this bit should be set... 315 | int mt = (int) (sign & NEG_INT_MASK); 316 | // complement negative value... 317 | value = Math.min(0x17, (sign ^ value)); 318 | 319 | m_os.write((int) (mt | value)); 320 | } 321 | 322 | /** 323 | * Writes a semantic tag in canonical CBOR format. 324 | * 325 | * @param tag the tag to write, should >= 0. 326 | * @throws IllegalArgumentException in case the given tag was negative; 327 | * @throws IOException in case of I/O problems writing the CBOR-encoded value to the underlying output stream. 328 | */ 329 | public void writeTag(long tag) throws IOException { 330 | if (tag < 0) { 331 | throw new IllegalArgumentException("Invalid tag specification, cannot be negative!"); 332 | } 333 | writeType(TYPE_TAG, tag); 334 | } 335 | 336 | /** 337 | * Writes an UTF-8 string in canonical CBOR-format. 338 | *

339 | * Note that this method is platform specific, as the given string value will be encoded in a byte array 340 | * using the platform encoding! This means that the encoding must be standardized and known. 341 | *

342 | * 343 | * @param value the UTF-8 string to write, can be null in which case an UTF-8 string of length 0 is written. 344 | * @throws IOException in case of I/O problems writing the CBOR-encoded value to the underlying output stream. 345 | */ 346 | public void writeTextString(String value) throws IOException { 347 | writeString(TYPE_TEXT_STRING, value == null ? null : value.getBytes("UTF-8")); 348 | } 349 | 350 | /** 351 | * Writes the start of an indefinite-length UTF-8 string. 352 | *

353 | * After calling this method, one is expected to write the given number of string parts. No length checks are performed.
354 | * After all string parts are written, one should write a single break value to end the string, see {@link #writeBreak()}. 355 | *

356 | * 357 | * @throws IOException in case of I/O problems writing the CBOR-encoded value to the underlying output stream. 358 | */ 359 | public void writeTextStringStart() throws IOException { 360 | writeSimpleType(TYPE_TEXT_STRING, BREAK); 361 | } 362 | 363 | /** 364 | * Writes an "undefined" value in canonical CBOR format. 365 | * 366 | * @throws IOException in case of I/O problems writing the CBOR-encoded value to the underlying output stream. 367 | */ 368 | public void writeUndefined() throws IOException { 369 | writeSimpleType(TYPE_FLOAT_SIMPLE, UNDEFINED); 370 | } 371 | 372 | /** 373 | * Encodes and writes the major type and value as a simple type. 374 | * 375 | * @param majorType the major type of the value to write, denotes what semantics the written value has; 376 | * @param value the value to write, values from [0..31] are supported. 377 | * @throws IOException in case of I/O problems writing the CBOR-encoded value to the underlying output stream. 378 | */ 379 | protected void writeSimpleType(int majorType, int value) throws IOException { 380 | m_os.write((majorType << 5) | (value & 0x1f)); 381 | } 382 | 383 | /** 384 | * Writes a byte string in canonical CBOR-format. 385 | * 386 | * @param majorType the major type of the string, should be either 0x40 or 0x60; 387 | * @param bytes the byte string to write, can be null in which case a byte-string of length 0 is written. 388 | * @throws IOException in case of I/O problems writing the CBOR-encoded value to the underlying output stream. 389 | */ 390 | protected void writeString(int majorType, byte[] bytes) throws IOException { 391 | int len = (bytes == null) ? 0 : bytes.length; 392 | writeType(majorType, len); 393 | for (int i = 0; i < len; i++) { 394 | m_os.write(bytes[i]); 395 | } 396 | } 397 | 398 | /** 399 | * Encodes and writes the major type indicator with a given payload (length). 400 | * 401 | * @param majorType the major type of the value to write, denotes what semantics the written value has; 402 | * @param value the value to write, values from {@link Long#MIN_VALUE} to {@link Long#MAX_VALUE} are supported. 403 | * @throws IOException in case of I/O problems writing the CBOR-encoded value to the underlying output stream. 404 | */ 405 | protected void writeType(int majorType, long value) throws IOException { 406 | writeUInt((majorType << 5), value); 407 | } 408 | 409 | /** 410 | * Encodes and writes an unsigned integer value, that is, tries to encode it in a little bytes as possible. 411 | * 412 | * @param mt the major type of the value to write, denotes what semantics the written value has; 413 | * @param value the value to write, values from {@link Long#MIN_VALUE} to {@link Long#MAX_VALUE} are supported. 414 | * @throws IOException in case of I/O problems writing the CBOR-encoded value to the underlying output stream. 415 | */ 416 | protected void writeUInt(int mt, long value) throws IOException { 417 | if (value < 0x18L) { 418 | m_os.write((int) (mt | value)); 419 | } else if (value < 0x100L) { 420 | writeUInt8(mt, (int) value); 421 | } else if (value < 0x10000L) { 422 | writeUInt16(mt, (int) value); 423 | } else if (value < 0x100000000L) { 424 | writeUInt32(mt, (int) value); 425 | } else { 426 | writeUInt64(mt, value); 427 | } 428 | } 429 | 430 | /** 431 | * Encodes and writes an unsigned 16-bit integer value 432 | * 433 | * @param mt the major type of the value to write, denotes what semantics the written value has; 434 | * @param value the value to write, values from {@link Long#MIN_VALUE} to {@link Long#MAX_VALUE} are supported. 435 | * @throws IOException in case of I/O problems writing the CBOR-encoded value to the underlying output stream. 436 | */ 437 | protected void writeUInt16(int mt, int value) throws IOException { 438 | m_os.write(mt | TWO_BYTES); 439 | m_os.write(value >> 8); 440 | m_os.write(value & 0xFF); 441 | } 442 | 443 | /** 444 | * Encodes and writes an unsigned 32-bit integer value 445 | * 446 | * @param mt the major type of the value to write, denotes what semantics the written value has; 447 | * @param value the value to write, values from {@link Long#MIN_VALUE} to {@link Long#MAX_VALUE} are supported. 448 | * @throws IOException in case of I/O problems writing the CBOR-encoded value to the underlying output stream. 449 | */ 450 | protected void writeUInt32(int mt, int value) throws IOException { 451 | m_os.write(mt | FOUR_BYTES); 452 | m_os.write(value >> 24); 453 | m_os.write(value >> 16); 454 | m_os.write(value >> 8); 455 | m_os.write(value & 0xFF); 456 | } 457 | 458 | /** 459 | * Encodes and writes an unsigned 64-bit integer value 460 | * 461 | * @param mt the major type of the value to write, denotes what semantics the written value has; 462 | * @param value the value to write, values from {@link Long#MIN_VALUE} to {@link Long#MAX_VALUE} are supported. 463 | * @throws IOException in case of I/O problems writing the CBOR-encoded value to the underlying output stream. 464 | */ 465 | protected void writeUInt64(int mt, long value) throws IOException { 466 | m_os.write(mt | EIGHT_BYTES); 467 | m_os.write((int) (value >> 56)); 468 | m_os.write((int) (value >> 48)); 469 | m_os.write((int) (value >> 40)); 470 | m_os.write((int) (value >> 32)); 471 | m_os.write((int) (value >> 24)); 472 | m_os.write((int) (value >> 16)); 473 | m_os.write((int) (value >> 8)); 474 | m_os.write((int) (value & 0xFF)); 475 | } 476 | 477 | /** 478 | * Encodes and writes an unsigned 8-bit integer value 479 | * 480 | * @param mt the major type of the value to write, denotes what semantics the written value has; 481 | * @param value the value to write, values from {@link Long#MIN_VALUE} to {@link Long#MAX_VALUE} are supported. 482 | * @throws IOException in case of I/O problems writing the CBOR-encoded value to the underlying output stream. 483 | */ 484 | protected void writeUInt8(int mt, int value) throws IOException { 485 | m_os.write(mt | ONE_BYTE); 486 | m_os.write(value & 0xFF); 487 | } 488 | } -------------------------------------------------------------------------------- /src/main/java/io/ipfs/api/cbor/CborObject.java: -------------------------------------------------------------------------------- 1 | package io.ipfs.api.cbor; 2 | 3 | import io.ipfs.cid.*; 4 | import io.ipfs.multiaddr.*; 5 | import io.ipfs.multihash.*; 6 | 7 | import java.io.*; 8 | import java.util.*; 9 | import java.util.stream.*; 10 | 11 | public interface CborObject { 12 | 13 | void serialize(CborEncoder encoder); 14 | 15 | default byte[] toByteArray() { 16 | ByteArrayOutputStream bout = new ByteArrayOutputStream(); 17 | CborEncoder encoder = new CborEncoder(bout); 18 | serialize(encoder); 19 | return bout.toByteArray(); 20 | } 21 | 22 | int LINK_TAG = 42; 23 | 24 | static CborObject fromByteArray(byte[] cbor) { 25 | return deserialize(new CborDecoder(new ByteArrayInputStream(cbor))); 26 | } 27 | 28 | static CborObject deserialize(CborDecoder decoder) { 29 | try { 30 | CborType type = decoder.peekType(); 31 | switch (type.getMajorType()) { 32 | case CborConstants.TYPE_TEXT_STRING: 33 | return new CborString(decoder.readTextString()); 34 | case CborConstants.TYPE_BYTE_STRING: 35 | return new CborByteArray(decoder.readByteString()); 36 | case CborConstants.TYPE_UNSIGNED_INTEGER: 37 | return new CborLong(decoder.readInt()); 38 | case CborConstants.TYPE_NEGATIVE_INTEGER: 39 | return new CborLong(decoder.readInt()); 40 | case CborConstants.TYPE_FLOAT_SIMPLE: 41 | if (type.getAdditionalInfo() == CborConstants.NULL) { 42 | decoder.readNull(); 43 | return new CborNull(); 44 | } 45 | if (type.getAdditionalInfo() == CborConstants.TRUE) { 46 | decoder.readBoolean(); 47 | return new CborBoolean(true); 48 | } 49 | if (type.getAdditionalInfo() == CborConstants.FALSE) { 50 | decoder.readBoolean(); 51 | return new CborBoolean(false); 52 | } 53 | throw new IllegalStateException("Unimplemented simple type! " + type.getAdditionalInfo()); 54 | case CborConstants.TYPE_MAP: { 55 | long nValues = decoder.readMapLength(); 56 | SortedMap result = new TreeMap<>(); 57 | for (long i=0; i < nValues; i++) { 58 | CborObject key = deserialize(decoder); 59 | CborObject value = deserialize(decoder); 60 | result.put(key, value); 61 | } 62 | return new CborMap(result); 63 | } 64 | case CborConstants.TYPE_ARRAY: 65 | long nItems = decoder.readArrayLength(); 66 | List res = new ArrayList<>((int) nItems); 67 | for (long i=0; i < nItems; i++) 68 | res.add(deserialize(decoder)); 69 | return new CborList(res); 70 | case CborConstants.TYPE_TAG: 71 | long tag = decoder.readTag(); 72 | if (tag == LINK_TAG) { 73 | CborObject value = deserialize(decoder); 74 | if (value instanceof CborString) 75 | return new CborMerkleLink(Cid.decode(((CborString) value).value)); 76 | if (value instanceof CborByteArray) { 77 | byte[] bytes = ((CborByteArray) value).value; 78 | if (bytes[0] == 0) // multibase for binary 79 | return new CborMerkleLink(Cid.cast(Arrays.copyOfRange(bytes, 1, bytes.length))); 80 | throw new IllegalStateException("Unknown Multibase decoding Merkle link: " + bytes[0]); 81 | } 82 | throw new IllegalStateException("Invalid type for merkle link: " + value); 83 | } 84 | throw new IllegalStateException("Unknown TAG in CBOR: " + type.getAdditionalInfo()); 85 | default: 86 | throw new IllegalStateException("Unimplemented cbor type: " + type); 87 | } 88 | } catch (IOException e) { 89 | throw new RuntimeException(e); 90 | } 91 | } 92 | 93 | final class CborMap implements CborObject { 94 | public final SortedMap values; 95 | 96 | public CborMap(SortedMap values) { 97 | this.values = values; 98 | } 99 | 100 | public static CborMap build(Map values) { 101 | SortedMap transformed = values.entrySet() 102 | .stream() 103 | .collect(Collectors.toMap( 104 | e -> new CborString(e.getKey()), 105 | e -> e.getValue(), 106 | (a, b) -> a, TreeMap::new)); 107 | return new CborMap(transformed); 108 | } 109 | 110 | @Override 111 | public void serialize(CborEncoder encoder) { 112 | try { 113 | encoder.writeMapStart(values.size()); 114 | for (Map.Entry entry : values.entrySet()) { 115 | entry.getKey().serialize(encoder); 116 | entry.getValue().serialize(encoder); 117 | } 118 | } catch (IOException e) { 119 | throw new RuntimeException(e); 120 | } 121 | } 122 | 123 | @Override 124 | public boolean equals(Object o) { 125 | if (this == o) return true; 126 | if (o == null || getClass() != o.getClass()) return false; 127 | 128 | CborMap cborMap = (CborMap) o; 129 | 130 | return values != null ? values.equals(cborMap.values) : cborMap.values == null; 131 | 132 | } 133 | 134 | @Override 135 | public int hashCode() { 136 | return values != null ? values.hashCode() : 0; 137 | } 138 | } 139 | 140 | final class CborMerkleLink implements CborObject { 141 | public final Multihash target; 142 | 143 | public CborMerkleLink(Multihash target) { 144 | this.target = target; 145 | } 146 | 147 | @Override 148 | public void serialize(CborEncoder encoder) { 149 | try { 150 | encoder.writeTag(LINK_TAG); 151 | byte[] cid = target.toBytes(); 152 | byte[] withMultibaseHeader = new byte[cid.length + 1]; 153 | System.arraycopy(cid, 0, withMultibaseHeader, 1, cid.length); 154 | encoder.writeByteString(withMultibaseHeader); 155 | } catch (IOException e) { 156 | throw new RuntimeException(e); 157 | } 158 | } 159 | 160 | @Override 161 | public boolean equals(Object o) { 162 | if (this == o) return true; 163 | if (o == null || getClass() != o.getClass()) return false; 164 | 165 | CborMerkleLink that = (CborMerkleLink) o; 166 | 167 | return target != null ? target.equals(that.target) : that.target == null; 168 | 169 | } 170 | 171 | @Override 172 | public int hashCode() { 173 | return target != null ? target.hashCode() : 0; 174 | } 175 | } 176 | 177 | final class CborList implements CborObject { 178 | public final List value; 179 | 180 | public CborList(List value) { 181 | this.value = value; 182 | } 183 | 184 | @Override 185 | public void serialize(CborEncoder encoder) { 186 | try { 187 | encoder.writeArrayStart(value.size()); 188 | for (CborObject object : value) { 189 | object.serialize(encoder); 190 | } 191 | } catch (IOException e) { 192 | throw new RuntimeException(e); 193 | } 194 | } 195 | 196 | @Override 197 | public boolean equals(Object o) { 198 | if (this == o) return true; 199 | if (o == null || getClass() != o.getClass()) return false; 200 | 201 | CborList cborList = (CborList) o; 202 | 203 | return value != null ? value.equals(cborList.value) : cborList.value == null; 204 | } 205 | 206 | @Override 207 | public int hashCode() { 208 | return value != null ? value.hashCode() : 0; 209 | } 210 | } 211 | 212 | final class CborBoolean implements CborObject { 213 | public final boolean value; 214 | 215 | public CborBoolean(boolean value) { 216 | this.value = value; 217 | } 218 | 219 | @Override 220 | public void serialize(CborEncoder encoder) { 221 | try { 222 | encoder.writeBoolean(value); 223 | } catch (IOException e) { 224 | throw new RuntimeException(e); 225 | } 226 | } 227 | 228 | @Override 229 | public boolean equals(Object o) { 230 | if (this == o) return true; 231 | if (o == null || getClass() != o.getClass()) return false; 232 | 233 | CborBoolean that = (CborBoolean) o; 234 | 235 | return value == that.value; 236 | 237 | } 238 | 239 | @Override 240 | public int hashCode() { 241 | return (value ? 1 : 0); 242 | } 243 | 244 | @Override 245 | public String toString() { 246 | return "CborBoolean{" + 247 | value + 248 | '}'; 249 | } 250 | } 251 | 252 | final class CborByteArray implements CborObject, Comparable { 253 | public final byte[] value; 254 | 255 | public CborByteArray(byte[] value) { 256 | this.value = value; 257 | } 258 | 259 | @Override 260 | public int compareTo(CborByteArray other) { 261 | return compare(value, other.value); 262 | } 263 | 264 | public static int compare(byte[] a, byte[] b) 265 | { 266 | for (int i=0; i < Math.min(a.length, b.length); i++) 267 | if (a[i] != b[i]) 268 | return a[i] & 0xff - b[i] & 0xff; 269 | return 0; 270 | } 271 | 272 | @Override 273 | public void serialize(CborEncoder encoder) { 274 | try { 275 | encoder.writeByteString(value); 276 | } catch (IOException e) { 277 | throw new RuntimeException(e); 278 | } 279 | } 280 | 281 | @Override 282 | public boolean equals(Object o) { 283 | if (this == o) return true; 284 | if (o == null || getClass() != o.getClass()) return false; 285 | 286 | CborByteArray that = (CborByteArray) o; 287 | 288 | return Arrays.equals(value, that.value); 289 | 290 | } 291 | 292 | @Override 293 | public int hashCode() { 294 | return Arrays.hashCode(value); 295 | } 296 | } 297 | 298 | final class CborString implements CborObject, Comparable { 299 | 300 | public final String value; 301 | 302 | public CborString(String value) { 303 | this.value = value; 304 | } 305 | 306 | @Override 307 | public int compareTo(CborString cborString) { 308 | int lenDiff = value.length() - cborString.value.length(); 309 | if (lenDiff != 0) 310 | return lenDiff; 311 | return value.compareTo(cborString.value); 312 | } 313 | 314 | @Override 315 | public void serialize(CborEncoder encoder) { 316 | try { 317 | encoder.writeTextString(value); 318 | } catch (IOException e) { 319 | throw new RuntimeException(e); 320 | } 321 | } 322 | 323 | @Override 324 | public boolean equals(Object o) { 325 | if (this == o) return true; 326 | if (o == null || getClass() != o.getClass()) return false; 327 | 328 | CborString that = (CborString) o; 329 | 330 | return value.equals(that.value); 331 | 332 | } 333 | 334 | @Override 335 | public int hashCode() { 336 | return value.hashCode(); 337 | } 338 | 339 | @Override 340 | public String toString() { 341 | return "CborString{\"" + 342 | value + 343 | "\"}"; 344 | } 345 | } 346 | 347 | final class CborLong implements CborObject, Comparable { 348 | public final long value; 349 | 350 | public CborLong(long value) { 351 | this.value = value; 352 | } 353 | 354 | @Override 355 | public int compareTo(CborLong other) { 356 | return Long.compare(value, other.value); 357 | } 358 | 359 | @Override 360 | public void serialize(CborEncoder encoder) { 361 | try { 362 | encoder.writeInt(value); 363 | } catch (IOException e) { 364 | throw new RuntimeException(e); 365 | } 366 | } 367 | 368 | @Override 369 | public boolean equals(Object o) { 370 | if (this == o) return true; 371 | if (o == null || getClass() != o.getClass()) return false; 372 | 373 | CborLong cborLong = (CborLong) o; 374 | 375 | return value == cborLong.value; 376 | 377 | } 378 | 379 | @Override 380 | public int hashCode() { 381 | return (int) (value ^ (value >>> 32)); 382 | } 383 | 384 | @Override 385 | public String toString() { 386 | return "CborLong{" + 387 | value + 388 | '}'; 389 | } 390 | } 391 | 392 | final class CborNull implements CborObject, Comparable { 393 | public CborNull() {} 394 | 395 | @Override 396 | public int compareTo(CborNull cborNull) { 397 | return 0; 398 | } 399 | 400 | @Override 401 | public void serialize(CborEncoder encoder) { 402 | try { 403 | encoder.writeNull(); 404 | } catch (IOException e) { 405 | throw new RuntimeException(e); 406 | } 407 | } 408 | 409 | @Override 410 | public boolean equals(Object o) { 411 | if (this == o) return true; 412 | if (o == null || getClass() != o.getClass()) return false; 413 | 414 | return true; 415 | } 416 | 417 | @Override 418 | public int hashCode() { 419 | return 0; 420 | } 421 | 422 | @Override 423 | public String toString() { 424 | return "CborNull{}"; 425 | } 426 | } 427 | } 428 | -------------------------------------------------------------------------------- /src/main/java/io/ipfs/api/cbor/CborType.java: -------------------------------------------------------------------------------- 1 | package io.ipfs.api.cbor; 2 | 3 | /* 4 | * JACOB - CBOR implementation in Java. 5 | * 6 | * (C) Copyright - 2013 - J.W. Janssen 7 | * 8 | * Licensed under Apache License v2.0. 9 | */ 10 | 11 | import static io.ipfs.api.cbor.CborConstants.*; 12 | 13 | /** 14 | * Represents the various major types in CBOR, along with their . 15 | *

16 | * The major type is encoded in the upper three bits of each initial byte. The lower 5 bytes represent any additional information. 17 | *

18 | */ 19 | public class CborType { 20 | private final int m_major; 21 | private final int m_additional; 22 | 23 | private CborType(int major, int additional) { 24 | m_major = major; 25 | m_additional = additional; 26 | } 27 | 28 | /** 29 | * Returns a descriptive string for the given major type. 30 | * 31 | * @param mt the major type to return as string, values from [0..7] are supported. 32 | * @return the name of the given major type, as String, never null. 33 | * @throws IllegalArgumentException in case the given major type is not supported. 34 | */ 35 | public static String getName(int mt) { 36 | switch (mt) { 37 | case TYPE_ARRAY: 38 | return "array"; 39 | case TYPE_BYTE_STRING: 40 | return "byte string"; 41 | case TYPE_FLOAT_SIMPLE: 42 | return "float/simple value"; 43 | case TYPE_MAP: 44 | return "map"; 45 | case TYPE_NEGATIVE_INTEGER: 46 | return "negative integer"; 47 | case TYPE_TAG: 48 | return "tag"; 49 | case TYPE_TEXT_STRING: 50 | return "text string"; 51 | case TYPE_UNSIGNED_INTEGER: 52 | return "unsigned integer"; 53 | default: 54 | throw new IllegalArgumentException("Invalid major type: " + mt); 55 | } 56 | } 57 | 58 | /** 59 | * Decodes a given byte value to a {@link CborType} value. 60 | * 61 | * @param i the input byte (8-bit) to decode into a {@link CborType} instance. 62 | * @return a {@link CborType} instance, never null. 63 | */ 64 | public static CborType valueOf(int i) { 65 | return new CborType((i & 0xff) >>> 5, i & 0x1f); 66 | } 67 | 68 | @Override 69 | public boolean equals(Object obj) { 70 | if (this == obj) { 71 | return true; 72 | } 73 | if (obj == null || getClass() != obj.getClass()) { 74 | return false; 75 | } 76 | 77 | CborType other = (CborType) obj; 78 | return (m_major == other.m_major) && (m_additional == other.m_additional); 79 | } 80 | 81 | /** 82 | * @return the additional information of this type, as integer value from [0..31]. 83 | */ 84 | public int getAdditionalInfo() { 85 | return m_additional; 86 | } 87 | 88 | /** 89 | * @return the major type, as integer value from [0..7]. 90 | */ 91 | public int getMajorType() { 92 | return m_major; 93 | } 94 | 95 | @Override 96 | public int hashCode() { 97 | final int prime = 31; 98 | int result = 1; 99 | result = prime * result + m_additional; 100 | result = prime * result + m_major; 101 | return result; 102 | } 103 | 104 | /** 105 | * @return true if this type allows for an infinite-length payload, 106 | * false if only definite-length payloads are allowed. 107 | */ 108 | public boolean isBreakAllowed() { 109 | return m_major == TYPE_ARRAY || m_major == TYPE_BYTE_STRING || m_major == TYPE_MAP 110 | || m_major == TYPE_TEXT_STRING; 111 | } 112 | 113 | /** 114 | * Determines whether the major type of a given {@link CborType} equals the major type of this {@link CborType}. 115 | * 116 | * @param other the {@link CborType} to compare against, cannot be null. 117 | * @return true if the given {@link CborType} is of the same major type as this {@link CborType}, false otherwise. 118 | * @throws IllegalArgumentException in case the given argument was null. 119 | */ 120 | public boolean isEqualType(CborType other) { 121 | if (other == null) { 122 | throw new IllegalArgumentException("Parameter cannot be null!"); 123 | } 124 | return m_major == other.m_major; 125 | } 126 | 127 | /** 128 | * Determines whether the major type of a given byte value (representing an encoded {@link CborType}) equals the major type of this {@link CborType}. 129 | * 130 | * @param encoded the encoded CBOR type to compare. 131 | * @return true if the given byte value represents the same major type as this {@link CborType}, false otherwise. 132 | */ 133 | public boolean isEqualType(int encoded) { 134 | return m_major == ((encoded & 0xff) >>> 5); 135 | } 136 | 137 | @Override 138 | public String toString() { 139 | StringBuilder sb = new StringBuilder(); 140 | sb.append(getName(m_major)).append('(').append(m_additional).append(')'); 141 | return sb.toString(); 142 | } 143 | } -------------------------------------------------------------------------------- /src/main/java/io/ipfs/api/cbor/Cborable.java: -------------------------------------------------------------------------------- 1 | package io.ipfs.api.cbor; 2 | 3 | public interface Cborable { 4 | 5 | CborObject toCbor(); 6 | 7 | default byte[] serialize() { 8 | return toCbor().toByteArray(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/test/java/io/ipfs/api/APITest.java: -------------------------------------------------------------------------------- 1 | package io.ipfs.api; 2 | 3 | import io.ipfs.api.cbor.CborObject; 4 | import io.ipfs.cid.Cid; 5 | import io.ipfs.multiaddr.MultiAddress; 6 | import io.ipfs.multihash.Multihash; 7 | import org.junit.Assert; 8 | import org.junit.Ignore; 9 | import org.junit.Test; 10 | 11 | import java.io.*; 12 | import java.nio.file.Files; 13 | import java.nio.file.Path; 14 | import java.util.*; 15 | import java.util.function.Function; 16 | import java.util.stream.Collectors; 17 | import java.util.stream.Stream; 18 | 19 | import static org.junit.Assert.assertTrue; 20 | 21 | public class APITest { 22 | 23 | private final IPFS ipfs = new IPFS(new MultiAddress("/ip4/127.0.0.1/tcp/5001")); 24 | private final Random r = new Random(33550336); // perfect 25 | 26 | @Test 27 | public void dag() throws IOException { 28 | String original = "{\"data\":1234}"; 29 | byte[] object = original.getBytes(); 30 | MerkleNode put = ipfs.dag.put("json", object); 31 | 32 | Cid expected = Cid.decode("zdpuAs3whHmb9T1NkHSLGF45ykcKrEBxSLiEx6YpLzmKbQLEB"); 33 | 34 | Multihash result = put.hash; 35 | Assert.assertTrue("Correct cid returned", result.equals(expected)); 36 | 37 | byte[] get = ipfs.dag.get(expected); 38 | Assert.assertTrue("Raw data equal", original.equals(new String(get).trim())); 39 | } 40 | 41 | @Test 42 | public void dagCbor() throws IOException { 43 | Map tmp = new LinkedHashMap<>(); 44 | String value = "G'day mate!"; 45 | tmp.put("data", new CborObject.CborString(value)); 46 | CborObject original = CborObject.CborMap.build(tmp); 47 | byte[] object = original.toByteArray(); 48 | MerkleNode put = ipfs.dag.put("cbor", object); 49 | 50 | Cid cid = (Cid) put.hash; 51 | 52 | byte[] get = ipfs.dag.get(cid); 53 | Assert.assertTrue("Raw data equal", ((Map) JSONParser.parse(new String(get))).get("data").equals(value)); 54 | 55 | Cid expected = Cid.decode("zdpuApemz4XMURSCkBr9W5y974MXkSbeDfLeZmiQTPpvkatFF"); 56 | Assert.assertTrue("Correct cid returned", cid.equals(expected)); 57 | } 58 | 59 | @Test 60 | public void keys() throws IOException { 61 | List existing = ipfs.key.list(); 62 | String name = "mykey" + System.nanoTime(); 63 | KeyInfo gen = ipfs.key.gen(name, Optional.of("rsa"), Optional.of("2048")); 64 | String newName = "bob" + System.nanoTime(); 65 | Object rename = ipfs.key.rename(name, newName); 66 | List rm = ipfs.key.rm(newName); 67 | List remaining = ipfs.key.list(); 68 | Assert.assertTrue("removed key", remaining.equals(existing)); 69 | } 70 | 71 | @Test 72 | public void ipldNode() { 73 | Function>, CborObject.CborMap> map = 74 | s -> CborObject.CborMap.build(s.collect(Collectors.toMap(p -> p.left, p -> p.right))); 75 | CborObject.CborMap a = map.apply(Stream.of(new Pair<>("b", new CborObject.CborLong(1)))); 76 | 77 | CborObject.CborMap cbor = map.apply(Stream.of(new Pair<>("a", a), new Pair<>("c", new CborObject.CborLong(2)))); 78 | 79 | IpldNode.CborIpldNode node = new IpldNode.CborIpldNode(cbor); 80 | List tree = node.tree("", -1); 81 | Assert.assertTrue("Correct tree", tree.equals(Arrays.asList("/a/b", "/c"))); 82 | } 83 | 84 | @Test 85 | public void singleFileTest() throws IOException { 86 | NamedStreamable.ByteArrayWrapper file = new NamedStreamable.ByteArrayWrapper("hello.txt", "G'day world! IPFS rocks!".getBytes()); 87 | fileTest(file); 88 | } 89 | 90 | @Test 91 | public void wrappedSingleFileTest() throws IOException { 92 | NamedStreamable.ByteArrayWrapper file = new NamedStreamable.ByteArrayWrapper("hello.txt", "G'day world! IPFS rocks!".getBytes()); 93 | List addParts = ipfs.add(file, true); 94 | MerkleNode filePart = addParts.get(0); 95 | MerkleNode dirPart = addParts.get(1); 96 | byte[] catResult = ipfs.cat(filePart.hash); 97 | byte[] getResult = ipfs.get(filePart.hash); 98 | if (!Arrays.equals(catResult, file.getContents())) 99 | throw new IllegalStateException("Different contents!"); 100 | List pinRm = ipfs.pin.rm(dirPart.hash, true); 101 | if (!pinRm.get(0).equals(dirPart.hash)) 102 | throw new IllegalStateException("Didn't remove file!"); 103 | Object gc = ipfs.repo.gc(); 104 | } 105 | 106 | @Test 107 | public void dirTest() throws IOException { 108 | Path test = Files.createTempDirectory("test"); 109 | Files.write(test.resolve("file.txt"), "G'day IPFS!".getBytes()); 110 | NamedStreamable dir = new NamedStreamable.FileWrapper(test.toFile()); 111 | List add = ipfs.add(dir); 112 | MerkleNode addResult = add.get(add.size() - 1); 113 | List ls = ipfs.ls(addResult.hash); 114 | Assert.assertTrue(ls.size() > 0); 115 | } 116 | 117 | @Test 118 | public void directoryTest() throws IOException { 119 | Random rnd = new Random(); 120 | String dirName = "folder" + rnd.nextInt(100); 121 | Path tmpDir = Files.createTempDirectory(dirName); 122 | 123 | String fileName = "afile" + rnd.nextInt(100); 124 | Path file = tmpDir.resolve(fileName); 125 | FileOutputStream fout = new FileOutputStream(file.toFile()); 126 | byte[] fileContents = "IPFS rocks!".getBytes(); 127 | fout.write(fileContents); 128 | fout.flush(); 129 | fout.close(); 130 | 131 | String subdirName = "subdir"; 132 | tmpDir.resolve(subdirName).toFile().mkdir(); 133 | 134 | String subfileName = "subdirfile" + rnd.nextInt(100); 135 | Path subdirfile = tmpDir.resolve(subdirName + "/" + subfileName); 136 | FileOutputStream fout2 = new FileOutputStream(subdirfile.toFile()); 137 | byte[] file2Contents = "IPFS still rocks!".getBytes(); 138 | fout2.write(file2Contents); 139 | fout2.flush(); 140 | fout2.close(); 141 | 142 | List addParts = ipfs.add(new NamedStreamable.FileWrapper(tmpDir.toFile())); 143 | MerkleNode addResult = addParts.get(addParts.size() - 1); 144 | List lsResult = ipfs.ls(addResult.hash); 145 | if (lsResult.size() != 2) 146 | throw new IllegalStateException("Incorrect number of objects in ls!"); 147 | if (!lsResult.stream().map(x -> x.name.get()).collect(Collectors.toSet()).equals(new HashSet<>(Arrays.asList(subdirName, fileName)))) 148 | throw new IllegalStateException("Dir not returned in ls!"); 149 | byte[] catResult = ipfs.cat(addResult.hash, "/" + fileName); 150 | if (!Arrays.equals(catResult, fileContents)) 151 | throw new IllegalStateException("Different contents!"); 152 | 153 | byte[] catResult2 = ipfs.cat(addResult.hash, "/" + subdirName + "/" + subfileName); 154 | if (!Arrays.equals(catResult2, file2Contents)) 155 | throw new IllegalStateException("Different contents!"); 156 | } 157 | 158 | // @Test 159 | public void largeFileTest() throws IOException { 160 | byte[] largerData = new byte[100 * 1024 * 1024]; 161 | new Random(1).nextBytes(largerData); 162 | NamedStreamable.ByteArrayWrapper largeFile = new NamedStreamable.ByteArrayWrapper("nontrivial.txt", largerData); 163 | fileTest(largeFile); 164 | } 165 | 166 | // @Test 167 | public void hugeFileStreamTest() throws IOException { 168 | byte[] hugeData = new byte[1000 * 1024 * 1024]; 169 | new Random(1).nextBytes(hugeData); 170 | NamedStreamable.ByteArrayWrapper largeFile = new NamedStreamable.ByteArrayWrapper("massive.txt", hugeData); 171 | MerkleNode addResult = ipfs.add(largeFile).get(0); 172 | InputStream in = ipfs.catStream(addResult.hash); 173 | 174 | byte[] res = new byte[hugeData.length]; 175 | int offset = 0; 176 | byte[] buf = new byte[4096]; 177 | int r; 178 | while ((r = in.read(buf)) >= 0) { 179 | try { 180 | System.arraycopy(buf, 0, res, offset, r); 181 | offset += r; 182 | } catch (Exception e) { 183 | e.printStackTrace(); 184 | } 185 | } 186 | if (!Arrays.equals(res, hugeData)) 187 | throw new IllegalStateException("Different contents!"); 188 | } 189 | 190 | @Test 191 | public void hostFileTest() throws IOException { 192 | Path tempFile = Files.createTempFile("IPFS", "tmp"); 193 | BufferedWriter w = new BufferedWriter(new FileWriter(tempFile.toFile())); 194 | w.append("Some data"); 195 | w.flush(); 196 | w.close(); 197 | NamedStreamable hostFile = new NamedStreamable.FileWrapper(tempFile.toFile()); 198 | fileTest(hostFile); 199 | } 200 | 201 | @Test 202 | public void hashOnly() throws IOException { 203 | byte[] data = randomBytes(4096); 204 | NamedStreamable file = new NamedStreamable.ByteArrayWrapper(data); 205 | MerkleNode addResult = ipfs.add(file, false, true).get(0); 206 | List local = ipfs.refs.local(); 207 | if (local.contains(addResult.hash)) 208 | throw new IllegalStateException("Object shouldn't be present!"); 209 | } 210 | 211 | public void fileTest(NamedStreamable file) throws IOException { 212 | MerkleNode addResult = ipfs.add(file).get(0); 213 | byte[] catResult = ipfs.cat(addResult.hash); 214 | byte[] getResult = ipfs.get(addResult.hash); 215 | if (!Arrays.equals(catResult, file.getContents())) 216 | throw new IllegalStateException("Different contents!"); 217 | List pinRm = ipfs.pin.rm(addResult.hash, true); 218 | if (!pinRm.get(0).equals(addResult.hash)) 219 | throw new IllegalStateException("Didn't remove file!"); 220 | Object gc = ipfs.repo.gc(); 221 | } 222 | 223 | @Test 224 | public void pinTest() throws IOException { 225 | MerkleNode file = ipfs.add(new NamedStreamable.ByteArrayWrapper("some data".getBytes())).get(0); 226 | Multihash hash = file.hash; 227 | Map ls1 = ipfs.pin.ls(IPFS.PinType.all); 228 | boolean pinned = ls1.containsKey(hash); 229 | List rm = ipfs.pin.rm(hash); 230 | // second rm should not throw a http 500, but return an empty list 231 | // List rm2 = ipfs.pin.rm(hash); 232 | List add2 = ipfs.pin.add(hash); 233 | // adding something already pinned should succeed 234 | List add3 = ipfs.pin.add(hash); 235 | Map ls = ipfs.pin.ls(IPFS.PinType.recursive); 236 | ipfs.repo.gc(); 237 | // object should still be present after gc 238 | Map ls2 = ipfs.pin.ls(IPFS.PinType.recursive); 239 | boolean stillPinned = ls2.containsKey(hash); 240 | Assert.assertTrue("Pinning works", pinned && stillPinned); 241 | } 242 | 243 | @Test 244 | public void pinUpdate() throws IOException { 245 | MerkleNode child1 = ipfs.add(new NamedStreamable.ByteArrayWrapper("some data".getBytes())).get(0); 246 | Multihash hashChild1 = child1.hash; 247 | System.out.println("child1: " + hashChild1); 248 | 249 | CborObject.CborMerkleLink root1 = new CborObject.CborMerkleLink(hashChild1); 250 | MerkleNode root1Res = ipfs.block.put(Collections.singletonList(root1.toByteArray()), Optional.of("cbor")).get(0); 251 | System.out.println("root1: " + root1Res.hash); 252 | ipfs.pin.add(root1Res.hash); 253 | 254 | CborObject.CborList root2 = new CborObject.CborList(Arrays.asList(new CborObject.CborMerkleLink(hashChild1), new CborObject.CborLong(42))); 255 | MerkleNode root2Res = ipfs.block.put(Collections.singletonList(root2.toByteArray()), Optional.of("cbor")).get(0); 256 | List update = ipfs.pin.update(root1Res.hash, root2Res.hash, true); 257 | 258 | Map ls = ipfs.pin.ls(IPFS.PinType.all); 259 | boolean childPresent = ls.containsKey(hashChild1); 260 | if (!childPresent) 261 | throw new IllegalStateException("Child not present!"); 262 | 263 | ipfs.repo.gc(); 264 | Map ls2 = ipfs.pin.ls(IPFS.PinType.all); 265 | boolean childPresentAfterGC = ls2.containsKey(hashChild1); 266 | if (!childPresentAfterGC) 267 | throw new IllegalStateException("Child not present!"); 268 | } 269 | 270 | @Test 271 | public void rawLeafNodePinUpdate() throws IOException { 272 | MerkleNode child1 = ipfs.block.put("some data".getBytes(), Optional.of("raw")); 273 | Multihash hashChild1 = child1.hash; 274 | System.out.println("child1: " + hashChild1); 275 | 276 | CborObject.CborMerkleLink root1 = new CborObject.CborMerkleLink(hashChild1); 277 | MerkleNode root1Res = ipfs.block.put(Collections.singletonList(root1.toByteArray()), Optional.of("cbor")).get(0); 278 | System.out.println("root1: " + root1Res.hash); 279 | ipfs.pin.add(root1Res.hash); 280 | 281 | MerkleNode child2 = ipfs.block.put("G'day new tree".getBytes(), Optional.of("raw")); 282 | Multihash hashChild2 = child2.hash; 283 | 284 | CborObject.CborList root2 = new CborObject.CborList(Arrays.asList( 285 | new CborObject.CborMerkleLink(hashChild1), 286 | new CborObject.CborMerkleLink(hashChild2), 287 | new CborObject.CborLong(42)) 288 | ); 289 | MerkleNode root2Res = ipfs.block.put(Collections.singletonList(root2.toByteArray()), Optional.of("cbor")).get(0); 290 | List update = ipfs.pin.update(root1Res.hash, root2Res.hash, false); 291 | } 292 | 293 | @Test 294 | public void indirectPinTest() throws IOException { 295 | Multihash EMPTY = ipfs.object._new(Optional.empty()).hash; 296 | io.ipfs.api.MerkleNode data = ipfs.object.patch(EMPTY, "set-data", Optional.of("childdata".getBytes()), Optional.empty(), Optional.empty()); 297 | Multihash child = data.hash; 298 | 299 | io.ipfs.api.MerkleNode tmp1 = ipfs.object.patch(EMPTY, "set-data", Optional.of("parent1_data".getBytes()), Optional.empty(), Optional.empty()); 300 | Multihash parent1 = ipfs.object.patch(tmp1.hash, "add-link", Optional.empty(), Optional.of(child.toString()), Optional.of(child)).hash; 301 | ipfs.pin.add(parent1); 302 | 303 | io.ipfs.api.MerkleNode tmp2 = ipfs.object.patch(EMPTY, "set-data", Optional.of("parent2_data".getBytes()), Optional.empty(), Optional.empty()); 304 | Multihash parent2 = ipfs.object.patch(tmp2.hash, "add-link", Optional.empty(), Optional.of(child.toString()), Optional.of(child)).hash; 305 | ipfs.pin.add(parent2); 306 | ipfs.pin.rm(parent1, true); 307 | 308 | Map ls = ipfs.pin.ls(IPFS.PinType.all); 309 | boolean childPresent = ls.containsKey(child); 310 | if (!childPresent) 311 | throw new IllegalStateException("Child not present!"); 312 | 313 | ipfs.repo.gc(); 314 | Map ls2 = ipfs.pin.ls(IPFS.PinType.all); 315 | boolean childPresentAfterGC = ls2.containsKey(child); 316 | if (!childPresentAfterGC) 317 | throw new IllegalStateException("Child not present!"); 318 | } 319 | 320 | @Test 321 | public void objectPatch() throws IOException { 322 | MerkleNode obj = ipfs.object._new(Optional.empty()); 323 | Multihash base = obj.hash; 324 | // link tests 325 | String linkName = "alink"; 326 | MerkleNode addLink = ipfs.object.patch(base, "add-link", Optional.empty(), Optional.of(linkName), Optional.of(base)); 327 | MerkleNode withLink = ipfs.object.get(addLink.hash); 328 | if (withLink.links.size() != 1 || !withLink.links.get(0).hash.equals(base) || !withLink.links.get(0).name.get().equals(linkName)) 329 | throw new RuntimeException("Added link not correct!"); 330 | MerkleNode rmLink = ipfs.object.patch(addLink.hash, "rm-link", Optional.empty(), Optional.of(linkName), Optional.empty()); 331 | if (!rmLink.hash.equals(base)) 332 | throw new RuntimeException("Adding not inverse of removing link!"); 333 | 334 | // data tests 335 | // byte[] data = "some random textual data".getBytes(); 336 | byte[] data = new byte[1024]; 337 | new Random().nextBytes(data); 338 | MerkleNode patched = ipfs.object.patch(base, "set-data", Optional.of(data), Optional.empty(), Optional.empty()); 339 | byte[] patchedResult = ipfs.object.data(patched.hash); 340 | if (!Arrays.equals(patchedResult, data)) 341 | throw new RuntimeException("object.patch: returned data != stored data!"); 342 | 343 | MerkleNode twicePatched = ipfs.object.patch(patched.hash, "append-data", Optional.of(data), Optional.empty(), Optional.empty()); 344 | byte[] twicePatchedResult = ipfs.object.data(twicePatched.hash); 345 | byte[] twice = new byte[2 * data.length]; 346 | for (int i = 0; i < 2; i++) 347 | System.arraycopy(data, 0, twice, i * data.length, data.length); 348 | if (!Arrays.equals(twicePatchedResult, twice)) 349 | throw new RuntimeException("object.patch: returned data after append != stored data!"); 350 | 351 | } 352 | 353 | @Test 354 | public void refsTest() throws IOException { 355 | List local = ipfs.refs.local(); 356 | for (Multihash ref : local) { 357 | Object refs = ipfs.refs(ref, false); 358 | } 359 | } 360 | 361 | @Test 362 | public void objectTest() throws IOException { 363 | MerkleNode _new = ipfs.object._new(Optional.empty()); 364 | Multihash pointer = Multihash.fromBase58("QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB"); 365 | MerkleNode object = ipfs.object.get(pointer); 366 | List newPointer = ipfs.object.put(Arrays.asList(object.toJSONString().getBytes())); 367 | List newPointer2 = ipfs.object.put("json", Arrays.asList(object.toJSONString().getBytes())); 368 | MerkleNode links = ipfs.object.links(pointer); 369 | byte[] data = ipfs.object.data(pointer); 370 | Map stat = ipfs.object.stat(pointer); 371 | } 372 | 373 | @Test 374 | public void blockTest() throws IOException { 375 | MerkleNode pointer = new MerkleNode("QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB"); 376 | Map stat = ipfs.block.stat(pointer.hash); 377 | byte[] object = ipfs.block.get(pointer.hash); 378 | List newPointer = ipfs.block.put(Arrays.asList("Some random data...".getBytes())); 379 | } 380 | 381 | @Test 382 | public void bulkBlockTest() throws IOException { 383 | CborObject cbor = new CborObject.CborString("G'day IPFS!"); 384 | byte[] raw = cbor.toByteArray(); 385 | List bulkPut = ipfs.block.put(Arrays.asList(raw, raw, raw, raw, raw), Optional.of("cbor")); 386 | List hashes = bulkPut.stream().map(m -> m.hash).collect(Collectors.toList()); 387 | byte[] result = ipfs.block.get(hashes.get(0)); 388 | System.out.println(); 389 | } 390 | 391 | @Ignore // Ignored because ipfs frequently times out internally in the publish call 392 | @Test 393 | public void publish() throws Exception { 394 | // JSON document 395 | String json = "{\"name\":\"blogpost\",\"documents\":[]}"; 396 | 397 | // Add a DAG node to IPFS 398 | MerkleNode merkleNode = ipfs.dag.put("json", json.getBytes()); 399 | Assert.assertEquals("expected to be zdpuAknRh1Kro2r2xBDKiXyTiwA3Nu5XcmvjRPA1VNjH41NF7", "zdpuAknRh1Kro2r2xBDKiXyTiwA3Nu5XcmvjRPA1VNjH41NF7", merkleNode.hash.toString()); 400 | 401 | // Get a DAG node 402 | byte[] res = ipfs.dag.get((Cid) merkleNode.hash); 403 | Assert.assertEquals("Should be equals", JSONParser.parse(json), JSONParser.parse(new String(res))); 404 | 405 | // Publish to IPNS 406 | Map result = ipfs.name.publish(merkleNode.hash); 407 | 408 | // Resolve from IPNS 409 | String resolved = ipfs.name.resolve((String) result.get("Name")); 410 | Assert.assertEquals("Should be equals", resolved, "/ipfs/" + merkleNode.hash.toString()); 411 | } 412 | 413 | @Test 414 | public void pubsubSynchronous() throws Exception { 415 | String topic = "topic" + System.nanoTime(); 416 | List> res = Collections.synchronizedList(new ArrayList<>()); 417 | new Thread(() -> { 418 | try { 419 | ipfs.pubsub.sub(topic, res::add, Throwable::printStackTrace); 420 | } catch (IOException e) { 421 | throw new RuntimeException(e); 422 | } 423 | }).start(); 424 | 425 | int nMessages = 100; 426 | int maxCount = 100; 427 | for (int i = 1; i < nMessages; ) { 428 | ipfs.pubsub.pub(topic, "Hello!"); 429 | if (res.size() >= i) { 430 | i++; 431 | } else { 432 | maxCount--; 433 | } 434 | if (maxCount <= 0) { 435 | break; 436 | } 437 | } 438 | Assert.assertTrue(res.size() > nMessages - 5); // pubsub is not reliable so it loses messages 439 | } 440 | 441 | @Test 442 | public void pubsub() throws Exception { 443 | String topic = "topic" + System.nanoTime(); 444 | Stream> sub = ipfs.pubsub.sub(topic); 445 | String data = "Hello!"; 446 | Object pub = ipfs.pubsub.pub(topic, data); 447 | Object pub2 = ipfs.pubsub.pub(topic, "G'day"); 448 | List results = sub.limit(2).collect(Collectors.toList()); 449 | Assert.assertTrue(!results.get(0).equals(Collections.emptyMap())); 450 | } 451 | 452 | private static String toEscapedHex(byte[] in) throws IOException { 453 | StringBuilder res = new StringBuilder(); 454 | for (byte b : in) { 455 | res.append("\\x"); 456 | res.append(String.format("%02x", b & 0xFF)); 457 | } 458 | return res.toString(); 459 | } 460 | 461 | /** 462 | * Test that merkle links in values of a cbor map are followed during recursive pins 463 | */ 464 | @Test 465 | public void merkleLinkInMap() throws IOException { 466 | Random r = new Random(); 467 | CborObject.CborByteArray target = new CborObject.CborByteArray(("g'day IPFS!").getBytes()); 468 | byte[] rawTarget = target.toByteArray(); 469 | MerkleNode targetRes = ipfs.block.put(Arrays.asList(rawTarget), Optional.of("cbor")).get(0); 470 | 471 | CborObject.CborMerkleLink link = new CborObject.CborMerkleLink(targetRes.hash); 472 | Map m = new TreeMap<>(); 473 | m.put("alink", link); 474 | m.put("arr", new CborObject.CborList(Collections.emptyList())); 475 | CborObject.CborMap source = CborObject.CborMap.build(m); 476 | byte[] rawSource = source.toByteArray(); 477 | MerkleNode sourceRes = ipfs.block.put(Arrays.asList(rawSource), Optional.of("cbor")).get(0); 478 | 479 | CborObject.fromByteArray(rawSource); 480 | 481 | List add = ipfs.pin.add(sourceRes.hash); 482 | ipfs.repo.gc(); 483 | ipfs.repo.gc(); 484 | 485 | List refs = ipfs.refs(sourceRes.hash, true); 486 | Assert.assertTrue("refs returns links", refs.contains(targetRes.hash)); 487 | 488 | byte[] bytes = ipfs.block.get(targetRes.hash); 489 | Assert.assertTrue("same contents after GC", Arrays.equals(bytes, rawTarget)); 490 | // These commands can be used to reproduce this on the command line 491 | String reproCommand1 = "printf \"" + toEscapedHex(rawTarget) + "\" | ipfs block put --format=cbor"; 492 | String reproCommand2 = "printf \"" + toEscapedHex(rawSource) + "\" | ipfs block put --format=cbor"; 493 | System.out.println(); 494 | } 495 | 496 | @Test 497 | public void recursiveRefs() throws IOException { 498 | CborObject.CborByteArray leaf1 = new CborObject.CborByteArray(("G'day IPFS!").getBytes()); 499 | byte[] rawLeaf1 = leaf1.toByteArray(); 500 | MerkleNode leaf1Res = ipfs.block.put(Arrays.asList(rawLeaf1), Optional.of("cbor")).get(0); 501 | 502 | CborObject.CborMerkleLink link = new CborObject.CborMerkleLink(leaf1Res.hash); 503 | Map m = new TreeMap<>(); 504 | m.put("link1", link); 505 | CborObject.CborMap source = CborObject.CborMap.build(m); 506 | MerkleNode sourceRes = ipfs.block.put(Arrays.asList(source.toByteArray()), Optional.of("cbor")).get(0); 507 | 508 | CborObject.CborByteArray leaf2 = new CborObject.CborByteArray(("G'day again, IPFS!").getBytes()); 509 | byte[] rawLeaf2 = leaf2.toByteArray(); 510 | MerkleNode leaf2Res = ipfs.block.put(Arrays.asList(rawLeaf2), Optional.of("cbor")).get(0); 511 | 512 | Map m2 = new TreeMap<>(); 513 | m2.put("link1", new CborObject.CborMerkleLink(sourceRes.hash)); 514 | m2.put("link2", new CborObject.CborMerkleLink(leaf2Res.hash)); 515 | CborObject.CborMap source2 = CborObject.CborMap.build(m2); 516 | MerkleNode rootRes = ipfs.block.put(Arrays.asList(source2.toByteArray()), Optional.of("cbor")).get(0); 517 | 518 | List refs = ipfs.refs(rootRes.hash, false); 519 | boolean correct = refs.contains(sourceRes.hash) && refs.contains(leaf2Res.hash) && refs.size() == 2; 520 | Assert.assertTrue("refs returns links", correct); 521 | 522 | List refsRecurse = ipfs.refs(rootRes.hash, true); 523 | boolean correctRecurse = refs.contains(sourceRes.hash) 524 | && refs.contains(leaf1Res.hash) 525 | && refs.contains(leaf2Res.hash) 526 | && refs.size() == 3; 527 | Assert.assertTrue("refs returns links", correct); 528 | } 529 | 530 | /** 531 | * Test that merkle links as a root object are followed during recursive pins 532 | */ 533 | @Test 534 | public void rootMerkleLink() throws IOException { 535 | Random r = new Random(); 536 | CborObject.CborByteArray target = new CborObject.CborByteArray(("g'day IPFS!" + r.nextInt()).getBytes()); 537 | byte[] rawTarget = target.toByteArray(); 538 | MerkleNode block1 = ipfs.block.put(Arrays.asList(rawTarget), Optional.of("cbor")).get(0); 539 | Multihash block1Hash = block1.hash; 540 | byte[] retrievedObj1 = ipfs.block.get(block1Hash); 541 | Assert.assertTrue("get inverse of put", Arrays.equals(retrievedObj1, rawTarget)); 542 | 543 | CborObject.CborMerkleLink cbor2 = new CborObject.CborMerkleLink(block1.hash); 544 | byte[] obj2 = cbor2.toByteArray(); 545 | MerkleNode block2 = ipfs.block.put(Arrays.asList(obj2), Optional.of("cbor")).get(0); 546 | byte[] retrievedObj2 = ipfs.block.get(block2.hash); 547 | Assert.assertTrue("get inverse of put", Arrays.equals(retrievedObj2, obj2)); 548 | 549 | List add = ipfs.pin.add(block2.hash); 550 | ipfs.repo.gc(); 551 | ipfs.repo.gc(); 552 | 553 | byte[] bytes = ipfs.block.get(block1.hash); 554 | Assert.assertTrue("same contents after GC", Arrays.equals(bytes, rawTarget)); 555 | // These commands can be used to reproduce this on the command line 556 | String reproCommand1 = "printf \"" + toEscapedHex(rawTarget) + "\" | ipfs block put --format=cbor"; 557 | String reproCommand2 = "printf \"" + toEscapedHex(obj2) + "\" | ipfs block put --format=cbor"; 558 | System.out.println(); 559 | } 560 | 561 | /** 562 | * Test that a cbor null is allowed as an object root 563 | */ 564 | @Test 565 | public void rootNull() throws IOException { 566 | CborObject.CborNull cbor = new CborObject.CborNull(); 567 | byte[] obj = cbor.toByteArray(); 568 | MerkleNode block = ipfs.block.put(Arrays.asList(obj), Optional.of("cbor")).get(0); 569 | byte[] retrievedObj = ipfs.block.get(block.hash); 570 | Assert.assertTrue("get inverse of put", Arrays.equals(retrievedObj, obj)); 571 | 572 | List add = ipfs.pin.add(block.hash); 573 | ipfs.repo.gc(); 574 | ipfs.repo.gc(); 575 | 576 | // These commands can be used to reproduce this on the command line 577 | String reproCommand1 = "printf \"" + toEscapedHex(obj) + "\" | ipfs block put --format=cbor"; 578 | System.out.println(); 579 | } 580 | 581 | /** 582 | * Test that merkle links in a cbor list are followed during recursive pins 583 | */ 584 | @Test 585 | public void merkleLinkInList() throws IOException { 586 | Random r = new Random(); 587 | CborObject.CborByteArray target = new CborObject.CborByteArray(("g'day IPFS!" + r.nextInt()).getBytes()); 588 | byte[] rawTarget = target.toByteArray(); 589 | MerkleNode targetRes = ipfs.block.put(Arrays.asList(rawTarget), Optional.of("cbor")).get(0); 590 | 591 | CborObject.CborMerkleLink link = new CborObject.CborMerkleLink(targetRes.hash); 592 | CborObject.CborList source = new CborObject.CborList(Arrays.asList(link)); 593 | byte[] rawSource = source.toByteArray(); 594 | MerkleNode sourceRes = ipfs.block.put(Arrays.asList(rawSource), Optional.of("cbor")).get(0); 595 | 596 | List add = ipfs.pin.add(sourceRes.hash); 597 | ipfs.repo.gc(); 598 | ipfs.repo.gc(); 599 | 600 | byte[] bytes = ipfs.block.get(targetRes.hash); 601 | Assert.assertTrue("same contents after GC", Arrays.equals(bytes, rawTarget)); 602 | // These commands can be used to reproduce this on the command line 603 | String reproCommand1 = "printf \"" + toEscapedHex(rawTarget) + "\" | ipfs block put --format=cbor"; 604 | String reproCommand2 = "printf \"" + toEscapedHex(rawSource) + "\" | ipfs block put --format=cbor"; 605 | } 606 | 607 | @Test 608 | public void fileContentsTest() throws IOException { 609 | ipfs.repo.gc(); 610 | List local = ipfs.refs.local(); 611 | for (Multihash hash : local) { 612 | try { 613 | Map ls = ipfs.file.ls(hash); 614 | return; 615 | } catch (Exception e) { 616 | } // non unixfs files will throw an exception here 617 | } 618 | } 619 | 620 | @Test 621 | @Ignore("name test may hang forever") 622 | public void nameTest() throws IOException { 623 | MerkleNode pointer = new MerkleNode("QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB"); 624 | Map pub = ipfs.name.publish(pointer.hash); 625 | String name = "key" + System.nanoTime(); 626 | Object gen = ipfs.key.gen(name, Optional.of("rsa"), Optional.of("2048")); 627 | Map mykey = ipfs.name.publish(pointer.hash, Optional.of(name)); 628 | String resolved = ipfs.name.resolve((String) pub.get("Name")); 629 | } 630 | 631 | @Test 632 | public void dnsTest() throws IOException { 633 | String domain = "ipfs.io"; 634 | String dns = ipfs.dns(domain, true); 635 | } 636 | 637 | public void mountTest() throws IOException { 638 | Map mount = ipfs.mount(null, null); 639 | } 640 | 641 | @Test 642 | public void dhtTest() throws IOException { 643 | MerkleNode raw = ipfs.block.put("Mathematics is wonderful".getBytes(), Optional.of("raw")); 644 | // Map get = ipfs.dht.get(raw.hash); 645 | // Map put = ipfs.dht.put("somekey", "somevalue"); 646 | List> findprovs = ipfs.dht.findprovs(raw.hash); 647 | List peers = ipfs.swarm.peers(); 648 | Map query = ipfs.dht.query(peers.get(0).id); 649 | Map find = ipfs.dht.findpeer(peers.get(0).id); 650 | } 651 | 652 | @Test 653 | public void localId() throws Exception { 654 | Map id = ipfs.id(); 655 | System.out.println(); 656 | } 657 | 658 | @Test 659 | public void statsTest() throws IOException { 660 | Map stats = ipfs.stats.bw(); 661 | } 662 | 663 | public void resolveTest() throws IOException { 664 | Multihash hash = Multihash.fromBase58("QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy"); 665 | Map res = ipfs.resolve("ipns", hash, false); 666 | } 667 | 668 | @Test 669 | public void swarmTest() throws IOException { 670 | Map> addrs = ipfs.swarm.addrs(); 671 | if (addrs.size() > 0) { 672 | boolean contacted = addrs.entrySet().stream() 673 | .anyMatch(e -> { 674 | Multihash target = e.getKey(); 675 | List nodeAddrs = e.getValue(); 676 | boolean contactable = nodeAddrs.stream() 677 | .anyMatch(addr -> { 678 | try { 679 | MultiAddress peer = new MultiAddress(addr.toString() + "/ipfs/" + target.toBase58()); 680 | Map connect = ipfs.swarm.connect(peer); 681 | Map disconnect = ipfs.swarm.disconnect(peer); 682 | return true; 683 | } catch (Exception ex) { 684 | return false; 685 | } 686 | }); 687 | try { 688 | Map id = ipfs.id(target); 689 | Map ping = ipfs.ping(target); 690 | return contactable; 691 | } catch (Exception ex) { 692 | // not all nodes have to be contactable 693 | return false; 694 | } 695 | }); 696 | if (!contacted) 697 | throw new IllegalStateException("Couldn't contact any node!"); 698 | } 699 | List peers = ipfs.swarm.peers(); 700 | System.out.println(peers); 701 | } 702 | 703 | @Test 704 | public void bootstrapTest() throws IOException { 705 | List bootstrap = ipfs.bootstrap.list(); 706 | System.out.println(bootstrap); 707 | List rm = ipfs.bootstrap.rm(bootstrap.get(0), false); 708 | List add = ipfs.bootstrap.add(bootstrap.get(0)); 709 | System.out.println(); 710 | } 711 | 712 | @Test 713 | public void diagTest() throws IOException { 714 | Map config = ipfs.config.show(); 715 | Object mdns = ipfs.config.get("Discovery.MDNS.Interval"); 716 | Object val = ipfs.config.get("Datastore.GCPeriod"); 717 | Map setResult = ipfs.config.set("Datastore.GCPeriod", val); 718 | ipfs.config.replace(new NamedStreamable.ByteArrayWrapper(JSONParser.toString(config).getBytes())); 719 | // Object log = ipfs.log(); 720 | String sys = ipfs.diag.sys(); 721 | String cmds = ipfs.diag.cmds(); 722 | } 723 | 724 | @Test 725 | public void toolsTest() throws IOException { 726 | String version = ipfs.version(); 727 | int major = Integer.parseInt(version.split("\\.")[0]); 728 | int minor = Integer.parseInt(version.split("\\.")[1]); 729 | assertTrue(major >= 0 && minor >= 4); // Requires at least 0.4.0 730 | Map commands = ipfs.commands(); 731 | } 732 | 733 | @Test(expected = RuntimeException.class) 734 | public void testTimeoutFail() throws IOException { 735 | IPFS ipfs = new IPFS(new MultiAddress("/ip4/127.0.0.1/tcp/5001")).timeout(1000); 736 | ipfs.cat(Multihash.fromBase58("QmYpbSXyiCTYCbyMpzrQNix72nBYB8WRv6i39JqRc8C1ry")); 737 | } 738 | 739 | @Test 740 | public void testTimeoutOK() throws IOException { 741 | IPFS ipfs = new IPFS(new MultiAddress("/ip4/127.0.0.1/tcp/5001")).timeout(1000); 742 | ipfs.cat(Multihash.fromBase58("Qmaisz6NMhDB51cCvNWa1GMS7LU1pAxdF4Ld6Ft9kZEP2a")); 743 | } 744 | 745 | // this api is disabled until deployment over IPFS is enabled 746 | public void updateTest() throws IOException { 747 | Object check = ipfs.update.check(); 748 | Object update = ipfs.update(); 749 | } 750 | 751 | private byte[] randomBytes(int len) { 752 | byte[] res = new byte[len]; 753 | r.nextBytes(res); 754 | return res; 755 | } 756 | } 757 | -------------------------------------------------------------------------------- /src/test/java/io/ipfs/api/AddTest.java: -------------------------------------------------------------------------------- 1 | package io.ipfs.api; 2 | 3 | import io.ipfs.multibase.*; 4 | import org.junit.*; 5 | 6 | import java.io.*; 7 | import java.net.*; 8 | 9 | public class AddTest { 10 | 11 | @Test 12 | public void add() throws IOException, URISyntaxException { 13 | String boundary = "At7ncPkda6xyWozoimjCd6aRySM13bEH"; 14 | byte[] multipartBody = Base16.decode("2d2d4174376e63506b6461367879576f7a6f696d6a436436615279534d31336245480d0a436f6e74656e742d446973706f736974696f6e3a2066696c653b2066696c656e616d653d2268746d6c220d0a436f6e74656e742d547970653a206170706c69636174696f6e2f782d6469726563746f72790d0a436f6e74656e742d5472616e736665722d456e636f64696e673a2062696e6172790d0a0d0a0d0a2d2d4174376e63506b6461367879576f7a6f696d6a436436615279534d31336245480d0a436f6e74656e742d446973706f736974696f6e3a2066696c653b2066696c656e616d653d2268746d6c25324663686170220d0a436f6e74656e742d547970653a206170706c69636174696f6e2f782d6469726563746f72790d0a436f6e74656e742d5472616e736665722d456e636f64696e673a2062696e6172790d0a0d0a0d0a2d2d4174376e63506b6461367879576f7a6f696d6a436436615279534d31336245480d0a436f6e74656e742d446973706f736974696f6e3a2066696c653b2066696c656e616d653d2268746d6c25324663686170253246636830312e68746d6c223b0d0a436f6e74656e742d547970653a206170706c69636174696f6e2f6f637465742d73747265616d0d0a436f6e74656e742d5472616e736665722d456e636f64696e673a2062696e6172790d0a0d0a3c21444f43545950452068746d6c3e0a3c68746d6c206c616e673d22656e223e0a3c686561643e0a202020203c7469746c653e495046533c2f7469746c653e0a202020203c6d65746120636861727365743d227574662d3822202f3e0a202020203c6c696e6b2072656c3d227374796c6573686565742220687265663d222e2f2e2e2f6373732f64656661756c742e637373223e0a3c2f686561643e0a3c626f64793e0a3c703e3c696d67207372633d222e2e2f696d672f6c6f676f2e706e672220616c743d226c6f676f22202f3e203c6120687265663d222e2e2f524541444d452e68746d6c223e486f6d653c2f613e3c2f703e0a3c68323e436861707465722030313c2f68323e0a3c703e5961646120796164613c2f703e0a3c2f626f64793e0a3c2f68746d6c3e0a0d0a2d2d4174376e63506b6461367879576f7a6f696d6a436436615279534d31336245480d0a436f6e74656e742d446973706f736974696f6e3a2066696c653b2066696c656e616d653d2268746d6c253246696d67220d0a436f6e74656e742d547970653a206170706c69636174696f6e2f782d6469726563746f72790d0a436f6e74656e742d5472616e736665722d456e636f64696e673a2062696e6172790d0a0d0a0d0a2d2d4174376e63506b6461367879576f7a6f696d6a436436615279534d31336245480d0a436f6e74656e742d446973706f736974696f6e3a2066696c653b2066696c656e616d653d2268746d6c253246696d672532466c6f676f2e706e67223b0d0a436f6e74656e742d547970653a206170706c69636174696f6e2f6f637465742d73747265616d0d0a436f6e74656e742d5472616e736665722d456e636f64696e673a2062696e6172790d0a0d0a89504e470d0a1a0a0000000d4948445200000090000000900806000000e746e2b8000000017352474200aece1ce9000000097048597300000b1300000b1301009a9c180000015969545874584d4c3a636f6d2e61646f62652e786d7000000000003c783a786d706d65746120786d6c6e733a783d2261646f62653a6e733a6d6574612f2220783a786d70746b3d22584d5020436f726520352e342e30223e0a2020203c7264663a52444620786d6c6e733a7264663d22687474703a2f2f7777772e77332e6f72672f313939392f30322f32322d7264662d73796e7461782d6e7323223e0a2020202020203c7264663a4465736372697074696f6e207264663a61626f75743d22220a202020202020202020202020786d6c6e733a746966663d22687474703a2f2f6e732e61646f62652e636f6d2f746966662f312e302f223e0a2020202020202020203c746966663a4f7269656e746174696f6e3e313c2f746966663a4f7269656e746174696f6e3e0a2020202020203c2f7264663a4465736372697074696f6e3e0a2020203c2f7264663a5244463e0a3c2f783a786d706d6574613e0a4cc227590000187b494441547801ed5d0b701dd5793ebb7b1f7a5b96e4277e90f018c084ce14525a024190804d93143253b96d42da8921a584694348681a62888c259b8c01134f6b4220b8cc94342052d292740a8c53f3340d246d99620a8618087e5b92653daeeebdfbe8f79dbb475a5fddab7baf2cd952f9ff994fbb77f79cb367bff3ed7ffe73f621a5c484016140181006840161401810068401614018100684016140181006840161401810068401614018100684016140181006840161401810068401614018100684016140181006840161401810068401614018100684016140181006840161401810068401614018100684016140181006840161401810068401614018100684016140181006840161401810068401614018100684016140181006840161401828c9401058adeded318565c9b4924018883060b53dfaa813f9adc2df22a42829b23e9681a8702edcbc79f6c51b367cb175e3c6469332badf6c93e5cc60604aaffef6f6769b3460e97379c986bb3fa702fff6784ded29eed0e04ecbb26fdb7af34d3f0ad31c9596dbc4a63f03532420c6396b9c6deded2e2968ddb0b1d50afcd54e3cfe8900bf7dd7cddab1589cfbbc6cf6693be174fcfcc61b9fe5ef56c447c8e7619549c5a63903932e2076475d2b575200ea931b377ed87783359eeb7e3e565d6df9e9b4ef7baeb26cc70e3ccf8788949d4cda6e2a8575e71f3ce5b73f7bf3cdbb98375a0e7f8b4d4f06264d406d6d6d4e575717bbaaa0adbd3dd15dd77053e0fadfb413f1068847d52d5ce841344eaaa747f9998cb2130955ddd4ac2cc7f2fa77ef71bce1616eef0b9cf8fafe86eabb7f79dd75598ed4da56aeb451ae16e4f4a4f0835dab631710e29cb6d7ceb2baba725ee7d2bbbffbd920ebae7512896556cc51b5f3e6b9c9d9b31d1c68e45841e02bc43f23cc07b0e1c3bdded0befd31dff3959749ff8f72d4addbbefef59f30515b1bbcdab21d810a63a9918cb272c2191869d409d484f3392371ce251bee395ff9feadca569f8278e05d9adcaa961607718f05cf83e24d48630e19fee6c2b2e0891c08271ba4ba0f79c33d3d312f9d5681653d613baae3e737ddf40bd60fc793f888444c2333ad595195a2f149eba64d8bacb4b71ab1cdaa5832194f3634f83573e628745b361c0b2266f46a104849635adb46524b31261a3a7040a5fbfb6d379dced84eec079613744248bb594ef4f825cb950453ca40192d3b7afc767457edfc89ae84ebff5ed7f015e5f9ab63f17893934cea38275157e72076c168bd4ce18c169f5b435e0b42a2650606bc81bd7b1d7a232f9be9568eb376dbd7beba092a6377a6eb827ae829825c66f97bbc19284f408c73ce429c138eae2eb9f39ee581ef75a07b3acf46d7533d779e5bdd84380786ed3887f28a1dff6429246a310810787b43fbf7c702c4479801f88513b36e85377a8af9b537da21f1d1f85c4eddde522d7d749c73d7a6df0a7c77b5e5fb7fe85455a9aac646af7ace1c1b314f2ece6137544e7755eef984e5313ef2d3197ff0e08120ddd7a7476ccab11fb503bb73ebcd37becae25a73f1919e772ab7784977ec0c14155034ceb862d3a639c369ef6f02cfbd1e5d5575bcae2ea89b37cf479ce35414e74cb4be1452181f655343dee0befd767670d0427c947262ce665ff97760c47688c547eb3dd1c349bef21938eae6a6ce86b997768cb137df70838e2d5a37dc759d9bf51e7162b1cbe2d5d5f1fa458bbcdab97311d7c66c1de730d3647a9d427537e563f80f6f67271b1b2d88d7f3329984f2830b30dff4850f2f5f7e64d7d34ffd6a4757978e8fd4b66d854a2ab68d41172f260328b6a0e5a733e90b2d5900b797634c5749d9f9c72b565f736c966d60f2725ff49866dde4296b79f409f2110b06a8b04befbce7e398365e8791d5c7f81b5d955bd3dcec20c0459c73e2e35606dabeef07a94387bcd4c183318ad8cda49fc729dcf2cc5fdff41ceb0ce3f9952257279cc23fa6614ec464288f4dabb4c1e858c85bc97ca3020ac573d5962d8d7ddd3d7721eb2acee7e02af79acf3cd38ad7d6da9c41d6663c42eed789f9cb6e0dc619edecc080dffdbfaf074e22e9e03e9bc264e40fdcde83373f7fc71dbdfab9a3f0a218a7a2a7625f0b403ed815ee04f22d860de700bc87574a9469a4e906f601262e63d944a1463905dbe7866947db041b4a18d35298af01a9bcb41481112dcbff6de06c60095003f07cb87f08e0f4c88e102c2b6c68ac953016a2adadabcbee428199b4774e5563d3aa4c7f3f7fb8a02ac6db0f0c6429a8d149c14aced31c65929691e0dac52d10d40f579ac51bb32e048fd19a7b8d6727fe1147dbaa6f858c12995f019e04c5b006f85cb8f3612caf0678f5b2b14d9a7aac3f01cc07c62398e571ff11e000c049d01f035b01ee8b36ac29fb366cff53802230c7c5eab8c6b212c05ee0626017c0bcdc4ea338ce03be015c023403a58c62a290fe197808f80d60ea88d5b13622a0653bda0208480531c57b569e0e909309c74d0dab81f7df57981de63c0f465eb3f53ccd89ecc628661e3f75f0901ad8b757719e88961d1cc29c514afb262b1133579fde57e20fbd8a3136422123916c30eeaf2a9420b2ad1aebb380c5c0b9c0f5c04f81eb803d405444f8a9bd1a9749a0d8f1b9bf9851ac34231e2ebf026c0458ef7ce37e22ff58f44c141dc10b8a75a7a88b8a68444048a42df0e0ef031fa3abdcc517abaad69e07b3c3eaf05b6fabaad98daa76c10285094354e118260ccd01cb5de258b909468b33d46a70ef5e953902ded09d524099817ece1961b066630890cf4bc98318e29930ba9e9f91a41863f7d40b500cd13c249bbc3601e6a13976639f069e052e017865b392269f59322fbbbed701963b9eb12e14eaab405f989065f2c25905dc136e338bed58791a60f7bc1fe055c70b611eb014a058884500ebc172291e96193d6ffc1cb53102e22e5eddc959b354cddcb90a13786ab8af4f2106426ce129ccc3a8341a8eb72bb89f020b02d4d9070753111bd19f70080f50c483b8c531dcdd833ae2e10f805dad9fcd42d8b3555573b31a3ed48d6d10967616a3275ac11ac92b66d17d1d48f47700afdaa8b7a318c82b1be542e0af00c61ec3c029c066e03300d3b13c2e8df1f70bc09580116674bf496796263fd318f12cc47aa74980650f400ff868645bb1558a9e75be1678304c143de770d3e862ac80b0257003857b5a0a733daa0a424a7577abfe3d7b5480064c603be20c354861f5f6f26ebbaa6e6951763c3ec9f1518e5f2b16d302193c78500d017e3603f1042a3b3408410da9784d9daa3f6991f688ac833b30088143f063cf6cf4ac27678d5e854661e45fa18c81e845881f014f001703141a3dd145c073001b3ddf4c834545999fc6fc8e8acbe4bb0a3b19a7b17e6481dde66300f71baf56281f8f47b1fd4b082cb48d5b8fa234d30b7144c32b9f22e1153eb06f9f1a04184c13f40847de7b8f41ac8e8fe8b57833940d7c6c36721b03dea65be17e9862b0cc8b358b63621251e101341c7391ae176fa7e8fac243eaae975499e63db68a8c97db34bef114f969590bf2db0fdc00fc1760f25c867523a0620dc4b4e51069d298e5f9c847e3b1ff1b30e2e1b652acb0ce3c2e2f08531e568b5b5101e9ee885d12e31c340cafee594b972acc05a9230caae17d6235351052527b82de9d3b55555393aa9b3f5f77773a1fe78b2ae9d64c778567857023558b35dd7718b5479c03af970de31c3c2aa26a5ae628dec0a570f4c81055c524964e5bdea91727a5cc3d86602ecd7a342bb7d113b1521c1aff12308dbb04ebb442f9727b72fbc6db6fd271c963182f3827b283c7a545f7e7b614fecbe3151374c11cc505144d6e84048fc458a8f9f4d355aab747e14942ddd0b8b5a1bd551aa2ca2046626cc41889f7cb74e35218512115f9cd07d0dc541a5dd5018509429d97710ee679b48012f50dbadc91001ec21e117ab4bed3739da32f6375e18a6974b37d3296a69b6259669d029a122b4f40e6d01041ee6ab75475730be2a4593a2e6117c311908e8f3022e26f0efb6be08de8b172c36e0a1b3e82232423487a286e85707cd753437bf76bf1d0dbd0b330c6c90e0da958b24a352c5e82c0be01f973dd552ee394f1a28b9fe43f1ca21b4b852b937502f41ca6ebe128ced80558a907d88d72aa825d58b95e0d494b5b65026279a12709c2f8a80e437a765d140d476cec56383bec313e7af75d1dc3d472fe08f11145c1f885a326a6c15c138ab3d01d1ed6c3728a85e60e23ce81d7e1e8ab76de7c5d3e1e1dc9755793f6b8883ed4f1f8c306a32730dd168fc96095564a40dc5f6e839bb25e429e3f01d87d2e06389cbf06c80234a6a3d8682cdb406fa8f44fe5023247304242371283181a4f3e195ea959f5233e4a1f3eac62e8ea6c888c416fef9b6fe25e5a8b16401a62d186fcc94606ddb6f656dc4661713e87c17b7216e69b384d0091d1bb8d3e676478ca15338dffb2a2e4970d77167006c0c6e276cef61633738266592c5d743bcb35dd21477d5f0328588a681570267027f03c7000c88f7328281e8f65b0acb26de20232878010cc231dc9fa7a9538e30c3dec1fd8bd5b65e05118af70e8cd59631a5fe5314631e5c401cf8461b907cf83970e73c3724c17e8119d89734a5eaca6d4e3b63457f1e809e50e6dae683686b9ea3bb0ce74dc07d7aab601b4428d65464a4610b994a5ffb22c7a3a0a8443f7c7812a8075f83de0c7c03e80c13c47849c58e434c33b40f4582ca36c21e59f3cf24ed028a4b0b119407348cfe79a39f467dc63c7d1ad0da7e15d703e484b71f8f02c8c73381dc0f91e3d2c6f6cd422d3a3abfc607b82559ba26c8361b99c5f2864e4f6a3c06dc00a8033bf8c831e01de0028c068c3e1a7b673f1f7ef01d3368544c684dc5e033c00fc1bc0f2e859b8e4ef4b01deca30233faceaf9a14f6149d00e023b8157806dc05680b3b0b462f5cbed0dff9a4a1eb571c23f200a1ae3237a9afa934ee2db19aa1b5d18dffb8ad5d6686fc4fb6b1ca6bbe91444e7ebd9ec86c58bf386e59578f009d7f858327e1e994f05280a7a0d23866aac2f04ce06d875f08aa617603a5ef1b700c58ce5306ef9b362090a6c7f19db28184318ebc1c6df0e7c0cf82c70357031d008448d437ee2028033e6bb80bf05ee064c39e6bcb069ac4dae804cf914928e5b200ec43078104d8fa638e1e7e0d6078365de6ed077f771213955c99c784c77150ad114370d97249593814439c611d053c02a805d8cb9ba4da36393366ea7d8e8ddf2f7e904913ff436b3008eb0f2cd343ed33c16a205cbdf0528968b008abb1930c63c1f02ee02ae005602bd80a92b56c7dad408287a1c8881f34401e676d84d515834339ce7ac350536c38c8dcb86e6b21887ecb2de07fe13f821f03840a34762c3163236d633c07500e3179295230c2b058c65ed0eb7e77b0afe66fd5826cb6010fad31058e89ba6e761c92eee1300bb5b1aebfd49e011603960ca29588f62278f7c9360100f6319c6449c18e48d4f4e2e1ae3a88b13931c71311d63a31962ac28af5492bc14a8036a016e4f01dd003dcd3b00e30c636ccc62e231691883ec343f2a58166a606e33c73362e2925d25c54dfc04a087bc1c60d7753a300cd0bb52c8df038ad67b6a058423d3e3e0f969d584d9ebc3bb76e9d8873cf39e5512cf16d5cd5fa0271a8d67629619626fa19e1ccd10e39969383664be9728948f5e85c6652151e89d913f4c536ebaa89858048541a337fd19c07b672f028b01da350005c47c3c8f31c7310560dfd419bd4b1cf7cd1a162d8a1cc482679aab631ffdf66a64cf0c594d86f5e4928d9d0f726b48370d17661977611a89622b0726fdb885e6ed641e82f532e248609d1ee95ec018bdd142f3a3d0f2b8088807d6c3f2fc6e8af1103173baae2887c69b9846c85f723f1b6926981113ebca79226314d5fcf0072f863176dc0434e6c8b261ba3160c49e89548ca2a1672d6affcf046438287abe1f941da6fbace47c8d87991bc9c4d888a3b7a2565c40a67b299a75faed18918fa162fa55f178d5c8749ff41ec5dbf8e8da9874bf13d9cc91e49ef0f708bd91fd450a4752ced3e8b99a68eae9bc0ec19bb9251d794cfdf872bab2c1799233c3ca312e33b11ac544564cb04fc1986d1cc6d3db2c06be0018e3e427e78598ae4c016186c07270871c77d4793394cff2cc842136c5c34740f8a606d77137e58366c6832cc3896f07fe1558057c08a0514c64854b82c232db289ed3807f02d885512cdc6f466405c583fd856751d9006c88eed75f572db8bb1ee39385ecd2a6a9f1c62c1f1be97b67971eed69d16bae2654e1f14e74bc7d133ad82466321df7d52893b738ae08d183e50ee015e06d8043754e74d2b3f066ec12e042a00de0bd328a8a1ee79bc0abe13ab715b4318e1e5f020bf056868767a06d36ca91f777aba6534f299879ba6ca4b8f9182c5f3bc24d5c3c3c54713b979ba1dc749552132d37ba5e4939a6abe2cc373d0abb255a13408110c62808a6a7508ce732fbb86d3df01d80a22c2a1eec1bcdfcda595d5ac1f80aaf83a70a29235cc878d113afc8b061a6d626ca592e1fe798f848086ed6f2b50c0bf5b72c1fcfbe966f6652903938f751c8c84f7564c7988b2fb2afd2d5728e5faa4c43e23a243c1bb80d7801a0b7c93772438145c5438ff42c7025700b50968d90d0d5d6a6155c93745e4d0da71f50b6752dee5bf163971ebec363e161319b4f0aeaab7bd227fe8cf72dabceb944f43261a0ef0e0efa781629c063b2313e4a8277e4b7c41aea7ec584e1a7878b156c48ef4482870012fa5e98d8ec334bdea3fa238002236fba7c2c8fe5ea3265df81721e0648c4b8a31eec2f653c873781b54007701a707ab85c8a253d1205cb74f4541498e9e25ec63a8dfb5837533f6e2b6847b75cf8850ea65cbef9fe8b504c274474117fe3f152176f5b387c7558dff8d4651f9d9de90a1a1a9b7109df6aedc6eb3f7c7b63e8c07e35fbd4d3f42d8ed1c7550be62eb891711a3e60ae3fef92e93b1ce39d7e7c47f105dc65fbd6f6f5eb9f0933b182254928708072f3959baec021a6741305c0ba4d44dcf44e65e7e381468d9f418188daf14dc427bffca5e79efc8b2f7d3c934afd39ba873d78683ed6bde3750b0fc07bbcf5c0b72326106b8c1e4baf55deb6140e0defa5797dbf7edbca1ce98be1d59f7d9e97bdfea5f5eb2ed4e241fd91a492c6657a124770bd58c54c1a2e2b291fc9c7b5fce38f9bb88c9dec4d2802d631bf6cfe26cc3eb3df6c2b5b3c284317c2e5188b7e2aee33f7ddd792b5e2dff0ddcc979d64554da2be3ea85fb0c0c7a318fc0a66e947318a7aa053f533d0253d10f343386c5574571e5e73b6f1e9190bdd550a9ee77bf812c71d2fb6b773d20b1f25d75fccaf888431272f1bca66802a1cd75a231faf5c71fffde7e03b0adf4280bd9243fbaad94d5edd82f916decab075a08d862e786374a20262798c73e0f1f06a333f42eee351587c911c17986d3f8648b9f3c575ebf4e314613d299c62de63dcf3949d1363a0a48074b1799ff9fdfdfbb75c862e632d1e943f9f8fa9d6ce9fefe2a1b1e29ff99da080e875f0017308a7c74bf7f6e8cfd8c15bbd82471b576f5f77fb93ac9bf638cb96c9bf419858fb1f73aef204141e86b1513bd7f9716fc44a2b1ed8f297e8466ec5e8a705df52e4eb385eb2a19ef1c1d1dd5a8502d2dd1586e67882d143b0edf0b15708a907130c1d2b12b1efa21efaf8ed6bd6305ed3a3471e53ecf8335091804cf5a2f1d1a7ef7de824d7716fc110ff5accbf24aa1a66f9750b17f0c30bb9afb8f2192058e1515818039979267657488b87eefdc14307f92607a70eb2f824ec83ae1d74bedcd9f91b96153d3e7f8b9d38062624a0b0ba566be49fadacf8fe968fe24353abb1ef0fe88df096aa1ef6233eb2387f545040a75040357066b92019f34de8aebabd6cff118cacf01f7d94f533df8977fc4767fb4b3c268e17db26ff8c2ea47f7a2c8e4540fa0cd085d8af45fe0dc2f2fb1fbc12ddda5a88e823140dde9d77212607b7182cdea0ed79e30dfd6918ce03359d76ba7ea81e137f013ec6a0ff4b0f2630f96af30ecb419cd379bb7e9341e29ce9219642b5386601994275b7c2d96ccc259d7bdf7df11615ff6ae0676fc1f7836671c4366bc9122f3338e8f4e1830b7c777e68ff3e55bf882f1326f43f54d18fbcba5e3fbed4b13eb374f19dfa1fcea1e78378e41fce1992a7e172d20464ce2d1a9facd8b2e564fcf3b93541d6bb1a2f14e23bd3699f5e099380363c905e8767b2f17a0f6e60c51f8e55c5befd7c7bfbaf5956b41c53b62ca71f03932e207d8a18a1b5ae19fda7bb973ff0c0c72d37588d3bfc9771848577e6b3c33ddd717ee205ddd556e5581d2f75746c635e8973348333e6cfd408283c7dc6475cc5520fc52ebff7fb7fecc463b7e3d6c8697defbcfb16466ddfdebe6eed0f75f2302d12cbb03ce44f162103ec8e0c19576d79bcf1d2ef6cf8e247aebf7eb6d9c620d9accb521828c6801515121385bfa7d40b16ab8c6c9fa90c303ec27c0eaa2fc299a96d28f5160684016140181006840161401810068401614018100684016140181006840161401810068401614018100684016140181006840161401810068401614018100684016140181006840161401810068401614018100684016140181006840161401810068401614018100684016140181006840161401810068401614018100684016140181006840161401810068401614018100684016140181006848199cdc0ff015ad992bcc9ca2eb60000000049454e44ae4260820d0a2d2d4174376e63506b6461367879576f7a6f696d6a436436615279534d31336245480d0a436f6e74656e742d446973706f736974696f6e3a2066696c653b2066696c656e616d653d2268746d6c253246637373220d0a436f6e74656e742d547970653a206170706c69636174696f6e2f782d6469726563746f72790d0a436f6e74656e742d5472616e736665722d456e636f64696e673a2062696e6172790d0a0d0a0d0a2d2d4174376e63506b6461367879576f7a6f696d6a436436615279534d31336245480d0a436f6e74656e742d446973706f736974696f6e3a2066696c653b2066696c656e616d653d2268746d6c25324663737325324664656661756c742e637373223b0d0a436f6e74656e742d547970653a206170706c69636174696f6e2f6f637465742d73747265616d0d0a436f6e74656e742d5472616e736665722d456e636f64696e673a2062696e6172790d0a0d0a626f6479207b0a20202020666f6e742d66616d696c793a202256657264616e61223b0a20202020636f6c6f723a20233133376362390a7d0a0a61207b0a2020202023746578742d6465636f726174696f6e3a206e6f6e653b0a20202020636f6c6f723a20233133376362390a7d0a0a612e67726179207b0a20202020636f6c6f723a20677261793b0a7d0a0a6831207b0a09666f6e742d7765696768743a206e6f726d616c3b0a20202020666f6e742d73697a653a20323070783b0a7d0a0a6832207b0a20202020666f6e742d7765696768743a206e6f726d616c3b0a09666f6e742d73697a653a20313570783b0a7d0a0a7468207b0a09746578742d616c69676e3a206c6566743b0a20202020666f6e742d7765696768743a206e6f726d616c3b0a20202020666f6e742d73697a653a20313470783b0a20202020636f6c6f723a20677261793b0a7d0a0a74642e67726179207b0a20202020636f6c6f723a20677261793b0a7d0a74722e67726179207b0a20202020636f6c6f723a20677261793b0a7d0a0a0d0a2d2d4174376e63506b6461367879576f7a6f696d6a436436615279534d31336245480d0a436f6e74656e742d446973706f736974696f6e3a2066696c653b2066696c656e616d653d2268746d6c253246696e6465782e68746d6c223b0d0a436f6e74656e742d547970653a206170706c69636174696f6e2f6f637465742d73747265616d0d0a436f6e74656e742d5472616e736665722d456e636f64696e673a2062696e6172790d0a0d0a3c21444f43545950452068746d6c3e0a3c68746d6c206c616e673d22656e223e0a3c686561643e0a202020203c7469746c653e495046533c2f7469746c653e0a202020203c6d65746120636861727365743d227574662d3822202f3e0a202020203c6c696e6b2072656c3d227374796c6573686565742220687265663d222e2f6373732f64656661756c742e637373223e0a3c2f686561643e0a3c626f64793e0a3c703e3c696d67207372633d22696d672f6c6f676f2e706e672220616c743d226c6f676f22202f3e203c6120687265663d22524541444d452e68746d6c223e486f6d653c2f613e3c2f703e0a3c703e3c6120687265663d22636861702f636830312e68746d6c223e63686170746572206f6e653c2f613e3c2f703e0a3c2f626f64793e0a3c2f68746d6c3e0a0d0a2d2d4174376e63506b6461367879576f7a6f696d6a436436615279534d31336245482d2d0d0a"); 15 | HttpURLConnection httpConn = (HttpURLConnection) new URL("http://localhost:5001/api/v0/add?stream-channels=true&w=false&n=true").openConnection(); 16 | // httpConn.setUseCaches(false); 17 | httpConn.setDoOutput(true); 18 | httpConn.setDoInput(true); 19 | httpConn.setRequestProperty("User-Agent", "Java IPFS Client"); 20 | httpConn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary); 21 | httpConn.getOutputStream().write(multipartBody); 22 | httpConn.getOutputStream().flush(); 23 | httpConn.getOutputStream().close(); 24 | 25 | int status = httpConn.getResponseCode(); 26 | StringBuilder b = new StringBuilder(); 27 | if (status == HttpURLConnection.HTTP_OK) { 28 | BufferedReader reader = new BufferedReader(new InputStreamReader(httpConn.getInputStream())); 29 | String line; 30 | while ((line = reader.readLine()) != null) { 31 | b.append(line); 32 | } 33 | reader.close(); 34 | httpConn.disconnect(); 35 | } 36 | if (b.toString().contains("rror")) 37 | throw new IllegalStateException("Error returned from IPFS: " + b.toString()); 38 | System.out.println(b.toString()); 39 | } 40 | 41 | public static void main(String[] a) throws Exception { 42 | new AddTest().add(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/test/java/io/ipfs/api/RecursiveAddTest.java: -------------------------------------------------------------------------------- 1 | package io.ipfs.api; 2 | 3 | import java.nio.file.*; 4 | import java.util.*; 5 | 6 | import org.junit.Assert; 7 | import org.junit.Test; 8 | 9 | import io.ipfs.multiaddr.MultiAddress; 10 | 11 | public class RecursiveAddTest { 12 | 13 | private final IPFS ipfs = new IPFS(new MultiAddress("/ip4/127.0.0.1/tcp/5001")); 14 | 15 | static Path TMPDATA = Paths.get("target/tmpdata"); 16 | 17 | @Test 18 | public void testAdd() throws Exception { 19 | System.out.println("ipfs version: " + ipfs.version()); 20 | 21 | String EXPECTED = "QmX5fZ6aUxNTAS7ZfYc8f4wPoMx6LctuNbMjuJZ9EmUSr6"; 22 | 23 | Path base = Files.createTempDirectory("test"); 24 | Files.write(base.resolve("index.html"), "".getBytes()); 25 | Path js = base.resolve("js"); 26 | js.toFile().mkdirs(); 27 | Files.write(js.resolve("func.js"), "function() {console.log('Hey');}".getBytes()); 28 | 29 | List add = ipfs.add(new NamedStreamable.FileWrapper(base.toFile())); 30 | MerkleNode node = add.get(add.size() - 1); 31 | Assert.assertEquals(EXPECTED, node.hash.toBase58()); 32 | } 33 | 34 | @Test 35 | public void binaryRecursiveAdd() throws Exception { 36 | String EXPECTED = "Qmd1dTx4Z1PHxSHDR9jYoyLJTrYsAau7zLPE3kqo14s84d"; 37 | 38 | Path base = TMPDATA.resolve("bindata"); 39 | base.toFile().mkdirs(); 40 | byte[] bindata = new byte[1024*1024]; 41 | new Random(28).nextBytes(bindata); 42 | Files.write(base.resolve("data.bin"), bindata); 43 | Path js = base.resolve("js"); 44 | js.toFile().mkdirs(); 45 | Files.write(js.resolve("func.js"), "function() {console.log('Hey');}".getBytes()); 46 | 47 | List add = ipfs.add(new NamedStreamable.FileWrapper(base.toFile())); 48 | MerkleNode node = add.get(add.size() - 1); 49 | Assert.assertEquals(EXPECTED, node.hash.toBase58()); 50 | } 51 | 52 | @Test 53 | public void largeBinaryRecursiveAdd() throws Exception { 54 | String EXPECTED = "QmZdfdj7nfxE68fBPUWAGrffGL3sYGx1MDEozMg73uD2wj"; 55 | 56 | Path base = TMPDATA.resolve("largebindata"); 57 | base.toFile().mkdirs(); 58 | byte[] bindata = new byte[100 * 1024*1024]; 59 | new Random(28).nextBytes(bindata); 60 | Files.write(base.resolve("data.bin"), bindata); 61 | new Random(496).nextBytes(bindata); 62 | Files.write(base.resolve("data2.bin"), bindata); 63 | Path js = base.resolve("js"); 64 | js.toFile().mkdirs(); 65 | Files.write(js.resolve("func.js"), "function() {console.log('Hey');}".getBytes()); 66 | 67 | List add = ipfs.add(new NamedStreamable.FileWrapper(base.toFile())); 68 | MerkleNode node = add.get(add.size() - 1); 69 | Assert.assertEquals(EXPECTED, node.hash.toBase58()); 70 | } 71 | 72 | @Test 73 | public void largeBinaryInSubdirRecursiveAdd() throws Exception { 74 | String EXPECTED = "QmUYuMwCpgaxJhNxRA5Pmje8EfpEgU3eQSB9t3VngbxYJk"; 75 | 76 | Path base = TMPDATA.resolve("largebininsubdirdata"); 77 | base.toFile().mkdirs(); 78 | Path bindir = base.resolve("moredata"); 79 | bindir.toFile().mkdirs(); 80 | byte[] bindata = new byte[100 * 1024*1024]; 81 | new Random(28).nextBytes(bindata); 82 | Files.write(bindir.resolve("data.bin"), bindata); 83 | new Random(496).nextBytes(bindata); 84 | Files.write(bindir.resolve("data2.bin"), bindata); 85 | 86 | Path js = base.resolve("js"); 87 | js.toFile().mkdirs(); 88 | Files.write(js.resolve("func.js"), "function() {console.log('Hey');}".getBytes()); 89 | 90 | List add = ipfs.add(new NamedStreamable.FileWrapper(base.toFile())); 91 | MerkleNode node = add.get(add.size() - 1); 92 | Assert.assertEquals(EXPECTED, node.hash.toBase58()); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/test/java/io/ipfs/api/SimpleAddTest.java: -------------------------------------------------------------------------------- 1 | package io.ipfs.api; 2 | 3 | import java.nio.file.Path; 4 | import java.nio.file.Paths; 5 | import java.util.LinkedHashMap; 6 | import java.util.List; 7 | import java.util.Map; 8 | 9 | import org.junit.Assert; 10 | import org.junit.Ignore; 11 | import org.junit.Test; 12 | 13 | import io.ipfs.api.NamedStreamable.FileWrapper; 14 | import io.ipfs.multiaddr.MultiAddress; 15 | 16 | /** 17 | * 18 | * ipfs daemon --enable-pubsub-experiment & 19 | * 20 | * ipfs pin rm `ipfs pin ls -qt recursive` 21 | * 22 | * ipfs --api=/ip4/127.0.0.1/tcp/5001 add -r src/test/resources/html 23 | * 24 | */ 25 | public class SimpleAddTest { 26 | 27 | static final Map cids = new LinkedHashMap<>(); 28 | static { 29 | cids.put("index.html", "QmVts3YjmhsCSqMv8Thk1CCy1nnpCbqEFjbkjS7PEzthZE"); 30 | cids.put("html", "QmUQvDumYa8najL94EnGhmGobyMyNzAmCSpfAxYnYcQHZD"); 31 | } 32 | 33 | IPFS ipfs = new IPFS(new MultiAddress("/ip4/127.0.0.1/tcp/5001")); 34 | 35 | @Test 36 | public void testSingle() throws Exception { 37 | Path path = Paths.get("src/test/resources/html/index.html"); 38 | NamedStreamable file = new FileWrapper(path.toFile()); 39 | List tree = ipfs.add(file); 40 | 41 | Assert.assertEquals(1, tree.size()); 42 | Assert.assertEquals("index.html", tree.get(0).name.get()); 43 | Assert.assertEquals(cids.get("index.html"), tree.get(0).hash.toBase58()); 44 | } 45 | 46 | @Test 47 | public void testSingleWrapped() throws Exception { 48 | 49 | Path path = Paths.get("src/test/resources/html/index.html"); 50 | NamedStreamable file = new FileWrapper(path.toFile()); 51 | List tree = ipfs.add(file, true); 52 | 53 | Assert.assertEquals(2, tree.size()); 54 | Assert.assertEquals("index.html", tree.get(0).name.get()); 55 | Assert.assertEquals(cids.get("index.html"), tree.get(0).hash.toBase58()); 56 | } 57 | 58 | @Test 59 | public void testSingleOnlyHash() throws Exception { 60 | 61 | Path path = Paths.get("src/test/resources/html/index.html"); 62 | NamedStreamable file = new FileWrapper(path.toFile()); 63 | List tree = ipfs.add(file, false, true); 64 | 65 | Assert.assertEquals(1, tree.size()); 66 | Assert.assertEquals("index.html", tree.get(0).name.get()); 67 | Assert.assertEquals(cids.get("index.html"), tree.get(0).hash.toBase58()); 68 | } 69 | 70 | @Test 71 | public void testRecursive() throws Exception { 72 | 73 | Path path = Paths.get("src/test/resources/html"); 74 | NamedStreamable file = new FileWrapper(path.toFile()); 75 | List tree = ipfs.add(file); 76 | 77 | Assert.assertEquals(8, tree.size()); 78 | Assert.assertEquals("html", tree.get(7).name.get()); 79 | Assert.assertEquals(cids.get("html"), tree.get(7).hash.toBase58()); 80 | } 81 | 82 | @Test 83 | public void testRecursiveOnlyHash() throws Exception { 84 | 85 | Path path = Paths.get("src/test/resources/html"); 86 | NamedStreamable file = new FileWrapper(path.toFile()); 87 | List tree = ipfs.add(file, false, true); 88 | 89 | Assert.assertEquals(8, tree.size()); 90 | Assert.assertEquals("html", tree.get(7).name.get()); 91 | Assert.assertEquals(cids.get("html"), tree.get(7).hash.toBase58()); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/test/java/io/ipfs/api/VersionsTest.java: -------------------------------------------------------------------------------- 1 | package io.ipfs.api; 2 | 3 | import org.junit.*; 4 | 5 | import java.util.*; 6 | import java.util.stream.*; 7 | 8 | public class VersionsTest { 9 | 10 | @Test 11 | public void sorting(){ 12 | List original = Arrays.asList("1.0.3", "0.4.9", "0.4.10", "0.5.1-rc1", "0.5.1-rc2", "0.5.1-rc2+meta"); 13 | List versions = original.stream().map(Version::parse).collect(Collectors.toList()); 14 | Collections.sort(versions); 15 | List sorted = versions.stream().map(Object::toString).collect(Collectors.toList()); 16 | List correct = Arrays.asList("0.4.9", "0.4.10", "0.5.1-rc1", "0.5.1-rc2", "0.5.1-rc2+meta", "1.0.3"); 17 | Assert.assertTrue("Correct version sorting", sorted.equals(correct)); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/test/resources/html/chap/ch01.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | IPFS 5 | 6 | 7 | 8 | 9 |

logo Home

10 |

Chapter 01

11 |

Yada yada

12 | 13 | 14 | -------------------------------------------------------------------------------- /src/test/resources/html/css/default.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: "Verdana"; 3 | color: #137cb9 4 | } 5 | 6 | a { 7 | #text-decoration: none; 8 | color: #137cb9 9 | } 10 | 11 | a.gray { 12 | color: gray; 13 | } 14 | 15 | h1 { 16 | font-weight: normal; 17 | font-size: 20px; 18 | } 19 | 20 | h2 { 21 | font-weight: normal; 22 | font-size: 15px; 23 | } 24 | 25 | th { 26 | text-align: left; 27 | font-weight: normal; 28 | font-size: 14px; 29 | color: gray; 30 | } 31 | 32 | td.gray { 33 | color: gray; 34 | } 35 | tr.gray { 36 | color: gray; 37 | } 38 | 39 | -------------------------------------------------------------------------------- /src/test/resources/html/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Openfabric/java-ipfs-http-client/ed9609ce8db035ca9517bae941757188669c2847/src/test/resources/html/img/logo.png -------------------------------------------------------------------------------- /src/test/resources/html/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | IPFS 5 | 6 | 7 | 8 | 9 |

logo Home

10 |

chapter one

11 | 12 | 13 | --------------------------------------------------------------------------------