@ipld/dag-cbor
Version:
JS implementation of DAG-CBOR
210 lines • 6.48 kB
JavaScript
import { Buffer } from 'buffer';
import { garbage } from 'ipld-garbage';
import chai from 'chai';
import * as dagcbor from '../index.js';
import {
bytes,
CID
} from 'multiformats';
const {encode, decode} = dagcbor;
const {assert} = chai;
const test = it;
const same = assert.deepStrictEqual;
describe('dag-cbor', () => {
const obj = {
someKey: 'someValue',
link: CID.parse('QmRgutAxd8t7oGkSm4wmeuByG6M51wcTso6cubDdQtuEfL'),
links: [
CID.parse('QmRgutAxd8t7oGkSm4wmeuByG6M51wcTso6cubDdQtuEfL'),
CID.parse('QmRgutAxd8t7oGkSm4wmeuByG6M51wcTso6cubDdQtuEfL')
],
nested: {
hello: 'world',
link: CID.parse('QmRgutAxd8t7oGkSm4wmeuByG6M51wcTso6cubDdQtuEfL')
},
bytes: new TextEncoder().encode('asdf')
};
const serializedObj = encode(obj);
test('.serialize and .deserialize', () => {
same(bytes.isBinary(serializedObj), true);
same(bytes.toHex(serializedObj).match(/d82a/g).length, 4);
const deserializedObj = decode(serializedObj);
same(deserializedObj, obj);
});
test('.serialize and .deserialize large objects', () => {
const dataSize = 128 * 1024;
const largeObj = { someKey: [].slice.call(new Uint8Array(dataSize)) };
const serialized = encode(largeObj);
same(bytes.isBinary(serialized), true);
const deserialized = decode(serialized);
same(largeObj, deserialized);
});
test('.serialize and .deserialize object with slash as property', () => {
const slashObject = { '/': true };
const serialized = encode(slashObject);
const deserialized = decode(serialized);
same(deserialized, slashObject);
});
test('CIDs have clean for deep comparison', () => {
const deserializedObj = decode(serializedObj);
const actual = deserializedObj.link.bytes.join(',');
const expected = obj.link.bytes.join(',');
same(actual, expected);
});
test('error on circular references', () => {
const circularObj = {};
circularObj.a = circularObj;
assert.throws(() => encode(circularObj), /object contains circular references/);
const circularArr = [circularObj];
circularObj.a = circularArr;
assert.throws(() => encode(circularArr), /object contains circular references/);
});
test('error on encoding undefined', () => {
assert.throws(() => encode(undefined), /\Wundefined\W.*not supported/);
const objWithUndefined = {
a: 'a',
b: undefined
};
assert.throws(() => encode(objWithUndefined), /\Wundefined\W.*not supported/);
});
test('error on encoding IEEE 754 specials', () => {
for (const special of [
NaN,
Infinity,
-Infinity
]) {
assert.throws(() => encode(special), new RegExp(`\\W${ String(special) }\\W.*not supported`));
const objWithSpecial = {
a: 'a',
b: special
};
assert.throws(() => encode(objWithSpecial), new RegExp(`\\W${ String(special) }\\W.*not supported`));
const arrWithSpecial = [
1,
1.1,
-1,
-1.1,
Number.MAX_SAFE_INTEGER,
special,
Number.MIN_SAFE_INTEGER
];
assert.throws(() => encode(arrWithSpecial), new RegExp(`\\W${ String(special) }\\W.*not supported`));
}
});
test('error on decoding IEEE 754 specials', () => {
const cases = [
[
'NaN',
'f97e00'
],
[
'NaN',
'f97ff8'
],
[
'NaN',
'fa7ff80000'
],
[
'NaN',
'fb7ff8000000000000'
],
[
'NaN',
'a2616161616162fb7ff8000000000000'
],
[
'NaN',
'8701fb3ff199999999999a20fbbff199999999999a1b001ffffffffffffffb7ff80000000000003b001ffffffffffffe'
],
[
'Infinity',
'f97c00'
],
[
'Infinity',
'fb7ff0000000000000'
],
[
'Infinity',
'a2616161616162fb7ff0000000000000'
],
[
'Infinity',
'8701fb3ff199999999999a20fbbff199999999999a1b001ffffffffffffffb7ff00000000000003b001ffffffffffffe'
],
[
'-Infinity',
'f9fc00'
],
[
'-Infinity',
'fbfff0000000000000'
],
[
'-Infinity',
'a2616161616162fbfff0000000000000'
],
[
'-Infinity',
'8701fb3ff199999999999a20fbbff199999999999a1b001ffffffffffffffbfff00000000000003b001ffffffffffffe'
]
];
for (const [typ, hex] of cases) {
const byts = bytes.fromHex(hex);
assert.throws(() => decode(byts), new RegExp(`\\W${ typ.replace(/^-/, '') }\\W.*not supported`));
}
});
test('fuzz serialize and deserialize with garbage', function () {
this.timeout(5000);
for (let ii = 0; ii < 1000; ii++) {
const original = garbage(100);
const encoded = encode(original);
const decoded = decode(encoded);
same(decoded, original);
}
});
test('CIDv1', () => {
const link = CID.parse('zdj7Wd8AMwqnhJGQCbFxBVodGSBG84TM7Hs1rcJuQMwTyfEDS');
const encoded = encode({ link });
const decoded = decode(encoded);
same(decoded, { link });
});
test('encode and decode consistency with Uint8Array and Buffer fields', () => {
const buffer = Buffer.from('some data');
const bytes = Uint8Array.from(buffer);
const s1 = encode({ data: buffer });
const s2 = encode({ data: bytes });
same(s1, s2);
const verify = s => {
same(typeof s, 'object');
same(Object.keys(s), ['data']);
assert(s.data instanceof Uint8Array);
same(s.data.buffer, bytes.buffer);
};
verify(decode(s1));
verify(decode(s2));
});
test('reject extraneous, but valid CBOR data after initial top-level object', () => {
assert.throws(() => {
const big = new Uint8Array(serializedObj.length + 1);
big.set(serializedObj, 0);
decode(big);
}, /too many terminals/);
});
test('reject bad CID lead-in', () => {
const encoded = bytes.fromHex('a1646c696e6bd82a582501017012207252523e6591fb8fe553d67ff55a86f84044b46a3e4176e10c58fa529a4aabd5');
assert.throws(() => decode(encoded), /Invalid CID for CBOR tag 42; expected leading 0x00/);
});
test('sloppy decode: coerce undefined', () => {
let encoded = bytes.fromHex('f7');
let decoded = decode(encoded);
same(null, decoded);
encoded = bytes.fromHex('a26362617af763666f6f63626172');
decoded = decode(encoded);
same({
foo: 'bar',
baz: null
}, decoded);
});
});