├── .github ├── dependabot.yml └── workflows │ ├── ant.yml │ ├── generated-pr.yml │ └── stale.yml ├── .gitignore ├── LICENSE ├── README.md ├── build.xml ├── docker-compose.yml ├── install-run-ipfs.sh ├── lib ├── cid.jar ├── hamcrest-2.2.jar ├── junit-4.13.2.jar ├── multiaddr.jar ├── multibase.jar └── multihash.jar ├── mac-install-run-ipfs.sh ├── pom.xml ├── print_test_errors.sh └── src ├── main └── java │ └── io │ └── ipfs │ └── api │ ├── AddArgs.java │ ├── IPFS.java │ ├── IpldNode.java │ ├── JSONParser.java │ ├── KeyInfo.java │ ├── MerkleNode.java │ ├── Multipart.java │ ├── NamedStreamable.java │ ├── Pair.java │ ├── Peer.java │ ├── RepoStat.java │ ├── Version.java │ ├── WriteFilesArgs.java │ ├── cbor │ ├── CborConstants.java │ ├── CborDecoder.java │ ├── CborEncoder.java │ ├── CborObject.java │ ├── CborType.java │ └── Cborable.java │ └── demo │ ├── UsageMFSFilesAPI.java │ └── UsageRemotePinningAPI.java └── test ├── java └── io │ └── ipfs │ └── api │ ├── APITest.java │ ├── AddTest.java │ ├── RecursiveAddTest.java │ ├── SimpleAddTest.java │ └── VersionsTest.java └── resources ├── folder └── 你好.html └── html ├── chap └── ch01.html ├── css └── default.css ├── img └── logo.png └── index.html /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | -------------------------------------------------------------------------------- /.github/workflows/ant.yml: -------------------------------------------------------------------------------- 1 | name: Java CI 2 | 3 | on: 4 | schedule: 5 | - cron: '42 0 * * 4' 6 | push: 7 | pull_request: 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v4 16 | - name: Set up JDK 11 17 | uses: actions/setup-java@v4 18 | with: 19 | distribution: temurin 20 | java-version: 11 21 | - name: Install and run ipfs 22 | run: ./install-run-ipfs.sh 23 | - name: Build with Ant 24 | run: ant -noinput -buildfile build.xml dist 25 | - name: Run tests 26 | timeout-minutes: 10 27 | run: ant -noinput -buildfile build.xml test 28 | -------------------------------------------------------------------------------- /.github/workflows/generated-pr.yml: -------------------------------------------------------------------------------- 1 | name: Close Generated PRs 2 | 3 | on: 4 | schedule: 5 | - cron: '0 0 * * *' 6 | workflow_dispatch: 7 | 8 | permissions: 9 | issues: write 10 | pull-requests: write 11 | 12 | jobs: 13 | stale: 14 | uses: ipdxco/unified-github-workflows/.github/workflows/reusable-generated-pr.yml@v1 15 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: Close Stale Issues 2 | 3 | on: 4 | schedule: 5 | - cron: '0 0 * * *' 6 | workflow_dispatch: 7 | 8 | permissions: 9 | issues: write 10 | pull-requests: write 11 | 12 | jobs: 13 | stale: 14 | uses: ipdxco/unified-github-workflows/.github/workflows/reusable-stale-issue.yml@v1 15 | -------------------------------------------------------------------------------- /.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 | More example usage found [here](./src/main/java/io/ipfs/api/demo) 100 | 101 | ## Dependencies 102 | 103 | Current versions of dependencies are included in the `./lib` directory. 104 | 105 | * [multibase](https://github.com/multiformats/java-multibase) 106 | * [multiaddr](https://github.com/multiformats/java-multiaddr) 107 | * [multihash](https://github.com/multiformats/java-multihash) 108 | * [cid](https://github.com/ipld/java-cid) 109 | 110 | ## Releasing 111 | 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. 112 | 113 | ## Contribute 114 | 115 | Feel free to join in. All welcome. Open an [issue](https://github.com/ipfs/java-ipfs-api/issues)! 116 | 117 | This repository falls under the IPFS [Code of Conduct](https://github.com/ipfs/community/blob/master/code-of-conduct.md). 118 | 119 | [![](https://cdn.rawgit.com/jbenet/contribute-ipfs-gif/master/img/contribute.gif)](https://github.com/ipfs/community/blob/master/CONTRIBUTING.md) 120 | 121 | ## License 122 | 123 | [MIT](LICENSE) 124 | -------------------------------------------------------------------------------- /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/kubo:v0.18.1' 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/kubo/v0.18.1/kubo_v0.18.1_linux-amd64.tar.gz -O /tmp/kubo_linux-amd64.tar.gz 3 | tar -xvf /tmp/kubo_linux-amd64.tar.gz 4 | export PATH=$PATH:$PWD/kubo/ 5 | ipfs init 6 | ipfs daemon --enable-pubsub-experiment --routing=dhtclient & 7 | -------------------------------------------------------------------------------- /lib/cid.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ipfs-shipyard/java-ipfs-http-client/a2b7915ff88afe10eca76a4c931095b9d78734db/lib/cid.jar -------------------------------------------------------------------------------- /lib/hamcrest-2.2.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ipfs-shipyard/java-ipfs-http-client/a2b7915ff88afe10eca76a4c931095b9d78734db/lib/hamcrest-2.2.jar -------------------------------------------------------------------------------- /lib/junit-4.13.2.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ipfs-shipyard/java-ipfs-http-client/a2b7915ff88afe10eca76a4c931095b9d78734db/lib/junit-4.13.2.jar -------------------------------------------------------------------------------- /lib/multiaddr.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ipfs-shipyard/java-ipfs-http-client/a2b7915ff88afe10eca76a4c931095b9d78734db/lib/multiaddr.jar -------------------------------------------------------------------------------- /lib/multibase.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ipfs-shipyard/java-ipfs-http-client/a2b7915ff88afe10eca76a4c931095b9d78734db/lib/multibase.jar -------------------------------------------------------------------------------- /lib/multihash.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ipfs-shipyard/java-ipfs-http-client/a2b7915ff88afe10eca76a4c931095b9d78734db/lib/multihash.jar -------------------------------------------------------------------------------- /mac-install-run-ipfs.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | wget https://dist.ipfs.io/kubo/v0.18.1/kubo_v0.18.1_darwin-arm64.tar.gz -O /tmp/kubo_darwin-arm64.tar.gz 3 | tar -xvf /tmp/kubo_darwin-arm64.tar.gz 4 | export PATH=$PATH:$PWD/kubo/ 5 | ipfs init 6 | ipfs daemon --enable-pubsub-experiment --routing=dhtclient & 7 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | com.github.ipfs 6 | java-ipfs-http-client 7 | v1.4.1 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.2 36 | 2.2 37 | v1.4.12 38 | 39 | 40 | 41 | 42 | jitpack.io 43 | https://jitpack.io 44 | 45 | 46 | 47 | 48 | 49 | com.github.multiformats 50 | java-multiaddr 51 | ${version.multiaddr} 52 | 53 | 54 | junit 55 | junit 56 | ${version.junit} 57 | test 58 | 59 | 60 | org.hamcrest 61 | hamcrest 62 | ${version.hamcrest} 63 | test 64 | 65 | 66 | 67 | 68 | 69 | 70 | org.apache.maven.plugins 71 | maven-compiler-plugin 72 | 3.8.0 73 | 74 | 11 75 | 11 76 | 77 | 78 | 79 | org.apache.maven.plugins 80 | maven-surefire-plugin 81 | 2.19.1 82 | 83 | 84 | org.apache.maven.plugins 85 | maven-jar-plugin 86 | 3.0.2 87 | 88 | 89 | 90 | true 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /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/AddArgs.java: -------------------------------------------------------------------------------- 1 | package io.ipfs.api; 2 | 3 | import java.net.URLEncoder; 4 | import java.util.*; 5 | import java.util.stream.Collectors; 6 | 7 | /* 8 | Example usage: 9 | AddArgs args = AddArgs.Builder.newInstance() 10 | .setInline() 11 | .setCidVersion(1) 12 | .build(); 13 | */ 14 | public final class AddArgs { 15 | 16 | private final Map args = new HashMap<>(); 17 | 18 | public AddArgs(Builder builder) 19 | { 20 | args.putAll(builder.args); 21 | } 22 | @Override 23 | public String toString() 24 | { 25 | List asList = args.entrySet() 26 | .stream() 27 | .sorted(Comparator.comparing(Map.Entry::getKey)) 28 | .map(e -> e.getKey() + " = " + e.getValue()).collect(Collectors.toList()); 29 | return Arrays.toString(asList.toArray()); 30 | } 31 | public String toQueryString() 32 | { 33 | StringBuilder sb = new StringBuilder(); 34 | for (Map.Entry entry: args.entrySet()) { 35 | sb.append("&").append(entry.getKey()) 36 | .append("=") 37 | .append(URLEncoder.encode(entry.getValue())); 38 | } 39 | return sb.length() > 0 ? sb.toString().substring(1) : sb.toString(); 40 | } 41 | public static class Builder { 42 | private static final String TRUE = "true"; 43 | private final Map args = new HashMap<>(); 44 | private Builder() {} 45 | public static Builder newInstance() 46 | { 47 | return new Builder(); 48 | } 49 | public Builder setQuiet() { 50 | args.put("quiet", TRUE); 51 | return this; 52 | } 53 | public Builder setQuieter() { 54 | args.put("quieter", TRUE); 55 | return this; 56 | } 57 | public Builder setSilent() { 58 | args.put("silent", TRUE); 59 | return this; 60 | } 61 | public Builder setTrickle() { 62 | args.put("trickle", TRUE); 63 | return this; 64 | } 65 | public Builder setOnlyHash() { 66 | args.put("only-hash", TRUE); 67 | return this; 68 | } 69 | public Builder setWrapWithDirectory() { 70 | args.put("wrap-with-directory", TRUE); 71 | return this; 72 | } 73 | public Builder setChunker(String chunker) { 74 | args.put("chunker", chunker); 75 | return this; 76 | } 77 | public Builder setRawLeaves() { 78 | args.put("raw-leaves", TRUE); 79 | return this; 80 | } 81 | public Builder setNocopy() { 82 | args.put("nocopy", TRUE); 83 | return this; 84 | } 85 | public Builder setFscache() { 86 | args.put("fscache", TRUE); 87 | return this; 88 | } 89 | public Builder setCidVersion(int version) { 90 | args.put("cid-version", String.valueOf(version)); 91 | return this; 92 | } 93 | public Builder setHash(String hashFunction) { 94 | args.put("hash", hashFunction); 95 | return this; 96 | } 97 | public Builder setInline() { 98 | args.put("inline", TRUE); 99 | return this; 100 | } 101 | public Builder setInlineLimit(int maxBlockSize) { 102 | args.put("inline-limit", String.valueOf(maxBlockSize)); 103 | return this; 104 | } 105 | public Builder setPin() { 106 | args.put("pin", TRUE); 107 | return this; 108 | } 109 | public Builder setToFiles(String path) { 110 | args.put("to-files", path); 111 | return this; 112 | } 113 | public AddArgs build() 114 | { 115 | return new AddArgs(this); 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /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 java.util.*; 4 | 5 | import java.lang.reflect.*; 6 | 7 | public class JSONParser 8 | { 9 | private static char skipSpaces(String json, int[] pos) 10 | { 11 | while (true) 12 | { 13 | if (pos[0] >= json.length()) 14 | return 0; 15 | char ch = json.charAt(pos[0]); 16 | if (Character.isWhitespace(ch)) 17 | pos[0]++; 18 | else 19 | return ch; 20 | } 21 | } 22 | 23 | private static Boolean parseBoolean(String json, int[] pos) 24 | { 25 | if (json.regionMatches(pos[0], "true", 0, 4)) 26 | { 27 | pos[0] += 4; 28 | return Boolean.TRUE; 29 | } 30 | 31 | if (json.regionMatches(pos[0], "false", 0, 5)) 32 | { 33 | pos[0] += 5; 34 | return Boolean.FALSE; 35 | } 36 | 37 | return null; 38 | } 39 | 40 | private static Number parseNumber(String json, int[] pos) 41 | { 42 | int endPos = json.length(); 43 | int startPos = pos[0]; 44 | 45 | boolean foundExp = false; 46 | boolean foundDot = false; 47 | boolean allowPM = true; 48 | for (int i=startPos; i parseStream(String json) 272 | { 273 | if (json == null) 274 | return null; 275 | int[] pos = new int[1]; 276 | List res = new ArrayList<>(); 277 | json = json.trim(); 278 | while (pos[0] < json.length()) 279 | res.add(parse(json, pos)); 280 | return res; 281 | } 282 | 283 | private static void escapeString(String s, StringBuffer buf) 284 | { 285 | buf.append('"'); 286 | for (int i=0; i= 0) 444 | { 445 | if ((json != null) && (json instanceof List)) 446 | json = ((List) json).get(index); 447 | else 448 | return null; 449 | } 450 | } 451 | 452 | return json; 453 | } 454 | } 455 | -------------------------------------------------------------------------------- /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 | httpConn.setChunkedStreamingMode(4096); 30 | out = httpConn.getOutputStream(); 31 | } catch (IOException e) { 32 | throw new RuntimeException(e.getMessage(), e); 33 | } 34 | } 35 | 36 | public static String createBoundary() { 37 | Random r = new Random(); 38 | String allowed = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; 39 | StringBuilder b = new StringBuilder(); 40 | for (int i=0; i < 32; i++) 41 | b.append(allowed.charAt(r.nextInt(allowed.length()))); 42 | return b.toString(); 43 | } 44 | 45 | private Multipart append(String value) throws IOException { 46 | out.write(value.getBytes(charset)); 47 | return this; 48 | } 49 | 50 | public void addFormField(String name, String value) throws IOException { 51 | append("--").append(boundary).append(LINE_FEED); 52 | append("Content-Disposition: form-data; name=\"").append(name).append("\"") 53 | .append(LINE_FEED); 54 | append("Content-Type: text/plain; charset=").append(charset).append(LINE_FEED); 55 | append(LINE_FEED); 56 | append(value).append(LINE_FEED); 57 | out.flush(); 58 | } 59 | 60 | public void addSubtree(Path parentPath, NamedStreamable dir) throws IOException { 61 | Path dirPath = parentPath.resolve(dir.getName().get()); 62 | addDirectoryPart(dirPath); 63 | for (NamedStreamable f: dir.getChildren()) { 64 | if (f.isDirectory()) 65 | addSubtree(dirPath, f); 66 | else 67 | addFilePart("file", dirPath, f); 68 | } 69 | } 70 | 71 | public void addDirectoryPart(Path path) throws IOException { 72 | append("--").append(boundary).append(LINE_FEED); 73 | append("Content-Disposition: file; filename=\"").append(encode(path.toString())).append("\"").append(LINE_FEED); 74 | append("Content-Type: application/x-directory").append(LINE_FEED); 75 | append("Content-Transfer-Encoding: binary").append(LINE_FEED); 76 | append(LINE_FEED); 77 | append(LINE_FEED); 78 | out.flush(); 79 | } 80 | 81 | private static String encode(String in) { 82 | try { 83 | return URLEncoder.encode(in, "UTF-8"); 84 | } catch (UnsupportedEncodingException e) { 85 | throw new RuntimeException(e); 86 | } 87 | } 88 | 89 | public void addFilePart(String fieldName, Path parent, NamedStreamable uploadFile) throws IOException { 90 | Optional fileName = uploadFile.getName().map(n -> encode(parent.resolve(n).toString().replace('\\','/'))); 91 | append("--").append(boundary).append(LINE_FEED); 92 | if (!fileName.isPresent()) 93 | append("Content-Disposition: file; name=\"").append(fieldName).append("\";").append(LINE_FEED); 94 | else 95 | append("Content-Disposition: file; filename=\"").append(fileName.get()).append("\";").append(LINE_FEED); 96 | append("Content-Type: application/octet-stream").append(LINE_FEED); 97 | append("Content-Transfer-Encoding: binary").append(LINE_FEED); 98 | append(LINE_FEED); 99 | out.flush(); 100 | 101 | try { 102 | InputStream inputStream = uploadFile.getInputStream(); 103 | byte[] buffer = new byte[4096]; 104 | int r; 105 | while ((r = inputStream.read(buffer)) != -1) 106 | out.write(buffer, 0, r); 107 | out.flush(); 108 | inputStream.close(); 109 | } catch (IOException e) { 110 | throw new RuntimeException(e.getMessage(), e); 111 | } 112 | 113 | append(LINE_FEED); 114 | out.flush(); 115 | } 116 | 117 | public void addHeaderField(String name, String value) throws IOException { 118 | append(name + ": " + value).append(LINE_FEED); 119 | out.flush(); 120 | } 121 | 122 | public String finish() throws IOException { 123 | StringBuilder b = new StringBuilder(); 124 | 125 | append("--" + boundary + "--").append(LINE_FEED); 126 | out.flush(); 127 | out.close(); 128 | 129 | try { 130 | int status = httpConn.getResponseCode(); 131 | if (status == HttpURLConnection.HTTP_OK) { 132 | BufferedReader reader = new BufferedReader(new InputStreamReader( 133 | httpConn.getInputStream())); 134 | String line; 135 | while ((line = reader.readLine()) != null) { 136 | b.append(line); 137 | } 138 | reader.close(); 139 | httpConn.disconnect(); 140 | } else { 141 | try { 142 | BufferedReader reader = new BufferedReader(new InputStreamReader( 143 | httpConn.getInputStream())); 144 | String line; 145 | while ((line = reader.readLine()) != null) { 146 | b.append(line); 147 | } 148 | reader.close(); 149 | } catch (Throwable t) { 150 | } 151 | throw new IOException("Server returned status: " + status + " with body: " + b.toString() + " and Trailer header: " + httpConn.getHeaderFields().get("Trailer")); 152 | } 153 | 154 | return b.toString(); 155 | } catch (IOException e) { 156 | throw new RuntimeException(e.getMessage(), e); 157 | } 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /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 | return Optional.of(source.getName()); 56 | } 57 | } 58 | 59 | class InputStreamWrapper implements NamedStreamable { 60 | private final Optional name; 61 | private final InputStream data; 62 | 63 | public InputStreamWrapper(InputStream data) { 64 | this(Optional.empty(), data); 65 | } 66 | 67 | public InputStreamWrapper(String name, InputStream data) { 68 | this(Optional.of(name), data); 69 | } 70 | 71 | public InputStreamWrapper(Optional name, InputStream data) { 72 | this.name = name; 73 | this.data = data; 74 | } 75 | 76 | public boolean isDirectory() { 77 | return false; 78 | } 79 | 80 | public InputStream getInputStream() { 81 | return data; 82 | } 83 | 84 | @Override 85 | public List getChildren() { 86 | return Collections.emptyList(); 87 | } 88 | 89 | public Optional getName() { 90 | return name; 91 | } 92 | } 93 | 94 | class ByteArrayWrapper implements NamedStreamable { 95 | private final Optional name; 96 | private final byte[] data; 97 | 98 | public ByteArrayWrapper(byte[] data) { 99 | this(Optional.empty(), data); 100 | } 101 | 102 | public ByteArrayWrapper(String name, byte[] data) { 103 | this(Optional.of(name), data); 104 | } 105 | 106 | public ByteArrayWrapper(Optional name, byte[] data) { 107 | this.name = name; 108 | this.data = data; 109 | } 110 | 111 | public boolean isDirectory() { 112 | return false; 113 | } 114 | 115 | public InputStream getInputStream() throws IOException { 116 | return new ByteArrayInputStream(data); 117 | } 118 | 119 | @Override 120 | public List getChildren() { 121 | return Collections.emptyList(); 122 | } 123 | 124 | public Optional getName() { 125 | return name; 126 | } 127 | } 128 | 129 | class DirWrapper implements NamedStreamable { 130 | 131 | private final String name; 132 | private final List children; 133 | 134 | public DirWrapper(String name, List children) { 135 | this.name = name; 136 | this.children = children; 137 | } 138 | 139 | @Override 140 | public InputStream getInputStream() throws IOException { 141 | throw new IllegalStateException("Cannot get an input stream for a directory!"); 142 | } 143 | 144 | @Override 145 | public Optional getName() { 146 | return Optional.of(name); 147 | } 148 | 149 | @Override 150 | public List getChildren() { 151 | return children; 152 | } 153 | 154 | @Override 155 | public boolean isDirectory() { 156 | return true; 157 | } 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /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.*; 4 | import io.ipfs.multiaddr.*; 5 | import io.ipfs.multihash.*; 6 | 7 | import java.util.*; 8 | import java.util.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/RepoStat.java: -------------------------------------------------------------------------------- 1 | package io.ipfs.api; 2 | 3 | import java.util.Map; 4 | 5 | public class RepoStat { 6 | 7 | public final long RepoSize; 8 | public final long StorageMax; 9 | public final long NumObjects; 10 | public final String RepoPath; 11 | public final String Version; 12 | 13 | public RepoStat(long repoSize, long storageMax, long numObjects, String repoPath, String version ) { 14 | this.RepoSize = repoSize; 15 | this.StorageMax = storageMax; 16 | this.NumObjects = numObjects; 17 | this.RepoPath = repoPath; 18 | this.Version = version; 19 | } 20 | public static RepoStat fromJson(Object rawjson) { 21 | Map json = (Map)rawjson; 22 | long repoSize = Long.parseLong(json.get("RepoSize").toString()); 23 | long storageMax = Long.parseLong(json.get("StorageMax").toString()); 24 | long numObjects = Long.parseLong(json.get("NumObjects").toString()); 25 | String repoPath = (String)json.get("RepoPath"); 26 | String version = (String)json.get("Version"); 27 | 28 | return new RepoStat(repoSize, storageMax, numObjects, repoPath, version); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /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/WriteFilesArgs.java: -------------------------------------------------------------------------------- 1 | package io.ipfs.api; 2 | 3 | import java.net.URLEncoder; 4 | import java.util.*; 5 | import java.util.stream.Collectors; 6 | 7 | /* 8 | Example usage: 9 | WriteFilesArgs args = WriteFilesArgs.Builder.newInstance() 10 | .setCreate() 11 | .setParents() 12 | .build(); 13 | */ 14 | final public class WriteFilesArgs { 15 | 16 | private final Map args = new HashMap<>(); 17 | 18 | public WriteFilesArgs(Builder builder) 19 | { 20 | args.putAll(builder.args); 21 | } 22 | @Override 23 | public String toString() 24 | { 25 | List asList = args.entrySet() 26 | .stream() 27 | .sorted(Comparator.comparing(Map.Entry::getKey)) 28 | .map(e -> e.getKey() + " = " + e.getValue()).collect(Collectors.toList()); 29 | return Arrays.toString(asList.toArray()); 30 | } 31 | public String toQueryString() 32 | { 33 | StringBuilder sb = new StringBuilder(); 34 | for (Map.Entry entry: args.entrySet()) { 35 | sb.append("&").append(entry.getKey()) 36 | .append("=") 37 | .append(URLEncoder.encode(entry.getValue())); 38 | } 39 | return sb.length() > 0 ? sb.toString().substring(1) : sb.toString(); 40 | } 41 | public static class Builder { 42 | private static final String TRUE = "true"; 43 | private final Map args = new HashMap<>(); 44 | private Builder() {} 45 | public static Builder newInstance() 46 | { 47 | return new Builder(); 48 | } 49 | 50 | public Builder setOffset(int offset) { 51 | args.put("offset", String.valueOf(offset)); 52 | return this; 53 | } 54 | public Builder setCreate() { 55 | args.put("create", TRUE); 56 | return this; 57 | } 58 | public Builder setParents() { 59 | args.put("parents", TRUE); 60 | return this; 61 | } 62 | public Builder setTruncate() { 63 | args.put("truncate", TRUE); 64 | return this; 65 | } 66 | public Builder setCount(int count) { 67 | args.put("count", String.valueOf(count)); 68 | return this; 69 | } 70 | public Builder setRawLeaves() { 71 | args.put("raw-leaves", TRUE); 72 | return this; 73 | } 74 | public Builder setCidVersion(int version) { 75 | args.put("cid-version", String.valueOf(version)); 76 | return this; 77 | } 78 | public Builder setHash(String hashFunction) { 79 | args.put("hash", hashFunction); 80 | return this; 81 | } 82 | public WriteFilesArgs build() 83 | { 84 | return new WriteFilesArgs(this); 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /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/main/java/io/ipfs/api/demo/UsageMFSFilesAPI.java: -------------------------------------------------------------------------------- 1 | package io.ipfs.api.demo; 2 | 3 | import io.ipfs.api.IPFS; 4 | import io.ipfs.api.NamedStreamable; 5 | import io.ipfs.api.WriteFilesArgs; 6 | import io.ipfs.multiaddr.MultiAddress; 7 | 8 | import java.io.IOException; 9 | import java.util.List; 10 | import java.util.Map; 11 | 12 | /* 13 | From MFS api documentation: https://github.com/ipfs/js-ipfs/blob/master/docs/core-api/FILES.md#the-mutable-files-api 14 | The Mutable File System (MFS) is a virtual file system on top of IPFS that exposes a Unix like API over a virtual directory. 15 | It enables users to write and read from paths without having to worry about updating the graph. 16 | 17 | Useful links: 18 | rpc api - https://docs.ipfs.tech/reference/kubo/rpc/#getting-started 19 | proto.school - https://proto.school/mutable-file-system/01 20 | ipfs.tech - https://docs.ipfs.tech/concepts/file-systems/#mutable-file-system-mfs 21 | 22 | */ 23 | public class UsageMFSFilesAPI { 24 | 25 | public UsageMFSFilesAPI(IPFS ipfsClient) { 26 | try { 27 | run(ipfsClient); 28 | } catch (IOException ioe) { 29 | ioe.printStackTrace(); 30 | } 31 | } 32 | private void run(IPFS ipfs) throws IOException { 33 | 34 | // remove 'my' directory to clean up from a previous run 35 | ipfs.files.rm("/my", true, true); 36 | 37 | // To create a new directory nested under others that don't yet exist, you need to explicitly set the value of parents to true 38 | ipfs.files.mkdir("/my/directory/example", true); 39 | 40 | // Check directory status 41 | String directoryPath = "/my/directory/example"; 42 | Map exampleDirectory = ipfs.files.stat(directoryPath); 43 | //{Hash=QmV1a2QoUnB9fPzjZd1GunGR53isuhcWWNCS5Bg3mJyv8N, Size=0, CumulativeSize=57, Blocks=1, Type=directory} 44 | 45 | // Add a file 46 | String contents = "hello world!"; 47 | String filename = "hello.txt"; 48 | String filePath = directoryPath + "/" + filename; 49 | NamedStreamable ns = new NamedStreamable.ByteArrayWrapper(filename, contents.getBytes()); 50 | ipfs.files.write(filePath, ns, true, true); 51 | 52 | // Read contents of a file 53 | String fileContents = new String(ipfs.files.read(filePath)); 54 | System.out.println(fileContents); 55 | 56 | // Write a file using builder pattern 57 | String ipfsFilename = "ipfs.txt"; 58 | String fullIpfsPath = directoryPath + "/" + ipfsFilename; 59 | NamedStreamable ipfsFile = new NamedStreamable.ByteArrayWrapper(ipfsFilename, "ipfs says hello".getBytes()); 60 | WriteFilesArgs args = WriteFilesArgs.Builder.newInstance() 61 | .setCreate() 62 | .setParents() 63 | .build(); 64 | ipfs.files.write(fullIpfsPath, ipfsFile, args); 65 | 66 | // List directory contents 67 | List ls = ipfs.files.ls(directoryPath); 68 | for(Map entry : ls) { 69 | System.out.println(entry.get("Name")); 70 | } 71 | 72 | // Copy file to another directory 73 | String copyDirectoryPath = "/my/copy/"; 74 | ipfs.files.cp(filePath, copyDirectoryPath + filename, true); 75 | ls = ipfs.files.ls(copyDirectoryPath); 76 | for(Map entry : ls) { 77 | System.out.println(entry.get("Name")); 78 | } 79 | 80 | // Move file to another directory 81 | String duplicateDirectoryPath = "/my/duplicate/"; 82 | ipfs.files.mkdir(duplicateDirectoryPath, false); 83 | ipfs.files.mv(copyDirectoryPath + filename, duplicateDirectoryPath + filename); 84 | ls = ipfs.files.ls(duplicateDirectoryPath); 85 | for(Map entry : ls) { 86 | System.out.println(entry.get("Name")); 87 | } 88 | 89 | // Remove a directory 90 | ipfs.files.rm(copyDirectoryPath, true, true); 91 | ls = ipfs.files.ls("/my"); 92 | for(Map entry : ls) { 93 | System.out.println(entry.get("Name")); 94 | } 95 | } 96 | public static void main(String[] args) { 97 | IPFS ipfsClient = new IPFS(new MultiAddress("/ip4/127.0.0.1/tcp/5001")); 98 | new UsageMFSFilesAPI(ipfsClient); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/main/java/io/ipfs/api/demo/UsageRemotePinningAPI.java: -------------------------------------------------------------------------------- 1 | package io.ipfs.api.demo; 2 | 3 | import io.ipfs.api.IPFS; 4 | import io.ipfs.api.MerkleNode; 5 | import io.ipfs.api.NamedStreamable; 6 | import io.ipfs.multiaddr.MultiAddress; 7 | import io.ipfs.multihash.Multihash; 8 | 9 | import java.io.IOException; 10 | import java.util.List; 11 | import java.util.Map; 12 | import java.util.Optional; 13 | 14 | /* 15 | This sample program demonstrates how to use the remote pinning API methods 16 | 17 | rpc api - https://docs.ipfs.tech/reference/kubo/rpc/#api-v0-pin-remote-add 18 | 19 | setup: 20 | For demonstration purposes it uses a mock pinning service: 21 | - https://github.com/ipfs-shipyard/js-mock-ipfs-pinning-service 22 | Follow the instructions in the README.md file of the above repository for installation 23 | 24 | Sample command to execute before running this program: 25 | npx mock-ipfs-pinning-service --port 3000 --token secret 26 | 27 | Note: The above parameters are referenced in the program below 28 | */ 29 | public class UsageRemotePinningAPI { 30 | 31 | public UsageRemotePinningAPI(IPFS ipfsClient) { 32 | try { 33 | run(ipfsClient); 34 | } catch (IOException ioe) { 35 | ioe.printStackTrace(); 36 | } 37 | } 38 | private void run(IPFS ipfs) throws IOException { 39 | 40 | // Add file to the local node 41 | MerkleNode file = ipfs.add(new NamedStreamable.ByteArrayWrapper("file.txt", "test data".getBytes())).get(0); 42 | // Retrieve CID 43 | Multihash hash = file.hash; 44 | 45 | //Add the service 46 | String serviceName = "mock"; 47 | ipfs.pin.remote.rmService(serviceName); //clean up if necessary 48 | ipfs.pin.remote.addService(serviceName, "http://127.0.0.1:3000", "secret"); 49 | 50 | //List services 51 | List services = ipfs.pin.remote.lsService(true); 52 | for(Map service : services) { 53 | System.out.println(service); 54 | } 55 | 56 | // Pin 57 | Map addHashResult = ipfs.pin.remote.add(serviceName, hash, Optional.empty(), true); 58 | System.out.println(addHashResult); 59 | 60 | // List 61 | List statusList = List.of(IPFS.PinStatus.values()); // all statuses 62 | Map ls = ipfs.pin.remote.ls(serviceName, Optional.empty(), Optional.of(statusList)); 63 | System.out.println(ls); 64 | 65 | // Remove pin from remote pinning service 66 | List queued = List.of(IPFS.PinStatus.queued); 67 | ipfs.pin.remote.rm(serviceName, Optional.empty(), Optional.of(queued), Optional.of(List.of(hash))); 68 | 69 | } 70 | 71 | public static void main(String[] args) { 72 | IPFS ipfsClient = new IPFS(new MultiAddress("/ip4/127.0.0.1/tcp/5001")); 73 | new UsageRemotePinningAPI(ipfsClient); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/test/java/io/ipfs/api/APITest.java: -------------------------------------------------------------------------------- 1 | package io.ipfs.api; 2 | 3 | import io.ipfs.api.cbor.*; 4 | import io.ipfs.cid.*; 5 | import io.ipfs.multihash.Multihash; 6 | import io.ipfs.multiaddr.MultiAddress; 7 | import org.junit.*; 8 | 9 | import java.io.*; 10 | import java.nio.file.*; 11 | import java.util.*; 12 | import java.util.function.*; 13 | import java.util.stream.*; 14 | 15 | import static org.junit.Assert.assertNotNull; 16 | import static org.junit.Assert.assertTrue; 17 | import static org.junit.Assert.assertEquals; 18 | import static org.junit.Assert.assertArrayEquals; 19 | 20 | @SuppressWarnings({"rawtypes", "unused"}) 21 | public class APITest { 22 | 23 | private final MultiAddress ipfsAddress = new MultiAddress("/ip4/127.0.0.1/tcp/5001"); 24 | private final IPFS ipfs = new IPFS(ipfsAddress.getHost(), ipfsAddress.getPort(), "/api/v0/", true, false); 25 | 26 | private final Random r = new Random(33550336); // perfect 27 | 28 | @Test 29 | public void dag() throws IOException { 30 | String original = "{\"data\":1234}"; 31 | byte[] object = original.getBytes(); 32 | MerkleNode put = ipfs.dag.put("json", object); 33 | 34 | Cid expected = Cid.decode("bafyreidbm2zncsc3j25zn7lofgd4woeh6eygdy73thfosuni2rwr3bhcvu"); 35 | 36 | Multihash result = put.hash; 37 | assertEquals("Correct cid returned", result, expected); 38 | 39 | byte[] get = ipfs.dag.get(expected); 40 | assertEquals("Raw data equal", original, new String(get).trim()); 41 | Map res = ipfs.dag.resolve("bafyreidbm2zncsc3j25zn7lofgd4woeh6eygdy73thfosuni2rwr3bhcvu"); 42 | assertNotNull("not resolved", res); 43 | res = ipfs.dag.stat(expected); 44 | assertNotNull("not found", res); 45 | } 46 | 47 | @Test 48 | public void dagCbor() throws IOException { 49 | Map tmp = new LinkedHashMap<>(); 50 | String value = "G'day mate!"; 51 | tmp.put("data", new CborObject.CborString(value)); 52 | CborObject original = CborObject.CborMap.build(tmp); 53 | byte[] object = original.toByteArray(); 54 | MerkleNode put = ipfs.dag.put("dag-cbor", object); 55 | 56 | Cid cid = (Cid) put.hash; 57 | 58 | byte[] get = ipfs.dag.get(cid); 59 | assertEquals("Raw data equal", ((Map) JSONParser.parse(new String(get))).get("data"), 60 | value); 61 | 62 | Cid expected = Cid.decode("zdpuApemz4XMURSCkBr9W5y974MXkSbeDfLeZmiQTPpvkatFF"); 63 | assertEquals("Correct cid returned", cid, expected); 64 | } 65 | 66 | @Test 67 | public void keys() throws IOException { 68 | List existing = ipfs.key.list(); 69 | String name = "mykey" + System.nanoTime(); 70 | KeyInfo gen = ipfs.key.gen(name, Optional.of("rsa"), Optional.of("2048")); 71 | String newName = "bob" + System.nanoTime(); 72 | Object rename = ipfs.key.rename(name, newName); 73 | List rm = ipfs.key.rm(newName); 74 | List remaining = ipfs.key.list(); 75 | assertEquals("removed key", remaining, existing); 76 | } 77 | 78 | @Test 79 | @Ignore("Not reliable") 80 | public void log() throws IOException { 81 | Map lsResult = ipfs.log.ls(); 82 | Assert.assertFalse("Log ls", lsResult.isEmpty()); 83 | Map levelResult = ipfs.log.level("all", "info"); 84 | Assert.assertTrue("Log level", ((String)levelResult.get("Message")).startsWith("Changed log level")); 85 | } 86 | 87 | @Test 88 | public void ipldNode() { 89 | Function>, CborObject.CborMap> map = 90 | s -> CborObject.CborMap.build(s.collect(Collectors.toMap(p -> p.left, p -> p.right))); 91 | CborObject.CborMap a = map.apply(Stream.of(new Pair<>("b", new CborObject.CborLong(1)))); 92 | 93 | CborObject.CborMap cbor = map.apply(Stream.of(new Pair<>("a", a), new Pair<>("c", new CborObject.CborLong(2)))); 94 | 95 | IpldNode.CborIpldNode node = new IpldNode.CborIpldNode(cbor); 96 | List tree = node.tree("", -1); 97 | assertEquals("Correct tree", tree, Arrays.asList("/a/b", "/c")); 98 | } 99 | 100 | @Test 101 | public void singleFileTest() throws IOException { 102 | NamedStreamable.ByteArrayWrapper file = new NamedStreamable.ByteArrayWrapper("hello.txt", "G'day world! IPFS rocks!".getBytes()); 103 | fileTest(file); 104 | } 105 | 106 | @Test 107 | public void wrappedSingleFileTest() throws IOException { 108 | NamedStreamable.ByteArrayWrapper file = new NamedStreamable.ByteArrayWrapper("hello.txt", "G'day world! IPFS rocks!".getBytes()); 109 | List addParts = ipfs.add(file, true); 110 | MerkleNode filePart = addParts.get(0); 111 | MerkleNode dirPart = addParts.get(1); 112 | byte[] catResult = ipfs.cat(filePart.hash); 113 | byte[] getResult = ipfs.get(filePart.hash); 114 | if (!Arrays.equals(catResult, file.getContents())) 115 | throw new IllegalStateException("Different contents!"); 116 | List pinRm = ipfs.pin.rm(dirPart.hash, true); 117 | if (!pinRm.get(0).equals(dirPart.hash)) 118 | throw new IllegalStateException("Didn't remove file!"); 119 | Object gc = ipfs.repo.gc(); 120 | } 121 | 122 | @Test 123 | public void dirTest() throws IOException { 124 | Path test = Files.createTempDirectory("test"); 125 | Files.write(test.resolve("file.txt"), "G'day IPFS!".getBytes()); 126 | NamedStreamable dir = new NamedStreamable.FileWrapper(test.toFile()); 127 | List add = ipfs.add(dir); 128 | MerkleNode addResult = add.get(add.size() - 1); 129 | List ls = ipfs.ls(addResult.hash); 130 | Assert.assertTrue(ls.size() > 0); 131 | } 132 | 133 | @Test 134 | public void directoryTest() throws IOException { 135 | Random rnd = new Random(); 136 | String dirName = "folder" + rnd.nextInt(100); 137 | Path tmpDir = Files.createTempDirectory(dirName); 138 | 139 | String fileName = "afile" + rnd.nextInt(100); 140 | Path file = tmpDir.resolve(fileName); 141 | FileOutputStream fout = new FileOutputStream(file.toFile()); 142 | byte[] fileContents = "IPFS rocks!".getBytes(); 143 | fout.write(fileContents); 144 | fout.flush(); 145 | fout.close(); 146 | 147 | String subdirName = "subdir"; 148 | tmpDir.resolve(subdirName).toFile().mkdir(); 149 | 150 | String subfileName = "subdirfile" + rnd.nextInt(100); 151 | Path subdirfile = tmpDir.resolve(subdirName + "/" + subfileName); 152 | FileOutputStream fout2 = new FileOutputStream(subdirfile.toFile()); 153 | byte[] file2Contents = "IPFS still rocks!".getBytes(); 154 | fout2.write(file2Contents); 155 | fout2.flush(); 156 | fout2.close(); 157 | 158 | List addParts = ipfs.add(new NamedStreamable.FileWrapper(tmpDir.toFile())); 159 | MerkleNode addResult = addParts.get(addParts.size() - 1); 160 | List lsResult = ipfs.ls(addResult.hash); 161 | if (lsResult.size() != 2) 162 | throw new IllegalStateException("Incorrect number of objects in ls!"); 163 | if (! lsResult.stream().map(x -> x.name.get()).collect(Collectors.toSet()).equals(new HashSet<>(Arrays.asList(subdirName, fileName)))) 164 | throw new IllegalStateException("Dir not returned in ls!"); 165 | byte[] catResult = ipfs.cat(addResult.hash, "/" + fileName); 166 | if (! Arrays.equals(catResult, fileContents)) 167 | throw new IllegalStateException("Different contents!"); 168 | 169 | byte[] catResult2 = ipfs.cat(addResult.hash, "/" + subdirName + "/" + subfileName); 170 | if (! Arrays.equals(catResult2, file2Contents)) 171 | throw new IllegalStateException("Different contents!"); 172 | } 173 | 174 | @Ignore 175 | @Test 176 | public void largeFileTest() throws IOException { 177 | byte[] largerData = new byte[100*1024*1024]; 178 | new Random(1).nextBytes(largerData); 179 | NamedStreamable.ByteArrayWrapper largeFile = new NamedStreamable.ByteArrayWrapper("nontrivial.txt", largerData); 180 | fileTest(largeFile); 181 | } 182 | 183 | @Ignore 184 | @Test 185 | public void hugeFileStreamTest() throws IOException { 186 | byte[] hugeData = new byte[1000*1024*1024]; 187 | new Random(1).nextBytes(hugeData); 188 | NamedStreamable.ByteArrayWrapper largeFile = new NamedStreamable.ByteArrayWrapper("massive.txt", hugeData); 189 | MerkleNode addResult = ipfs.add(largeFile).get(0); 190 | InputStream in = ipfs.catStream(addResult.hash); 191 | 192 | byte[] res = new byte[hugeData.length]; 193 | int offset = 0; 194 | byte[] buf = new byte[4096]; 195 | int r; 196 | while ((r = in.read(buf)) >= 0) { 197 | try { 198 | System.arraycopy(buf, 0, res, offset, r); 199 | offset += r; 200 | }catch (Exception e){ 201 | e.printStackTrace(); 202 | } 203 | } 204 | if (!Arrays.equals(res, hugeData)) 205 | throw new IllegalStateException("Different contents!"); 206 | } 207 | 208 | @Test 209 | public void hostFileTest() throws IOException { 210 | Path tempFile = Files.createTempFile("IPFS", "tmp"); 211 | BufferedWriter w = new BufferedWriter(new FileWriter(tempFile.toFile())); 212 | w.append("Some data"); 213 | w.flush(); 214 | w.close(); 215 | NamedStreamable hostFile = new NamedStreamable.FileWrapper(tempFile.toFile()); 216 | fileTest(hostFile); 217 | } 218 | 219 | @Test 220 | public void hashOnly() throws IOException { 221 | byte[] data = randomBytes(4096); 222 | NamedStreamable file = new NamedStreamable.ByteArrayWrapper(data); 223 | MerkleNode addResult = ipfs.add(file, false, true).get(0); 224 | List local = ipfs.refs.local(); 225 | if (local.contains(addResult.hash)) 226 | throw new IllegalStateException("Object shouldn't be present!"); 227 | } 228 | 229 | public void fileTest(NamedStreamable file) throws IOException{ 230 | MerkleNode addResult = ipfs.add(file).get(0); 231 | byte[] catResult = ipfs.cat(addResult.hash); 232 | byte[] getResult = ipfs.get(addResult.hash); 233 | if (!Arrays.equals(catResult, file.getContents())) 234 | throw new IllegalStateException("Different contents!"); 235 | List pinRm = ipfs.pin.rm(addResult.hash, true); 236 | if (!pinRm.get(0).equals(addResult.hash)) 237 | throw new IllegalStateException("Didn't remove file!"); 238 | Object gc = ipfs.repo.gc(); 239 | } 240 | @Test 241 | public void filesTest() throws IOException { 242 | 243 | ipfs.files.rm("/filesTest", true, true); 244 | String filename = "hello.txt"; 245 | String folder = "/filesTest/one/two"; 246 | String path = folder + "/" + filename; 247 | String contents = "hello world!"; 248 | NamedStreamable ns = new NamedStreamable.ByteArrayWrapper(filename, contents.getBytes()); 249 | String res = ipfs.files.write(path, ns, true, true); 250 | Map stat = ipfs.files.stat( path); 251 | Map stat2 = ipfs.files.stat( path, Optional.of(""), true); 252 | String readContents = new String(ipfs.files.read(path)); 253 | assertEquals("Should be equals", contents, readContents); 254 | res = ipfs.files.rm(path, false, false); 255 | 256 | String tempFilename = "temp.txt"; 257 | String tempFolder = "/filesTest/a/b/c"; 258 | String tempPath = tempFolder + "/" + tempFilename; 259 | String mkdir = ipfs.files.mkdir(tempFolder, true); 260 | stat = ipfs.files.stat(tempFolder); 261 | NamedStreamable tempFile = new NamedStreamable.ByteArrayWrapper(tempFilename, contents.getBytes()); 262 | res = ipfs.files.write(tempPath, tempFile, true, false); 263 | res = ipfs.files.mv(tempPath, "/" + tempFilename); 264 | stat = ipfs.files.stat("/" + tempFilename); 265 | List lsMap = ipfs.files.ls("/"); 266 | List lsMap2 = ipfs.files.ls("/", true, false); 267 | 268 | String flushFolder = "/filesTest/f/l/u/s/h"; 269 | res = ipfs.files.mkdir(flushFolder, true); 270 | Map flushMap = ipfs.files.flush(flushFolder); 271 | 272 | String copyFilename = "copy.txt"; 273 | String copyFromFolder = "/filesTest/fromThere"; 274 | String copyToFolder = "/filesTest/toHere"; 275 | String copyFromPath = copyFromFolder + "/" + copyFilename; 276 | String copyToPath = copyToFolder + "/" + copyFilename; 277 | NamedStreamable copyFile = new NamedStreamable.ByteArrayWrapper(copyFilename, "copy".getBytes()); 278 | WriteFilesArgs args = WriteFilesArgs.Builder.newInstance() 279 | .setCreate() 280 | .setParents() 281 | .build(); 282 | res = ipfs.files.write(copyFromPath, copyFile, args); 283 | res = ipfs.files.cp(copyFromPath, copyToPath, true); 284 | stat = ipfs.files.stat(copyToPath); 285 | String cidRes = ipfs.files.chcid(copyToPath); 286 | stat = ipfs.files.stat(copyToPath); 287 | String cidV0Res = ipfs.files.chcid(copyToPath, Optional.of(0), Optional.empty()); 288 | stat = ipfs.files.stat(copyToPath); 289 | ipfs.files.rm("/filesTest", false, true); 290 | } 291 | 292 | @Test 293 | public void multibaseTest() throws IOException { 294 | List encodings = ipfs.multibase.list(true, false); 295 | Assert.assertFalse("multibase/list works", encodings.isEmpty()); 296 | String encoded = ipfs.multibase.encode(Optional.empty(), new NamedStreamable.ByteArrayWrapper("hello".getBytes())); 297 | assertEquals("multibase/encode works", "uaGVsbG8", encoded); 298 | String decoded = ipfs.multibase.decode(new NamedStreamable.ByteArrayWrapper(encoded.getBytes())); 299 | assertEquals("multibase/decode works", "hello", decoded); 300 | String input = "f68656c6c6f"; 301 | String transcode = ipfs.multibase.transcode(Optional.of("base64url"), new NamedStreamable.ByteArrayWrapper(input.getBytes())); 302 | assertEquals("multibase/transcode works", transcode, encoded); 303 | } 304 | 305 | @Test 306 | @Ignore("Experimental feature not enabled by default") 307 | public void fileStoreTest() throws IOException { 308 | ipfs.fileStore.dups(); 309 | Map res = ipfs.fileStore.ls(true); 310 | ipfs.fileStore.verify(true); 311 | } 312 | 313 | @Test 314 | public void pinTest() throws IOException { 315 | MerkleNode file = ipfs.add(new NamedStreamable.ByteArrayWrapper("some data".getBytes())).get(0); 316 | Multihash hash = file.hash; 317 | Map ls1 = ipfs.pin.ls(IPFS.PinType.all); 318 | boolean pinned = ls1.containsKey(hash); 319 | List rm = ipfs.pin.rm(hash); 320 | // second rm should not throw a http 500, but return an empty list 321 | // List rm2 = ipfs.pin.rm(hash); 322 | List add2 = ipfs.pin.add(hash); 323 | // adding something already pinned should succeed 324 | List add3 = ipfs.pin.add(hash); 325 | Map ls = ipfs.pin.ls(IPFS.PinType.recursive); 326 | ipfs.repo.gc(); 327 | // object should still be present after gc 328 | Map ls2 = ipfs.pin.ls(IPFS.PinType.recursive); 329 | boolean stillPinned = ls2.containsKey(hash); 330 | Assert.assertTrue("Pinning works", pinned && stillPinned); 331 | } 332 | 333 | @Test 334 | @Ignore 335 | public void remotePinTest() throws IOException { 336 | MerkleNode file = ipfs.add(new NamedStreamable.ByteArrayWrapper("test data".getBytes())).get(0); 337 | Multihash hash = file.hash; 338 | String service = "mock"; 339 | String rmRemoteService = ipfs.pin.remote.rmService(service); 340 | List lsRemoteService = ipfs.pin.remote.lsService(false); 341 | String endpoint = "http://127.0.0.1:3000"; 342 | String key = "SET_VALUE_HERE"; 343 | String added = ipfs.pin.remote.addService(service, endpoint, key); 344 | lsRemoteService = ipfs.pin.remote.lsService(false); 345 | Map addHash = ipfs.pin.remote.add(service, hash, Optional.empty(), true); 346 | Map lsRemote = ipfs.pin.remote.ls(service, Optional.empty(), Optional.of(List.of(IPFS.PinStatus.values()))); 347 | String rmRemote = ipfs.pin.remote.rm(service, Optional.empty(), Optional.of(List.of(IPFS.PinStatus.queued)), Optional.of(List.of(hash))); 348 | lsRemote = ipfs.pin.remote.ls(service, Optional.empty(), Optional.of(List.of(IPFS.PinStatus.values()))); 349 | } 350 | 351 | @Test 352 | public void pinUpdate() throws IOException { 353 | MerkleNode child1 = ipfs.add(new NamedStreamable.ByteArrayWrapper("some data".getBytes())).get(0); 354 | Multihash hashChild1 = child1.hash; 355 | 356 | CborObject.CborMerkleLink root1 = new CborObject.CborMerkleLink(hashChild1); 357 | MerkleNode root1Res = ipfs.block.put(Collections.singletonList(root1.toByteArray()), Optional.of("cbor")).get(0); 358 | ipfs.pin.add(root1Res.hash); 359 | 360 | CborObject.CborList root2 = new CborObject.CborList(Arrays.asList(new CborObject.CborMerkleLink(hashChild1), new CborObject.CborLong(System.currentTimeMillis()))); 361 | MerkleNode root2Res = ipfs.block.put(Collections.singletonList(root2.toByteArray()), Optional.of("cbor")).get(0); 362 | List update = ipfs.pin.update(root1Res.hash, root2Res.hash, true); 363 | 364 | Map ls = ipfs.pin.ls(IPFS.PinType.all); 365 | boolean childPresent = ls.containsKey(hashChild1); 366 | if (!childPresent) 367 | throw new IllegalStateException("Child not present!"); 368 | 369 | ipfs.repo.gc(); 370 | Map ls2 = ipfs.pin.ls(IPFS.PinType.all); 371 | boolean childPresentAfterGC = ls2.containsKey(hashChild1); 372 | if (!childPresentAfterGC) 373 | throw new IllegalStateException("Child not present!"); 374 | } 375 | 376 | @Test 377 | public void rawLeafNodePinUpdate() throws IOException { 378 | MerkleNode child1 = ipfs.block.put("some data".getBytes(), Optional.of("raw")); 379 | Multihash hashChild1 = child1.hash; 380 | 381 | CborObject.CborMerkleLink root1 = new CborObject.CborMerkleLink(hashChild1); 382 | MerkleNode root1Res = ipfs.block.put(Collections.singletonList(root1.toByteArray()), Optional.of("cbor")).get(0); 383 | ipfs.pin.add(root1Res.hash); 384 | 385 | MerkleNode child2 = ipfs.block.put("G'day new tree".getBytes(), Optional.of("raw")); 386 | Multihash hashChild2 = child2.hash; 387 | 388 | CborObject.CborList root2 = new CborObject.CborList(Arrays.asList( 389 | new CborObject.CborMerkleLink(hashChild1), 390 | new CborObject.CborMerkleLink(hashChild2), 391 | new CborObject.CborLong(System.currentTimeMillis())) 392 | ); 393 | MerkleNode root2Res = ipfs.block.put(Collections.singletonList(root2.toByteArray()), Optional.of("cbor")).get(0); 394 | List update = ipfs.pin.update(root1Res.hash, root2Res.hash, false); 395 | } 396 | 397 | @Test 398 | public void indirectPinTest() throws IOException { 399 | Multihash EMPTY = ipfs.object._new(Optional.empty()).hash; 400 | io.ipfs.api.MerkleNode data = ipfs.object.patch(EMPTY, "set-data", Optional.of("childdata".getBytes()), Optional.empty(), Optional.empty()); 401 | Multihash child = data.hash; 402 | 403 | io.ipfs.api.MerkleNode tmp1 = ipfs.object.patch(EMPTY, "set-data", Optional.of("parent1_data".getBytes()), Optional.empty(), Optional.empty()); 404 | Multihash parent1 = ipfs.object.patch(tmp1.hash, "add-link", Optional.empty(), Optional.of(child.toString()), Optional.of(child)).hash; 405 | ipfs.pin.add(parent1); 406 | 407 | io.ipfs.api.MerkleNode tmp2 = ipfs.object.patch(EMPTY, "set-data", Optional.of("parent2_data".getBytes()), Optional.empty(), Optional.empty()); 408 | Multihash parent2 = ipfs.object.patch(tmp2.hash, "add-link", Optional.empty(), Optional.of(child.toString()), Optional.of(child)).hash; 409 | ipfs.pin.add(parent2); 410 | ipfs.pin.rm(parent1, true); 411 | 412 | Map ls = ipfs.pin.ls(IPFS.PinType.all); 413 | boolean childPresent = ls.containsKey(child); 414 | if (!childPresent) 415 | throw new IllegalStateException("Child not present!"); 416 | 417 | ipfs.repo.gc(); 418 | Map ls2 = ipfs.pin.ls(IPFS.PinType.all); 419 | boolean childPresentAfterGC = ls2.containsKey(child); 420 | if (!childPresentAfterGC) 421 | throw new IllegalStateException("Child not present!"); 422 | } 423 | 424 | @Test 425 | public void objectPatch() throws IOException { 426 | MerkleNode obj = ipfs.object._new(Optional.empty()); 427 | Multihash base = obj.hash; 428 | // link tests 429 | String linkName = "alink"; 430 | MerkleNode addLink = ipfs.object.patch(base, "add-link", Optional.empty(), Optional.of(linkName), Optional.of(base)); 431 | MerkleNode withLink = ipfs.object.get(addLink.hash); 432 | if (withLink.links.size() != 1 || !withLink.links.get(0).hash.equals(base) || !withLink.links.get(0).name.get().equals(linkName)) 433 | throw new RuntimeException("Added link not correct!"); 434 | MerkleNode rmLink = ipfs.object.patch(addLink.hash, "rm-link", Optional.empty(), Optional.of(linkName), Optional.empty()); 435 | if (!rmLink.hash.equals(base)) 436 | throw new RuntimeException("Adding not inverse of removing link!"); 437 | 438 | // data tests 439 | // byte[] data = "some random textual data".getBytes(); 440 | byte[] data = new byte[1024]; 441 | new Random().nextBytes(data); 442 | MerkleNode patched = ipfs.object.patch(base, "set-data", Optional.of(data), Optional.empty(), Optional.empty()); 443 | byte[] patchedResult = ipfs.object.data(patched.hash); 444 | if (!Arrays.equals(patchedResult, data)) 445 | throw new RuntimeException("object.patch: returned data != stored data!"); 446 | 447 | MerkleNode twicePatched = ipfs.object.patch(patched.hash, "append-data", Optional.of(data), Optional.empty(), Optional.empty()); 448 | byte[] twicePatchedResult = ipfs.object.data(twicePatched.hash); 449 | byte[] twice = new byte[2*data.length]; 450 | for (int i=0; i < 2; i++) 451 | System.arraycopy(data, 0, twice, i*data.length, data.length); 452 | if (!Arrays.equals(twicePatchedResult, twice)) 453 | throw new RuntimeException("object.patch: returned data after append != stored data!"); 454 | 455 | } 456 | 457 | @Test 458 | public void refsTest() throws IOException { 459 | List local = ipfs.refs.local(); 460 | for (Multihash ref: local) { 461 | Object refs = ipfs.refs(ref, false); 462 | } 463 | } 464 | 465 | @Test 466 | public void objectTest() throws IOException { 467 | MerkleNode _new = ipfs.object._new(Optional.empty()); 468 | Multihash pointer = Multihash.fromBase58("QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB"); 469 | MerkleNode object = ipfs.object.get(pointer); 470 | List newPointer = ipfs.object.put(Collections.singletonList(object.toJSONString().getBytes())); 471 | List newPointer2 = ipfs.object.put("json", Collections.singletonList(object.toJSONString().getBytes())); 472 | MerkleNode links = ipfs.object.links(pointer); 473 | byte[] data = ipfs.object.data(pointer); 474 | Map stat = ipfs.object.stat(pointer); 475 | } 476 | 477 | @Test 478 | public void blockTest() throws IOException { 479 | MerkleNode pointer = new MerkleNode("QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB"); 480 | Map stat = ipfs.block.stat(pointer.hash); 481 | byte[] object = ipfs.block.get(pointer.hash); 482 | List newPointer = ipfs.block.put(Collections.singletonList("Some random data...".getBytes())); 483 | } 484 | 485 | @Test 486 | public void bulkBlockTest() throws IOException { 487 | CborObject cbor = new CborObject.CborString("G'day IPFS!"); 488 | byte[] raw = cbor.toByteArray(); 489 | List bulkPut = ipfs.block.put(Arrays.asList(raw, raw, raw, raw, raw), Optional.of("cbor")); 490 | List hashes = bulkPut.stream().map(m -> m.hash).collect(Collectors.toList()); 491 | byte[] result = ipfs.block.get(hashes.get(0)); 492 | System.out.println(); 493 | } 494 | 495 | // @Ignore // Ignored because ipfs frequently times out internally in the publish call 496 | @Test 497 | public void publish() throws Exception { 498 | // JSON document 499 | String json = "{\"name\":\"blogpost\",\"documents\":[]}"; 500 | 501 | // Add a DAG node to IPFS 502 | MerkleNode merkleNode = ipfs.dag.put("json", json.getBytes()); 503 | assertEquals("expected to be bafyreiafmbgul64c4nyybvgivswmkuhifamc24cdfuj4ij5xtnhpsfelky" , "bafyreiafmbgul64c4nyybvgivswmkuhifamc24cdfuj4ij5xtnhpsfelky", merkleNode.hash.toString()); 504 | 505 | // Get a DAG node 506 | byte[] res = ipfs.dag.get((Cid) merkleNode.hash); 507 | assertEquals("Should be equals", JSONParser.parse(json), JSONParser.parse(new String(res))); 508 | 509 | // Publish to IPNS 510 | Map result = ipfs.name.publish(merkleNode.hash); 511 | 512 | // Resolve from IPNS 513 | String resolved = ipfs.name.resolve(Cid.decode((String) result.get("Name"))); 514 | assertEquals("Should be equals", resolved, "/ipfs/" + merkleNode.hash); 515 | } 516 | 517 | @Test 518 | public void pubsubSynchronous() { 519 | String topic = "topic" + System.nanoTime(); 520 | List> res = Collections.synchronizedList(new ArrayList<>()); 521 | new Thread(() -> { 522 | try { 523 | ipfs.pubsub.sub(topic, res::add, t -> t.printStackTrace()); 524 | } catch (IOException e) { 525 | throw new RuntimeException(e);} 526 | }).start(); 527 | 528 | int nMessages = 100; 529 | for (int i = 1; i < nMessages; ) { 530 | ipfs.pubsub.pub(topic, "Hello World!"); 531 | if (res.size() >= i) { 532 | i++; 533 | } 534 | } 535 | Assert.assertTrue(res.size() > nMessages - 5); // pubsub is not reliable so it loses messages 536 | } 537 | 538 | @Test 539 | public void pubsub() throws Exception { 540 | String topic = "topic" + System.nanoTime(); 541 | Stream> sub = ipfs.pubsub.sub(topic); 542 | String data = "Hello World!"; 543 | ipfs.pubsub.pub(topic, data); 544 | ipfs.pubsub.pub(topic, "G'day"); 545 | List results = sub.limit(2).collect(Collectors.toList()); 546 | Assert.assertNotEquals(results.get(0), Collections.emptyMap()); 547 | } 548 | 549 | private static String toEscapedHex(byte[] in) throws IOException { 550 | StringBuilder res = new StringBuilder(); 551 | for (byte b : in) { 552 | res.append("\\x"); 553 | res.append(String.format("%02x", b & 0xFF)); 554 | } 555 | return res.toString(); 556 | } 557 | 558 | /** 559 | * Test that merkle links in values of a cbor map are followed during recursive pins 560 | */ 561 | @Test 562 | public void merkleLinkInMap() throws IOException { 563 | Random r = new Random(); 564 | CborObject.CborByteArray target = new CborObject.CborByteArray(("g'day IPFS!").getBytes()); 565 | byte[] rawTarget = target.toByteArray(); 566 | MerkleNode targetRes = ipfs.block.put(Collections.singletonList(rawTarget), Optional.of("cbor")).get(0); 567 | 568 | CborObject.CborMerkleLink link = new CborObject.CborMerkleLink(targetRes.hash); 569 | Map m = new TreeMap<>(); 570 | m.put("alink", link); 571 | m.put("arr", new CborObject.CborList(Collections.emptyList())); 572 | CborObject.CborMap source = CborObject.CborMap.build(m); 573 | byte[] rawSource = source.toByteArray(); 574 | MerkleNode sourceRes = ipfs.block.put(Collections.singletonList(rawSource), Optional.of("cbor")).get(0); 575 | 576 | CborObject.fromByteArray(rawSource); 577 | 578 | List add = ipfs.pin.add(sourceRes.hash); 579 | ipfs.repo.gc(); 580 | ipfs.repo.gc(); 581 | 582 | List refs = ipfs.refs(sourceRes.hash, true); 583 | Assert.assertTrue("refs returns links", refs.contains(targetRes.hash)); 584 | 585 | byte[] bytes = ipfs.block.get(targetRes.hash); 586 | assertArrayEquals("same contents after GC", bytes, rawTarget); 587 | // These commands can be used to reproduce this on the command line 588 | String reproCommand1 = "printf \"" + toEscapedHex(rawTarget) + "\" | ipfs block put --format=cbor"; 589 | String reproCommand2 = "printf \"" + toEscapedHex(rawSource) + "\" | ipfs block put --format=cbor"; 590 | System.out.println(); 591 | } 592 | 593 | @Test 594 | public void recursiveRefs() throws IOException { 595 | CborObject.CborByteArray leaf1 = new CborObject.CborByteArray(("G'day IPFS!").getBytes()); 596 | byte[] rawLeaf1 = leaf1.toByteArray(); 597 | MerkleNode leaf1Res = ipfs.block.put(Collections.singletonList(rawLeaf1), Optional.of("cbor")).get(0); 598 | 599 | CborObject.CborMerkleLink link = new CborObject.CborMerkleLink(leaf1Res.hash); 600 | Map m = new TreeMap<>(); 601 | m.put("link1", link); 602 | CborObject.CborMap source = CborObject.CborMap.build(m); 603 | MerkleNode sourceRes = ipfs.block.put(Collections.singletonList(source.toByteArray()), Optional.of("cbor")).get(0); 604 | 605 | CborObject.CborByteArray leaf2 = new CborObject.CborByteArray(("G'day again, IPFS!").getBytes()); 606 | byte[] rawLeaf2 = leaf2.toByteArray(); 607 | MerkleNode leaf2Res = ipfs.block.put(Collections.singletonList(rawLeaf2), Optional.of("cbor")).get(0); 608 | 609 | Map m2 = new TreeMap<>(); 610 | m2.put("link1", new CborObject.CborMerkleLink(sourceRes.hash)); 611 | m2.put("link2", new CborObject.CborMerkleLink(leaf2Res.hash)); 612 | CborObject.CborMap source2 = CborObject.CborMap.build(m2); 613 | MerkleNode rootRes = ipfs.block.put(Collections.singletonList(source2.toByteArray()), Optional.of("cbor")).get(0); 614 | 615 | List refs = ipfs.refs(rootRes.hash, false); 616 | boolean correct = refs.contains(sourceRes.hash) && refs.contains(leaf2Res.hash) && refs.size() == 2; 617 | Assert.assertTrue("refs returns links", correct); 618 | 619 | List refsRecurse = ipfs.refs(rootRes.hash, true); 620 | boolean correctRecurse = refsRecurse.contains(sourceRes.hash) 621 | && refsRecurse.contains(leaf1Res.hash) 622 | && refsRecurse.contains(leaf2Res.hash) 623 | && refsRecurse.size() == 3; 624 | Assert.assertTrue("refs returns links", correctRecurse); 625 | } 626 | 627 | /** 628 | * Test that merkle links as a root object are followed during recursive pins 629 | */ 630 | @Test 631 | public void rootMerkleLink() throws IOException { 632 | Random r = new Random(); 633 | CborObject.CborByteArray target = new CborObject.CborByteArray(("g'day IPFS!" + r.nextInt()).getBytes()); 634 | byte[] rawTarget = target.toByteArray(); 635 | MerkleNode block1 = ipfs.block.put(Collections.singletonList(rawTarget), Optional.of("cbor")).get(0); 636 | Multihash block1Hash = block1.hash; 637 | byte[] retrievedObj1 = ipfs.block.get(block1Hash); 638 | assertArrayEquals("get inverse of put", retrievedObj1, rawTarget); 639 | 640 | CborObject.CborMerkleLink cbor2 = new CborObject.CborMerkleLink(block1.hash); 641 | byte[] obj2 = cbor2.toByteArray(); 642 | MerkleNode block2 = ipfs.block.put(Collections.singletonList(obj2), Optional.of("cbor")).get(0); 643 | byte[] retrievedObj2 = ipfs.block.get(block2.hash); 644 | assertArrayEquals("get inverse of put", retrievedObj2, obj2); 645 | 646 | List add = ipfs.pin.add(block2.hash); 647 | ipfs.repo.gc(); 648 | ipfs.repo.gc(); 649 | 650 | byte[] bytes = ipfs.block.get(block1.hash); 651 | assertArrayEquals("same contents after GC", bytes, rawTarget); 652 | // These commands can be used to reproduce this on the command line 653 | String reproCommand1 = "printf \"" + toEscapedHex(rawTarget) + "\" | ipfs block put --format=cbor"; 654 | String reproCommand2 = "printf \"" + toEscapedHex(obj2) + "\" | ipfs block put --format=cbor"; 655 | System.out.println(); 656 | } 657 | 658 | /** 659 | * Test that a cbor null is allowed as an object root 660 | */ 661 | @Test 662 | public void rootNull() throws IOException { 663 | CborObject.CborNull cbor = new CborObject.CborNull(); 664 | byte[] obj = cbor.toByteArray(); 665 | MerkleNode block = ipfs.block.put(Collections.singletonList(obj), Optional.of("cbor")).get(0); 666 | byte[] retrievedObj = ipfs.block.get(block.hash); 667 | assertArrayEquals("get inverse of put", retrievedObj, obj); 668 | 669 | List add = ipfs.pin.add(block.hash); 670 | ipfs.repo.gc(); 671 | ipfs.repo.gc(); 672 | 673 | // These commands can be used to reproduce this on the command line 674 | String reproCommand1 = "printf \"" + toEscapedHex(obj) + "\" | ipfs block put --format=cbor"; 675 | System.out.println(); 676 | } 677 | 678 | /** 679 | * Test that merkle links in a cbor list are followed during recursive pins 680 | */ 681 | @Test 682 | public void merkleLinkInList() throws IOException { 683 | Random r = new Random(); 684 | CborObject.CborByteArray target = new CborObject.CborByteArray(("g'day IPFS!" + r.nextInt()).getBytes()); 685 | byte[] rawTarget = target.toByteArray(); 686 | MerkleNode targetRes = ipfs.block.put(Collections.singletonList(rawTarget), Optional.of("cbor")).get(0); 687 | 688 | CborObject.CborMerkleLink link = new CborObject.CborMerkleLink(targetRes.hash); 689 | CborObject.CborList source = new CborObject.CborList(Collections.singletonList(link)); 690 | byte[] rawSource = source.toByteArray(); 691 | MerkleNode sourceRes = ipfs.block.put(Collections.singletonList(rawSource), Optional.of("cbor")).get(0); 692 | 693 | List add = ipfs.pin.add(sourceRes.hash); 694 | ipfs.repo.gc(); 695 | ipfs.repo.gc(); 696 | 697 | byte[] bytes = ipfs.block.get(targetRes.hash); 698 | assertArrayEquals("same contents after GC", bytes, rawTarget); 699 | // These commands can be used to reproduce this on the command line 700 | String reproCommand1 = "printf \"" + toEscapedHex(rawTarget) + "\" | ipfs block put --format=cbor"; 701 | String reproCommand2 = "printf \"" + toEscapedHex(rawSource) + "\" | ipfs block put --format=cbor"; 702 | } 703 | 704 | @Test 705 | public void fileContentsTest() throws IOException { 706 | ipfs.repo.gc(); 707 | List local = ipfs.refs.local(); 708 | for (Multihash hash: local) { 709 | try { 710 | Map ls = ipfs.file.ls(hash); 711 | return; 712 | } catch (Exception e) {} // non unixfs files will throw an exception here 713 | } 714 | } 715 | 716 | @Test 717 | @Ignore 718 | public void repoTest() throws IOException { 719 | ipfs.repo.gc(); 720 | Multihash res = ipfs.repo.ls(); 721 | //String migration = ipfs.repo.migrate(false); 722 | RepoStat stat = ipfs.repo.stat(false); 723 | RepoStat stat2 = ipfs.repo.stat(true); 724 | Map verify = ipfs.repo.verify(); 725 | Map version = ipfs.repo.version(); 726 | } 727 | @Test 728 | @Ignore("name test may hang forever") 729 | public void nameTest() throws IOException { 730 | MerkleNode pointer = new MerkleNode("QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB"); 731 | Map pub = ipfs.name.publish(pointer.hash); 732 | String name = "key" + System.nanoTime(); 733 | Object gen = ipfs.key.gen(name, Optional.of("rsa"), Optional.of("2048")); 734 | Map mykey = ipfs.name.publish(pointer.hash, Optional.of(name)); 735 | String resolved = ipfs.name.resolve(Cid.decode((String) pub.get("Name"))); 736 | } 737 | 738 | @Test 739 | public void dnsTest() throws IOException { 740 | String domain = "ipfs.io"; 741 | String dns = ipfs.dns(domain, true); 742 | } 743 | 744 | public void mountTest() throws IOException { 745 | Map mount = ipfs.mount(null, null); 746 | } 747 | 748 | @Test 749 | @Ignore("dhtTest may fail with timeout") 750 | public void dhtTest() throws IOException { 751 | MerkleNode raw = ipfs.block.put("Mathematics is wonderful".getBytes(), Optional.of("raw")); 752 | // Map get = ipfs.dht.get(raw.hash); 753 | // Map put = ipfs.dht.put("somekey", "somevalue"); 754 | List> findprovs = ipfs.dht.findprovs(raw.hash); 755 | List peers = ipfs.swarm.peers(); 756 | Map query = ipfs.dht.query(peers.get(0).id); 757 | Map find = ipfs.dht.findpeer(peers.get(0).id); 758 | } 759 | 760 | @Test 761 | public void localId() throws Exception { 762 | Map id = ipfs.id(); 763 | System.out.println(); 764 | } 765 | 766 | @Test 767 | public void statsTest() throws IOException { 768 | Map stats = ipfs.stats.bw(); 769 | Map bitswap = ipfs.stats.bitswap(true); 770 | Map dht = ipfs.stats.dht(); 771 | //{"Message":"can only return stats if Experimental.AcceleratedDHTClient is enabled","Code":0,"Type":"error"} 772 | //requires Map provide = ipfs.stats.provide(); 773 | RepoStat repo = ipfs.stats.repo(false); 774 | } 775 | 776 | public void resolveTest() throws IOException { 777 | Multihash hash = Multihash.fromBase58("QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy"); 778 | Map res = ipfs.resolve("ipns", hash, false); 779 | } 780 | 781 | @Test 782 | @Ignore 783 | public void swarmTest() throws IOException { 784 | Map> addrs = ipfs.swarm.addrs(); 785 | if (addrs.size() > 0) { 786 | boolean contacted = addrs.entrySet().stream() 787 | .anyMatch(e -> { 788 | Multihash target = e.getKey(); 789 | List nodeAddrs = e.getValue(); 790 | boolean contactable = nodeAddrs.stream() 791 | .anyMatch(addr -> { 792 | try { 793 | MultiAddress peer = new MultiAddress(addr.toString() + "/ipfs/" + target.toBase58()); 794 | Map connect = ipfs.swarm.connect(peer); 795 | Map disconnect = ipfs.swarm.disconnect(peer); 796 | return true; 797 | } catch (Exception ex) { 798 | return false; 799 | } 800 | }); 801 | try { 802 | Map id = ipfs.id(target); 803 | Map ping = ipfs.ping(target); 804 | return contactable; 805 | } catch (Exception ex) { 806 | // not all nodes have to be contactable 807 | return false; 808 | } 809 | }); 810 | if (!contacted) 811 | throw new IllegalStateException("Couldn't contact any node!"); 812 | } 813 | List peers = ipfs.swarm.peers(); 814 | } 815 | 816 | @Test 817 | public void versionTest() throws IOException { 818 | Map listenAddrs = ipfs.version.versionDeps(); 819 | System.currentTimeMillis(); 820 | } 821 | 822 | @Test 823 | public void swarmTestFilters() throws IOException { 824 | Map listenAddrs = ipfs.swarm.listenAddrs(); 825 | Map localAddrs = ipfs.swarm.localAddrs(true); 826 | String multiAddrFilter = "/ip4/192.168.0.0/ipcidr/16"; 827 | Map rm = ipfs.swarm.rmFilter(multiAddrFilter); 828 | Map filters = ipfs.swarm.filters(); 829 | List filtersList = (List)filters.get("Strings"); 830 | Assert.assertNull("Filters empty", filtersList); 831 | 832 | Map added = ipfs.swarm.addFilter(multiAddrFilter); 833 | filters = ipfs.swarm.filters(); 834 | filtersList = (List)filters.get("Strings"); 835 | Assert.assertFalse("Filters NOT empty", filtersList.isEmpty()); 836 | rm = ipfs.swarm.rmFilter(multiAddrFilter); 837 | } 838 | 839 | @Test 840 | @Ignore 841 | public void swarmTestPeering() throws IOException { 842 | String id = "INSERT_VAL_HERE"; 843 | Multihash hash = Multihash.fromBase58(id); 844 | String peer = "/ip6/::1/tcp/4001/p2p/" + id; 845 | MultiAddress ma = new MultiAddress(peer); 846 | Map addPeering = ipfs.swarm.addPeering(ma); 847 | Map lsPeering = ipfs.swarm.lsPeering(); 848 | List peeringList = (List)lsPeering.get("Peers"); 849 | Assert.assertFalse("Filters not empty", peeringList.isEmpty()); 850 | Map rmPeering = ipfs.swarm.rmPeering(hash); 851 | lsPeering = ipfs.swarm.lsPeering(); 852 | peeringList = (List)lsPeering.get("Peers"); 853 | Assert.assertTrue("Filters empty", peeringList.isEmpty()); 854 | } 855 | 856 | @Test 857 | public void bitswapTest() throws IOException { 858 | List peers = ipfs.swarm.peers(); 859 | Map ledger = ipfs.bitswap.ledger(peers.get(0).id); 860 | Map want = ipfs.bitswap.wantlist(peers.get(0).id); 861 | //String reprovide = ipfs.bitswap.reprovide(); 862 | Map stat = ipfs.bitswap.stat(); 863 | Map stat2 = ipfs.bitswap.stat(true); 864 | } 865 | @Test 866 | public void bootstrapTest() throws IOException { 867 | List bootstrap = ipfs.bootstrap.list(); 868 | List rm = ipfs.bootstrap.rm(bootstrap.get(0), false); 869 | List add = ipfs.bootstrap.add(bootstrap.get(0)); 870 | List defaultPeers = ipfs.bootstrap.add(); 871 | List peers = ipfs.bootstrap.list(); 872 | } 873 | 874 | @Test 875 | public void cidTest() throws IOException { 876 | List bases = ipfs.cid.bases(true, true); 877 | List codecs = ipfs.cid.codecs(true, true); 878 | Map stat = ipfs.files.stat("/"); 879 | String rootFolderHash = (String)stat.get("Hash"); 880 | Map base32 = ipfs.cid.base32(Cid.decode(rootFolderHash)); 881 | Map format = ipfs.cid.format(Cid.decode(rootFolderHash), 882 | Optional.of("%s"), Optional.of("1"), 883 | Optional.empty(), Optional.empty()); 884 | 885 | List hashes = ipfs.cid.hashes(false, false); 886 | 887 | System.currentTimeMillis(); 888 | } 889 | 890 | @Test 891 | public void diagTest() throws IOException { 892 | Map config = ipfs.config.show(); 893 | Object api = ipfs.config.get("Addresses.API"); 894 | Object val = ipfs.config.get("Datastore.GCPeriod"); 895 | Map setResult = ipfs.config.set("Datastore.GCPeriod", val); 896 | ipfs.config.replace(new NamedStreamable.ByteArrayWrapper(JSONParser.toString(config).getBytes())); 897 | // Object log = ipfs.log(); 898 | Map sys = ipfs.diag.sys(); 899 | List cmds = ipfs.diag.cmds(); 900 | String res = ipfs.diag.clearCmds(); 901 | List cmds2 = ipfs.diag.cmds(true); 902 | //res = ipfs.diag.profile(); 903 | //String profile = "default"; 904 | //ipfs.config.profileApply(profile, true); 905 | //Map entry = ipfs.config("Addresses.API", Optional.of("/ip4/127.0.0.1/tcp/5001"), Optional.empty()); 906 | } 907 | 908 | @Test 909 | public void toolsTest() throws IOException { 910 | String version = ipfs.version(); 911 | int major = Integer.parseInt(version.split("\\.")[0]); 912 | int minor = Integer.parseInt(version.split("\\.")[1]); 913 | assertTrue(major >= 0 && minor >= 4); // Requires at least 0.4.0 914 | Map commands = ipfs.commands(); 915 | } 916 | 917 | @Test(expected = RuntimeException.class) 918 | public void testTimeoutFail() throws IOException { 919 | IPFS ipfs = new IPFS(new MultiAddress("/ip4/127.0.0.1/tcp/5001")).timeout(1000); 920 | ipfs.cat(Multihash.fromBase58("QmYpbSXyiCTYCbyMpzrQNix72nBYB8WRv6i39JqRc8C1ry")); 921 | } 922 | 923 | @Test 924 | public void testTimeoutOK() throws IOException { 925 | IPFS ipfs = new IPFS(new MultiAddress("/ip4/127.0.0.1/tcp/5001")).timeout(1000); 926 | ipfs.cat(Multihash.fromBase58("Qmaisz6NMhDB51cCvNWa1GMS7LU1pAxdF4Ld6Ft9kZEP2a")); 927 | } 928 | 929 | @Test 930 | public void addArgsTest() { 931 | AddArgs args = AddArgs.Builder.newInstance() 932 | .setInline() 933 | .setCidVersion(1) 934 | .build(); 935 | String res = args.toString(); 936 | assertEquals("args toString() format", "[cid-version = 1, inline = true]", res); 937 | String queryStr = args.toQueryString(); 938 | assertEquals("args toQueryString() format", "inline=true&cid-version=1", queryStr); 939 | } 940 | 941 | // this api is disabled until deployment over IPFS is enabled 942 | public void updateTest() throws IOException { 943 | Object check = ipfs.update.check(); 944 | Object update = ipfs.update(); 945 | } 946 | 947 | private byte[] randomBytes(int len) { 948 | byte[] res = new byte[len]; 949 | r.nextBytes(res); 950 | return res; 951 | } 952 | } 953 | -------------------------------------------------------------------------------- /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 | } 39 | 40 | public static void main(String[] a) throws Exception { 41 | new AddTest().add(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/test/java/io/ipfs/api/RecursiveAddTest.java: -------------------------------------------------------------------------------- 1 | package io.ipfs.api; 2 | 3 | import java.io.File; 4 | import java.nio.file.*; 5 | import java.util.*; 6 | 7 | import org.junit.Assert; 8 | import org.junit.BeforeClass; 9 | import org.junit.Rule; 10 | import org.junit.Test; 11 | import org.junit.rules.TemporaryFolder; 12 | 13 | import io.ipfs.multiaddr.MultiAddress; 14 | 15 | public class RecursiveAddTest { 16 | 17 | private final IPFS ipfs = new IPFS(new MultiAddress("/ip4/127.0.0.1/tcp/5001")); 18 | 19 | static File TMPDATA = new File("target/tmpdata"); 20 | 21 | @BeforeClass 22 | public static void createTmpData() { 23 | TMPDATA.mkdirs(); 24 | } 25 | 26 | @Rule 27 | public TemporaryFolder tempFolder = new TemporaryFolder(TMPDATA); 28 | 29 | @Test 30 | public void testAdd() throws Exception { 31 | System.out.println("ipfs version: " + ipfs.version()); 32 | 33 | String EXPECTED = "QmX5fZ6aUxNTAS7ZfYc8f4wPoMx6LctuNbMjuJZ9EmUSr6"; 34 | 35 | Path base = tempFolder.newFolder().toPath(); 36 | Files.write(base.resolve("index.html"), "".getBytes()); 37 | Path js = base.resolve("js"); 38 | js.toFile().mkdirs(); 39 | Files.write(js.resolve("func.js"), "function() {console.log('Hey');}".getBytes()); 40 | 41 | List add = ipfs.add(new NamedStreamable.FileWrapper(base.toFile())); 42 | MerkleNode node = add.get(add.size() - 1); 43 | Assert.assertEquals(EXPECTED, node.hash.toBase58()); 44 | } 45 | 46 | @Test 47 | public void binaryRecursiveAdd() throws Exception { 48 | String EXPECTED = "Qmd1dTx4Z1PHxSHDR9jYoyLJTrYsAau7zLPE3kqo14s84d"; 49 | 50 | Path base = tempFolder.newFolder().toPath(); 51 | base.toFile().mkdirs(); 52 | byte[] bindata = new byte[1024*1024]; 53 | new Random(28).nextBytes(bindata); 54 | Files.write(base.resolve("data.bin"), bindata); 55 | Path js = base.resolve("js"); 56 | js.toFile().mkdirs(); 57 | Files.write(js.resolve("func.js"), "function() {console.log('Hey');}".getBytes()); 58 | 59 | List add = ipfs.add(new NamedStreamable.FileWrapper(base.toFile())); 60 | MerkleNode node = add.get(add.size() - 1); 61 | Assert.assertEquals(EXPECTED, node.hash.toBase58()); 62 | } 63 | 64 | @Test 65 | public void largeBinaryRecursiveAdd() throws Exception { 66 | String EXPECTED = "QmZdfdj7nfxE68fBPUWAGrffGL3sYGx1MDEozMg73uD2wj"; 67 | 68 | Path base = tempFolder.newFolder().toPath(); 69 | base.toFile().mkdirs(); 70 | byte[] bindata = new byte[100 * 1024*1024]; 71 | new Random(28).nextBytes(bindata); 72 | Files.write(base.resolve("data.bin"), bindata); 73 | new Random(496).nextBytes(bindata); 74 | Files.write(base.resolve("data2.bin"), bindata); 75 | Path js = base.resolve("js"); 76 | js.toFile().mkdirs(); 77 | Files.write(js.resolve("func.js"), "function() {console.log('Hey');}".getBytes()); 78 | 79 | List add = ipfs.add(new NamedStreamable.FileWrapper(base.toFile())); 80 | MerkleNode node = add.get(add.size() - 1); 81 | Assert.assertEquals(EXPECTED, node.hash.toBase58()); 82 | } 83 | 84 | @Test 85 | public void largeBinaryInSubdirRecursiveAdd() throws Exception { 86 | String EXPECTED = "QmUYuMwCpgaxJhNxRA5Pmje8EfpEgU3eQSB9t3VngbxYJk"; 87 | 88 | Path base = tempFolder.newFolder().toPath(); 89 | base.toFile().mkdirs(); 90 | Path bindir = base.resolve("moredata"); 91 | bindir.toFile().mkdirs(); 92 | byte[] bindata = new byte[100 * 1024*1024]; 93 | new Random(28).nextBytes(bindata); 94 | Files.write(bindir.resolve("data.bin"), bindata); 95 | new Random(496).nextBytes(bindata); 96 | Files.write(bindir.resolve("data2.bin"), bindata); 97 | 98 | Path js = base.resolve("js"); 99 | js.toFile().mkdirs(); 100 | Files.write(js.resolve("func.js"), "function() {console.log('Hey');}".getBytes()); 101 | 102 | List add = ipfs.add(new NamedStreamable.FileWrapper(base.toFile())); 103 | MerkleNode node = add.get(add.size() - 1); 104 | Assert.assertEquals(EXPECTED, node.hash.toBase58()); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /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 testAddArgs() throws Exception { 48 | Path path = Paths.get("src/test/resources/html/index.html"); 49 | NamedStreamable file = new FileWrapper(path.toFile()); 50 | AddArgs args = AddArgs.Builder.newInstance() 51 | .setInline() 52 | .setCidVersion(1) 53 | .build(); 54 | List tree = ipfs.add(file, args); 55 | 56 | Assert.assertEquals(1, tree.size()); 57 | Assert.assertEquals("index.html", tree.get(0).name.get()); 58 | } 59 | @Test 60 | public void testFilenameEncoding() throws Exception { 61 | Path path = Paths.get("src/test/resources/folder/你好.html"); 62 | NamedStreamable file = new FileWrapper(path.toFile()); 63 | List tree = ipfs.add(file); 64 | 65 | Assert.assertEquals(1, tree.size()); 66 | Assert.assertEquals("你好.html", tree.get(0).name.get()); 67 | } 68 | 69 | @Test 70 | public void testSingleWrapped() throws Exception { 71 | 72 | Path path = Paths.get("src/test/resources/html/index.html"); 73 | NamedStreamable file = new FileWrapper(path.toFile()); 74 | List tree = ipfs.add(file, true); 75 | 76 | Assert.assertEquals(2, tree.size()); 77 | Assert.assertEquals("index.html", tree.get(0).name.get()); 78 | Assert.assertEquals(cids.get("index.html"), tree.get(0).hash.toBase58()); 79 | } 80 | 81 | @Test 82 | public void testSingleOnlyHash() throws Exception { 83 | 84 | Path path = Paths.get("src/test/resources/html/index.html"); 85 | NamedStreamable file = new FileWrapper(path.toFile()); 86 | List tree = ipfs.add(file, false, true); 87 | 88 | Assert.assertEquals(1, tree.size()); 89 | Assert.assertEquals("index.html", tree.get(0).name.get()); 90 | Assert.assertEquals(cids.get("index.html"), tree.get(0).hash.toBase58()); 91 | } 92 | 93 | @Test 94 | public void testRecursive() throws Exception { 95 | 96 | Path path = Paths.get("src/test/resources/html"); 97 | NamedStreamable file = new FileWrapper(path.toFile()); 98 | List tree = ipfs.add(file); 99 | 100 | Assert.assertEquals(8, tree.size()); 101 | Assert.assertEquals("html", tree.get(7).name.get()); 102 | Assert.assertEquals(cids.get("html"), tree.get(7).hash.toBase58()); 103 | } 104 | 105 | @Test 106 | public void testRecursiveOnlyHash() throws Exception { 107 | 108 | Path path = Paths.get("src/test/resources/html"); 109 | NamedStreamable file = new FileWrapper(path.toFile()); 110 | List tree = ipfs.add(file, false, true); 111 | 112 | Assert.assertEquals(8, tree.size()); 113 | Assert.assertEquals("html", tree.get(7).name.get()); 114 | Assert.assertEquals(cids.get("html"), tree.get(7).hash.toBase58()); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /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/folder/你好.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Blank page 5 | 6 | 7 | 8 |

blank

9 | 10 | 11 | -------------------------------------------------------------------------------- /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/ipfs-shipyard/java-ipfs-http-client/a2b7915ff88afe10eca76a4c931095b9d78734db/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 | --------------------------------------------------------------------------------