7 | *
8 | * Licensed under Apache License v2.0.
9 | */
10 |
11 | import java.io.*;
12 |
13 | import static io.ipfs.api.cbor.CborConstants.*;
14 |
15 | /**
16 | * Provides an encoder capable of encoding data into CBOR format to a given {@link OutputStream}.
17 | */
18 | public class CborEncoder {
19 | private static final int NEG_INT_MASK = TYPE_NEGATIVE_INTEGER << 5;
20 |
21 | private final OutputStream m_os;
22 |
23 | /**
24 | * Creates a new {@link CborEncoder} instance.
25 | *
26 | * @param os the actual output stream to write the CBOR-encoded data to, cannot be null
.
27 | */
28 | public CborEncoder(OutputStream os) {
29 | if (os == null) {
30 | throw new IllegalArgumentException("OutputStream cannot be null!");
31 | }
32 | m_os = os;
33 | }
34 |
35 | /**
36 | * Interprets a given float-value as a half-precision float value and
37 | * converts it to its raw integer form, as defined in IEEE 754.
38 | *
39 | * Taken from: this Stack Overflow answer.
40 | *
41 | *
42 | * @param fval the value to convert.
43 | * @return the raw integer representation of the given float value.
44 | */
45 | static int halfPrecisionToRawIntBits(float fval) {
46 | int fbits = Float.floatToIntBits(fval);
47 | int sign = (fbits >>> 16) & 0x8000;
48 | int val = (fbits & 0x7fffffff) + 0x1000;
49 |
50 | // might be or become NaN/Inf
51 | if (val >= 0x47800000) {
52 | if ((fbits & 0x7fffffff) >= 0x47800000) { // is or must become NaN/Inf
53 | if (val < 0x7f800000) {
54 | // was value but too large, make it +/-Inf
55 | return sign | 0x7c00;
56 | }
57 | return sign | 0x7c00 | (fbits & 0x007fffff) >>> 13; // keep NaN (and Inf) bits
58 | }
59 | return sign | 0x7bff; // unrounded not quite Inf
60 | }
61 | if (val >= 0x38800000) {
62 | // remains normalized value
63 | return sign | val - 0x38000000 >>> 13; // exp - 127 + 15
64 | }
65 | if (val < 0x33000000) {
66 | // too small for subnormal
67 | return sign; // becomes +/-0
68 | }
69 |
70 | val = (fbits & 0x7fffffff) >>> 23;
71 | // add subnormal bit, round depending on cut off and div by 2^(1-(exp-127+15)) and >> 13 | exp=0
72 | return sign | ((fbits & 0x7fffff | 0x800000) + (0x800000 >>> val - 102) >>> 126 - val);
73 | }
74 |
75 | /**
76 | * Writes the start of an indefinite-length array.
77 | *
78 | * After calling this method, one is expected to write the given number of array elements, which can be of any type. No length checks are performed.
79 | * After all array elements are written, one should write a single break value to end the array, see {@link #writeBreak()}.
80 | *
81 | *
82 | * @throws IOException in case of I/O problems writing the CBOR-encoded value to the underlying output stream.
83 | */
84 | public void writeArrayStart() throws IOException {
85 | writeSimpleType(TYPE_ARRAY, BREAK);
86 | }
87 |
88 | /**
89 | * Writes the start of a definite-length array.
90 | *
91 | * After calling this method, one is expected to write the given number of array elements, which can be of any type. No length checks are performed.
92 | *
93 | *
94 | * @param length the number of array elements to write, should >= 0.
95 | * @throws IllegalArgumentException in case the given length was negative;
96 | * @throws IOException in case of I/O problems writing the CBOR-encoded value to the underlying output stream.
97 | */
98 | public void writeArrayStart(int length) throws IOException {
99 | if (length < 0) {
100 | throw new IllegalArgumentException("Invalid array-length!");
101 | }
102 | writeType(TYPE_ARRAY, length);
103 | }
104 |
105 | /**
106 | * Writes a boolean value in canonical CBOR format.
107 | *
108 | * @param value the boolean to write.
109 | * @throws IOException in case of I/O problems writing the CBOR-encoded value to the underlying output stream.
110 | */
111 | public void writeBoolean(boolean value) throws IOException {
112 | writeSimpleType(TYPE_FLOAT_SIMPLE, value ? TRUE : FALSE);
113 | }
114 |
115 | /**
116 | * Writes a "break" stop-value in canonical CBOR format.
117 | *
118 | * @throws IOException in case of I/O problems writing the CBOR-encoded value to the underlying output stream.
119 | */
120 | public void writeBreak() throws IOException {
121 | writeSimpleType(TYPE_FLOAT_SIMPLE, BREAK);
122 | }
123 |
124 | /**
125 | * Writes a byte string in canonical CBOR-format.
126 | *
127 | * @param bytes the byte string to write, can be null
in which case a byte-string of length 0 is written.
128 | * @throws IOException in case of I/O problems writing the CBOR-encoded value to the underlying output stream.
129 | */
130 | public void writeByteString(byte[] bytes) throws IOException {
131 | writeString(TYPE_BYTE_STRING, bytes);
132 | }
133 |
134 | /**
135 | * Writes the start of an indefinite-length byte string.
136 | *
137 | * After calling this method, one is expected to write the given number of string parts. No length checks are performed.
138 | * After all string parts are written, one should write a single break value to end the string, see {@link #writeBreak()}.
139 | *
140 | *
141 | * @throws IOException in case of I/O problems writing the CBOR-encoded value to the underlying output stream.
142 | */
143 | public void writeByteStringStart() throws IOException {
144 | writeSimpleType(TYPE_BYTE_STRING, BREAK);
145 | }
146 |
147 | /**
148 | * Writes a double-precision float value in canonical CBOR format.
149 | *
150 | * @param value the value to write, values from {@link Double#MIN_VALUE} to {@link Double#MAX_VALUE} are supported.
151 | * @throws IOException in case of I/O problems writing the CBOR-encoded value to the underlying output stream.
152 | */
153 | public void writeDouble(double value) throws IOException {
154 | writeUInt64(TYPE_FLOAT_SIMPLE << 5, Double.doubleToRawLongBits(value));
155 | }
156 |
157 | /**
158 | * Writes a single-precision float value in canonical CBOR format.
159 | *
160 | * @param value the value to write, values from {@link Float#MIN_VALUE} to {@link Float#MAX_VALUE} are supported.
161 | * @throws IOException in case of I/O problems writing the CBOR-encoded value to the underlying output stream.
162 | */
163 | public void writeFloat(float value) throws IOException {
164 | writeUInt32(TYPE_FLOAT_SIMPLE << 5, Float.floatToRawIntBits(value));
165 | }
166 |
167 | /**
168 | * Writes a half-precision float value in canonical CBOR format.
169 | *
170 | * @param value the value to write, values from {@link Float#MIN_VALUE} to {@link Float#MAX_VALUE} are supported.
171 | * @throws IOException in case of I/O problems writing the CBOR-encoded value to the underlying output stream.
172 | */
173 | public void writeHalfPrecisionFloat(float value) throws IOException {
174 | writeUInt16(TYPE_FLOAT_SIMPLE << 5, halfPrecisionToRawIntBits(value));
175 | }
176 |
177 | /**
178 | * Writes a signed or unsigned integer value in canonical CBOR format, that is, tries to encode it in a little bytes as possible..
179 | *
180 | * @param value the value to write, values from {@link Long#MIN_VALUE} to {@link Long#MAX_VALUE} are supported.
181 | * @throws IOException in case of I/O problems writing the CBOR-encoded value to the underlying output stream.
182 | */
183 | public void writeInt(long value) throws IOException {
184 | // extends the sign over all bits...
185 | long sign = value >> 63;
186 | // in case value is negative, this bit should be set...
187 | int mt = (int) (sign & NEG_INT_MASK);
188 | // complement negative value...
189 | value = (sign ^ value);
190 |
191 | writeUInt(mt, value);
192 | }
193 |
194 | /**
195 | * Writes a signed or unsigned 16-bit integer value in CBOR format.
196 | *
197 | * @param value the value to write, values from [-65536..65535] are supported.
198 | * @throws IOException in case of I/O problems writing the CBOR-encoded value to the underlying output stream.
199 | */
200 | public void writeInt16(int value) throws IOException {
201 | // extends the sign over all bits...
202 | int sign = value >> 31;
203 | // in case value is negative, this bit should be set...
204 | int mt = (int) (sign & NEG_INT_MASK);
205 | // complement negative value...
206 | writeUInt16(mt, (sign ^ value) & 0xffff);
207 | }
208 |
209 | /**
210 | * Writes a signed or unsigned 32-bit integer value in CBOR format.
211 | *
212 | * @param value the value to write, values in the range [-4294967296..4294967295] are supported.
213 | * @throws IOException in case of I/O problems writing the CBOR-encoded value to the underlying output stream.
214 | */
215 | public void writeInt32(long value) throws IOException {
216 | // extends the sign over all bits...
217 | long sign = value >> 63;
218 | // in case value is negative, this bit should be set...
219 | int mt = (int) (sign & NEG_INT_MASK);
220 | // complement negative value...
221 | writeUInt32(mt, (int) ((sign ^ value) & 0xffffffffL));
222 | }
223 |
224 | /**
225 | * Writes a signed or unsigned 64-bit integer value in CBOR format.
226 | *
227 | * @param value the value to write, values from {@link Long#MIN_VALUE} to {@link Long#MAX_VALUE} are supported.
228 | * @throws IOException in case of I/O problems writing the CBOR-encoded value to the underlying output stream.
229 | */
230 | public void writeInt64(long value) throws IOException {
231 | // extends the sign over all bits...
232 | long sign = value >> 63;
233 | // in case value is negative, this bit should be set...
234 | int mt = (int) (sign & NEG_INT_MASK);
235 | // complement negative value...
236 | writeUInt64(mt, sign ^ value);
237 | }
238 |
239 | /**
240 | * Writes a signed or unsigned 8-bit integer value in CBOR format.
241 | *
242 | * @param value the value to write, values in the range [-256..255] are supported.
243 | * @throws IOException in case of I/O problems writing the CBOR-encoded value to the underlying output stream.
244 | */
245 | public void writeInt8(int value) throws IOException {
246 | // extends the sign over all bits...
247 | int sign = value >> 31;
248 | // in case value is negative, this bit should be set...
249 | int mt = (int) (sign & NEG_INT_MASK);
250 | // complement negative value...
251 | writeUInt8(mt, (sign ^ value) & 0xff);
252 | }
253 |
254 | /**
255 | * Writes the start of an indefinite-length map.
256 | *
257 | * After calling this method, one is expected to write any number of map entries, as separate key and value. Keys and values can both be of any type. No length checks are performed.
258 | * After all map entries are written, one should write a single break value to end the map, see {@link #writeBreak()}.
259 | *
260 | *
261 | * @throws IOException in case of I/O problems writing the CBOR-encoded value to the underlying output stream.
262 | */
263 | public void writeMapStart() throws IOException {
264 | writeSimpleType(TYPE_MAP, BREAK);
265 | }
266 |
267 | /**
268 | * Writes the start of a finite-length map.
269 | *
270 | * After calling this method, one is expected to write any number of map entries, as separate key and value. Keys and values can both be of any type. No length checks are performed.
271 | *
272 | *
273 | * @param length the number of map entries to write, should >= 0.
274 | * @throws IllegalArgumentException in case the given length was negative;
275 | * @throws IOException in case of I/O problems writing the CBOR-encoded value to the underlying output stream.
276 | */
277 | public void writeMapStart(int length) throws IOException {
278 | if (length < 0) {
279 | throw new IllegalArgumentException("Invalid length of map!");
280 | }
281 | writeType(TYPE_MAP, length);
282 | }
283 |
284 | /**
285 | * Writes a null
value in canonical CBOR format.
286 | *
287 | * @throws IOException in case of I/O problems writing the CBOR-encoded value to the underlying output stream.
288 | */
289 | public void writeNull() throws IOException {
290 | writeSimpleType(TYPE_FLOAT_SIMPLE, NULL);
291 | }
292 |
293 | /**
294 | * Writes a simple value, i.e., an "atom" or "constant" value in canonical CBOR format.
295 | *
296 | * @param simpleValue the (unsigned byte) value to write, values from 32 to 255 are supported (though not enforced).
297 | * @throws IOException in case of I/O problems writing the CBOR-encoded value to the underlying output stream.
298 | */
299 | public void writeSimpleValue(byte simpleValue) throws IOException {
300 | // convert to unsigned value...
301 | int value = (simpleValue & 0xff);
302 | writeType(TYPE_FLOAT_SIMPLE, value);
303 | }
304 |
305 | /**
306 | * Writes a signed or unsigned small (<= 23) integer value in CBOR format.
307 | *
308 | * @param value the value to write, values in the range [-24..23] are supported.
309 | * @throws IOException in case of I/O problems writing the CBOR-encoded value to the underlying output stream.
310 | */
311 | public void writeSmallInt(int value) throws IOException {
312 | // extends the sign over all bits...
313 | int sign = value >> 31;
314 | // in case value is negative, this bit should be set...
315 | int mt = (int) (sign & NEG_INT_MASK);
316 | // complement negative value...
317 | value = Math.min(0x17, (sign ^ value));
318 |
319 | m_os.write((int) (mt | value));
320 | }
321 |
322 | /**
323 | * Writes a semantic tag in canonical CBOR format.
324 | *
325 | * @param tag the tag to write, should >= 0.
326 | * @throws IllegalArgumentException in case the given tag was negative;
327 | * @throws IOException in case of I/O problems writing the CBOR-encoded value to the underlying output stream.
328 | */
329 | public void writeTag(long tag) throws IOException {
330 | if (tag < 0) {
331 | throw new IllegalArgumentException("Invalid tag specification, cannot be negative!");
332 | }
333 | writeType(TYPE_TAG, tag);
334 | }
335 |
336 | /**
337 | * Writes an UTF-8 string in canonical CBOR-format.
338 | *
339 | * Note that this method is platform specific, as the given string value will be encoded in a byte array
340 | * using the platform encoding! This means that the encoding must be standardized and known.
341 | *
342 | *
343 | * @param value the UTF-8 string to write, can be null
in which case an UTF-8 string of length 0 is written.
344 | * @throws IOException in case of I/O problems writing the CBOR-encoded value to the underlying output stream.
345 | */
346 | public void writeTextString(String value) throws IOException {
347 | writeString(TYPE_TEXT_STRING, value == null ? null : value.getBytes("UTF-8"));
348 | }
349 |
350 | /**
351 | * Writes the start of an indefinite-length UTF-8 string.
352 | *
353 | * After calling this method, one is expected to write the given number of string parts. No length checks are performed.
354 | * After all string parts are written, one should write a single break value to end the string, see {@link #writeBreak()}.
355 | *
356 | *
357 | * @throws IOException in case of I/O problems writing the CBOR-encoded value to the underlying output stream.
358 | */
359 | public void writeTextStringStart() throws IOException {
360 | writeSimpleType(TYPE_TEXT_STRING, BREAK);
361 | }
362 |
363 | /**
364 | * Writes an "undefined" value in canonical CBOR format.
365 | *
366 | * @throws IOException in case of I/O problems writing the CBOR-encoded value to the underlying output stream.
367 | */
368 | public void writeUndefined() throws IOException {
369 | writeSimpleType(TYPE_FLOAT_SIMPLE, UNDEFINED);
370 | }
371 |
372 | /**
373 | * Encodes and writes the major type and value as a simple type.
374 | *
375 | * @param majorType the major type of the value to write, denotes what semantics the written value has;
376 | * @param value the value to write, values from [0..31] are supported.
377 | * @throws IOException in case of I/O problems writing the CBOR-encoded value to the underlying output stream.
378 | */
379 | protected void writeSimpleType(int majorType, int value) throws IOException {
380 | m_os.write((majorType << 5) | (value & 0x1f));
381 | }
382 |
383 | /**
384 | * Writes a byte string in canonical CBOR-format.
385 | *
386 | * @param majorType the major type of the string, should be either 0x40 or 0x60;
387 | * @param bytes the byte string to write, can be null
in which case a byte-string of length 0 is written.
388 | * @throws IOException in case of I/O problems writing the CBOR-encoded value to the underlying output stream.
389 | */
390 | protected void writeString(int majorType, byte[] bytes) throws IOException {
391 | int len = (bytes == null) ? 0 : bytes.length;
392 | writeType(majorType, len);
393 | for (int i = 0; i < len; i++) {
394 | m_os.write(bytes[i]);
395 | }
396 | }
397 |
398 | /**
399 | * Encodes and writes the major type indicator with a given payload (length).
400 | *
401 | * @param majorType the major type of the value to write, denotes what semantics the written value has;
402 | * @param value the value to write, values from {@link Long#MIN_VALUE} to {@link Long#MAX_VALUE} are supported.
403 | * @throws IOException in case of I/O problems writing the CBOR-encoded value to the underlying output stream.
404 | */
405 | protected void writeType(int majorType, long value) throws IOException {
406 | writeUInt((majorType << 5), value);
407 | }
408 |
409 | /**
410 | * Encodes and writes an unsigned integer value, that is, tries to encode it in a little bytes as possible.
411 | *
412 | * @param mt the major type of the value to write, denotes what semantics the written value has;
413 | * @param value the value to write, values from {@link Long#MIN_VALUE} to {@link Long#MAX_VALUE} are supported.
414 | * @throws IOException in case of I/O problems writing the CBOR-encoded value to the underlying output stream.
415 | */
416 | protected void writeUInt(int mt, long value) throws IOException {
417 | if (value < 0x18L) {
418 | m_os.write((int) (mt | value));
419 | } else if (value < 0x100L) {
420 | writeUInt8(mt, (int) value);
421 | } else if (value < 0x10000L) {
422 | writeUInt16(mt, (int) value);
423 | } else if (value < 0x100000000L) {
424 | writeUInt32(mt, (int) value);
425 | } else {
426 | writeUInt64(mt, value);
427 | }
428 | }
429 |
430 | /**
431 | * Encodes and writes an unsigned 16-bit integer value
432 | *
433 | * @param mt the major type of the value to write, denotes what semantics the written value has;
434 | * @param value the value to write, values from {@link Long#MIN_VALUE} to {@link Long#MAX_VALUE} are supported.
435 | * @throws IOException in case of I/O problems writing the CBOR-encoded value to the underlying output stream.
436 | */
437 | protected void writeUInt16(int mt, int value) throws IOException {
438 | m_os.write(mt | TWO_BYTES);
439 | m_os.write(value >> 8);
440 | m_os.write(value & 0xFF);
441 | }
442 |
443 | /**
444 | * Encodes and writes an unsigned 32-bit integer value
445 | *
446 | * @param mt the major type of the value to write, denotes what semantics the written value has;
447 | * @param value the value to write, values from {@link Long#MIN_VALUE} to {@link Long#MAX_VALUE} are supported.
448 | * @throws IOException in case of I/O problems writing the CBOR-encoded value to the underlying output stream.
449 | */
450 | protected void writeUInt32(int mt, int value) throws IOException {
451 | m_os.write(mt | FOUR_BYTES);
452 | m_os.write(value >> 24);
453 | m_os.write(value >> 16);
454 | m_os.write(value >> 8);
455 | m_os.write(value & 0xFF);
456 | }
457 |
458 | /**
459 | * Encodes and writes an unsigned 64-bit integer value
460 | *
461 | * @param mt the major type of the value to write, denotes what semantics the written value has;
462 | * @param value the value to write, values from {@link Long#MIN_VALUE} to {@link Long#MAX_VALUE} are supported.
463 | * @throws IOException in case of I/O problems writing the CBOR-encoded value to the underlying output stream.
464 | */
465 | protected void writeUInt64(int mt, long value) throws IOException {
466 | m_os.write(mt | EIGHT_BYTES);
467 | m_os.write((int) (value >> 56));
468 | m_os.write((int) (value >> 48));
469 | m_os.write((int) (value >> 40));
470 | m_os.write((int) (value >> 32));
471 | m_os.write((int) (value >> 24));
472 | m_os.write((int) (value >> 16));
473 | m_os.write((int) (value >> 8));
474 | m_os.write((int) (value & 0xFF));
475 | }
476 |
477 | /**
478 | * Encodes and writes an unsigned 8-bit integer value
479 | *
480 | * @param mt the major type of the value to write, denotes what semantics the written value has;
481 | * @param value the value to write, values from {@link Long#MIN_VALUE} to {@link Long#MAX_VALUE} are supported.
482 | * @throws IOException in case of I/O problems writing the CBOR-encoded value to the underlying output stream.
483 | */
484 | protected void writeUInt8(int mt, int value) throws IOException {
485 | m_os.write(mt | ONE_BYTE);
486 | m_os.write(value & 0xFF);
487 | }
488 | }
--------------------------------------------------------------------------------
/src/main/java/io/ipfs/api/cbor/CborObject.java:
--------------------------------------------------------------------------------
1 | package io.ipfs.api.cbor;
2 |
3 | import io.ipfs.cid.*;
4 | import io.ipfs.multiaddr.*;
5 | import io.ipfs.multihash.*;
6 |
7 | import java.io.*;
8 | import java.util.*;
9 | import java.util.stream.*;
10 |
11 | public interface CborObject {
12 |
13 | void serialize(CborEncoder encoder);
14 |
15 | default byte[] toByteArray() {
16 | ByteArrayOutputStream bout = new ByteArrayOutputStream();
17 | CborEncoder encoder = new CborEncoder(bout);
18 | serialize(encoder);
19 | return bout.toByteArray();
20 | }
21 |
22 | int LINK_TAG = 42;
23 |
24 | static CborObject fromByteArray(byte[] cbor) {
25 | return deserialize(new CborDecoder(new ByteArrayInputStream(cbor)));
26 | }
27 |
28 | static CborObject deserialize(CborDecoder decoder) {
29 | try {
30 | CborType type = decoder.peekType();
31 | switch (type.getMajorType()) {
32 | case CborConstants.TYPE_TEXT_STRING:
33 | return new CborString(decoder.readTextString());
34 | case CborConstants.TYPE_BYTE_STRING:
35 | return new CborByteArray(decoder.readByteString());
36 | case CborConstants.TYPE_UNSIGNED_INTEGER:
37 | return new CborLong(decoder.readInt());
38 | case CborConstants.TYPE_NEGATIVE_INTEGER:
39 | return new CborLong(decoder.readInt());
40 | case CborConstants.TYPE_FLOAT_SIMPLE:
41 | if (type.getAdditionalInfo() == CborConstants.NULL) {
42 | decoder.readNull();
43 | return new CborNull();
44 | }
45 | if (type.getAdditionalInfo() == CborConstants.TRUE) {
46 | decoder.readBoolean();
47 | return new CborBoolean(true);
48 | }
49 | if (type.getAdditionalInfo() == CborConstants.FALSE) {
50 | decoder.readBoolean();
51 | return new CborBoolean(false);
52 | }
53 | throw new IllegalStateException("Unimplemented simple type! " + type.getAdditionalInfo());
54 | case CborConstants.TYPE_MAP: {
55 | long nValues = decoder.readMapLength();
56 | SortedMap result = new TreeMap<>();
57 | for (long i=0; i < nValues; i++) {
58 | CborObject key = deserialize(decoder);
59 | CborObject value = deserialize(decoder);
60 | result.put(key, value);
61 | }
62 | return new CborMap(result);
63 | }
64 | case CborConstants.TYPE_ARRAY:
65 | long nItems = decoder.readArrayLength();
66 | List res = new ArrayList<>((int) nItems);
67 | for (long i=0; i < nItems; i++)
68 | res.add(deserialize(decoder));
69 | return new CborList(res);
70 | case CborConstants.TYPE_TAG:
71 | long tag = decoder.readTag();
72 | if (tag == LINK_TAG) {
73 | CborObject value = deserialize(decoder);
74 | if (value instanceof CborString)
75 | return new CborMerkleLink(Cid.decode(((CborString) value).value));
76 | if (value instanceof CborByteArray) {
77 | byte[] bytes = ((CborByteArray) value).value;
78 | if (bytes[0] == 0) // multibase for binary
79 | return new CborMerkleLink(Cid.cast(Arrays.copyOfRange(bytes, 1, bytes.length)));
80 | throw new IllegalStateException("Unknown Multibase decoding Merkle link: " + bytes[0]);
81 | }
82 | throw new IllegalStateException("Invalid type for merkle link: " + value);
83 | }
84 | throw new IllegalStateException("Unknown TAG in CBOR: " + type.getAdditionalInfo());
85 | default:
86 | throw new IllegalStateException("Unimplemented cbor type: " + type);
87 | }
88 | } catch (IOException e) {
89 | throw new RuntimeException(e);
90 | }
91 | }
92 |
93 | final class CborMap implements CborObject {
94 | public final SortedMap values;
95 |
96 | public CborMap(SortedMap values) {
97 | this.values = values;
98 | }
99 |
100 | public static CborMap build(Map values) {
101 | SortedMap transformed = values.entrySet()
102 | .stream()
103 | .collect(Collectors.toMap(
104 | e -> new CborString(e.getKey()),
105 | e -> e.getValue(),
106 | (a, b) -> a, TreeMap::new));
107 | return new CborMap(transformed);
108 | }
109 |
110 | @Override
111 | public void serialize(CborEncoder encoder) {
112 | try {
113 | encoder.writeMapStart(values.size());
114 | for (Map.Entry entry : values.entrySet()) {
115 | entry.getKey().serialize(encoder);
116 | entry.getValue().serialize(encoder);
117 | }
118 | } catch (IOException e) {
119 | throw new RuntimeException(e);
120 | }
121 | }
122 |
123 | @Override
124 | public boolean equals(Object o) {
125 | if (this == o) return true;
126 | if (o == null || getClass() != o.getClass()) return false;
127 |
128 | CborMap cborMap = (CborMap) o;
129 |
130 | return values != null ? values.equals(cborMap.values) : cborMap.values == null;
131 |
132 | }
133 |
134 | @Override
135 | public int hashCode() {
136 | return values != null ? values.hashCode() : 0;
137 | }
138 | }
139 |
140 | final class CborMerkleLink implements CborObject {
141 | public final Multihash target;
142 |
143 | public CborMerkleLink(Multihash target) {
144 | this.target = target;
145 | }
146 |
147 | @Override
148 | public void serialize(CborEncoder encoder) {
149 | try {
150 | encoder.writeTag(LINK_TAG);
151 | byte[] cid = target.toBytes();
152 | byte[] withMultibaseHeader = new byte[cid.length + 1];
153 | System.arraycopy(cid, 0, withMultibaseHeader, 1, cid.length);
154 | encoder.writeByteString(withMultibaseHeader);
155 | } catch (IOException e) {
156 | throw new RuntimeException(e);
157 | }
158 | }
159 |
160 | @Override
161 | public boolean equals(Object o) {
162 | if (this == o) return true;
163 | if (o == null || getClass() != o.getClass()) return false;
164 |
165 | CborMerkleLink that = (CborMerkleLink) o;
166 |
167 | return target != null ? target.equals(that.target) : that.target == null;
168 |
169 | }
170 |
171 | @Override
172 | public int hashCode() {
173 | return target != null ? target.hashCode() : 0;
174 | }
175 | }
176 |
177 | final class CborList implements CborObject {
178 | public final List value;
179 |
180 | public CborList(List value) {
181 | this.value = value;
182 | }
183 |
184 | @Override
185 | public void serialize(CborEncoder encoder) {
186 | try {
187 | encoder.writeArrayStart(value.size());
188 | for (CborObject object : value) {
189 | object.serialize(encoder);
190 | }
191 | } catch (IOException e) {
192 | throw new RuntimeException(e);
193 | }
194 | }
195 |
196 | @Override
197 | public boolean equals(Object o) {
198 | if (this == o) return true;
199 | if (o == null || getClass() != o.getClass()) return false;
200 |
201 | CborList cborList = (CborList) o;
202 |
203 | return value != null ? value.equals(cborList.value) : cborList.value == null;
204 | }
205 |
206 | @Override
207 | public int hashCode() {
208 | return value != null ? value.hashCode() : 0;
209 | }
210 | }
211 |
212 | final class CborBoolean implements CborObject {
213 | public final boolean value;
214 |
215 | public CborBoolean(boolean value) {
216 | this.value = value;
217 | }
218 |
219 | @Override
220 | public void serialize(CborEncoder encoder) {
221 | try {
222 | encoder.writeBoolean(value);
223 | } catch (IOException e) {
224 | throw new RuntimeException(e);
225 | }
226 | }
227 |
228 | @Override
229 | public boolean equals(Object o) {
230 | if (this == o) return true;
231 | if (o == null || getClass() != o.getClass()) return false;
232 |
233 | CborBoolean that = (CborBoolean) o;
234 |
235 | return value == that.value;
236 |
237 | }
238 |
239 | @Override
240 | public int hashCode() {
241 | return (value ? 1 : 0);
242 | }
243 |
244 | @Override
245 | public String toString() {
246 | return "CborBoolean{" +
247 | value +
248 | '}';
249 | }
250 | }
251 |
252 | final class CborByteArray implements CborObject, Comparable {
253 | public final byte[] value;
254 |
255 | public CborByteArray(byte[] value) {
256 | this.value = value;
257 | }
258 |
259 | @Override
260 | public int compareTo(CborByteArray other) {
261 | return compare(value, other.value);
262 | }
263 |
264 | public static int compare(byte[] a, byte[] b)
265 | {
266 | for (int i=0; i < Math.min(a.length, b.length); i++)
267 | if (a[i] != b[i])
268 | return a[i] & 0xff - b[i] & 0xff;
269 | return 0;
270 | }
271 |
272 | @Override
273 | public void serialize(CborEncoder encoder) {
274 | try {
275 | encoder.writeByteString(value);
276 | } catch (IOException e) {
277 | throw new RuntimeException(e);
278 | }
279 | }
280 |
281 | @Override
282 | public boolean equals(Object o) {
283 | if (this == o) return true;
284 | if (o == null || getClass() != o.getClass()) return false;
285 |
286 | CborByteArray that = (CborByteArray) o;
287 |
288 | return Arrays.equals(value, that.value);
289 |
290 | }
291 |
292 | @Override
293 | public int hashCode() {
294 | return Arrays.hashCode(value);
295 | }
296 | }
297 |
298 | final class CborString implements CborObject, Comparable {
299 |
300 | public final String value;
301 |
302 | public CborString(String value) {
303 | this.value = value;
304 | }
305 |
306 | @Override
307 | public int compareTo(CborString cborString) {
308 | int lenDiff = value.length() - cborString.value.length();
309 | if (lenDiff != 0)
310 | return lenDiff;
311 | return value.compareTo(cborString.value);
312 | }
313 |
314 | @Override
315 | public void serialize(CborEncoder encoder) {
316 | try {
317 | encoder.writeTextString(value);
318 | } catch (IOException e) {
319 | throw new RuntimeException(e);
320 | }
321 | }
322 |
323 | @Override
324 | public boolean equals(Object o) {
325 | if (this == o) return true;
326 | if (o == null || getClass() != o.getClass()) return false;
327 |
328 | CborString that = (CborString) o;
329 |
330 | return value.equals(that.value);
331 |
332 | }
333 |
334 | @Override
335 | public int hashCode() {
336 | return value.hashCode();
337 | }
338 |
339 | @Override
340 | public String toString() {
341 | return "CborString{\"" +
342 | value +
343 | "\"}";
344 | }
345 | }
346 |
347 | final class CborLong implements CborObject, Comparable {
348 | public final long value;
349 |
350 | public CborLong(long value) {
351 | this.value = value;
352 | }
353 |
354 | @Override
355 | public int compareTo(CborLong other) {
356 | return Long.compare(value, other.value);
357 | }
358 |
359 | @Override
360 | public void serialize(CborEncoder encoder) {
361 | try {
362 | encoder.writeInt(value);
363 | } catch (IOException e) {
364 | throw new RuntimeException(e);
365 | }
366 | }
367 |
368 | @Override
369 | public boolean equals(Object o) {
370 | if (this == o) return true;
371 | if (o == null || getClass() != o.getClass()) return false;
372 |
373 | CborLong cborLong = (CborLong) o;
374 |
375 | return value == cborLong.value;
376 |
377 | }
378 |
379 | @Override
380 | public int hashCode() {
381 | return (int) (value ^ (value >>> 32));
382 | }
383 |
384 | @Override
385 | public String toString() {
386 | return "CborLong{" +
387 | value +
388 | '}';
389 | }
390 | }
391 |
392 | final class CborNull implements CborObject, Comparable {
393 | public CborNull() {}
394 |
395 | @Override
396 | public int compareTo(CborNull cborNull) {
397 | return 0;
398 | }
399 |
400 | @Override
401 | public void serialize(CborEncoder encoder) {
402 | try {
403 | encoder.writeNull();
404 | } catch (IOException e) {
405 | throw new RuntimeException(e);
406 | }
407 | }
408 |
409 | @Override
410 | public boolean equals(Object o) {
411 | if (this == o) return true;
412 | if (o == null || getClass() != o.getClass()) return false;
413 |
414 | return true;
415 | }
416 |
417 | @Override
418 | public int hashCode() {
419 | return 0;
420 | }
421 |
422 | @Override
423 | public String toString() {
424 | return "CborNull{}";
425 | }
426 | }
427 | }
428 |
--------------------------------------------------------------------------------
/src/main/java/io/ipfs/api/cbor/CborType.java:
--------------------------------------------------------------------------------
1 | package io.ipfs.api.cbor;
2 |
3 | /*
4 | * JACOB - CBOR implementation in Java.
5 | *
6 | * (C) Copyright - 2013 - J.W. Janssen
7 | *
8 | * Licensed under Apache License v2.0.
9 | */
10 |
11 | import static io.ipfs.api.cbor.CborConstants.*;
12 |
13 | /**
14 | * Represents the various major types in CBOR, along with their .
15 | *
16 | * The major type is encoded in the upper three bits of each initial byte. The lower 5 bytes represent any additional information.
17 | *
18 | */
19 | public class CborType {
20 | private final int m_major;
21 | private final int m_additional;
22 |
23 | private CborType(int major, int additional) {
24 | m_major = major;
25 | m_additional = additional;
26 | }
27 |
28 | /**
29 | * Returns a descriptive string for the given major type.
30 | *
31 | * @param mt the major type to return as string, values from [0..7] are supported.
32 | * @return the name of the given major type, as String, never null
.
33 | * @throws IllegalArgumentException in case the given major type is not supported.
34 | */
35 | public static String getName(int mt) {
36 | switch (mt) {
37 | case TYPE_ARRAY:
38 | return "array";
39 | case TYPE_BYTE_STRING:
40 | return "byte string";
41 | case TYPE_FLOAT_SIMPLE:
42 | return "float/simple value";
43 | case TYPE_MAP:
44 | return "map";
45 | case TYPE_NEGATIVE_INTEGER:
46 | return "negative integer";
47 | case TYPE_TAG:
48 | return "tag";
49 | case TYPE_TEXT_STRING:
50 | return "text string";
51 | case TYPE_UNSIGNED_INTEGER:
52 | return "unsigned integer";
53 | default:
54 | throw new IllegalArgumentException("Invalid major type: " + mt);
55 | }
56 | }
57 |
58 | /**
59 | * Decodes a given byte value to a {@link CborType} value.
60 | *
61 | * @param i the input byte (8-bit) to decode into a {@link CborType} instance.
62 | * @return a {@link CborType} instance, never null
.
63 | */
64 | public static CborType valueOf(int i) {
65 | return new CborType((i & 0xff) >>> 5, i & 0x1f);
66 | }
67 |
68 | @Override
69 | public boolean equals(Object obj) {
70 | if (this == obj) {
71 | return true;
72 | }
73 | if (obj == null || getClass() != obj.getClass()) {
74 | return false;
75 | }
76 |
77 | CborType other = (CborType) obj;
78 | return (m_major == other.m_major) && (m_additional == other.m_additional);
79 | }
80 |
81 | /**
82 | * @return the additional information of this type, as integer value from [0..31].
83 | */
84 | public int getAdditionalInfo() {
85 | return m_additional;
86 | }
87 |
88 | /**
89 | * @return the major type, as integer value from [0..7].
90 | */
91 | public int getMajorType() {
92 | return m_major;
93 | }
94 |
95 | @Override
96 | public int hashCode() {
97 | final int prime = 31;
98 | int result = 1;
99 | result = prime * result + m_additional;
100 | result = prime * result + m_major;
101 | return result;
102 | }
103 |
104 | /**
105 | * @return true
if this type allows for an infinite-length payload,
106 | * false
if only definite-length payloads are allowed.
107 | */
108 | public boolean isBreakAllowed() {
109 | return m_major == TYPE_ARRAY || m_major == TYPE_BYTE_STRING || m_major == TYPE_MAP
110 | || m_major == TYPE_TEXT_STRING;
111 | }
112 |
113 | /**
114 | * Determines whether the major type of a given {@link CborType} equals the major type of this {@link CborType}.
115 | *
116 | * @param other the {@link CborType} to compare against, cannot be null
.
117 | * @return true
if the given {@link CborType} is of the same major type as this {@link CborType}, false
otherwise.
118 | * @throws IllegalArgumentException in case the given argument was null
.
119 | */
120 | public boolean isEqualType(CborType other) {
121 | if (other == null) {
122 | throw new IllegalArgumentException("Parameter cannot be null!");
123 | }
124 | return m_major == other.m_major;
125 | }
126 |
127 | /**
128 | * Determines whether the major type of a given byte value (representing an encoded {@link CborType}) equals the major type of this {@link CborType}.
129 | *
130 | * @param encoded the encoded CBOR type to compare.
131 | * @return true
if the given byte value represents the same major type as this {@link CborType}, false
otherwise.
132 | */
133 | public boolean isEqualType(int encoded) {
134 | return m_major == ((encoded & 0xff) >>> 5);
135 | }
136 |
137 | @Override
138 | public String toString() {
139 | StringBuilder sb = new StringBuilder();
140 | sb.append(getName(m_major)).append('(').append(m_additional).append(')');
141 | return sb.toString();
142 | }
143 | }
--------------------------------------------------------------------------------
/src/main/java/io/ipfs/api/cbor/Cborable.java:
--------------------------------------------------------------------------------
1 | package io.ipfs.api.cbor;
2 |
3 | public interface Cborable {
4 |
5 | CborObject toCbor();
6 |
7 | default byte[] serialize() {
8 | return toCbor().toByteArray();
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/test/java/io/ipfs/api/APITest.java:
--------------------------------------------------------------------------------
1 | package io.ipfs.api;
2 |
3 | import io.ipfs.api.cbor.CborObject;
4 | import io.ipfs.cid.Cid;
5 | import io.ipfs.multiaddr.MultiAddress;
6 | import io.ipfs.multihash.Multihash;
7 | import org.junit.Assert;
8 | import org.junit.Ignore;
9 | import org.junit.Test;
10 |
11 | import java.io.*;
12 | import java.nio.file.Files;
13 | import java.nio.file.Path;
14 | import java.util.*;
15 | import java.util.function.Function;
16 | import java.util.stream.Collectors;
17 | import java.util.stream.Stream;
18 |
19 | import static org.junit.Assert.assertTrue;
20 |
21 | public class APITest {
22 |
23 | private final IPFS ipfs = new IPFS(new MultiAddress("/ip4/127.0.0.1/tcp/5001"));
24 | private final Random r = new Random(33550336); // perfect
25 |
26 | @Test
27 | public void dag() throws IOException {
28 | String original = "{\"data\":1234}";
29 | byte[] object = original.getBytes();
30 | MerkleNode put = ipfs.dag.put("json", object);
31 |
32 | Cid expected = Cid.decode("zdpuAs3whHmb9T1NkHSLGF45ykcKrEBxSLiEx6YpLzmKbQLEB");
33 |
34 | Multihash result = put.hash;
35 | Assert.assertTrue("Correct cid returned", result.equals(expected));
36 |
37 | byte[] get = ipfs.dag.get(expected);
38 | Assert.assertTrue("Raw data equal", original.equals(new String(get).trim()));
39 | }
40 |
41 | @Test
42 | public void dagCbor() throws IOException {
43 | Map tmp = new LinkedHashMap<>();
44 | String value = "G'day mate!";
45 | tmp.put("data", new CborObject.CborString(value));
46 | CborObject original = CborObject.CborMap.build(tmp);
47 | byte[] object = original.toByteArray();
48 | MerkleNode put = ipfs.dag.put("cbor", object);
49 |
50 | Cid cid = (Cid) put.hash;
51 |
52 | byte[] get = ipfs.dag.get(cid);
53 | Assert.assertTrue("Raw data equal", ((Map) JSONParser.parse(new String(get))).get("data").equals(value));
54 |
55 | Cid expected = Cid.decode("zdpuApemz4XMURSCkBr9W5y974MXkSbeDfLeZmiQTPpvkatFF");
56 | Assert.assertTrue("Correct cid returned", cid.equals(expected));
57 | }
58 |
59 | @Test
60 | public void keys() throws IOException {
61 | List existing = ipfs.key.list();
62 | String name = "mykey" + System.nanoTime();
63 | KeyInfo gen = ipfs.key.gen(name, Optional.of("rsa"), Optional.of("2048"));
64 | String newName = "bob" + System.nanoTime();
65 | Object rename = ipfs.key.rename(name, newName);
66 | List rm = ipfs.key.rm(newName);
67 | List remaining = ipfs.key.list();
68 | Assert.assertTrue("removed key", remaining.equals(existing));
69 | }
70 |
71 | @Test
72 | public void ipldNode() {
73 | Function>, CborObject.CborMap> map =
74 | s -> CborObject.CborMap.build(s.collect(Collectors.toMap(p -> p.left, p -> p.right)));
75 | CborObject.CborMap a = map.apply(Stream.of(new Pair<>("b", new CborObject.CborLong(1))));
76 |
77 | CborObject.CborMap cbor = map.apply(Stream.of(new Pair<>("a", a), new Pair<>("c", new CborObject.CborLong(2))));
78 |
79 | IpldNode.CborIpldNode node = new IpldNode.CborIpldNode(cbor);
80 | List tree = node.tree("", -1);
81 | Assert.assertTrue("Correct tree", tree.equals(Arrays.asList("/a/b", "/c")));
82 | }
83 |
84 | @Test
85 | public void singleFileTest() throws IOException {
86 | NamedStreamable.ByteArrayWrapper file = new NamedStreamable.ByteArrayWrapper("hello.txt", "G'day world! IPFS rocks!".getBytes());
87 | fileTest(file);
88 | }
89 |
90 | @Test
91 | public void wrappedSingleFileTest() throws IOException {
92 | NamedStreamable.ByteArrayWrapper file = new NamedStreamable.ByteArrayWrapper("hello.txt", "G'day world! IPFS rocks!".getBytes());
93 | List addParts = ipfs.add(file, true);
94 | MerkleNode filePart = addParts.get(0);
95 | MerkleNode dirPart = addParts.get(1);
96 | byte[] catResult = ipfs.cat(filePart.hash);
97 | byte[] getResult = ipfs.get(filePart.hash);
98 | if (!Arrays.equals(catResult, file.getContents()))
99 | throw new IllegalStateException("Different contents!");
100 | List pinRm = ipfs.pin.rm(dirPart.hash, true);
101 | if (!pinRm.get(0).equals(dirPart.hash))
102 | throw new IllegalStateException("Didn't remove file!");
103 | Object gc = ipfs.repo.gc();
104 | }
105 |
106 | @Test
107 | public void dirTest() throws IOException {
108 | Path test = Files.createTempDirectory("test");
109 | Files.write(test.resolve("file.txt"), "G'day IPFS!".getBytes());
110 | NamedStreamable dir = new NamedStreamable.FileWrapper(test.toFile());
111 | List add = ipfs.add(dir);
112 | MerkleNode addResult = add.get(add.size() - 1);
113 | List ls = ipfs.ls(addResult.hash);
114 | Assert.assertTrue(ls.size() > 0);
115 | }
116 |
117 | @Test
118 | public void directoryTest() throws IOException {
119 | Random rnd = new Random();
120 | String dirName = "folder" + rnd.nextInt(100);
121 | Path tmpDir = Files.createTempDirectory(dirName);
122 |
123 | String fileName = "afile" + rnd.nextInt(100);
124 | Path file = tmpDir.resolve(fileName);
125 | FileOutputStream fout = new FileOutputStream(file.toFile());
126 | byte[] fileContents = "IPFS rocks!".getBytes();
127 | fout.write(fileContents);
128 | fout.flush();
129 | fout.close();
130 |
131 | String subdirName = "subdir";
132 | tmpDir.resolve(subdirName).toFile().mkdir();
133 |
134 | String subfileName = "subdirfile" + rnd.nextInt(100);
135 | Path subdirfile = tmpDir.resolve(subdirName + "/" + subfileName);
136 | FileOutputStream fout2 = new FileOutputStream(subdirfile.toFile());
137 | byte[] file2Contents = "IPFS still rocks!".getBytes();
138 | fout2.write(file2Contents);
139 | fout2.flush();
140 | fout2.close();
141 |
142 | List addParts = ipfs.add(new NamedStreamable.FileWrapper(tmpDir.toFile()));
143 | MerkleNode addResult = addParts.get(addParts.size() - 1);
144 | List lsResult = ipfs.ls(addResult.hash);
145 | if (lsResult.size() != 2)
146 | throw new IllegalStateException("Incorrect number of objects in ls!");
147 | if (!lsResult.stream().map(x -> x.name.get()).collect(Collectors.toSet()).equals(new HashSet<>(Arrays.asList(subdirName, fileName))))
148 | throw new IllegalStateException("Dir not returned in ls!");
149 | byte[] catResult = ipfs.cat(addResult.hash, "/" + fileName);
150 | if (!Arrays.equals(catResult, fileContents))
151 | throw new IllegalStateException("Different contents!");
152 |
153 | byte[] catResult2 = ipfs.cat(addResult.hash, "/" + subdirName + "/" + subfileName);
154 | if (!Arrays.equals(catResult2, file2Contents))
155 | throw new IllegalStateException("Different contents!");
156 | }
157 |
158 | // @Test
159 | public void largeFileTest() throws IOException {
160 | byte[] largerData = new byte[100 * 1024 * 1024];
161 | new Random(1).nextBytes(largerData);
162 | NamedStreamable.ByteArrayWrapper largeFile = new NamedStreamable.ByteArrayWrapper("nontrivial.txt", largerData);
163 | fileTest(largeFile);
164 | }
165 |
166 | // @Test
167 | public void hugeFileStreamTest() throws IOException {
168 | byte[] hugeData = new byte[1000 * 1024 * 1024];
169 | new Random(1).nextBytes(hugeData);
170 | NamedStreamable.ByteArrayWrapper largeFile = new NamedStreamable.ByteArrayWrapper("massive.txt", hugeData);
171 | MerkleNode addResult = ipfs.add(largeFile).get(0);
172 | InputStream in = ipfs.catStream(addResult.hash);
173 |
174 | byte[] res = new byte[hugeData.length];
175 | int offset = 0;
176 | byte[] buf = new byte[4096];
177 | int r;
178 | while ((r = in.read(buf)) >= 0) {
179 | try {
180 | System.arraycopy(buf, 0, res, offset, r);
181 | offset += r;
182 | } catch (Exception e) {
183 | e.printStackTrace();
184 | }
185 | }
186 | if (!Arrays.equals(res, hugeData))
187 | throw new IllegalStateException("Different contents!");
188 | }
189 |
190 | @Test
191 | public void hostFileTest() throws IOException {
192 | Path tempFile = Files.createTempFile("IPFS", "tmp");
193 | BufferedWriter w = new BufferedWriter(new FileWriter(tempFile.toFile()));
194 | w.append("Some data");
195 | w.flush();
196 | w.close();
197 | NamedStreamable hostFile = new NamedStreamable.FileWrapper(tempFile.toFile());
198 | fileTest(hostFile);
199 | }
200 |
201 | @Test
202 | public void hashOnly() throws IOException {
203 | byte[] data = randomBytes(4096);
204 | NamedStreamable file = new NamedStreamable.ByteArrayWrapper(data);
205 | MerkleNode addResult = ipfs.add(file, false, true).get(0);
206 | List local = ipfs.refs.local();
207 | if (local.contains(addResult.hash))
208 | throw new IllegalStateException("Object shouldn't be present!");
209 | }
210 |
211 | public void fileTest(NamedStreamable file) throws IOException {
212 | MerkleNode addResult = ipfs.add(file).get(0);
213 | byte[] catResult = ipfs.cat(addResult.hash);
214 | byte[] getResult = ipfs.get(addResult.hash);
215 | if (!Arrays.equals(catResult, file.getContents()))
216 | throw new IllegalStateException("Different contents!");
217 | List pinRm = ipfs.pin.rm(addResult.hash, true);
218 | if (!pinRm.get(0).equals(addResult.hash))
219 | throw new IllegalStateException("Didn't remove file!");
220 | Object gc = ipfs.repo.gc();
221 | }
222 |
223 | @Test
224 | public void pinTest() throws IOException {
225 | MerkleNode file = ipfs.add(new NamedStreamable.ByteArrayWrapper("some data".getBytes())).get(0);
226 | Multihash hash = file.hash;
227 | Map ls1 = ipfs.pin.ls(IPFS.PinType.all);
228 | boolean pinned = ls1.containsKey(hash);
229 | List rm = ipfs.pin.rm(hash);
230 | // second rm should not throw a http 500, but return an empty list
231 | // List rm2 = ipfs.pin.rm(hash);
232 | List add2 = ipfs.pin.add(hash);
233 | // adding something already pinned should succeed
234 | List add3 = ipfs.pin.add(hash);
235 | Map ls = ipfs.pin.ls(IPFS.PinType.recursive);
236 | ipfs.repo.gc();
237 | // object should still be present after gc
238 | Map ls2 = ipfs.pin.ls(IPFS.PinType.recursive);
239 | boolean stillPinned = ls2.containsKey(hash);
240 | Assert.assertTrue("Pinning works", pinned && stillPinned);
241 | }
242 |
243 | @Test
244 | public void pinUpdate() throws IOException {
245 | MerkleNode child1 = ipfs.add(new NamedStreamable.ByteArrayWrapper("some data".getBytes())).get(0);
246 | Multihash hashChild1 = child1.hash;
247 | System.out.println("child1: " + hashChild1);
248 |
249 | CborObject.CborMerkleLink root1 = new CborObject.CborMerkleLink(hashChild1);
250 | MerkleNode root1Res = ipfs.block.put(Collections.singletonList(root1.toByteArray()), Optional.of("cbor")).get(0);
251 | System.out.println("root1: " + root1Res.hash);
252 | ipfs.pin.add(root1Res.hash);
253 |
254 | CborObject.CborList root2 = new CborObject.CborList(Arrays.asList(new CborObject.CborMerkleLink(hashChild1), new CborObject.CborLong(42)));
255 | MerkleNode root2Res = ipfs.block.put(Collections.singletonList(root2.toByteArray()), Optional.of("cbor")).get(0);
256 | List update = ipfs.pin.update(root1Res.hash, root2Res.hash, true);
257 |
258 | Map ls = ipfs.pin.ls(IPFS.PinType.all);
259 | boolean childPresent = ls.containsKey(hashChild1);
260 | if (!childPresent)
261 | throw new IllegalStateException("Child not present!");
262 |
263 | ipfs.repo.gc();
264 | Map ls2 = ipfs.pin.ls(IPFS.PinType.all);
265 | boolean childPresentAfterGC = ls2.containsKey(hashChild1);
266 | if (!childPresentAfterGC)
267 | throw new IllegalStateException("Child not present!");
268 | }
269 |
270 | @Test
271 | public void rawLeafNodePinUpdate() throws IOException {
272 | MerkleNode child1 = ipfs.block.put("some data".getBytes(), Optional.of("raw"));
273 | Multihash hashChild1 = child1.hash;
274 | System.out.println("child1: " + hashChild1);
275 |
276 | CborObject.CborMerkleLink root1 = new CborObject.CborMerkleLink(hashChild1);
277 | MerkleNode root1Res = ipfs.block.put(Collections.singletonList(root1.toByteArray()), Optional.of("cbor")).get(0);
278 | System.out.println("root1: " + root1Res.hash);
279 | ipfs.pin.add(root1Res.hash);
280 |
281 | MerkleNode child2 = ipfs.block.put("G'day new tree".getBytes(), Optional.of("raw"));
282 | Multihash hashChild2 = child2.hash;
283 |
284 | CborObject.CborList root2 = new CborObject.CborList(Arrays.asList(
285 | new CborObject.CborMerkleLink(hashChild1),
286 | new CborObject.CborMerkleLink(hashChild2),
287 | new CborObject.CborLong(42))
288 | );
289 | MerkleNode root2Res = ipfs.block.put(Collections.singletonList(root2.toByteArray()), Optional.of("cbor")).get(0);
290 | List update = ipfs.pin.update(root1Res.hash, root2Res.hash, false);
291 | }
292 |
293 | @Test
294 | public void indirectPinTest() throws IOException {
295 | Multihash EMPTY = ipfs.object._new(Optional.empty()).hash;
296 | io.ipfs.api.MerkleNode data = ipfs.object.patch(EMPTY, "set-data", Optional.of("childdata".getBytes()), Optional.empty(), Optional.empty());
297 | Multihash child = data.hash;
298 |
299 | io.ipfs.api.MerkleNode tmp1 = ipfs.object.patch(EMPTY, "set-data", Optional.of("parent1_data".getBytes()), Optional.empty(), Optional.empty());
300 | Multihash parent1 = ipfs.object.patch(tmp1.hash, "add-link", Optional.empty(), Optional.of(child.toString()), Optional.of(child)).hash;
301 | ipfs.pin.add(parent1);
302 |
303 | io.ipfs.api.MerkleNode tmp2 = ipfs.object.patch(EMPTY, "set-data", Optional.of("parent2_data".getBytes()), Optional.empty(), Optional.empty());
304 | Multihash parent2 = ipfs.object.patch(tmp2.hash, "add-link", Optional.empty(), Optional.of(child.toString()), Optional.of(child)).hash;
305 | ipfs.pin.add(parent2);
306 | ipfs.pin.rm(parent1, true);
307 |
308 | Map ls = ipfs.pin.ls(IPFS.PinType.all);
309 | boolean childPresent = ls.containsKey(child);
310 | if (!childPresent)
311 | throw new IllegalStateException("Child not present!");
312 |
313 | ipfs.repo.gc();
314 | Map ls2 = ipfs.pin.ls(IPFS.PinType.all);
315 | boolean childPresentAfterGC = ls2.containsKey(child);
316 | if (!childPresentAfterGC)
317 | throw new IllegalStateException("Child not present!");
318 | }
319 |
320 | @Test
321 | public void objectPatch() throws IOException {
322 | MerkleNode obj = ipfs.object._new(Optional.empty());
323 | Multihash base = obj.hash;
324 | // link tests
325 | String linkName = "alink";
326 | MerkleNode addLink = ipfs.object.patch(base, "add-link", Optional.empty(), Optional.of(linkName), Optional.of(base));
327 | MerkleNode withLink = ipfs.object.get(addLink.hash);
328 | if (withLink.links.size() != 1 || !withLink.links.get(0).hash.equals(base) || !withLink.links.get(0).name.get().equals(linkName))
329 | throw new RuntimeException("Added link not correct!");
330 | MerkleNode rmLink = ipfs.object.patch(addLink.hash, "rm-link", Optional.empty(), Optional.of(linkName), Optional.empty());
331 | if (!rmLink.hash.equals(base))
332 | throw new RuntimeException("Adding not inverse of removing link!");
333 |
334 | // data tests
335 | // byte[] data = "some random textual data".getBytes();
336 | byte[] data = new byte[1024];
337 | new Random().nextBytes(data);
338 | MerkleNode patched = ipfs.object.patch(base, "set-data", Optional.of(data), Optional.empty(), Optional.empty());
339 | byte[] patchedResult = ipfs.object.data(patched.hash);
340 | if (!Arrays.equals(patchedResult, data))
341 | throw new RuntimeException("object.patch: returned data != stored data!");
342 |
343 | MerkleNode twicePatched = ipfs.object.patch(patched.hash, "append-data", Optional.of(data), Optional.empty(), Optional.empty());
344 | byte[] twicePatchedResult = ipfs.object.data(twicePatched.hash);
345 | byte[] twice = new byte[2 * data.length];
346 | for (int i = 0; i < 2; i++)
347 | System.arraycopy(data, 0, twice, i * data.length, data.length);
348 | if (!Arrays.equals(twicePatchedResult, twice))
349 | throw new RuntimeException("object.patch: returned data after append != stored data!");
350 |
351 | }
352 |
353 | @Test
354 | public void refsTest() throws IOException {
355 | List local = ipfs.refs.local();
356 | for (Multihash ref : local) {
357 | Object refs = ipfs.refs(ref, false);
358 | }
359 | }
360 |
361 | @Test
362 | public void objectTest() throws IOException {
363 | MerkleNode _new = ipfs.object._new(Optional.empty());
364 | Multihash pointer = Multihash.fromBase58("QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB");
365 | MerkleNode object = ipfs.object.get(pointer);
366 | List newPointer = ipfs.object.put(Arrays.asList(object.toJSONString().getBytes()));
367 | List newPointer2 = ipfs.object.put("json", Arrays.asList(object.toJSONString().getBytes()));
368 | MerkleNode links = ipfs.object.links(pointer);
369 | byte[] data = ipfs.object.data(pointer);
370 | Map stat = ipfs.object.stat(pointer);
371 | }
372 |
373 | @Test
374 | public void blockTest() throws IOException {
375 | MerkleNode pointer = new MerkleNode("QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB");
376 | Map stat = ipfs.block.stat(pointer.hash);
377 | byte[] object = ipfs.block.get(pointer.hash);
378 | List newPointer = ipfs.block.put(Arrays.asList("Some random data...".getBytes()));
379 | }
380 |
381 | @Test
382 | public void bulkBlockTest() throws IOException {
383 | CborObject cbor = new CborObject.CborString("G'day IPFS!");
384 | byte[] raw = cbor.toByteArray();
385 | List bulkPut = ipfs.block.put(Arrays.asList(raw, raw, raw, raw, raw), Optional.of("cbor"));
386 | List hashes = bulkPut.stream().map(m -> m.hash).collect(Collectors.toList());
387 | byte[] result = ipfs.block.get(hashes.get(0));
388 | System.out.println();
389 | }
390 |
391 | @Ignore // Ignored because ipfs frequently times out internally in the publish call
392 | @Test
393 | public void publish() throws Exception {
394 | // JSON document
395 | String json = "{\"name\":\"blogpost\",\"documents\":[]}";
396 |
397 | // Add a DAG node to IPFS
398 | MerkleNode merkleNode = ipfs.dag.put("json", json.getBytes());
399 | Assert.assertEquals("expected to be zdpuAknRh1Kro2r2xBDKiXyTiwA3Nu5XcmvjRPA1VNjH41NF7", "zdpuAknRh1Kro2r2xBDKiXyTiwA3Nu5XcmvjRPA1VNjH41NF7", merkleNode.hash.toString());
400 |
401 | // Get a DAG node
402 | byte[] res = ipfs.dag.get((Cid) merkleNode.hash);
403 | Assert.assertEquals("Should be equals", JSONParser.parse(json), JSONParser.parse(new String(res)));
404 |
405 | // Publish to IPNS
406 | Map result = ipfs.name.publish(merkleNode.hash);
407 |
408 | // Resolve from IPNS
409 | String resolved = ipfs.name.resolve((String) result.get("Name"));
410 | Assert.assertEquals("Should be equals", resolved, "/ipfs/" + merkleNode.hash.toString());
411 | }
412 |
413 | @Test
414 | public void pubsubSynchronous() throws Exception {
415 | String topic = "topic" + System.nanoTime();
416 | List