Add comprehensive installation and setup documentation

- Add GETTING_STARTED.md with quick start guide and development modes
- Add INSTALL.sh automated installation script
- Add INSTALLATION_CHECKLIST.md, INSTALLATION_SUCCESS.md, and INSTALLATION_SUMMARY.md
- Add QUICK_REFERENCE.md for common commands
- Add SETUP_GUIDE.md with detailed setup instructions
- Update README.md with improved project overview
- Add did-wallet app dependencies and node_modules
This commit is contained in:
Dorian
2026-01-27 17:18:21 +00:00
parent a81f655133
commit 0d073fa89e
22658 changed files with 4494151 additions and 6 deletions
+640
View File
@@ -0,0 +1,640 @@
const fixtures = [
{
cbor: 'AA==',
hex: '00',
roundtrip: true,
decoded: 0
},
{
cbor: 'AQ==',
hex: '01',
roundtrip: true,
decoded: 1
},
{
cbor: 'Cg==',
hex: '0a',
roundtrip: true,
decoded: 10
},
{
cbor: 'Fw==',
hex: '17',
roundtrip: true,
decoded: 23
},
{
cbor: 'GBg=',
hex: '1818',
roundtrip: true,
decoded: 24
},
{
cbor: 'GBk=',
hex: '1819',
roundtrip: true,
decoded: 25
},
{
cbor: 'GGQ=',
hex: '1864',
roundtrip: true,
decoded: 100
},
{
cbor: 'GQPo',
hex: '1903e8',
roundtrip: true,
decoded: 1000
},
{
cbor: 'GgAPQkA=',
hex: '1a000f4240',
roundtrip: true,
decoded: 1000000
},
{
cbor: 'GwAAAOjUpRAA',
hex: '1b000000e8d4a51000',
roundtrip: true,
decoded: 1000000000000
},
{
cbor: 'G///////////',
hex: '1bffffffffffffffff',
roundtrip: true,
decoded: BigInt('18446744073709551615')
},
{
cbor: 'wkkBAAAAAAAAAAA=',
hex: 'c249010000000000000000',
roundtrip: true,
decoded: BigInt('18446744073709551616'),
noTagDecodeError: /tag not supported \(2\)/,
noTagEncodeError: /BigInt larger than allowable range/
},
{
cbor: 'O///////////',
hex: '3bffffffffffffffff',
roundtrip: true,
decoded: BigInt('-18446744073709551616')
},
{
cbor: 'w0kBAAAAAAAAAAA=',
hex: 'c349010000000000000000',
roundtrip: true,
decoded: BigInt('-18446744073709551617'),
noTagDecodeError: /tag not supported \(3\)/,
noTagEncodeError: /BigInt larger than allowable range/
},
{
cbor: 'IA==',
hex: '20',
roundtrip: true,
decoded: -1
},
{
cbor: 'KQ==',
hex: '29',
roundtrip: true,
decoded: -10
},
{
cbor: 'OGM=',
hex: '3863',
roundtrip: true,
decoded: -100
},
{
cbor: 'OQPn',
hex: '3903e7',
roundtrip: true,
decoded: -1000
},
{
cbor: '+QAA',
hex: 'f90000',
roundtrip: false,
decoded: 0
},
{
cbor: '+YAA',
hex: 'f98000',
roundtrip: false,
decoded: -0
},
{
cbor: '+TwA',
hex: 'f93c00',
roundtrip: false,
decoded: 1
},
{
cbor: '+z/xmZmZmZma',
hex: 'fb3ff199999999999a',
roundtrip: true,
decoded: 1.1
},
{
cbor: '+T4A',
hex: 'f93e00',
roundtrip: true,
decoded: 1.5
},
{
cbor: '+Xv/',
hex: 'f97bff',
roundtrip: false,
decoded: 65504
},
{
cbor: '+kfDUAA=',
hex: 'fa47c35000',
roundtrip: false,
decoded: 100000
},
{
cbor: '+n9///8=',
hex: 'fa7f7fffff',
roundtrip: true,
decoded: 3.4028234663852886e+38
},
{
cbor: '+3435DyIAHWc',
hex: 'fb7e37e43c8800759c',
roundtrip: true,
decoded: 1e+300
},
{
cbor: '+QAB',
hex: 'f90001',
roundtrip: true,
decoded: 5.960464477539063e-8
},
{
cbor: '+QQA',
hex: 'f90400',
roundtrip: true,
decoded: 0.00006103515625
},
{
cbor: '+cQA',
hex: 'f9c400',
roundtrip: false,
decoded: -4
},
{
cbor: '+8AQZmZmZmZm',
hex: 'fbc010666666666666',
roundtrip: true,
decoded: -4.1
},
{
cbor: '+XwA',
hex: 'f97c00',
roundtrip: true,
diagnostic: Infinity
},
{
cbor: '+X4A',
hex: 'f97e00',
roundtrip: true,
diagnostic: NaN
},
{
cbor: '+fwA',
hex: 'f9fc00',
roundtrip: true,
diagnostic: -Infinity
},
{
cbor: '+n+AAAA=',
hex: 'fa7f800000',
roundtrip: false,
diagnostic: Infinity
},
{
cbor: '+n/AAAA=',
hex: 'fa7fc00000',
roundtrip: false,
diagnostic: NaN
},
{
cbor: '+v+AAAA=',
hex: 'faff800000',
roundtrip: false,
diagnostic: -Infinity
},
{
cbor: '+3/wAAAAAAAA',
hex: 'fb7ff0000000000000',
roundtrip: false,
diagnostic: Infinity
},
{
cbor: '+3/4AAAAAAAA',
hex: 'fb7ff8000000000000',
roundtrip: false,
diagnostic: NaN
},
{
cbor: '+//wAAAAAAAA',
hex: 'fbfff0000000000000',
roundtrip: false,
diagnostic: -Infinity
},
{
cbor: '9A==',
hex: 'f4',
roundtrip: true,
decoded: false
},
{
cbor: '9Q==',
hex: 'f5',
roundtrip: true,
decoded: true
},
{
cbor: '9g==',
hex: 'f6',
roundtrip: true,
decoded: null
},
{
cbor: '9w==',
hex: 'f7',
roundtrip: true,
diagnostic: undefined
},
{
cbor: '8A==',
hex: 'f0',
roundtrip: true,
diagnostic: 'simple(16)',
error: /simple values are not supported/
},
{
cbor: '+Bg=',
hex: 'f818',
roundtrip: true,
diagnostic: 'simple(24)',
error: /simple values are not supported/
},
{
cbor: '+P8=',
hex: 'f8ff',
roundtrip: true,
diagnostic: 'simple(255)',
error: /simple values are not supported/
},
{
cbor: 'wHQyMDEzLTAzLTIxVDIwOjA0OjAwWg==',
hex: 'c074323031332d30332d32315432303a30343a30305a',
roundtrip: false,
diagnostic: '0("2013-03-21T20:04:00Z")'
},
{
cbor: 'wRpRS2ew',
hex: 'c11a514b67b0',
roundtrip: false,
diagnostic: '1(1363896240)'
},
{
cbor: 'wftB1FLZ7CAAAA==',
hex: 'c1fb41d452d9ec200000',
roundtrip: false,
diagnostic: '1(1363896240.5)'
},
{
cbor: '10QBAgME',
hex: 'd74401020304',
roundtrip: false,
diagnostic: '23(h\'01020304\')'
},
{
cbor: '2BhFZElFVEY=',
hex: 'd818456449455446',
roundtrip: false,
diagnostic: '24(h\'6449455446\')'
},
{
cbor: '2CB2aHR0cDovL3d3dy5leGFtcGxlLmNvbQ==',
hex: 'd82076687474703a2f2f7777772e6578616d706c652e636f6d',
roundtrip: false,
diagnostic: '32("http://www.example.com")'
},
{
cbor: 'QA==',
hex: '40',
roundtrip: true,
diagnostic: 'h\'\''
},
{
cbor: 'RAECAwQ=',
hex: '4401020304',
roundtrip: true,
diagnostic: 'h\'01020304\''
},
{
cbor: 'YA==',
hex: '60',
roundtrip: true,
decoded: ''
},
{
cbor: 'YWE=',
hex: '6161',
roundtrip: true,
decoded: 'a'
},
{
cbor: 'ZElFVEY=',
hex: '6449455446',
roundtrip: true,
decoded: 'IETF'
},
{
cbor: 'YiJc',
hex: '62225c',
roundtrip: true,
decoded: '"\\'
},
{
cbor: 'YsO8',
hex: '62c3bc',
roundtrip: true,
decoded: 'ü'
},
{
cbor: 'Y+awtA==',
hex: '63e6b0b4',
roundtrip: true,
decoded: '水'
},
{
cbor: 'ZPCQhZE=',
hex: '64f0908591',
roundtrip: true,
decoded: '\uD800\uDD51'
},
{
cbor: 'gA==',
hex: '80',
roundtrip: true,
decoded: []
},
{
cbor: 'gwECAw==',
hex: '83010203',
roundtrip: true,
decoded: [
1,
2,
3
]
},
{
cbor: 'gwGCAgOCBAU=',
hex: '8301820203820405',
roundtrip: true,
decoded: [
1,
[
2,
3
],
[
4,
5
]
]
},
{
cbor: 'mBkBAgMEBQYHCAkKCwwNDg8QERITFBUWFxgYGBk=',
hex: '98190102030405060708090a0b0c0d0e0f101112131415161718181819',
roundtrip: true,
decoded: [
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
16,
17,
18,
19,
20,
21,
22,
23,
24,
25
]
},
{
cbor: 'oA==',
hex: 'a0',
roundtrip: true,
decoded: {}
},
{
cbor: 'ogECAwQ=',
hex: 'a201020304',
roundtrip: true,
diagnostic: '{1: 2, 3: 4}',
error: /non-string keys not supported/
},
{
cbor: 'omFhAWFiggID',
hex: 'a26161016162820203',
roundtrip: true,
decoded: {
a: 1,
b: [
2,
3
]
}
},
{
cbor: 'gmFhoWFiYWM=',
hex: '826161a161626163',
roundtrip: true,
decoded: [
'a',
{ b: 'c' }
]
},
{
cbor: 'pWFhYUFhYmFCYWNhQ2FkYURhZWFF',
hex: 'a56161614161626142616361436164614461656145',
roundtrip: true,
decoded: {
a: 'A',
b: 'B',
c: 'C',
d: 'D',
e: 'E'
}
},
{
cbor: 'X0IBAkMDBAX/',
hex: '5f42010243030405ff',
roundtrip: false,
diagnostic: '(_ h\'0102\', h\'030405\')',
error: /indefinite length bytes\/strings are not supported/
},
{
cbor: 'f2VzdHJlYWRtaW5n/w==',
hex: '7f657374726561646d696e67ff',
roundtrip: false,
decoded: 'streaming',
error: /indefinite length bytes\/strings are not supported/
},
{
cbor: 'n/8=',
hex: '9fff',
roundtrip: false,
decoded: []
},
{
cbor: 'nwGCAgOfBAX//w==',
hex: '9f018202039f0405ffff',
roundtrip: false,
decoded: [
1,
[
2,
3
],
[
4,
5
]
]
},
{
cbor: 'nwGCAgOCBAX/',
hex: '9f01820203820405ff',
roundtrip: false,
decoded: [
1,
[
2,
3
],
[
4,
5
]
]
},
{
cbor: 'gwGCAgOfBAX/',
hex: '83018202039f0405ff',
roundtrip: false,
decoded: [
1,
[
2,
3
],
[
4,
5
]
]
},
{
cbor: 'gwGfAgP/ggQF',
hex: '83019f0203ff820405',
roundtrip: false,
decoded: [
1,
[
2,
3
],
[
4,
5
]
]
},
{
cbor: 'nwECAwQFBgcICQoLDA0ODxAREhMUFRYXGBgYGf8=',
hex: '9f0102030405060708090a0b0c0d0e0f101112131415161718181819ff',
roundtrip: false,
decoded: [
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
16,
17,
18,
19,
20,
21,
22,
23,
24,
25
]
},
{
cbor: 'v2FhAWFinwID//8=',
hex: 'bf61610161629f0203ffff',
roundtrip: false,
decoded: {
a: 1,
b: [
2,
3
]
}
},
{
cbor: 'gmFhv2FiYWP/',
hex: '826161bf61626163ff',
roundtrip: false,
decoded: [
'a',
{ b: 'c' }
]
},
{
cbor: 'v2NGdW71Y0FtdCH/',
hex: 'bf6346756ef563416d7421ff',
roundtrip: false,
decoded: {
Fun: true,
Amt: -2
}
}
];
export {
fixtures
};
+19
View File
@@ -0,0 +1,19 @@
import {
Token,
Type
} from '../lib/token.js';
export function dateDecoder(obj) {
if (typeof obj !== 'string') {
throw new Error('expected string for tag 1');
}
return new Date(obj);
}
export function dateEncoder(obj) {
if (!(obj instanceof Date)) {
throw new Error('expected Date for "Date" encoder');
}
return [
new Token(Type.tag, 0),
new Token(Type.string, obj.toISOString().replace(/\.000Z$/, 'Z'))
];
}
+339
View File
@@ -0,0 +1,339 @@
import chai from 'chai';
import { exec } from 'child_process';
import process from 'process';
import path from 'path';
import { platform } from 'os';
import { fileURLToPath } from 'url';
const {assert} = chai;
const fixture1JsonString = '{"a":1,"b":[2,3],"smile":"\uD83D\uDE00"}';
const fixture1JsonPrettyString = `{
"a": 1,
"b": [
2,
3
],
"smile": "😀"
}
`;
const fixture1HexString = 'a3616101616282020365736d696c6564f09f9880';
const fixture1Bin = fromHex(fixture1HexString);
const fixture1BinString = new TextDecoder().decode(fixture1Bin);
const fixture1DiagnosticString = `a3 # map(3)
61 # string(1)
61 # "a"
01 # uint(1)
61 # string(1)
62 # "b"
82 # array(2)
02 # uint(2)
03 # uint(3)
65 # string(5)
736d696c65 # "smile"
64 # string(2)
f09f9880 # "😀"
`;
const fixture2HexString = 'a4616101616282020363627566440102036165736d696c6564f09f9880';
const fixture2DiagnosticString = `a4 # map(4)
61 # string(1)
61 # "a"
01 # uint(1)
61 # string(1)
62 # "b"
82 # array(2)
02 # uint(2)
03 # uint(3)
63 # string(3)
627566 # "buf"
44 # bytes(4)
01020361 # "\\x01\\x02\\x03a"
65 # string(5)
736d696c65 # "smile"
64 # string(2)
f09f9880 # "😀"
`;
const binPath = path.join(path.dirname(fileURLToPath(import.meta.url)), '../lib/bin.js');
function fromHex(hex) {
return new Uint8Array(hex.split('').map((c, i, d) => i % 2 === 0 ? `0x${ c }${ d[i + 1] }` : '').filter(Boolean).map(e => parseInt(e, 16)));
}
async function execBin(cmd, stdin) {
return new Promise((resolve, reject) => {
const cp = exec(`"${ process.execPath }" "${ binPath }" ${ cmd }`, (err, stdout, stderr) => {
if (err) {
err.stdout = stdout;
err.stderr = stderr;
return reject(err);
}
resolve({
stdout,
stderr
});
});
if (stdin != null) {
cp.on('spawn', () => {
cp.stdin.write(stdin);
cp.stdin.end();
});
}
});
}
describe('Bin', () => {
it('usage', async () => {
try {
await execBin('');
assert.fail('should have errored');
} catch (e) {
assert.strictEqual(e.stdout, '');
assert.strictEqual(e.stderr, `Usage: cborg <command> <args>
Valid commands:
\tbin2diag [binary input]
\tbin2hex [binary input]
\tbin2json [--pretty] [binary input]
\tdiag2bin [diagnostic input]
\tdiag2hex [diagnostic input]
\tdiag2json [--pretty] [diagnostic input]
\thex2bin [hex input]
\thex2diag [hex input]
\thex2json [--pretty] [hex input]
\tjson2bin '[json input]'
\tjson2diag '[json input]'
\tjson2hex '[json input]'
Input may either be supplied as an argument or piped via stdin
`);
}
});
it('bad cmd', async () => {
try {
await execBin('blip');
assert.fail('should have errored');
} catch (e) {
assert.strictEqual(e.stdout, '');
assert.strictEqual(e.stderr, `Unknown command: 'blip'
Usage: cborg <command> <args>
Valid commands:
\tbin2diag [binary input]
\tbin2hex [binary input]
\tbin2json [--pretty] [binary input]
\tdiag2bin [diagnostic input]
\tdiag2hex [diagnostic input]
\tdiag2json [--pretty] [diagnostic input]
\thex2bin [hex input]
\thex2diag [hex input]
\thex2json [--pretty] [hex input]
\tjson2bin '[json input]'
\tjson2diag '[json input]'
\tjson2hex '[json input]'
Input may either be supplied as an argument or piped via stdin
`);
}
});
it('help', async () => {
const {stdout, stderr} = await execBin('help');
assert.strictEqual(stdout, '');
assert.strictEqual(stderr, `Usage: cborg <command> <args>
Valid commands:
\tbin2diag [binary input]
\tbin2hex [binary input]
\tbin2json [--pretty] [binary input]
\tdiag2bin [diagnostic input]
\tdiag2hex [diagnostic input]
\tdiag2json [--pretty] [diagnostic input]
\thex2bin [hex input]
\thex2diag [hex input]
\thex2json [--pretty] [hex input]
\tjson2bin '[json input]'
\tjson2diag '[json input]'
\tjson2hex '[json input]'
Input may either be supplied as an argument or piped via stdin
`);
});
it('bin2diag (stdin)', async () => {
const {stdout, stderr} = await execBin('bin2diag', fixture1Bin);
assert.strictEqual(stderr, '');
assert.strictEqual(stdout, fixture1DiagnosticString);
});
it('bin2hex (stdin)', async () => {
const {stdout, stderr} = await execBin('bin2hex', fixture1Bin);
assert.strictEqual(stderr, '');
assert.strictEqual(stdout, `${ fixture1HexString }\n`);
});
it('bin2json (stdin)', async () => {
const {stdout, stderr} = await execBin('bin2json', fixture1Bin);
assert.strictEqual(stderr, '');
assert.strictEqual(stdout, `${ fixture1JsonString }\n`);
});
it('bin2json pretty (stdin)', async () => {
const {stdout, stderr} = await execBin('bin2json --pretty', fixture1Bin);
assert.strictEqual(stderr, '');
assert.strictEqual(stdout, fixture1JsonPrettyString);
});
for (const stdin of [
true,
false
]) {
if (platform() !== 'win32' || stdin) {
it(`diag2bin${ stdin ? ' (stdin)' : '' }`, async () => {
const {stdout, stderr} = !stdin ? await execBin(`diag2bin '${ fixture1DiagnosticString }'`) : await execBin('diag2bin', fixture1DiagnosticString);
assert.strictEqual(stderr, '');
assert.strictEqual(stdout, fixture1BinString);
});
it(`diag2hex${ stdin ? ' (stdin)' : '' }`, async () => {
const {stdout, stderr} = !stdin ? await execBin(`diag2hex '${ fixture1DiagnosticString }'`) : await execBin('diag2hex', fixture1DiagnosticString);
assert.strictEqual(stderr, '');
assert.strictEqual(stdout, `${ fixture1HexString }\n`);
});
it(`diag2json${ stdin ? ' (stdin)' : '' }`, async () => {
const {stdout, stderr} = !stdin ? await execBin(`diag2json '${ fixture1DiagnosticString }'`) : await execBin('diag2json', fixture1DiagnosticString);
assert.strictEqual(stderr, '');
assert.strictEqual(stdout, `${ fixture1JsonString }\n`);
});
it(`diag2json pretty${ stdin ? ' (stdin)' : '' }`, async () => {
const {stdout, stderr} = !stdin ? await execBin(`diag2json --pretty '${ fixture1DiagnosticString }'`) : await execBin('diag2json --pretty', fixture1DiagnosticString);
assert.strictEqual(stderr, '');
assert.strictEqual(stdout, fixture1JsonPrettyString);
});
}
it(`hex2bin${ stdin ? ' (stdin)' : '' }`, async () => {
const {stdout, stderr} = !stdin ? await execBin(`hex2bin ${ fixture1HexString }`) : await execBin('hex2bin', fixture1HexString);
assert.strictEqual(stderr, '');
assert.strictEqual(stdout, fixture1BinString);
});
it(`hex2diag${ stdin ? ' (stdin)' : '' }`, async () => {
const {stdout, stderr} = !stdin ? await execBin(`hex2diag ${ fixture2HexString }`) : await execBin('hex2diag', fixture2HexString);
assert.strictEqual(stderr, '');
assert.strictEqual(stdout, fixture2DiagnosticString);
});
it(`hex2json${ stdin ? ' (stdin)' : '' }`, async () => {
const {stdout, stderr} = !stdin ? await execBin(`hex2json ${ fixture1HexString }`) : await execBin('hex2json', fixture1HexString);
assert.strictEqual(stderr, '');
assert.strictEqual(stdout, `${ fixture1JsonString }\n`);
});
it(`hex2json pretty${ stdin ? ' (stdin)' : '' }`, async () => {
const {stdout, stderr} = !stdin ? await execBin(`hex2json --pretty ${ fixture1HexString }`) : await execBin('hex2json --pretty', fixture1HexString);
assert.strictEqual(stderr, '');
assert.strictEqual(stdout, fixture1JsonPrettyString);
});
it(`json2bin${ stdin ? ' (stdin)' : '' }`, async () => {
const {stdout, stderr} = !stdin ? await execBin('json2bin "{\\"a\\":1,\\"b\\":[2,3],\\"smile\\":\\"\uD83D\uDE00\\"}"') : await execBin('json2bin', fixture1JsonString);
assert.strictEqual(stderr, '');
assert.strictEqual(stdout, fixture1BinString);
});
it(`json2diag${ stdin ? ' (stdin)' : '' }`, async () => {
const {stdout, stderr} = !stdin ? await execBin('json2diag "{\\"a\\":1,\\"b\\":[2,3],\\"smile\\":\\"\uD83D\uDE00\\"}"') : await execBin('json2diag', fixture1JsonString);
assert.strictEqual(stderr, '');
assert.strictEqual(stdout, fixture1DiagnosticString);
});
it(`json2hex${ stdin ? ' (stdin)' : '' }`, async () => {
const {stdout, stderr} = !stdin ? await execBin(`json2hex "${ fixture1JsonString.replace(/"/g, '\\"') }"`) : await execBin('json2hex', fixture1JsonString);
assert.strictEqual(stderr, '');
assert.strictEqual(stdout, `${ fixture1HexString }\n`);
});
}
it('diag indenting', async () => {
const {stdout, stderr} = await execBin('json2diag', '{"a":[],"b":{},"c":{"a":1,"b":{"a":{"a":{}}}},"d":{"a":{"a":{"a":1},"b":2,"c":[]}},"e":[[[[{"a":{}}]]]],"f":1}');
assert.strictEqual(stderr, '');
assert.strictEqual(stdout, `a6 # map(6)
61 # string(1)
61 # "a"
80 # array(0)
61 # string(1)
62 # "b"
a0 # map(0)
61 # string(1)
63 # "c"
a2 # map(2)
61 # string(1)
61 # "a"
01 # uint(1)
61 # string(1)
62 # "b"
a1 # map(1)
61 # string(1)
61 # "a"
a1 # map(1)
61 # string(1)
61 # "a"
a0 # map(0)
61 # string(1)
64 # "d"
a1 # map(1)
61 # string(1)
61 # "a"
a3 # map(3)
61 # string(1)
61 # "a"
a1 # map(1)
61 # string(1)
61 # "a"
01 # uint(1)
61 # string(1)
62 # "b"
02 # uint(2)
61 # string(1)
63 # "c"
80 # array(0)
61 # string(1)
65 # "e"
81 # array(1)
81 # array(1)
81 # array(1)
81 # array(1)
a1 # map(1)
61 # string(1)
61 # "a"
a0 # map(0)
61 # string(1)
66 # "f"
01 # uint(1)
`);
});
describe('diag length bytes', () => {
it('compact', async () => {
const {stdout, stderr} = await execBin('json2diag', '"aaaaaaaaaaaaaaaaaaaaaaa"');
assert.strictEqual(stderr, '');
assert.strictEqual(stdout, `77 # string(23)
6161616161616161616161616161616161616161616161 # "aaaaaaaaaaaaaaaaaaaaaaa"
`);
});
it('1-byte', async () => {
const {stdout, stderr} = await execBin('json2diag', '"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"');
assert.strictEqual(stderr, '');
assert.strictEqual(stdout, `78 23 # string(35)
6161616161616161616161616161616161616161616161 # "aaaaaaaaaaaaaaaaaaaaaaa"
616161616161616161616161 # "aaaaaaaaaaaa"
`);
});
it('2-byte', async () => {
const {stdout, stderr} = await execBin('json2diag', '"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"');
assert.strictEqual(stderr, '');
assert.strictEqual(stdout, `79 0100 # string(256)
6161616161616161616161616161616161616161616161 # "aaaaaaaaaaaaaaaaaaaaaaa"
6161616161616161616161616161616161616161616161 # "aaaaaaaaaaaaaaaaaaaaaaa"
6161616161616161616161616161616161616161616161 # "aaaaaaaaaaaaaaaaaaaaaaa"
6161616161616161616161616161616161616161616161 # "aaaaaaaaaaaaaaaaaaaaaaa"
6161616161616161616161616161616161616161616161 # "aaaaaaaaaaaaaaaaaaaaaaa"
6161616161616161616161616161616161616161616161 # "aaaaaaaaaaaaaaaaaaaaaaa"
6161616161616161616161616161616161616161616161 # "aaaaaaaaaaaaaaaaaaaaaaa"
6161616161616161616161616161616161616161616161 # "aaaaaaaaaaaaaaaaaaaaaaa"
6161616161616161616161616161616161616161616161 # "aaaaaaaaaaaaaaaaaaaaaaa"
6161616161616161616161616161616161616161616161 # "aaaaaaaaaaaaaaaaaaaaaaa"
6161616161616161616161616161616161616161616161 # "aaaaaaaaaaaaaaaaaaaaaaa"
616161 # "aaa"
`);
});
});
it('diag non-utf8 and non-printable ascii', async () => {
const input = '7864f55ff8f12508b63ef2bfeca7557ae90df6311a5ec1631b4a1fa843310bd9c3a710eaace5a1bdd72ad0bfe049771c11e756338bd93865e645f1adec9b9c99ef407fbd4fc6859e7904c5ad7dc9bd10a5cc16973d5b28ec1a6dd43d9f82f9f18c3d03418e35';
let {stdout, stderr} = await execBin(`hex2diag ${ input }`);
assert.strictEqual(stderr, '');
assert.strictEqual(stdout, `78 64 # string(86)
f55ff8f12508b63ef2bfeca7557ae90df6311a5ec1631b # "õ_øñ%\\x08¶>ò¿ì§Uzé\\x0dö1\\x1a^Ác\\x1b"
4a1fa843310bd9c3a710eaace5a1bdd72ad0bfe049771c # "J\\x1f¨C1\\x0bÙç\\x10ê¬å¡½×*пàIw\\x1c"
11e756338bd93865e645f1adec9b9c99ef407fbd4fc685 # "\\x11çV3\\x8bÙ8eæEñ\\xadì\\x9b\\x9c\\x99ï@\\x7f½OÆ\\x85"
9e7904c5ad7dc9bd10a5cc16973d5b28ec1a6dd43d9f82 # "\\x9ey\\x04Å\\xad}ɽ\\x10¥Ì\\x16\\x97=[(ì\\x1amÔ=\\x9f\\x82"
f9f18c3d03418e35 # "ùñ\\x8c=\\x03A\\x8e5"
`);
({stdout, stderr} = await execBin('diag2hex', stdout));
assert.strictEqual(stderr, '');
assert.strictEqual(stdout, `${ input }\n`);
});
});
+1
View File
@@ -0,0 +1 @@
import bin from '../lib/bin.js';
+155
View File
@@ -0,0 +1,155 @@
import chai from 'chai';
import {
decode,
encode
} from '../cborg.js';
import {
fromHex,
toHex
} from '../lib/byte-utils.js';
const {assert} = chai;
const fixtures = [
{
data: '00',
expected: 0,
type: 'uint8'
},
{
data: '02',
expected: 2,
type: 'uint8'
},
{
data: '18ff',
expected: 255,
type: 'uint8'
},
{
data: '1901f4',
expected: 500,
type: 'uint16'
},
{
data: '1900ff',
expected: 255,
type: 'uint16',
strict: false
},
{
data: '19ffff',
expected: 65535,
type: 'uint16'
},
{
data: '1a000000ff',
expected: 255,
type: 'uint32',
strict: false
},
{
data: '1a00010000',
expected: 65536,
type: 'uint32'
},
{
data: '1a000f4240',
expected: 1000000,
type: 'uint32'
},
{
data: '1aa5f702b3',
expected: 2784428723,
type: 'uint32'
},
{
data: '1b00000000000000ff',
expected: 255,
type: 'uint64',
strict: false
},
{
data: '1b0016db6db6db6db7',
expected: Number.MAX_SAFE_INTEGER / 1.4,
type: 'uint64'
},
{
data: '1b001fffffffffffff',
expected: Number.MAX_SAFE_INTEGER,
type: 'uint64'
},
{
data: '1ba5f702b3a5f702b3',
expected: BigInt('11959030306112471731'),
type: 'uint64'
},
{
data: '1bffffffffffffffff',
expected: BigInt('18446744073709551615'),
type: 'uint64'
}
];
describe('uint', () => {
describe('decode', () => {
for (const fixture of fixtures) {
const data = fromHex(fixture.data);
it(`should decode ${ fixture.type }=${ fixture.expected }`, () => {
assert.ok(decode(data) === fixture.expected, `decode ${ fixture.type } ${ decode(data) } != ${ fixture.expected }`);
if (fixture.strict === false) {
assert.throws(() => decode(data, { strict: true }), Error, 'CBOR decode error: integer encoded in more bytes than necessary (strict decode)');
} else {
assert.ok(decode(data, { strict: true }) === fixture.expected, `decode ${ fixture.type }`);
}
});
}
});
it('should throw error', () => {
assert.throws(() => decode(fromHex('1ca5f702b3a5f702b3')), Error, 'CBOR decode error: encountered invalid minor (28) for major 0');
assert.throws(() => decode(fromHex('1ba5f702b3a5f702')), Error, 'CBOR decode error: not enough data for type');
});
describe('encode', () => {
for (const fixture of fixtures) {
it(`should encode ${ fixture.type }=${ fixture.expected }`, () => {
if (fixture.strict === false) {
assert.notStrictEqual(toHex(encode(fixture.expected)), fixture.data, `encode ${ fixture.type } !strict`);
} else {
assert.strictEqual(toHex(encode(fixture.expected)), fixture.data, `encode ${ fixture.type }`);
}
});
}
});
describe('roundtrip', () => {
for (const fixture of fixtures) {
if (fixture.strict !== false) {
it(`should roundtrip ${ fixture.type }=${ fixture.expected }`, () => {
assert.ok(decode(encode(fixture.expected)) === fixture.expected, `roundtrip ${ fixture.type }`);
});
}
}
});
describe('toobig', () => {
it('bigger than 64-bit', () => {
assert.doesNotThrow(() => encode(BigInt('18446744073709551615')));
assert.throws(() => encode(BigInt('18446744073709551616')), /BigInt larger than allowable range/);
});
it('disallow BigInt', () => {
for (const fixture of fixtures) {
const data = fromHex(fixture.data);
if (!Number.isSafeInteger(fixture.expected)) {
assert.throws(() => decode(data, { allowBigInt: false }), /safe integer range/);
} else {
assert.ok(decode(data, { allowBigInt: false }) === fixture.expected, `decode ${ fixture.type }`);
}
}
});
});
describe('toosmall', () => {
for (const fixture of fixtures) {
if (fixture.strict !== false && typeof fixture.expected === 'number') {
const small = BigInt(fixture.expected);
it(`should encode ${ small }n`, () => {
assert.strictEqual(toHex(encode(BigInt(small))), fixture.data, `encode ${ small }`);
});
}
}
});
});
+149
View File
@@ -0,0 +1,149 @@
import chai from 'chai';
import {
decode,
encode
} from '../cborg.js';
import {
fromHex,
toHex
} from '../lib/byte-utils.js';
const {assert} = chai;
const fixtures = [
{
data: '20',
expected: -1,
type: 'negint8'
},
{
data: '22',
expected: -3,
type: 'negint8'
},
{
data: '3863',
expected: -100,
type: 'negint8'
},
{
data: '38ff',
expected: -256,
type: 'negint8'
},
{
data: '3900ff',
expected: -256,
type: 'negint16',
strict: false
},
{
data: '3901f4',
expected: -501,
type: 'negint16'
},
{
data: '3a000000ff',
expected: -256,
type: 'negint32',
strict: false
},
{
data: '3aa5f702b3',
expected: -2784428724,
type: 'negint32'
},
{
data: '3b00000000000000ff',
expected: -256,
type: 'negint32',
strict: false
},
{
data: '3b0016db6db6db6db7',
expected: Number.MIN_SAFE_INTEGER / 1.4 - 1,
type: 'negint64'
},
{
data: '3b001ffffffffffffe',
expected: Number.MIN_SAFE_INTEGER,
type: 'negint64'
},
{
data: '3b001fffffffffffff',
expected: BigInt('-9007199254740992'),
type: 'negint64'
},
{
data: '3b0020000000000000',
expected: BigInt('-9007199254740993'),
type: 'negint64'
},
{
data: '3ba5f702b3a5f702b3',
expected: BigInt('-11959030306112471732'),
type: 'negint64'
},
{
data: '3bffffffffffffffff',
expected: BigInt('-18446744073709551616'),
type: 'negint64'
}
];
describe('negint', () => {
describe('decode', () => {
for (const fixture of fixtures) {
const data = fromHex(fixture.data);
it(`should decode ${ fixture.type }=${ fixture.expected }`, () => {
assert.ok(decode(data) === fixture.expected, `decode ${ fixture.type } (${ decode(data) } != ${ fixture.expected })`);
if (fixture.strict === false) {
assert.throws(() => decode(data, { strict: true }), Error, 'CBOR decode error: integer encoded in more bytes than necessary (strict decode)');
} else {
assert.strictEqual(decode(data, { strict: true }), fixture.expected, `decode ${ fixture.type }`);
}
});
}
});
describe('encode', () => {
for (const fixture of fixtures) {
it(`should encode ${ fixture.type }=${ fixture.expected }`, () => {
if (fixture.strict === false) {
assert.notStrictEqual(toHex(encode(fixture.expected)), fixture.data, `encode ${ fixture.type } !strict`);
} else {
assert.strictEqual(toHex(encode(fixture.expected)), fixture.data, `encode ${ fixture.type }`);
}
});
}
});
describe('roundtrip', () => {
for (const fixture of fixtures) {
it(`should roundtrip ${ fixture.type }=${ fixture.expected }`, () => {
assert.ok(decode(encode(fixture.expected)) === fixture.expected, `roundtrip ${ fixture.type }`);
});
}
});
describe('toobig', () => {
it('bigger than 64-bit', () => {
assert.doesNotThrow(() => encode(BigInt('-18446744073709551616')));
assert.throws(() => encode(BigInt('-18446744073709551617')), /BigInt larger than allowable range/);
});
it('disallow BigInt', () => {
for (const fixture of fixtures) {
const data = fromHex(fixture.data);
if (!Number.isSafeInteger(fixture.expected)) {
assert.throws(() => decode(data, { allowBigInt: false }), /safe integer range/);
} else {
assert.ok(decode(data, { allowBigInt: false }) === fixture.expected, `decode ${ fixture.type }`);
}
}
});
});
describe('toosmall', () => {
for (const fixture of fixtures) {
if (fixture.strict !== false && typeof fixture.expected === 'number') {
const small = BigInt(fixture.expected);
it(`should encode ${ small }n`, () => {
assert.strictEqual(toHex(encode(BigInt(small))), fixture.data, `encode ${ small }`);
});
}
}
});
});
+252
View File
@@ -0,0 +1,252 @@
import chai from 'chai';
import {
decode,
encode
} from '../cborg.js';
import {
useBuffer,
fromHex,
toHex
} from '../lib/byte-utils.js';
const {assert} = chai;
const fixtures = [
{
data: '40',
expected: '',
type: 'bytes'
},
{
data: '41a1',
expected: 'a1',
type: 'bytes'
},
{
data: '5801a1',
expected: 'a1',
type: 'bytes',
strict: false
},
{
data: '58ff000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfe',
expected: '000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfe',
type: 'bytes',
label: 'long bytes, 8-bit length'
},
{
data: '5900ff000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfe',
expected: '000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfe',
type: 'bytes',
label: 'long bytes, 16-bit length',
strict: false
},
{
data: '5a000000ff000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfe',
expected: '000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfe',
type: 'bytes',
label: 'long bytes, 32-bit length',
strict: false
},
{
data: '5b00000000000000ff000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfe',
expected: '000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfe',
type: 'bytes',
label: 'long bytes, 64-bit length',
strict: false
}
];
(() => {
function rnd(length) {
return new Uint8Array(Array.from({ length }, () => Math.floor(Math.random() * 255)));
}
const expected16 = rnd(256);
fixtures.push({
data: new Uint8Array([
...fromHex('590100'),
...expected16
]),
expected: expected16,
type: 'bytes',
label: 'long bytes, 16-bit length strict-compat'
});
const expected32 = rnd(65536);
fixtures.push({
data: new Uint8Array([
...fromHex('5a00010000'),
...expected32
]),
expected: expected32,
type: 'bytes',
label: 'long bytes, 32-bit length strict-compat'
});
})();
describe('bytes', () => {
describe('decode', () => {
for (const fixture of fixtures) {
const data = fromHex(fixture.data);
it(`should decode ${ fixture.type }=${ fixture.label || fixture.expected }`, () => {
let actual = decode(data);
assert.strictEqual(toHex(actual), toHex(fromHex(fixture.expected)), `decode ${ fixture.type }`);
if (fixture.strict === false) {
assert.throws(() => decode(data, { strict: true }), Error, 'CBOR decode error: integer encoded in more bytes than necessary (strict decode)');
} else {
actual = decode(data, { strict: true });
assert.strictEqual(toHex(actual), toHex(fromHex(fixture.expected)), `decode ${ fixture.type } strict`);
}
});
it('should fail to decode very large length', () => {
assert.throws(() => decode(fromHex('5ba5f702b3a5f702b3000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfe')), /CBOR decode error: 64-bit integer bytes lengths not supported/);
});
}
});
describe('encode', () => {
for (const fixture of fixtures) {
if (fixture.data.length >= 100000000) {
it.skip(`(TODO) skipping encode of very large bytes ${ fixture.type }=${ fixture.label || fixture.expected }`, () => {
});
continue;
}
const data = fromHex(fixture.expected);
const expectedHex = toHex(fixture.data);
it(`should encode ${ fixture.type }=${ fixture.label || fixture.expected }`, () => {
if (fixture.unsafe) {
assert.throws(() => encode(data), Error, /^CBOR encode error: number too large to encode \(-\d+\)$/);
} else if (fixture.strict === false) {
assert.notStrictEqual(toHex(encode(data)), expectedHex, `encode ${ fixture.type } !strict`);
} else {
assert.strictEqual(toHex(encode(data)), expectedHex, `encode ${ fixture.type }`);
}
});
}
});
describe('typedarrays', () => {
const cases = [
{
obj: Uint8Array.from([
1,
2,
3
]),
hex: '43010203'
},
{
obj: Uint8ClampedArray.from([
1,
2,
3
]),
hex: '43010203'
},
{
obj: Uint16Array.from([
1,
2,
3
]),
hex: '46010002000300'
},
{
obj: Uint32Array.from([
1,
2,
3
]),
hex: '4c010000000200000003000000'
},
{
obj: Int8Array.from([
1,
2,
-3
]),
hex: '430102fd'
},
{
obj: Int16Array.from([
1,
2,
-3
]),
hex: '4601000200fdff'
},
{
obj: Int32Array.from([
1,
2,
-3
]),
hex: '4c0100000002000000fdffffff'
},
{
obj: Float32Array.from([
1,
2,
-3
]),
hex: '4c0000803f00000040000040c0'
},
{
obj: Float64Array.from([
1,
2,
-3
]),
hex: '5818000000000000f03f000000000000004000000000000008c0'
},
{
obj: BigUint64Array.from([
BigInt(1),
BigInt(2),
BigInt(3)
]),
hex: '5818010000000000000002000000000000000300000000000000'
},
{
obj: BigInt64Array.from([
BigInt(1),
BigInt(2),
BigInt(-3)
]),
hex: '581801000000000000000200000000000000fdffffffffffffff'
},
{
obj: new DataView(Uint8Array.from([
1,
2,
3
]).buffer),
hex: '43010203'
},
{
obj: Uint8Array.from([
1,
2,
3
]).buffer,
hex: '43010203'
}
];
for (const testCase of cases) {
it(testCase.obj.constructor.name, () => {
assert.equal(toHex(encode(testCase.obj)), testCase.hex);
const decoded = decode(fromHex(testCase.hex));
assert.instanceOf(decoded, Uint8Array);
assert.equal(toHex(decoded), toHex(testCase.obj));
});
}
});
if (useBuffer) {
describe('buffer', () => {
it('can encode Node.js Buffers', () => {
const obj = global.Buffer.from([
1,
2,
3
]);
assert.equal(toHex(encode(obj)), '43010203');
const decoded = decode(fromHex('43010203'));
assert.instanceOf(decoded, Uint8Array);
assert.equal(toHex(decoded), toHex(obj));
});
});
}
});
+141
View File
@@ -0,0 +1,141 @@
import chai from 'chai';
import {
decode,
encode
} from '../cborg.js';
import {
fromHex,
toHex
} from '../lib/byte-utils.js';
const {assert} = chai;
const fixtures = [
{
data: '60',
expected: '',
type: 'string'
},
{
data: '6161',
expected: 'a',
type: 'string'
},
{
data: '780161',
expected: 'a',
type: 'string',
strict: false
},
{
data: '6c48656c6c6f20776f726c6421',
expected: 'Hello world!',
type: 'string'
},
{
data: '6fc48c6175657320c39f76c49b746521',
expected: 'Čaues ßvěte!',
type: 'string'
},
{
data: '78964c6f72656d20697073756d20646f6c6f722073697420616d65742c20636f6e73656374657475722061646970697363696e6720656c69742e20446f6e6563206d692074656c6c75732c20696163756c6973206e656320766573746962756c756d20717569732c206665726d656e74756d206e6f6e2066656c69732e204d616563656e6173207574206a7573746f20706f73756572652e',
expected: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec mi tellus, iaculis nec vestibulum quis, fermentum non felis. Maecenas ut justo posuere.',
type: 'string',
label: 'long string, 8-bit length'
},
{
data: '7900964c6f72656d20697073756d20646f6c6f722073697420616d65742c20636f6e73656374657475722061646970697363696e6720656c69742e20446f6e6563206d692074656c6c75732c20696163756c6973206e656320766573746962756c756d20717569732c206665726d656e74756d206e6f6e2066656c69732e204d616563656e6173207574206a7573746f20706f73756572652e',
expected: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec mi tellus, iaculis nec vestibulum quis, fermentum non felis. Maecenas ut justo posuere.',
type: 'string',
label: 'long string, 16-bit length',
strict: false
},
{
data: '7a000000964c6f72656d20697073756d20646f6c6f722073697420616d65742c20636f6e73656374657475722061646970697363696e6720656c69742e20446f6e6563206d692074656c6c75732c20696163756c6973206e656320766573746962756c756d20717569732c206665726d656e74756d206e6f6e2066656c69732e204d616563656e6173207574206a7573746f20706f73756572652e',
expected: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec mi tellus, iaculis nec vestibulum quis, fermentum non felis. Maecenas ut justo posuere.',
type: 'string',
label: 'long string, 32-bit length',
strict: false
},
{
data: '7b00000000000000964c6f72656d20697073756d20646f6c6f722073697420616d65742c20636f6e73656374657475722061646970697363696e6720656c69742e20446f6e6563206d692074656c6c75732c20696163756c6973206e656320766573746962756c756d20717569732c206665726d656e74756d206e6f6e2066656c69732e204d616563656e6173207574206a7573746f20706f73756572652e',
expected: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec mi tellus, iaculis nec vestibulum quis, fermentum non felis. Maecenas ut justo posuere.',
type: 'string',
label: 'long string, 64-bit length',
strict: false
}
];
(() => {
function rnd(length) {
const sa = [];
let l = 0;
while (l < length) {
const ascii = length - l < 3;
const base = ascii ? 32 : 126976;
const max = ascii ? 126 : 130816;
const cc = Math.floor(Math.random() * (max - base)) + base;
const s = String.fromCharCode(cc);
l += new TextEncoder().encode(s).length;
sa.push(s);
}
return sa.join('');
}
const expected16 = rnd(256);
fixtures.push({
data: new Uint8Array([
...fromHex('790100'),
...new TextEncoder().encode(expected16)
]),
expected: expected16,
type: 'string',
label: 'long string, 16-bit length strict-compat'
});
const expected32 = rnd(65536);
fixtures.push({
data: new Uint8Array([
...fromHex('7a00010000'),
...new TextEncoder().encode(expected32)
]),
expected: expected32,
type: 'string',
label: 'long string, 32-bit length strict-compat'
});
})();
describe('string', () => {
describe('decode', () => {
for (const fixture of fixtures) {
const data = fromHex(fixture.data);
it(`should decode ${ fixture.type }=${ fixture.label || fixture.expected }`, () => {
let actual = decode(data);
assert.strictEqual(actual, fixture.expected, `decode ${ fixture.type }`);
if (fixture.strict === false) {
assert.throws(() => decode(data, { strict: true }), Error, 'CBOR decode error: integer encoded in more bytes than necessary (strict decode)');
} else {
actual = decode(data, { strict: true });
assert.strictEqual(actual, fixture.expected, `decode ${ fixture.type } strict`);
}
});
it('should fail to decode very large length', () => {
assert.throws(() => decode(fromHex('7ba5f702b3a5f702b34c6f72656d20697073756d20646f6c6f722073697420616d65742c20636f6e73656374657475722061646970697363696e6720656c69742e20446f6e6563206d692074656c6c75732c20696163756c6973206e656320766573746962756c756d20717569732c206665726d656e74756d206e6f6e2066656c69732e204d616563656e6173207574206a7573746f20706f73756572652e')), /CBOR decode error: 64-bit integer string lengths not supported/);
});
}
});
describe('encode', () => {
for (const fixture of fixtures) {
if (fixture.data.length >= 100000000) {
it.skip(`(TODO) skipping encode of very large string ${ fixture.type }=${ fixture.label || fixture.expected }`, () => {
});
continue;
}
const data = fixture.expected;
const expectedHex = toHex(fixture.data);
it(`should encode ${ fixture.type }=${ fixture.label || fixture.expected }`, () => {
if (fixture.unsafe) {
assert.throws(() => encode(data), Error, /^CBOR encode error: number too large to encode \(-\d+\)$/);
} else if (fixture.strict === false) {
assert.notStrictEqual(toHex(encode(data)), expectedHex, `encode ${ fixture.type } !strict`);
} else {
assert.strictEqual(toHex(encode(data)), expectedHex, `encode ${ fixture.type }`);
}
});
}
});
});
+197
View File
@@ -0,0 +1,197 @@
import chai from 'chai';
import {
decode,
encode
} from '../cborg.js';
import {
fromHex,
toHex
} from '../lib/byte-utils.js';
const {assert} = chai;
const fixtures = [
{
data: '80',
expected: [],
type: 'array empty'
},
{
data: '8102',
expected: [2],
type: 'array 1 compact uint'
},
{
data: '8118ff',
expected: [255],
type: 'array 1 uint8'
},
{
data: '811901f4',
expected: [500],
type: 'array 1 uint16'
},
{
data: '811a00010000',
expected: [65536],
type: 'array 1 uint32'
},
{
data: '811b00000000000000ff',
expected: [255],
type: 'array 1 uint64',
strict: false
},
{
data: '811b0016db6db6db6db7',
expected: [Number.MAX_SAFE_INTEGER / 1.4],
type: 'array 1 uint64'
},
{
data: '811b001fffffffffffff',
expected: [Number.MAX_SAFE_INTEGER],
type: 'array 1 uint64'
},
{
data: '8403040506',
expected: [
3,
4,
5,
6
],
type: 'array 4 ints'
},
{
data: '8c1b0016db6db6db6db71a000100001901f40200202238ff3aa5f702b33b0016db6db6db6db74261316fc48c6175657320c39f76c49b746521',
expected: [
Number.MAX_SAFE_INTEGER / 1.4,
65536,
500,
2,
0,
-1,
-3,
-256,
-2784428724,
Number.MIN_SAFE_INTEGER / 1.4 - 1,
new TextEncoder().encode('a1'),
'Čaues ßvěte!'
],
type: 'array mixed terminals',
label: '[]'
},
{
data: '8265617272617982626f66820582666e657374656482666172726179736121',
expected: [
'array',
[
'of',
[
5,
[
'nested',
[
'arrays',
'!'
]
]
]
]
],
type: 'array nested'
},
{
data: '980403040506',
expected: [
3,
4,
5,
6
],
type: 'array 4 ints, length8',
strict: false
},
{
data: '99000403040506',
expected: [
3,
4,
5,
6
],
type: 'array 4 ints, length16',
strict: false
},
{
data: '9a0000000403040506',
expected: [
3,
4,
5,
6
],
type: 'array 4 ints, length32',
strict: false
},
{
data: '9b000000000000000403040506',
expected: [
3,
4,
5,
6
],
type: 'array 4 ints, length64',
strict: false
}
];
describe('array', () => {
describe('decode', () => {
for (const fixture of fixtures) {
const data = fromHex(fixture.data);
it(`should decode ${ fixture.type }=${ fixture.label || fixture.expected }`, () => {
assert.deepStrictEqual(decode(data), fixture.expected, `decode ${ fixture.type }`);
if (fixture.strict === false) {
assert.throws(() => decode(data, { strict: true }), Error, 'CBOR decode error: integer encoded in more bytes than necessary (strict decode)');
} else {
assert.deepStrictEqual(decode(data, { strict: true }), fixture.expected, `decode ${ fixture.type }`);
}
});
it('should fail to decode very large length', () => {
assert.throws(() => decode(fromHex('9ba5f702b3a5f7020403040506')), /CBOR decode error: 64-bit integer array lengths not supported/);
});
}
});
describe('encode', () => {
for (const fixture of fixtures) {
it(`should encode ${ fixture.type }=${ fixture.label || fixture.expected }`, () => {
if (fixture.unsafe) {
assert.throws(encode.bind(null, fixture.expected), Error, /^CBOR encode error: number too large to encode \(\d+\)$/);
} else if (fixture.strict === false) {
assert.notDeepEqual(toHex(encode(fixture.expected)), fixture.data, `encode ${ fixture.type } !strict`);
} else {
assert.strictEqual(toHex(encode(fixture.expected)), fixture.data, `encode ${ fixture.type }`);
}
});
}
});
describe('roundtrip', () => {
for (const fixture of fixtures) {
if (!fixture.unsafe && fixture.strict !== false) {
it(`should roundtrip ${ fixture.type }=${ fixture.label || fixture.expected }`, () => {
assert.deepStrictEqual(decode(encode(fixture.expected)), fixture.expected, `roundtrip ${ fixture.type }`);
});
}
}
});
describe('specials', () => {
it('can decode indefinite length items', () => {
assert.deepStrictEqual(decode(fromHex('9f616f6174ff')), [
'o',
't'
]);
});
it('can switch off indefinite length support', () => {
assert.throws(() => decode(fromHex('9f616f6174ff'), { allowIndefinite: false }), /indefinite/);
});
});
});
+664
View File
@@ -0,0 +1,664 @@
import chai from 'chai';
import {
decode,
encode
} from '../cborg.js';
import {
fromHex,
toHex
} from '../lib/byte-utils.js';
const {assert} = chai;
const fixtures = [
{
data: 'a0',
expected: {},
type: 'map empty'
},
{
data: 'a0',
expected: new Map(),
type: 'map empty (useMaps)',
useMaps: true
},
{
data: 'a1616101',
expected: { a: 1 },
type: 'map 1 pair'
},
{
data: 'a161316161',
expected: { 1: 'a' },
type: 'map 1 pair (rev)'
},
{
data: 'a1016161',
expected: toMap([[
1,
'a'
]]),
type: 'map 1 pair (int key as Map w/ useMaps)',
useMaps: true
},
{
data: 'a243010203633132334302030463323334',
expected: toMap([
[
Uint8Array.from([
1,
2,
3
]),
'123'
],
[
Uint8Array.from([
2,
3,
4
]),
'234'
]
]),
type: 'map 2 pair (bytes keys Map w/ useMaps)',
useMaps: true
},
{
data: 'a1666f626a656374a16477697468a26134666e6573746564676f626a65637473a161216121',
expected: {
object: {
with: {
4: 'nested',
objects: { '!': '!' }
}
}
},
type: 'map nested'
},
{
data: 'a1666f626a656374a16477697468a204666e6573746564676f626a65637473a161216121',
expected: toMap([[
'object',
toMap([[
'with',
toMap([
[
4,
'nested'
],
[
'objects',
toMap([[
'!',
'!'
]])
]
])
]])
]]),
type: 'map nested w/ useMaps',
useMaps: true
},
{
data: 'ae636f6e651b0016db6db6db6db763736978206374656e3b0016db6db6db6db76374776f1a0001000064666976650064666f757202646e696e653aa5f702b365656967687438ff65736576656e226574687265651901f466656c6576656e426131667477656c76656fc48c6175657320c39f76c49b74652168666f75727465656ea4616664666f7572616f016174026274680368746869727465656e840203046466697665',
encode: {
one: Number.MAX_SAFE_INTEGER / 1.4,
two: 65536,
three: 500,
four: 2,
five: 0,
six: -1,
seven: -3,
eight: -256,
nine: -2784428724,
ten: Number.MIN_SAFE_INTEGER / 1.4 - 1,
eleven: new TextEncoder().encode('a1'),
twelve: 'Čaues ßvěte!',
thirteen: [
2,
3,
4,
'five'
],
fourteen: {
o: 1,
t: 2,
th: 3,
f: 'four'
}
},
expected: {
one: Number.MAX_SAFE_INTEGER / 1.4,
six: -1,
ten: Number.MIN_SAFE_INTEGER / 1.4 - 1,
two: 65536,
five: 0,
four: 2,
nine: -2784428724,
eight: -256,
seven: -3,
three: 500,
eleven: new TextEncoder().encode('a1'),
twelve: 'Čaues ßvěte!',
fourteen: {
f: 'four',
o: 1,
t: 2,
th: 3
},
thirteen: [
2,
3,
4,
'five'
]
},
type: 'map with complex entries',
label: '{}'
},
{
data: 'ad01636f6e65026374776f1901f46c666976652068756e647265641902586b7369782068756e647265641a00010000636269671b0016db6db6db6db76662696767657220696d696e7573206f6e6521696d696e75732074776f38ff781f6d696e75732074776f2068756e6472656420616e64206669667479207369783901f4781a6d696e757820666976652068756e6472656420616e64206f6e653901f5781a6d696e757820666976652068756e6472656420616e642074776f3aa5f702b367626967206e65673b0016db6db6db6db76a626967676572206e6567',
encode: toMap([
[
2,
'two'
],
[
1,
'one'
],
[
-2,
'minus two'
],
[
-1,
'minus one'
],
[
600,
'six hundred'
],
[
500,
'five hundred'
],
[
-256,
'minus two hundred and fifty six'
],
[
-502,
'minux five hundred and two'
],
[
-501,
'minux five hundred and one'
],
[
65536,
'big'
],
[
-2784428724,
'big neg'
],
[
6433713753386423,
'bigger'
],
[
-6433713753386424,
'bigger neg'
]
]),
expected: toMap([
[
1,
'one'
],
[
2,
'two'
],
[
500,
'five hundred'
],
[
600,
'six hundred'
],
[
65536,
'big'
],
[
6433713753386423,
'bigger'
],
[
-1,
'minus one'
],
[
-2,
'minus two'
],
[
-256,
'minus two hundred and fifty six'
],
[
-501,
'minux five hundred and one'
],
[
-502,
'minux five hundred and two'
],
[
-2784428724,
'big neg'
],
[
-6433713753386424,
'bigger neg'
]
]),
type: 'map with ints and negints',
useMaps: true
},
{
data: 'a44104636f6e65430102026374776f430102036574687265654301020464666f7572',
encode: toMap([
[
Uint8Array.from([
1,
2,
3
]),
'three'
],
[
Uint8Array.from([4]),
'one'
],
[
Uint8Array.from([
1,
2,
4
]),
'four'
],
[
Uint8Array.from([
1,
2,
2
]),
'two'
]
]),
expected: toMap([
[
Uint8Array.from([4]),
'one'
],
[
Uint8Array.from([
1,
2,
2
]),
'two'
],
[
Uint8Array.from([
1,
2,
3
]),
'three'
],
[
Uint8Array.from([
1,
2,
4
]),
'four'
]
]),
type: 'map with bytes keys',
useMaps: true
},
{
data: 'b801616101',
expected: { a: 1 },
type: 'map 1 pair, length8',
strict: false
},
{
data: 'b90001616101',
expected: { a: 1 },
type: 'map 1 pair, length16',
strict: false
},
{
data: 'ba00000001616101',
expected: { a: 1 },
type: 'map 1 pair, length32',
strict: false
},
{
data: 'bb0000000000000001616101',
expected: { a: 1 },
type: 'map 1 pair, length64',
strict: false
}
];
function toMap(arr) {
const m = new Map();
for (const [key, value] of arr) {
m.set(key, value);
}
return m;
}
function entries(map) {
function nest(a) {
for (const e of a) {
e[0] = entries(e[0]);
e[1] = entries(e[1]);
}
return a;
}
if (Object.getPrototypeOf(map) === Map.prototype) {
return nest([...map.entries()]);
}
if (typeof map === 'object') {
return nest([...Object.entries(map)]);
}
return map;
}
describe('map', () => {
describe('decode', () => {
for (const fixture of fixtures) {
const data = fromHex(fixture.data);
it(`should decode ${ fixture.type }=${ fixture.label || JSON.stringify(fixture.expected) }`, () => {
let options = fixture.useMaps ? { useMaps: true } : undefined;
const decoded = decode(data, options);
if (fixture.useMaps) {
assert.strictEqual(Object.getPrototypeOf(decoded), Map.prototype, 'is Map');
} else {
assert.isObject(decoded, 'is object');
}
assert.deepStrictEqual(entries(decoded), entries(fixture.expected), `decode ${ fixture.type }`);
options = Object.assign({ strict: true }, options);
if (fixture.strict === false) {
assert.throws(() => decode(data, options), Error, 'CBOR decode error: integer encoded in more bytes than necessary (strict decode)');
} else {
assert.deepStrictEqual(entries(decode(data, options)), entries(fixture.expected), `decode ${ fixture.type }`);
}
});
it('should fail to decode very large length', () => {
assert.throws(() => decode(fromHex('bba5f702b3a5f70201616101')), /CBOR decode error: 64-bit integer map lengths not supported/);
});
}
it('errors', () => {
assert.throws(() => decode(fromHex('a1016161')), /non-string keys not supported \(got number\)/);
});
});
describe('encode', () => {
for (const fixture of fixtures) {
it(`should encode ${ fixture.type }=${ fixture.label || JSON.stringify(fixture.expected) }`, () => {
const toEncode = fixture.encode || fixture.expected;
if (fixture.unsafe) {
assert.throws(encode.bind(null, toEncode), Error, /^CBOR encode error: number too large to encode \(\d+\)$/);
} else if (fixture.strict === false || fixture.roundtrip === false) {
assert.notDeepEqual(toHex(encode(toEncode)), fixture.data, `encode ${ fixture.type } !strict`);
} else {
assert.strictEqual(toHex(encode(toEncode)), fixture.data, `encode ${ fixture.type }`);
}
});
}
});
describe('roundtrip', () => {
for (const fixture of fixtures) {
if (!fixture.unsafe && fixture.strict !== false && fixture.roundtrip !== false) {
it(`should roundtrip ${ fixture.type }=${ fixture.label || JSON.stringify(fixture.expected) }`, () => {
const toEncode = fixture.encode || fixture.expected;
const options = fixture.useMaps ? { useMaps: true } : undefined;
const rt = decode(encode(toEncode), options);
if (fixture.useMaps) {
assert.strictEqual(Object.getPrototypeOf(rt), Map.prototype, 'is Map');
} else {
assert.isObject(rt, 'is object');
}
assert.deepStrictEqual(entries(rt), entries(fixture.expected), `roundtrip ${ fixture.type }`);
});
}
}
});
describe('specials', () => {
it('can decode indefinite length items', () => {
assert.deepStrictEqual(decode(fromHex('bf616f01617402ff')), {
o: 1,
t: 2
});
});
it('can switch off indefinite length support', () => {
assert.throws(() => decode(fromHex('bf616f01617402ff'), { allowIndefinite: false }), /indefinite/);
});
});
describe('sorting', () => {
it('sorts int map keys', () => {
assert.strictEqual(toHex(encode(new Map([
[
1,
1
],
[
2,
2
]
]))), 'a201010202');
assert.strictEqual(toHex(encode(new Map([
[
2,
1
],
[
1,
2
]
]))), 'a201020201');
});
it('sorts negint map keys', () => {
assert.strictEqual(toHex(encode(new Map([
[
-1,
1
],
[
-2,
2
]
]))), 'a220012102');
assert.strictEqual(toHex(encode(new Map([
[
-2,
1
],
[
-1,
2
]
]))), 'a220022101');
});
it('sorts bytes map keys', () => {
assert.strictEqual(toHex(encode(new Map([
[
Uint8Array.from([
1,
2
]),
1
],
[
Uint8Array.from([
2,
1
]),
2
]
]))), 'a24201020142020102');
assert.strictEqual(toHex(encode(new Map([
[
Uint8Array.from([
2,
1
]),
1
],
[
Uint8Array.from([
1,
2
]),
2
]
]))), 'a24201020242020101');
assert.strictEqual(toHex(encode(new Map([
[
Uint8Array.from([
1,
2
]),
1
],
[
Uint8Array.from([
2,
1
]),
2
],
[
Uint8Array.from([200]),
3
]
]))), 'a341c8034201020142020102');
});
it('sorts bytes map keys', () => {
assert.strictEqual(toHex(encode(new Map([
[
Uint8Array.from([
1,
2
]),
1
],
[
Uint8Array.from([
2,
1
]),
2
]
]))), 'a24201020142020102');
assert.strictEqual(toHex(encode(new Map([
[
Uint8Array.from([
2,
1
]),
1
],
[
Uint8Array.from([
1,
2
]),
2
]
]))), 'a24201020242020101');
assert.strictEqual(toHex(encode(new Map([
[
Uint8Array.from([
1,
2
]),
1
],
[
Uint8Array.from([
2,
1
]),
2
],
[
Uint8Array.from([200]),
3
]
]))), 'a341c8034201020142020102');
});
it('sorts array map keys (length only)', () => {
assert.strictEqual(toHex(encode(new Map([
[
[1],
1
],
[
[
1,
1
],
2
]
]))), 'a281010182010102');
assert.strictEqual(toHex(encode(new Map([
[
[
1,
1
],
1
],
[
[1],
2
]
]))), 'a281010282010101');
});
it('sorts map map keys (length only)', () => {
assert.strictEqual(toHex(encode(new Map([
[
{ a: 1 },
1
],
[
{
a: 1,
b: 1
},
2
]
]))), 'a2a161610101a261610161620102');
assert.strictEqual(toHex(encode(new Map([
[
{
a: 1,
b: 1
},
1
],
[
{ a: 1 },
2
]
]))), 'a2a161610102a261610161620101');
});
});
});
+78
View File
@@ -0,0 +1,78 @@
import chai from 'chai';
import {
Token,
Type
} from '../lib/token.js';
import {
decode,
encode
} from '../cborg.js';
import {
fromHex,
toHex
} from '../lib/byte-utils.js';
import {
dateDecoder,
dateEncoder
} from './common.js';
const {assert} = chai;
function Uint16ArrayDecoder(obj) {
if (typeof obj !== 'string') {
throw new Error('expected string for tag 23');
}
const u8a = fromHex(obj);
return new Uint16Array(u8a.buffer, u8a.byteOffset, u8a.length / 2);
}
function Uint16ArrayEncoder(obj) {
if (!(obj instanceof Uint16Array)) {
throw new Error('expected Uint16Array for "Uint16Array" encoder');
}
return [
new Token(Type.tag, 23),
new Token(Type.string, toHex(obj))
];
}
describe('tag', () => {
it('date', () => {
assert.throws(() => encode({ d: new Date() }), /unsupported type: Date/);
assert.equal(toHex(encode(new Date('2013-03-21T20:04:00Z'), { typeEncoders: { Date: dateEncoder } })), 'c074323031332d30332d32315432303a30343a30305a');
const decodedDate = decode(fromHex('c074323031332d30332d32315432303a30343a30305a'), { tags: { 0: dateDecoder } });
assert.instanceOf(decodedDate, Date);
assert.equal(decodedDate.toISOString(), new Date('2013-03-21T20:04:00Z').toISOString());
});
it('Uint16Array as hex/23 (overide existing type)', () => {
assert.equal(toHex(encode(Uint16Array.from([
1,
2,
3
]), { typeEncoders: { Uint16Array: Uint16ArrayEncoder } })), 'd76c303130303032303030333030');
const decoded = decode(fromHex('d76c303130303032303030333030'), { tags: { 23: Uint16ArrayDecoder } });
assert.instanceOf(decoded, Uint16Array);
assert.equal(toHex(decoded), toHex(Uint16Array.from([
1,
2,
3
])));
});
it('tag int too large', () => {
const verify = (hex, strict) => {
if (!strict) {
assert.throws(() => decode(fromHex(hex), {
tags: { 8: dateDecoder },
strict: true
}), /integer encoded in more bytes than necessary/);
}
const decodedDate = decode(fromHex(hex), {
tags: { 8: dateDecoder },
strict
});
assert.instanceOf(decodedDate, Date);
assert.equal(decodedDate.toISOString(), new Date('2013-03-21T20:04:00Z').toISOString());
};
verify('c874323031332d30332d32315432303a30343a30305a', true);
verify('d80874323031332d30332d32315432303a30343a30305a', false);
verify('d9000874323031332d30332d32315432303a30343a30305a', false);
verify('da0000000874323031332d30332d32315432303a30343a30305a', false);
verify('db000000000000000874323031332d30332d32315432303a30343a30305a', false);
});
});
+250
View File
@@ -0,0 +1,250 @@
import chai from 'chai';
import {
decode,
encode
} from '../cborg.js';
import {
fromHex,
toHex
} from '../lib/byte-utils.js';
const {assert} = chai;
const fixtures = [
{
data: '8601f5f4f6f720',
expected: [
1,
true,
false,
null,
undefined,
-1
],
type: 'array of float specials'
},
{
data: 'f93800',
expected: 0.5,
type: 'float16'
},
{
data: 'f9b800',
expected: -0.5,
type: 'float16'
},
{
data: 'fa33c00000',
expected: 8.940696716308594e-8,
type: 'float32'
},
{
data: 'fab3c00000',
expected: -8.940696716308594e-8,
type: 'float32'
},
{
data: 'fb3ff199999999999a',
expected: 1.1,
type: 'float64'
},
{
data: 'fbbff199999999999a',
expected: -1.1,
type: 'float64'
},
{
data: 'fb3ff1c71c71c71c72',
expected: 1.1111111111111112,
type: 'float64'
},
{
data: 'fb0000000000000002',
expected: 1e-323,
type: 'float64'
},
{
data: 'fb8000000000000002',
expected: -1e-323,
type: 'float64'
},
{
data: 'fb3fefffffffffffff',
expected: 0.9999999999999999,
type: 'float64'
},
{
data: 'fbbfefffffffffffff',
expected: -0.9999999999999999,
type: 'float64'
},
{
data: 'f97c00',
expected: Infinity,
type: 'Infinity'
},
{
data: 'fb7ff0000000000000',
expected: Infinity,
type: 'Infinity',
strict: false
},
{
data: 'f9fc00',
expected: -Infinity,
type: '-Infinity'
},
{
data: 'fbfff0000000000000',
expected: -Infinity,
type: '-Infinity',
strict: false
},
{
data: 'f97e00',
expected: NaN,
type: 'NaN'
},
{
data: 'f97ff8',
expected: NaN,
type: 'NaN',
strict: false
},
{
data: 'fa7ff80000',
expected: NaN,
type: 'NaN',
strict: false
},
{
data: 'fb7ff8000000000000',
expected: NaN,
type: 'NaN',
strict: false
},
{
data: 'fb7ff8cafedeadbeef',
expected: NaN,
type: 'NaN',
strict: false
},
{
data: 'fb40f4241a31a5a515',
expected: 82497.63712086187,
type: 'float64'
}
];
describe('float', () => {
describe('decode', () => {
for (const fixture of fixtures) {
const data = fromHex(fixture.data);
it(`should decode ${ fixture.type }=${ fixture.expected }`, () => {
assert.deepStrictEqual(decode(data), fixture.expected, `decode ${ fixture.type }`);
assert.deepStrictEqual(decode(data, { strict: true }), fixture.expected, `decode ${ fixture.type }`);
});
}
});
it('error', () => {
assert.throws(() => decode(fromHex('f80000')), Error, 'simple values are not supported');
assert.throws(() => decode(fromHex('f900')), Error, 'not enough data for float16');
assert.throws(() => decode(fromHex('fa0000')), Error, 'not enough data for float32');
assert.throws(() => decode(fromHex('fb00000000')), Error, 'not enough data for float64');
});
describe('encode', () => {
for (const fixture of fixtures) {
if (fixture.strict !== false) {
it(`should encode ${ fixture.type }=${ fixture.expected }`, () => {
assert.strictEqual(toHex(encode(fixture.expected)), fixture.data, `encode ${ fixture.type }`);
});
}
}
});
describe('encode float64', () => {
for (const fixture of fixtures) {
if (fixture.type.startsWith('float')) {
it(`should encode ${ fixture.type }=${ fixture.expected }`, () => {
const encoded = encode(fixture.expected, { float64: true });
assert.strictEqual(encoded.length, 9);
assert.strictEqual(encoded[0], 251);
assert.strictEqual(decode(encoded), fixture.expected, `encode float64 ${ fixture.type }`);
});
}
}
});
describe('roundtrip', () => {
for (const fixture of fixtures) {
if (!fixture.unsafe && fixture.strict !== false) {
it(`should roundtrip ${ fixture.type }=${ fixture.expected }`, () => {
assert.deepStrictEqual(decode(encode(fixture.expected)), fixture.expected, `roundtrip ${ fixture.type }`);
});
}
}
});
describe('specials', () => {
it('indefinite length switch fails on BREAK', () => {
assert.throws(() => decode(Uint8Array.from([
131,
1,
2,
255
])), /unexpected break to lengthed array/);
assert.throws(() => decode(Uint8Array.from([
131,
1,
2,
255
]), { allowIndefinite: false }), /indefinite/);
});
it('can switch off undefined support', () => {
assert.deepStrictEqual(decode(fromHex('f7')), undefined);
assert.throws(() => decode(fromHex('f7'), { allowUndefined: false }), /undefined/);
assert.deepStrictEqual(decode(fromHex('830102f7')), [
1,
2,
undefined
]);
assert.throws(() => decode(fromHex('830102f7'), { allowUndefined: false }), /undefined/);
});
it('can coerce undefined to null', () => {
assert.deepStrictEqual(decode(fromHex('f7'), { coerceUndefinedToNull: false }), undefined);
assert.deepStrictEqual(decode(fromHex('f7'), { coerceUndefinedToNull: true }), null);
assert.deepStrictEqual(decode(fromHex('830102f7'), { coerceUndefinedToNull: false }), [
1,
2,
undefined
]);
assert.deepStrictEqual(decode(fromHex('830102f7'), { coerceUndefinedToNull: true }), [
1,
2,
null
]);
});
it('can switch off Infinity support', () => {
assert.deepStrictEqual(decode(fromHex('830102f97c00')), [
1,
2,
Infinity
]);
assert.deepStrictEqual(decode(fromHex('830102f9fc00')), [
1,
2,
-Infinity
]);
assert.throws(() => decode(fromHex('830102f97c00'), { allowInfinity: false }), /Infinity/);
assert.throws(() => decode(fromHex('830102f9fc00'), { allowInfinity: false }), /Infinity/);
for (const fixture of fixtures.filter(f => f.type.endsWith('Infinity'))) {
assert.throws(() => decode(fromHex(fixture.data), { allowInfinity: false }), /Infinity/);
}
});
it('can switch off NaN support', () => {
assert.deepStrictEqual(decode(fromHex('830102f97e00')), [
1,
2,
NaN
]);
assert.throws(() => decode(fromHex('830102f97e00'), { allowNaN: false }), /NaN/);
for (const fixture of fixtures.filter(f => f.type === 'NaN')) {
assert.throws(() => decode(fromHex(fixture.data), { allowNaN: false }), /NaN/);
}
});
});
});
+84
View File
@@ -0,0 +1,84 @@
import chai from 'chai';
import { Bl } from '../lib/bl.js';
const {assert} = chai;
describe('Internal bytes list', () => {
describe('push', () => {
it('push bits', () => {
const bl = new Bl(10);
const expected = [];
for (let i = 0; i < 25; i++) {
bl.push([i + 1]);
expected.push(i + 1);
}
assert.deepEqual([...bl.toBytes()], expected);
});
for (let i = 4; i < 21; i++) {
it(`push Bl(${ i })`, () => {
const bl = new Bl(i);
const expected = [
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
100,
110,
120,
11,
12,
130,
13,
14,
15,
16,
17,
18,
19,
20,
21,
22,
23
];
for (let i = 0; i < 5; i++) {
bl.push([i + 1]);
}
bl.push(Uint8Array.from([
6,
7,
8,
9,
10
]));
bl.push([100]);
bl.push(Uint8Array.from([
110,
120
]));
bl.push(Uint8Array.from([
11,
12
]));
bl.push([130]);
bl.push(Uint8Array.from([
13,
14,
15,
16,
17,
18,
19,
20,
21,
22,
23
]));
assert.deepEqual([...bl.toBytes()], expected);
});
}
});
});
@@ -0,0 +1,95 @@
import chai from 'chai';
import {
decode,
encode
} from '../cborg.js';
import * as taglib from '../taglib.js';
import {
fromHex,
toHex
} from '../lib/byte-utils.js';
import { fixtures } from './appendix_a.js';
const {assert} = chai;
const tags = [];
const typeEncoders = {};
tags[0] = function (obj) {
if (typeof obj !== 'string') {
throw new Error('expected string for tag 1');
}
return `0("${ new Date(obj).toISOString().replace(/\.000Z$/, 'Z') }")`;
};
tags[1] = function (obj) {
if (typeof obj !== 'number') {
throw new Error('expected number for tag 1');
}
return `1(${ obj })`;
};
tags[2] = taglib.bigIntDecoder;
typeEncoders.bigint = taglib.bigIntEncoder;
tags[3] = taglib.bigNegIntDecoder;
tags[23] = function (obj) {
if (!(obj instanceof Uint8Array)) {
throw new Error('expected byte array for tag 23');
}
return `23(h'${ toHex(obj) }')`;
};
tags[24] = function (obj) {
return tags[23](obj).replace(/^23/, '24');
};
tags[32] = function (obj) {
if (typeof obj !== 'string') {
throw new Error('expected string for tag 32');
}
;
(() => new URL(obj))();
return `32("${ obj }")`;
};
describe('cbor/test-vectors', () => {
let i = 0;
for (const fixture of fixtures) {
const u8a = fromHex(fixture.hex);
let expected = fixture.decoded !== undefined ? fixture.decoded : fixture.diagnostic;
if (typeof expected === 'string' && expected.startsWith('h\'')) {
expected = fromHex(expected.replace(/(^h)'|('$)/g, ''));
}
it(`test vector #${ i }: ${ inspect(expected).replace(/\n\s*/g, '') }`, () => {
if (fixture.error) {
assert.throws(() => decode(u8a, { tags }), fixture.error);
} else {
if (fixture.noTagDecodeError) {
assert.throws(() => decode(u8a), fixture.noTagDecodeError);
}
let actual = decode(u8a, { tags });
if (typeof actual === 'bigint') {
actual = inspect(actual);
}
if (typeof expected === 'bigint') {
expected = inspect(expected);
}
assert.deepEqual(actual, expected);
if (fixture.roundtrip) {
if (fixture.noTagEncodeError) {
assert.throws(() => encode(decode(u8a, { tags })), fixture.noTagEncodeError);
}
const reencoded = encode(decode(u8a, { tags }), { typeEncoders });
assert.equal(toHex(reencoded), fixture.hex);
}
}
});
i++;
}
it.skip('encode w/ tags', () => {
});
});
function inspect(o) {
if (typeof o === 'string') {
return `'${ o }'`;
}
if (o instanceof Uint8Array) {
return `Uint8Array<${ o.join(',') }>`;
}
if (o == null || typeof o !== 'object') {
return String(o);
}
return JSON.stringify(o);
}
@@ -0,0 +1,69 @@
import chai from 'chai';
import { decode } from '../cborg.js';
import { fromHex } from '../lib/byte-utils.js';
const {assert} = chai;
describe('decode errors', () => {
it('not Uint8Array', () => {
for (const arg of [
true,
false,
null,
undefined,
'string',
{ obj: 'ect' },
{},
['array'],
[],
[
1,
2,
3
],
0,
100,
1.1,
-1,
Symbol.for('nope')
]) {
assert.throws(() => decode(arg), /CBOR decode error.*must be a Uint8Array/);
}
});
it('no data', () => {
assert.throws(() => decode(new Uint8Array('')), /CBOR decode error.*content/);
});
it('break only', () => {
assert.throws(() => decode(new Uint8Array([255])), /CBOR decode error.*break/);
});
it('not enough map entries (value)', () => {
assert.throws(() => decode(fromHex('a2616f016174')), /map.*not enough entries.*value/);
});
it('not enough map entries (key)', () => {
assert.throws(() => decode(fromHex('a2616f01')), /map.*not enough entries.*key/);
});
it('break in lengthed map', () => {
assert.throws(() => decode(fromHex('a2616f01ff740f')), /unexpected break to lengthed map/);
});
it('not enough array entries', () => {
assert.throws(() => decode(fromHex('82616f')), /array.*not enough entries/);
});
it('break in lengthed array', () => {
assert.throws(() => decode(fromHex('82ff')), /unexpected break to lengthed array/);
});
it('no such decoder', () => {
assert.throws(() => decode(fromHex('82ff')), /unexpected break to lengthed array/);
});
it('too many terminals', () => {
assert.throws(() => decode(fromHex('0101')), /too many terminals/);
});
it('rejectDuplicateMapKeys enabled on duplicate keys', () => {
assert.deepStrictEqual(decode(fromHex('a3636261720363666f6f0163666f6f02')), {
foo: 2,
bar: 3
});
assert.throws(() => decode(fromHex('a3636261720363666f6f0163666f6f02'), { rejectDuplicateMapKeys: true }), /CBOR decode error: found repeat map key "foo"/);
assert.throws(() => decode(fromHex('a3636261720363666f6f0163666f6f02'), {
useMaps: true,
rejectDuplicateMapKeys: true
}), /CBOR decode error: found repeat map key "foo"/);
});
});
+50
View File
@@ -0,0 +1,50 @@
import { garbage } from 'ipld-garbage';
import {
decode,
encode
} from '../cborg.js';
import chai from 'chai';
const {assert} = chai;
describe('Fuzz round-trip', () => {
it('random objects', function () {
this.timeout(5000);
for (let i = 0; i < 1000; i++) {
const obj = garbage(300, { weights: { CID: 0 } });
const byts = encode(obj);
const decoded = decode(byts);
assert.deepEqual(decoded, obj);
}
});
it('circular references error', () => {
let obj = {};
obj.obj = obj;
assert.throws(() => encode(obj), /circular references/);
obj = {
blip: [
1,
2,
{ blop: {} }
]
};
obj.blip[2].blop.boop = obj;
assert.throws(() => encode(obj), /circular references/);
obj = {
blip: [
1,
2,
{ blop: {} }
]
};
obj.blip[2].blop.boop = obj.blip;
assert.throws(() => encode(obj), /circular references/);
obj = {
blip: {},
bloop: {}
};
obj.bloop = obj.blip;
assert.doesNotThrow(() => encode(obj));
const arr = [];
arr[0] = arr;
assert.throws(() => encode(arr), /circular references/);
});
});
+279
View File
@@ -0,0 +1,279 @@
import { assert } from 'chai';
import {
decode,
encode
} from '../lib/json/json.js';
const toBytes = str => new TextEncoder().encode(str);
function verifyRoundTrip(obj, sorting) {
const encoded = new TextDecoder().decode(encode(obj, sorting === false ? { mapSorter: null } : undefined));
const json = JSON.stringify(obj);
assert.strictEqual(encoded, json);
const decoded = decode(toBytes(JSON.stringify(obj)));
assert.deepStrictEqual(decoded, obj);
}
function verifyEncodedForm(testCase) {
const obj = JSON.parse(testCase);
const encoded = encode(obj);
assert.strictEqual(new TextDecoder().decode(encoded), JSON.stringify(obj));
const decoded = decode(encoded);
assert.deepStrictEqual(decoded, obj);
const decoded2 = decode(toBytes(testCase));
assert.deepStrictEqual(decoded2, obj);
}
describe('json basics', () => {
it('can round-trip basic literals', () => {
const testCases = [
'null',
'true',
'false',
'0',
'9007199254740991',
'-9007199254740991',
JSON.stringify(Number.MAX_VALUE),
JSON.stringify(Number.MIN_VALUE)
];
for (const testCase of testCases) {
verifyEncodedForm(testCase);
}
assert.strictEqual(decode(toBytes('1E1')), 10);
assert.strictEqual(decode(toBytes('0.1e1')), 1);
assert.strictEqual(decode(toBytes('1e-1')), 0.1);
assert.strictEqual(decode(toBytes('1e+00')), 1);
assert.strictEqual(decode(toBytes('10.0')), 10);
assert.deepStrictEqual(decode(toBytes('[-10.0,1.0,0.0,100.0]')), [
-10,
1,
0,
100
]);
verifyRoundTrip(true);
verifyRoundTrip(false);
verifyRoundTrip(null);
verifyRoundTrip(100);
verifyRoundTrip(-100);
verifyRoundTrip(1.11);
verifyRoundTrip(-100.11111);
verifyRoundTrip(11100000000);
verifyRoundTrip(1.0011111e-18);
});
it('handles large integers as BigInt', () => {
const verify = (inp, str) => {
if (str === undefined) {
str = String(inp);
}
assert.strictEqual(decode(toBytes(str), { allowBigInt: true }), inp);
assert.strictEqual(decode(toBytes(str)), parseFloat(str));
};
verify(Number.MAX_SAFE_INTEGER);
verify(-Number.MAX_SAFE_INTEGER);
verify(BigInt('9007199254740992'));
verify(BigInt('9007199254740993'));
verify(BigInt('11959030306112471731'));
verify(BigInt('18446744073709551615'));
verify(BigInt('9223372036854775807'));
verify(BigInt('-9007199254740992'));
verify(BigInt('-9007199254740993'));
verify(BigInt('-9223372036854776000'));
verify(BigInt('-11959030306112471732'));
verify(BigInt('-18446744073709551616'));
verify(-9007199254740992, '-9007199254740992.0');
verify(-9223372036854776000, '-9223372036854776000.0');
verify(-18446744073709552000, '-18446744073709551616.0');
});
it('can round-trip string literals', () => {
const testCases = [
JSON.stringify(''),
JSON.stringify(' '),
JSON.stringify('"'),
JSON.stringify('\\'),
JSON.stringify('\b\f\n\r\t'),
JSON.stringify('"'),
JSON.stringify('&#34; %22 0x22 034 &#x22;'),
'"\uD83D\uDE00"'
];
for (const testCase of testCases) {
verifyEncodedForm(testCase);
}
assert.strictEqual(decode(toBytes('"/ & \\/"')), '/ & /');
verifyRoundTrip('this is a string');
verifyRoundTrip('this \uD834\uDD1E is a \u263A\u263A \u2663 string ̐ ̀\n\r');
verifyRoundTrip('');
verifyRoundTrip('foo\\bar\nbaz\tbop\rbing"bip\'bang');
});
it('can round-trip array literals', () => {
const testCases = [
'[]',
'[null]',
'[true, false]',
'[ \n 0,1, 2\n , 3,\n4] \n ',
'[-10.0, 1.0, 0.0, 100.0]',
'[["2 deep"]]'
];
for (const testCase of testCases) {
verifyEncodedForm(testCase);
}
verifyRoundTrip([
1,
2,
3,
'string',
true,
4
]);
verifyRoundTrip([
1,
2,
3,
'string',
true,
[
'and',
'a',
'nested',
'array',
true
],
4
]);
});
it('can round-trip object literals', () => {
const testCases = [
'{}',
'\n {\n "\\b"\n :\n""\n }\n ',
'{"":""}',
'{"1":{"2":0,"3":"deep"}}'
];
for (const testCase of testCases) {
verifyEncodedForm(testCase);
}
});
it('will sort map keys', () => {
const unsorted = {
one: 1,
two: 2,
three: 3.1,
str: 'string',
bool: true,
four: 4
};
verifyRoundTrip(unsorted, false);
assert.strictEqual(new TextDecoder().decode(encode(unsorted)), '{"bool":true,"four":4,"one":1,"str":"string","three":3.1,"two":2}');
});
it('can handle novel cases', () => {
assert.strictEqual(decode(toBytes('"this \\uD834\\uDD1E is a \\u263a\\u263a string"')), 'this \uD834\uDD1E is a \u263A\u263A string');
verifyRoundTrip({
one: 1,
two: 2,
three: 3.1,
str: 'string',
arr: [
'and',
'a',
'nested',
[],
'array',
[
true,
1
],
false
],
bool: true,
obj: {
nested: 'object',
a: [],
o: {}
},
four: 4
}, false);
verifyRoundTrip([
false,
[
{
'#nFzU': {},
'\\w>': -0.9441451951197325,
'\t\'': '\'JB+2Wg\tw"IrM*#e^L/d&4rrzUuwq(1mH6aVRredB&Bfs]S"KqK(Tz1Q"URBAfw',
'\n@FrfM': 'M[D]q&'
},
'J4>\'Xdc+u2$%',
4227406737130333
]
], false);
verifyRoundTrip([
0.12995619865708727,
-4973404279772543,
{
drG2: [true],
';#K^Qf>V': null,
'`2=': 'ecc<e/$+-.;U>Gr5RdZDJ\n5+:{=QHNN.tVVN~dX$FWFwu`6>"&=tW!*1*^\u263A)JFM1p|}&X.B|${*\\f@!w2\u263A+'
}
], false);
assert.strictEqual(`${ decode(encode(9007199254740991)) }`, '9007199254740991');
assert.strictEqual(`${ decode(encode(9007199254740992)) }`, '9007199254740992');
assert.strictEqual(`${ decode(encode(900719925474099100n)) }`, '900719925474099100');
});
it('should throw on bad types', () => {
assert.throws(() => encode(new Uint8Array([
1,
2
])), /CBOR encode error: unsupported type: Uint8Array/);
assert.throws(() => encode({
boop: new Uint8Array([
1,
2
])
}), /CBOR encode error: unsupported type: Uint8Array/);
assert.throws(() => encode(undefined), /CBOR encode error: unsupported type: undefined/);
assert.throws(() => encode(new Map([
[
1,
2
],
[
2,
3
]
])), /CBOR encode error: non-string map keys are not supported/);
assert.throws(() => encode(new Map([
[
[
'foo',
'bar'
],
2
],
[
[
'bar',
'foo'
],
3
]
])), /CBOR encode error: complex map keys are not supported/);
});
it('should throw on bad decode failure modes', () => {
assert.throws(() => decode(toBytes('{"a":1 & "b":2}')), 'CBOR decode error: unexpected character at position 7, was expecting object delimiter but found \'&\'');
assert.throws(() => decode(toBytes('{"a":1,"b"!2}')), 'CBOR decode error: unexpected character at position 10, was expecting key/value delimiter \':\' but found \'!\'');
assert.throws(() => decode(toBytes('[1,2&3]')), 'CBOR decode error: unexpected character at position 4, was expecting array delimiter but found \'&\'');
assert.throws(() => decode(toBytes('{"a":!}')), 'CBOR decode error: unexpected character at position 5');
assert.throws(() => decode(toBytes('"abc')), 'CBOR decode error: unexpected end of string at position 4');
assert.throws(() => decode(toBytes('"ab\\xc"')), 'CBOR decode error: unexpected string escape character at position 5');
assert.throws(() => decode(toBytes('"ab\x1Ec"')), 'CBOR decode error: invalid control character at position 3');
assert.throws(() => decode(toBytes('"ab\\')), 'CBOR decode error: unexpected string termination at position 4');
assert.throws(() => decode(toBytes('"\u263A').subarray(0, 3)), 'CBOR decode error: unexpected unicode sequence at position 1');
assert.throws(() => decode(toBytes('"\\uxyza"')), 'CBOR decode error: unexpected unicode escape character at position 3');
assert.throws(() => decode(toBytes('"\\u11"')), 'CBOR decode error: unexpected end of unicode escape sequence at position 3');
assert.throws(() => decode(toBytes('-boop')), 'CBOR decode error: unexpected token at position 1');
assert.throws(() => decode(toBytes('{"v":nope}')), 'CBOR decode error: unexpected token at position 7, expected to find \'null\'');
assert.throws(() => decode(toBytes('[n]')), 'CBOR decode error: unexpected end of input at position 1');
assert.throws(() => decode(toBytes('{"v":truu}')), 'CBOR decode error: unexpected token at position 9, expected to find \'true\'');
assert.throws(() => decode(toBytes('[tr]')), 'CBOR decode error: unexpected end of input at position 1');
assert.throws(() => decode(toBytes('{"v":flase}')), 'CBOR decode error: unexpected token at position 7, expected to find \'false\'');
assert.throws(() => decode(toBytes('[fa]')), 'CBOR decode error: unexpected end of input at position 1');
assert.throws(() => decode(toBytes('-0..1')), 'CBOR decode error: unexpected token at position 3');
});
it('should throw when rejectDuplicateMapKeys enabled on duplicate keys', () => {
assert.deepStrictEqual(decode(toBytes('{"foo":1,"foo":2}')), { foo: 2 });
assert.throws(() => decode(toBytes('{"foo":1,"foo":2}'), { rejectDuplicateMapKeys: true }), /CBOR decode error: found repeat map key "foo"/);
});
});
+55
View File
@@ -0,0 +1,55 @@
import chai from 'chai';
import { garbage } from 'ipld-garbage';
import { uintBoundaries } from '../lib/0uint.js';
import { encode } from '../cborg.js';
import { encodedLength } from '../lib/length.js';
import { dateEncoder } from './common.js';
const {assert} = chai;
function verifyLength(object, options) {
const len = encodedLength(object, options);
const encoded = encode(object, options);
const actual = encoded.length;
assert.strictEqual(actual, len, JSON.stringify(object));
}
describe('encodedLength', () => {
it('int boundaries', () => {
for (let ii = 0; ii < 4; ii++) {
verifyLength(uintBoundaries[ii]);
verifyLength(uintBoundaries[ii] - 1);
verifyLength(uintBoundaries[ii] + 1);
verifyLength(-1 * uintBoundaries[ii]);
verifyLength(-1 * uintBoundaries[ii] - 1);
verifyLength(-1 * uintBoundaries[ii] + 1);
}
});
it('tags', () => {
verifyLength({ date: new Date('2013-03-21T20:04:00Z') }, { typeEncoders: { Date: dateEncoder } });
});
it('floats', () => {
verifyLength(0.5);
verifyLength(0.5, { float64: true });
verifyLength(8.940696716308594e-8);
verifyLength(8.940696716308594e-8, { float64: true });
});
it('small garbage', function () {
this.timeout(10000);
for (let ii = 0; ii < 1000; ii++) {
const gbg = garbage(1 << 6, { weights: { CID: 0 } });
verifyLength(gbg);
}
});
it('medium garbage', function () {
this.timeout(10000);
for (let ii = 0; ii < 100; ii++) {
const gbg = garbage(1 << 16, { weights: { CID: 0 } });
verifyLength(gbg);
}
});
it('large garbage', function () {
this.timeout(10000);
for (let ii = 0; ii < 10; ii++) {
const gbg = garbage(1 << 20, { weights: { CID: 0 } });
verifyLength(gbg);
}
});
});
+12
View File
@@ -0,0 +1,12 @@
import { encode } from './lib/encode.js';
import { decode } from './lib/decode.js';
import {
Token,
Type
} from './lib/token.js';
export {
decode,
encode,
Token,
Type
};
+152
View File
@@ -0,0 +1,152 @@
import {
Token,
Type
} from './token.js';
import {
decodeErrPrefix,
assertEnoughData
} from './common.js';
export const uintBoundaries = [
24,
256,
65536,
4294967296,
BigInt('18446744073709551616')
];
export function readUint8(data, offset, options) {
assertEnoughData(data, offset, 1);
const value = data[offset];
if (options.strict === true && value < uintBoundaries[0]) {
throw new Error(`${ decodeErrPrefix } integer encoded in more bytes than necessary (strict decode)`);
}
return value;
}
export function readUint16(data, offset, options) {
assertEnoughData(data, offset, 2);
const value = data[offset] << 8 | data[offset + 1];
if (options.strict === true && value < uintBoundaries[1]) {
throw new Error(`${ decodeErrPrefix } integer encoded in more bytes than necessary (strict decode)`);
}
return value;
}
export function readUint32(data, offset, options) {
assertEnoughData(data, offset, 4);
const value = data[offset] * 16777216 + (data[offset + 1] << 16) + (data[offset + 2] << 8) + data[offset + 3];
if (options.strict === true && value < uintBoundaries[2]) {
throw new Error(`${ decodeErrPrefix } integer encoded in more bytes than necessary (strict decode)`);
}
return value;
}
export function readUint64(data, offset, options) {
assertEnoughData(data, offset, 8);
const hi = data[offset] * 16777216 + (data[offset + 1] << 16) + (data[offset + 2] << 8) + data[offset + 3];
const lo = data[offset + 4] * 16777216 + (data[offset + 5] << 16) + (data[offset + 6] << 8) + data[offset + 7];
const value = (BigInt(hi) << BigInt(32)) + BigInt(lo);
if (options.strict === true && value < uintBoundaries[3]) {
throw new Error(`${ decodeErrPrefix } integer encoded in more bytes than necessary (strict decode)`);
}
if (value <= Number.MAX_SAFE_INTEGER) {
return Number(value);
}
if (options.allowBigInt === true) {
return value;
}
throw new Error(`${ decodeErrPrefix } integers outside of the safe integer range are not supported`);
}
export function decodeUint8(data, pos, _minor, options) {
return new Token(Type.uint, readUint8(data, pos + 1, options), 2);
}
export function decodeUint16(data, pos, _minor, options) {
return new Token(Type.uint, readUint16(data, pos + 1, options), 3);
}
export function decodeUint32(data, pos, _minor, options) {
return new Token(Type.uint, readUint32(data, pos + 1, options), 5);
}
export function decodeUint64(data, pos, _minor, options) {
return new Token(Type.uint, readUint64(data, pos + 1, options), 9);
}
export function encodeUint(buf, token) {
return encodeUintValue(buf, 0, token.value);
}
export function encodeUintValue(buf, major, uint) {
if (uint < uintBoundaries[0]) {
const nuint = Number(uint);
buf.push([major | nuint]);
} else if (uint < uintBoundaries[1]) {
const nuint = Number(uint);
buf.push([
major | 24,
nuint
]);
} else if (uint < uintBoundaries[2]) {
const nuint = Number(uint);
buf.push([
major | 25,
nuint >>> 8,
nuint & 255
]);
} else if (uint < uintBoundaries[3]) {
const nuint = Number(uint);
buf.push([
major | 26,
nuint >>> 24 & 255,
nuint >>> 16 & 255,
nuint >>> 8 & 255,
nuint & 255
]);
} else {
const buint = BigInt(uint);
if (buint < uintBoundaries[4]) {
const set = [
major | 27,
0,
0,
0,
0,
0,
0,
0
];
let lo = Number(buint & BigInt(4294967295));
let hi = Number(buint >> BigInt(32) & BigInt(4294967295));
set[8] = lo & 255;
lo = lo >> 8;
set[7] = lo & 255;
lo = lo >> 8;
set[6] = lo & 255;
lo = lo >> 8;
set[5] = lo & 255;
set[4] = hi & 255;
hi = hi >> 8;
set[3] = hi & 255;
hi = hi >> 8;
set[2] = hi & 255;
hi = hi >> 8;
set[1] = hi & 255;
buf.push(set);
} else {
throw new Error(`${ decodeErrPrefix } encountered BigInt larger than allowable range`);
}
}
}
encodeUint.encodedSize = function encodedSize(token) {
return encodeUintValue.encodedSize(token.value);
};
encodeUintValue.encodedSize = function encodedSize(uint) {
if (uint < uintBoundaries[0]) {
return 1;
}
if (uint < uintBoundaries[1]) {
return 2;
}
if (uint < uintBoundaries[2]) {
return 3;
}
if (uint < uintBoundaries[3]) {
return 5;
}
return 9;
};
encodeUint.compareTokens = function compareTokens(tok1, tok2) {
return tok1.value < tok2.value ? -1 : tok1.value > tok2.value ? 1 : 0;
};
+55
View File
@@ -0,0 +1,55 @@
import {
Token,
Type
} from './token.js';
import * as uint from './0uint.js';
import { decodeErrPrefix } from './common.js';
export function decodeNegint8(data, pos, _minor, options) {
return new Token(Type.negint, -1 - uint.readUint8(data, pos + 1, options), 2);
}
export function decodeNegint16(data, pos, _minor, options) {
return new Token(Type.negint, -1 - uint.readUint16(data, pos + 1, options), 3);
}
export function decodeNegint32(data, pos, _minor, options) {
return new Token(Type.negint, -1 - uint.readUint32(data, pos + 1, options), 5);
}
const neg1b = BigInt(-1);
const pos1b = BigInt(1);
export function decodeNegint64(data, pos, _minor, options) {
const int = uint.readUint64(data, pos + 1, options);
if (typeof int !== 'bigint') {
const value = -1 - int;
if (value >= Number.MIN_SAFE_INTEGER) {
return new Token(Type.negint, value, 9);
}
}
if (options.allowBigInt !== true) {
throw new Error(`${ decodeErrPrefix } integers outside of the safe integer range are not supported`);
}
return new Token(Type.negint, neg1b - BigInt(int), 9);
}
export function encodeNegint(buf, token) {
const negint = token.value;
const unsigned = typeof negint === 'bigint' ? negint * neg1b - pos1b : negint * -1 - 1;
uint.encodeUintValue(buf, token.type.majorEncoded, unsigned);
}
encodeNegint.encodedSize = function encodedSize(token) {
const negint = token.value;
const unsigned = typeof negint === 'bigint' ? negint * neg1b - pos1b : negint * -1 - 1;
if (unsigned < uint.uintBoundaries[0]) {
return 1;
}
if (unsigned < uint.uintBoundaries[1]) {
return 2;
}
if (unsigned < uint.uintBoundaries[2]) {
return 3;
}
if (unsigned < uint.uintBoundaries[3]) {
return 5;
}
return 9;
};
encodeNegint.compareTokens = function compareTokens(tok1, tok2) {
return tok1.value < tok2.value ? 1 : tok1.value > tok2.value ? -1 : 0;
};
+59
View File
@@ -0,0 +1,59 @@
import {
Token,
Type
} from './token.js';
import {
assertEnoughData,
decodeErrPrefix
} from './common.js';
import * as uint from './0uint.js';
import {
compare,
fromString,
slice
} from './byte-utils.js';
function toToken(data, pos, prefix, length) {
assertEnoughData(data, pos, prefix + length);
const buf = slice(data, pos + prefix, pos + prefix + length);
return new Token(Type.bytes, buf, prefix + length);
}
export function decodeBytesCompact(data, pos, minor, _options) {
return toToken(data, pos, 1, minor);
}
export function decodeBytes8(data, pos, _minor, options) {
return toToken(data, pos, 2, uint.readUint8(data, pos + 1, options));
}
export function decodeBytes16(data, pos, _minor, options) {
return toToken(data, pos, 3, uint.readUint16(data, pos + 1, options));
}
export function decodeBytes32(data, pos, _minor, options) {
return toToken(data, pos, 5, uint.readUint32(data, pos + 1, options));
}
export function decodeBytes64(data, pos, _minor, options) {
const l = uint.readUint64(data, pos + 1, options);
if (typeof l === 'bigint') {
throw new Error(`${ decodeErrPrefix } 64-bit integer bytes lengths not supported`);
}
return toToken(data, pos, 9, l);
}
function tokenBytes(token) {
if (token.encodedBytes === undefined) {
token.encodedBytes = token.type === Type.string ? fromString(token.value) : token.value;
}
return token.encodedBytes;
}
export function encodeBytes(buf, token) {
const bytes = tokenBytes(token);
uint.encodeUintValue(buf, token.type.majorEncoded, bytes.length);
buf.push(bytes);
}
encodeBytes.encodedSize = function encodedSize(token) {
const bytes = tokenBytes(token);
return uint.encodeUintValue.encodedSize(bytes.length) + bytes.length;
};
encodeBytes.compareTokens = function compareTokens(tok1, tok2) {
return compareBytes(tokenBytes(tok1), tokenBytes(tok2));
};
export function compareBytes(b1, b2) {
return b1.length < b2.length ? -1 : b1.length > b2.length ? 1 : compare(b1, b2);
}
+43
View File
@@ -0,0 +1,43 @@
import {
Token,
Type
} from './token.js';
import {
assertEnoughData,
decodeErrPrefix
} from './common.js';
import * as uint from './0uint.js';
import { encodeBytes } from './2bytes.js';
import {
toString,
slice
} from './byte-utils.js';
function toToken(data, pos, prefix, length, options) {
const totLength = prefix + length;
assertEnoughData(data, pos, totLength);
const tok = new Token(Type.string, toString(data, pos + prefix, pos + totLength), totLength);
if (options.retainStringBytes === true) {
tok.byteValue = slice(data, pos + prefix, pos + totLength);
}
return tok;
}
export function decodeStringCompact(data, pos, minor, options) {
return toToken(data, pos, 1, minor, options);
}
export function decodeString8(data, pos, _minor, options) {
return toToken(data, pos, 2, uint.readUint8(data, pos + 1, options), options);
}
export function decodeString16(data, pos, _minor, options) {
return toToken(data, pos, 3, uint.readUint16(data, pos + 1, options), options);
}
export function decodeString32(data, pos, _minor, options) {
return toToken(data, pos, 5, uint.readUint32(data, pos + 1, options), options);
}
export function decodeString64(data, pos, _minor, options) {
const l = uint.readUint64(data, pos + 1, options);
if (typeof l === 'bigint') {
throw new Error(`${ decodeErrPrefix } 64-bit integer string lengths not supported`);
}
return toToken(data, pos, 9, l, options);
}
export const encodeString = encodeBytes;
+41
View File
@@ -0,0 +1,41 @@
import {
Token,
Type
} from './token.js';
import * as uint from './0uint.js';
import { decodeErrPrefix } from './common.js';
function toToken(_data, _pos, prefix, length) {
return new Token(Type.array, length, prefix);
}
export function decodeArrayCompact(data, pos, minor, _options) {
return toToken(data, pos, 1, minor);
}
export function decodeArray8(data, pos, _minor, options) {
return toToken(data, pos, 2, uint.readUint8(data, pos + 1, options));
}
export function decodeArray16(data, pos, _minor, options) {
return toToken(data, pos, 3, uint.readUint16(data, pos + 1, options));
}
export function decodeArray32(data, pos, _minor, options) {
return toToken(data, pos, 5, uint.readUint32(data, pos + 1, options));
}
export function decodeArray64(data, pos, _minor, options) {
const l = uint.readUint64(data, pos + 1, options);
if (typeof l === 'bigint') {
throw new Error(`${ decodeErrPrefix } 64-bit integer array lengths not supported`);
}
return toToken(data, pos, 9, l);
}
export function decodeArrayIndefinite(data, pos, _minor, options) {
if (options.allowIndefinite === false) {
throw new Error(`${ decodeErrPrefix } indefinite length items not allowed`);
}
return toToken(data, pos, 1, Infinity);
}
export function encodeArray(buf, token) {
uint.encodeUintValue(buf, Type.array.majorEncoded, token.value);
}
encodeArray.compareTokens = uint.encodeUint.compareTokens;
encodeArray.encodedSize = function encodedSize(token) {
return uint.encodeUintValue.encodedSize(token.value);
};
+41
View File
@@ -0,0 +1,41 @@
import {
Token,
Type
} from './token.js';
import * as uint from './0uint.js';
import { decodeErrPrefix } from './common.js';
function toToken(_data, _pos, prefix, length) {
return new Token(Type.map, length, prefix);
}
export function decodeMapCompact(data, pos, minor, _options) {
return toToken(data, pos, 1, minor);
}
export function decodeMap8(data, pos, _minor, options) {
return toToken(data, pos, 2, uint.readUint8(data, pos + 1, options));
}
export function decodeMap16(data, pos, _minor, options) {
return toToken(data, pos, 3, uint.readUint16(data, pos + 1, options));
}
export function decodeMap32(data, pos, _minor, options) {
return toToken(data, pos, 5, uint.readUint32(data, pos + 1, options));
}
export function decodeMap64(data, pos, _minor, options) {
const l = uint.readUint64(data, pos + 1, options);
if (typeof l === 'bigint') {
throw new Error(`${ decodeErrPrefix } 64-bit integer map lengths not supported`);
}
return toToken(data, pos, 9, l);
}
export function decodeMapIndefinite(data, pos, _minor, options) {
if (options.allowIndefinite === false) {
throw new Error(`${ decodeErrPrefix } indefinite length items not allowed`);
}
return toToken(data, pos, 1, Infinity);
}
export function encodeMap(buf, token) {
uint.encodeUintValue(buf, Type.map.majorEncoded, token.value);
}
encodeMap.compareTokens = uint.encodeUint.compareTokens;
encodeMap.encodedSize = function encodedSize(token) {
return uint.encodeUintValue.encodedSize(token.value);
};
+27
View File
@@ -0,0 +1,27 @@
import {
Token,
Type
} from './token.js';
import * as uint from './0uint.js';
export function decodeTagCompact(_data, _pos, minor, _options) {
return new Token(Type.tag, minor, 1);
}
export function decodeTag8(data, pos, _minor, options) {
return new Token(Type.tag, uint.readUint8(data, pos + 1, options), 2);
}
export function decodeTag16(data, pos, _minor, options) {
return new Token(Type.tag, uint.readUint16(data, pos + 1, options), 3);
}
export function decodeTag32(data, pos, _minor, options) {
return new Token(Type.tag, uint.readUint32(data, pos + 1, options), 5);
}
export function decodeTag64(data, pos, _minor, options) {
return new Token(Type.tag, uint.readUint64(data, pos + 1, options), 9);
}
export function encodeTag(buf, token) {
uint.encodeUintValue(buf, Type.tag.majorEncoded, token.value);
}
encodeTag.compareTokens = uint.encodeUint.compareTokens;
encodeTag.encodedSize = function encodedSize(token) {
return uint.encodeUintValue.encodedSize(token.value);
};
+179
View File
@@ -0,0 +1,179 @@
import {
Token,
Type
} from './token.js';
import { decodeErrPrefix } from './common.js';
import { encodeUint } from './0uint.js';
const MINOR_FALSE = 20;
const MINOR_TRUE = 21;
const MINOR_NULL = 22;
const MINOR_UNDEFINED = 23;
export function decodeUndefined(_data, _pos, _minor, options) {
if (options.allowUndefined === false) {
throw new Error(`${ decodeErrPrefix } undefined values are not supported`);
} else if (options.coerceUndefinedToNull === true) {
return new Token(Type.null, null, 1);
}
return new Token(Type.undefined, undefined, 1);
}
export function decodeBreak(_data, _pos, _minor, options) {
if (options.allowIndefinite === false) {
throw new Error(`${ decodeErrPrefix } indefinite length items not allowed`);
}
return new Token(Type.break, undefined, 1);
}
function createToken(value, bytes, options) {
if (options) {
if (options.allowNaN === false && Number.isNaN(value)) {
throw new Error(`${ decodeErrPrefix } NaN values are not supported`);
}
if (options.allowInfinity === false && (value === Infinity || value === -Infinity)) {
throw new Error(`${ decodeErrPrefix } Infinity values are not supported`);
}
}
return new Token(Type.float, value, bytes);
}
export function decodeFloat16(data, pos, _minor, options) {
return createToken(readFloat16(data, pos + 1), 3, options);
}
export function decodeFloat32(data, pos, _minor, options) {
return createToken(readFloat32(data, pos + 1), 5, options);
}
export function decodeFloat64(data, pos, _minor, options) {
return createToken(readFloat64(data, pos + 1), 9, options);
}
export function encodeFloat(buf, token, options) {
const float = token.value;
if (float === false) {
buf.push([Type.float.majorEncoded | MINOR_FALSE]);
} else if (float === true) {
buf.push([Type.float.majorEncoded | MINOR_TRUE]);
} else if (float === null) {
buf.push([Type.float.majorEncoded | MINOR_NULL]);
} else if (float === undefined) {
buf.push([Type.float.majorEncoded | MINOR_UNDEFINED]);
} else {
let decoded;
let success = false;
if (!options || options.float64 !== true) {
encodeFloat16(float);
decoded = readFloat16(ui8a, 1);
if (float === decoded || Number.isNaN(float)) {
ui8a[0] = 249;
buf.push(ui8a.slice(0, 3));
success = true;
} else {
encodeFloat32(float);
decoded = readFloat32(ui8a, 1);
if (float === decoded) {
ui8a[0] = 250;
buf.push(ui8a.slice(0, 5));
success = true;
}
}
}
if (!success) {
encodeFloat64(float);
decoded = readFloat64(ui8a, 1);
ui8a[0] = 251;
buf.push(ui8a.slice(0, 9));
}
}
}
encodeFloat.encodedSize = function encodedSize(token, options) {
const float = token.value;
if (float === false || float === true || float === null || float === undefined) {
return 1;
}
if (!options || options.float64 !== true) {
encodeFloat16(float);
let decoded = readFloat16(ui8a, 1);
if (float === decoded || Number.isNaN(float)) {
return 3;
}
encodeFloat32(float);
decoded = readFloat32(ui8a, 1);
if (float === decoded) {
return 5;
}
}
return 9;
};
const buffer = new ArrayBuffer(9);
const dataView = new DataView(buffer, 1);
const ui8a = new Uint8Array(buffer, 0);
function encodeFloat16(inp) {
if (inp === Infinity) {
dataView.setUint16(0, 31744, false);
} else if (inp === -Infinity) {
dataView.setUint16(0, 64512, false);
} else if (Number.isNaN(inp)) {
dataView.setUint16(0, 32256, false);
} else {
dataView.setFloat32(0, inp);
const valu32 = dataView.getUint32(0);
const exponent = (valu32 & 2139095040) >> 23;
const mantissa = valu32 & 8388607;
if (exponent === 255) {
dataView.setUint16(0, 31744, false);
} else if (exponent === 0) {
dataView.setUint16(0, (inp & 2147483648) >> 16 | mantissa >> 13, false);
} else {
const logicalExponent = exponent - 127;
if (logicalExponent < -24) {
dataView.setUint16(0, 0);
} else if (logicalExponent < -14) {
dataView.setUint16(0, (valu32 & 2147483648) >> 16 | 1 << 24 + logicalExponent, false);
} else {
dataView.setUint16(0, (valu32 & 2147483648) >> 16 | logicalExponent + 15 << 10 | mantissa >> 13, false);
}
}
}
}
function readFloat16(ui8a, pos) {
if (ui8a.length - pos < 2) {
throw new Error(`${ decodeErrPrefix } not enough data for float16`);
}
const half = (ui8a[pos] << 8) + ui8a[pos + 1];
if (half === 31744) {
return Infinity;
}
if (half === 64512) {
return -Infinity;
}
if (half === 32256) {
return NaN;
}
const exp = half >> 10 & 31;
const mant = half & 1023;
let val;
if (exp === 0) {
val = mant * 2 ** -24;
} else if (exp !== 31) {
val = (mant + 1024) * 2 ** (exp - 25);
} else {
val = mant === 0 ? Infinity : NaN;
}
return half & 32768 ? -val : val;
}
function encodeFloat32(inp) {
dataView.setFloat32(0, inp, false);
}
function readFloat32(ui8a, pos) {
if (ui8a.length - pos < 4) {
throw new Error(`${ decodeErrPrefix } not enough data for float32`);
}
const offset = (ui8a.byteOffset || 0) + pos;
return new DataView(ui8a.buffer, offset, 4).getFloat32(0, false);
}
function encodeFloat64(inp) {
dataView.setFloat64(0, inp, false);
}
function readFloat64(ui8a, pos) {
if (ui8a.length - pos < 8) {
throw new Error(`${ decodeErrPrefix } not enough data for float64`);
}
const offset = (ui8a.byteOffset || 0) + pos;
return new DataView(ui8a.buffer, offset, 8).getFloat64(0, false);
}
encodeFloat.compareTokens = encodeUint.compareTokens;
+137
View File
@@ -0,0 +1,137 @@
import process from 'process';
import {
decode,
encode
} from '../cborg.js';
import {
tokensToDiagnostic,
fromDiag
} from './diagnostic.js';
import {
fromHex as _fromHex,
toHex
} from './byte-utils.js';
function usage(code) {
console.error('Usage: cborg <command> <args>');
console.error('Valid commands:');
console.error('\tbin2diag [binary input]');
console.error('\tbin2hex [binary input]');
console.error('\tbin2json [--pretty] [binary input]');
console.error('\tdiag2bin [diagnostic input]');
console.error('\tdiag2hex [diagnostic input]');
console.error('\tdiag2json [--pretty] [diagnostic input]');
console.error('\thex2bin [hex input]');
console.error('\thex2diag [hex input]');
console.error('\thex2json [--pretty] [hex input]');
console.error('\tjson2bin \'[json input]\'');
console.error('\tjson2diag \'[json input]\'');
console.error('\tjson2hex \'[json input]\'');
console.error('Input may either be supplied as an argument or piped via stdin');
process.exit(code || 0);
}
async function fromStdin() {
const chunks = [];
for await (const chunk of process.stdin) {
chunks.push(chunk);
}
return Buffer.concat(chunks);
}
function fromHex(str) {
str = str.replace(/\r?\n/g, '');
if (!/^([0-9a-f]{2})*$/i.test(str)) {
throw new Error('Input string is not hexadecimal format');
}
return _fromHex(str);
}
function argvPretty() {
const argv = process.argv.filter(s => s !== '--pretty');
const pretty = argv.length !== process.argv.length;
return {
argv,
pretty
};
}
async function run() {
const cmd = process.argv[2];
switch (cmd) {
case 'help': {
return usage(0);
}
case 'bin2diag': {
const bin = process.argv.length < 4 ? await fromStdin() : new TextEncoder().encode(process.argv[3]);
for (const line of tokensToDiagnostic(bin)) {
console.log(line);
}
return;
}
case 'bin2hex': {
const bin = process.argv.length < 4 ? await fromStdin() : new TextEncoder().encode(process.argv[3]);
return console.log(toHex(bin));
}
case 'bin2json': {
const {argv, pretty} = argvPretty();
const bin = argv.length < 4 ? await fromStdin() : new TextEncoder().encode(argv[3]);
return console.log(JSON.stringify(decode(bin), undefined, pretty ? 2 : undefined));
}
case 'diag2bin': {
const bin = fromDiag(process.argv.length < 4 ? (await fromStdin()).toString() : process.argv[3]);
return process.stdout.write(bin);
}
case 'diag2hex': {
const bin = fromDiag(process.argv.length < 4 ? (await fromStdin()).toString() : process.argv[3]);
return console.log(toHex(bin));
}
case 'diag2json': {
const {argv, pretty} = argvPretty();
const bin = fromDiag(argv.length < 4 ? (await fromStdin()).toString() : argv[3]);
return console.log(JSON.stringify(decode(bin), undefined, pretty ? 2 : undefined));
}
case 'hex2bin': {
const bin = fromHex(process.argv.length < 4 ? (await fromStdin()).toString() : process.argv[3]);
return process.stdout.write(bin);
}
case 'hex2diag': {
const bin = fromHex(process.argv.length < 4 ? (await fromStdin()).toString() : process.argv[3]);
for (const line of tokensToDiagnostic(bin)) {
console.log(line);
}
return;
}
case 'hex2json': {
const {argv, pretty} = argvPretty();
const bin = fromHex(argv.length < 4 ? (await fromStdin()).toString() : argv[3]);
return console.log(JSON.stringify(decode(bin), undefined, pretty ? 2 : undefined));
}
case 'json2bin': {
const inp = process.argv.length < 4 ? (await fromStdin()).toString() : process.argv[3];
const obj = JSON.parse(inp);
return process.stdout.write(encode(obj));
}
case 'json2diag': {
const inp = process.argv.length < 4 ? (await fromStdin()).toString() : process.argv[3];
const obj = JSON.parse(inp);
for (const line of tokensToDiagnostic(encode(obj))) {
console.log(line);
}
return;
}
case 'json2hex': {
const inp = process.argv.length < 4 ? (await fromStdin()).toString() : process.argv[3];
const obj = JSON.parse(inp);
return console.log(toHex(encode(obj)));
}
default: {
if (process.argv.findIndex(a => a.endsWith('mocha')) === -1) {
if (cmd) {
console.error(`Unknown command: '${ cmd }'`);
}
usage(1);
}
}
}
}
run().catch(err => {
console.error(err);
process.exit(1);
});
export default true;
+74
View File
@@ -0,0 +1,74 @@
import {
alloc,
concat,
slice
} from './byte-utils.js';
const defaultChunkSize = 256;
export class Bl {
constructor(chunkSize = defaultChunkSize) {
this.chunkSize = chunkSize;
this.cursor = 0;
this.maxCursor = -1;
this.chunks = [];
this._initReuseChunk = null;
}
reset() {
this.cursor = 0;
this.maxCursor = -1;
if (this.chunks.length) {
this.chunks = [];
}
if (this._initReuseChunk !== null) {
this.chunks.push(this._initReuseChunk);
this.maxCursor = this._initReuseChunk.length - 1;
}
}
push(bytes) {
let topChunk = this.chunks[this.chunks.length - 1];
const newMax = this.cursor + bytes.length;
if (newMax <= this.maxCursor + 1) {
const chunkPos = topChunk.length - (this.maxCursor - this.cursor) - 1;
topChunk.set(bytes, chunkPos);
} else {
if (topChunk) {
const chunkPos = topChunk.length - (this.maxCursor - this.cursor) - 1;
if (chunkPos < topChunk.length) {
this.chunks[this.chunks.length - 1] = topChunk.subarray(0, chunkPos);
this.maxCursor = this.cursor - 1;
}
}
if (bytes.length < 64 && bytes.length < this.chunkSize) {
topChunk = alloc(this.chunkSize);
this.chunks.push(topChunk);
this.maxCursor += topChunk.length;
if (this._initReuseChunk === null) {
this._initReuseChunk = topChunk;
}
topChunk.set(bytes, 0);
} else {
this.chunks.push(bytes);
this.maxCursor += bytes.length;
}
}
this.cursor += bytes.length;
}
toBytes(reset = false) {
let byts;
if (this.chunks.length === 1) {
const chunk = this.chunks[0];
if (reset && this.cursor > chunk.length / 2) {
byts = this.cursor === chunk.length ? chunk : chunk.subarray(0, this.cursor);
this._initReuseChunk = null;
this.chunks = [];
} else {
byts = slice(chunk, 0, this.cursor);
}
} else {
byts = concat(this.chunks, this.cursor);
}
if (reset) {
this.reset();
}
return byts;
}
}
+228
View File
@@ -0,0 +1,228 @@
export const useBuffer = globalThis.process && !globalThis.process.browser && globalThis.Buffer && typeof globalThis.Buffer.isBuffer === 'function';
const textDecoder = new TextDecoder();
const textEncoder = new TextEncoder();
function isBuffer(buf) {
return useBuffer && globalThis.Buffer.isBuffer(buf);
}
export function asU8A(buf) {
if (!(buf instanceof Uint8Array)) {
return Uint8Array.from(buf);
}
return isBuffer(buf) ? new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength) : buf;
}
export const toString = useBuffer ? (bytes, start, end) => {
return end - start > 64 ? globalThis.Buffer.from(bytes.subarray(start, end)).toString('utf8') : utf8Slice(bytes, start, end);
} : (bytes, start, end) => {
return end - start > 64 ? textDecoder.decode(bytes.subarray(start, end)) : utf8Slice(bytes, start, end);
};
export const fromString = useBuffer ? string => {
return string.length > 64 ? globalThis.Buffer.from(string) : utf8ToBytes(string);
} : string => {
return string.length > 64 ? textEncoder.encode(string) : utf8ToBytes(string);
};
export const fromArray = arr => {
return Uint8Array.from(arr);
};
export const slice = useBuffer ? (bytes, start, end) => {
if (isBuffer(bytes)) {
return new Uint8Array(bytes.subarray(start, end));
}
return bytes.slice(start, end);
} : (bytes, start, end) => {
return bytes.slice(start, end);
};
export const concat = useBuffer ? (chunks, length) => {
chunks = chunks.map(c => c instanceof Uint8Array ? c : globalThis.Buffer.from(c));
return asU8A(globalThis.Buffer.concat(chunks, length));
} : (chunks, length) => {
const out = new Uint8Array(length);
let off = 0;
for (let b of chunks) {
if (off + b.length > out.length) {
b = b.subarray(0, out.length - off);
}
out.set(b, off);
off += b.length;
}
return out;
};
export const alloc = useBuffer ? size => {
return globalThis.Buffer.allocUnsafe(size);
} : size => {
return new Uint8Array(size);
};
export const toHex = useBuffer ? d => {
if (typeof d === 'string') {
return d;
}
return globalThis.Buffer.from(toBytes(d)).toString('hex');
} : d => {
if (typeof d === 'string') {
return d;
}
return Array.prototype.reduce.call(toBytes(d), (p, c) => `${ p }${ c.toString(16).padStart(2, '0') }`, '');
};
export const fromHex = useBuffer ? hex => {
if (hex instanceof Uint8Array) {
return hex;
}
return globalThis.Buffer.from(hex, 'hex');
} : hex => {
if (hex instanceof Uint8Array) {
return hex;
}
if (!hex.length) {
return new Uint8Array(0);
}
return new Uint8Array(hex.split('').map((c, i, d) => i % 2 === 0 ? `0x${ c }${ d[i + 1] }` : '').filter(Boolean).map(e => parseInt(e, 16)));
};
function toBytes(obj) {
if (obj instanceof Uint8Array && obj.constructor.name === 'Uint8Array') {
return obj;
}
if (obj instanceof ArrayBuffer) {
return new Uint8Array(obj);
}
if (ArrayBuffer.isView(obj)) {
return new Uint8Array(obj.buffer, obj.byteOffset, obj.byteLength);
}
throw new Error('Unknown type, must be binary type');
}
export function compare(b1, b2) {
if (isBuffer(b1) && isBuffer(b2)) {
return b1.compare(b2);
}
for (let i = 0; i < b1.length; i++) {
if (b1[i] === b2[i]) {
continue;
}
return b1[i] < b2[i] ? -1 : 1;
}
return 0;
}
function utf8ToBytes(string, units = Infinity) {
let codePoint;
const length = string.length;
let leadSurrogate = null;
const bytes = [];
for (let i = 0; i < length; ++i) {
codePoint = string.charCodeAt(i);
if (codePoint > 55295 && codePoint < 57344) {
if (!leadSurrogate) {
if (codePoint > 56319) {
if ((units -= 3) > -1)
bytes.push(239, 191, 189);
continue;
} else if (i + 1 === length) {
if ((units -= 3) > -1)
bytes.push(239, 191, 189);
continue;
}
leadSurrogate = codePoint;
continue;
}
if (codePoint < 56320) {
if ((units -= 3) > -1)
bytes.push(239, 191, 189);
leadSurrogate = codePoint;
continue;
}
codePoint = (leadSurrogate - 55296 << 10 | codePoint - 56320) + 65536;
} else if (leadSurrogate) {
if ((units -= 3) > -1)
bytes.push(239, 191, 189);
}
leadSurrogate = null;
if (codePoint < 128) {
if ((units -= 1) < 0)
break;
bytes.push(codePoint);
} else if (codePoint < 2048) {
if ((units -= 2) < 0)
break;
bytes.push(codePoint >> 6 | 192, codePoint & 63 | 128);
} else if (codePoint < 65536) {
if ((units -= 3) < 0)
break;
bytes.push(codePoint >> 12 | 224, codePoint >> 6 & 63 | 128, codePoint & 63 | 128);
} else if (codePoint < 1114112) {
if ((units -= 4) < 0)
break;
bytes.push(codePoint >> 18 | 240, codePoint >> 12 & 63 | 128, codePoint >> 6 & 63 | 128, codePoint & 63 | 128);
} else {
throw new Error('Invalid code point');
}
}
return bytes;
}
function utf8Slice(buf, offset, end) {
const res = [];
while (offset < end) {
const firstByte = buf[offset];
let codePoint = null;
let bytesPerSequence = firstByte > 239 ? 4 : firstByte > 223 ? 3 : firstByte > 191 ? 2 : 1;
if (offset + bytesPerSequence <= end) {
let secondByte, thirdByte, fourthByte, tempCodePoint;
switch (bytesPerSequence) {
case 1:
if (firstByte < 128) {
codePoint = firstByte;
}
break;
case 2:
secondByte = buf[offset + 1];
if ((secondByte & 192) === 128) {
tempCodePoint = (firstByte & 31) << 6 | secondByte & 63;
if (tempCodePoint > 127) {
codePoint = tempCodePoint;
}
}
break;
case 3:
secondByte = buf[offset + 1];
thirdByte = buf[offset + 2];
if ((secondByte & 192) === 128 && (thirdByte & 192) === 128) {
tempCodePoint = (firstByte & 15) << 12 | (secondByte & 63) << 6 | thirdByte & 63;
if (tempCodePoint > 2047 && (tempCodePoint < 55296 || tempCodePoint > 57343)) {
codePoint = tempCodePoint;
}
}
break;
case 4:
secondByte = buf[offset + 1];
thirdByte = buf[offset + 2];
fourthByte = buf[offset + 3];
if ((secondByte & 192) === 128 && (thirdByte & 192) === 128 && (fourthByte & 192) === 128) {
tempCodePoint = (firstByte & 15) << 18 | (secondByte & 63) << 12 | (thirdByte & 63) << 6 | fourthByte & 63;
if (tempCodePoint > 65535 && tempCodePoint < 1114112) {
codePoint = tempCodePoint;
}
}
}
}
if (codePoint === null) {
codePoint = 65533;
bytesPerSequence = 1;
} else if (codePoint > 65535) {
codePoint -= 65536;
res.push(codePoint >>> 10 & 1023 | 55296);
codePoint = 56320 | codePoint & 1023;
}
res.push(codePoint);
offset += bytesPerSequence;
}
return decodeCodePointsArray(res);
}
const MAX_ARGUMENTS_LENGTH = 4096;
export function decodeCodePointsArray(codePoints) {
const len = codePoints.length;
if (len <= MAX_ARGUMENTS_LENGTH) {
return String.fromCharCode.apply(String, codePoints);
}
let res = '';
let i = 0;
while (i < len) {
res += String.fromCharCode.apply(String, codePoints.slice(i, i += MAX_ARGUMENTS_LENGTH));
}
return res;
}
+19
View File
@@ -0,0 +1,19 @@
const decodeErrPrefix = 'CBOR decode error:';
const encodeErrPrefix = 'CBOR encode error:';
const uintMinorPrefixBytes = [];
uintMinorPrefixBytes[23] = 1;
uintMinorPrefixBytes[24] = 2;
uintMinorPrefixBytes[25] = 3;
uintMinorPrefixBytes[26] = 5;
uintMinorPrefixBytes[27] = 9;
function assertEnoughData(data, pos, need) {
if (data.length - pos < need) {
throw new Error(`${ decodeErrPrefix } not enough data for type`);
}
}
export {
decodeErrPrefix,
encodeErrPrefix,
uintMinorPrefixBytes,
assertEnoughData
};
+139
View File
@@ -0,0 +1,139 @@
import { decodeErrPrefix } from './common.js';
import { Type } from './token.js';
import {
jump,
quick
} from './jump.js';
const defaultDecodeOptions = {
strict: false,
allowIndefinite: true,
allowUndefined: true,
allowBigInt: true
};
class Tokeniser {
constructor(data, options = {}) {
this.pos = 0;
this.data = data;
this.options = options;
}
done() {
return this.pos >= this.data.length;
}
next() {
const byt = this.data[this.pos];
let token = quick[byt];
if (token === undefined) {
const decoder = jump[byt];
if (!decoder) {
throw new Error(`${ decodeErrPrefix } no decoder for major type ${ byt >>> 5 } (byte 0x${ byt.toString(16).padStart(2, '0') })`);
}
const minor = byt & 31;
token = decoder(this.data, this.pos, minor, this.options);
}
this.pos += token.encodedLength;
return token;
}
}
const DONE = Symbol.for('DONE');
const BREAK = Symbol.for('BREAK');
function tokenToArray(token, tokeniser, options) {
const arr = [];
for (let i = 0; i < token.value; i++) {
const value = tokensToObject(tokeniser, options);
if (value === BREAK) {
if (token.value === Infinity) {
break;
}
throw new Error(`${ decodeErrPrefix } got unexpected break to lengthed array`);
}
if (value === DONE) {
throw new Error(`${ decodeErrPrefix } found array but not enough entries (got ${ i }, expected ${ token.value })`);
}
arr[i] = value;
}
return arr;
}
function tokenToMap(token, tokeniser, options) {
const useMaps = options.useMaps === true;
const obj = useMaps ? undefined : {};
const m = useMaps ? new Map() : undefined;
for (let i = 0; i < token.value; i++) {
const key = tokensToObject(tokeniser, options);
if (key === BREAK) {
if (token.value === Infinity) {
break;
}
throw new Error(`${ decodeErrPrefix } got unexpected break to lengthed map`);
}
if (key === DONE) {
throw new Error(`${ decodeErrPrefix } found map but not enough entries (got ${ i } [no key], expected ${ token.value })`);
}
if (useMaps !== true && typeof key !== 'string') {
throw new Error(`${ decodeErrPrefix } non-string keys not supported (got ${ typeof key })`);
}
if (options.rejectDuplicateMapKeys === true) {
if (useMaps && m.has(key) || !useMaps && key in obj) {
throw new Error(`${ decodeErrPrefix } found repeat map key "${ key }"`);
}
}
const value = tokensToObject(tokeniser, options);
if (value === DONE) {
throw new Error(`${ decodeErrPrefix } found map but not enough entries (got ${ i } [no value], expected ${ token.value })`);
}
if (useMaps) {
m.set(key, value);
} else {
obj[key] = value;
}
}
return useMaps ? m : obj;
}
function tokensToObject(tokeniser, options) {
if (tokeniser.done()) {
return DONE;
}
const token = tokeniser.next();
if (token.type === Type.break) {
return BREAK;
}
if (token.type.terminal) {
return token.value;
}
if (token.type === Type.array) {
return tokenToArray(token, tokeniser, options);
}
if (token.type === Type.map) {
return tokenToMap(token, tokeniser, options);
}
if (token.type === Type.tag) {
if (options.tags && typeof options.tags[token.value] === 'function') {
const tagged = tokensToObject(tokeniser, options);
return options.tags[token.value](tagged);
}
throw new Error(`${ decodeErrPrefix } tag not supported (${ token.value })`);
}
throw new Error('unsupported');
}
function decode(data, options) {
if (!(data instanceof Uint8Array)) {
throw new Error(`${ decodeErrPrefix } data to decode must be a Uint8Array`);
}
options = Object.assign({}, defaultDecodeOptions, options);
const tokeniser = options.tokenizer || new Tokeniser(data, options);
const decoded = tokensToObject(tokeniser, options);
if (decoded === DONE) {
throw new Error(`${ decodeErrPrefix } did not find any content to decode`);
}
if (decoded === BREAK) {
throw new Error(`${ decodeErrPrefix } got unexpected break`);
}
if (!tokeniser.done()) {
throw new Error(`${ decodeErrPrefix } too many terminals, data makes no sense`);
}
return decoded;
}
export {
Tokeniser,
tokensToObject,
decode
};
+123
View File
@@ -0,0 +1,123 @@
import { Tokeniser } from './decode.js';
import {
toHex,
fromHex
} from './byte-utils.js';
import { uintBoundaries } from './0uint.js';
const utf8Encoder = new TextEncoder();
const utf8Decoder = new TextDecoder();
function* tokensToDiagnostic(inp, width = 100) {
const tokeniser = new Tokeniser(inp, {
retainStringBytes: true,
allowBigInt: true
});
let pos = 0;
const indent = [];
const slc = (start, length) => {
return toHex(inp.slice(pos + start, pos + start + length));
};
while (!tokeniser.done()) {
const token = tokeniser.next();
let margin = ''.padStart(indent.length * 2, ' ');
let vLength = token.encodedLength - 1;
let v = String(token.value);
let outp = `${ margin }${ slc(0, 1) }`;
const str = token.type.name === 'bytes' || token.type.name === 'string';
if (token.type.name === 'string') {
v = v.length;
vLength -= v;
} else if (token.type.name === 'bytes') {
v = token.value.length;
vLength -= v;
}
let multilen;
switch (token.type.name) {
case 'string':
case 'bytes':
case 'map':
case 'array':
multilen = token.type.name === 'string' ? utf8Encoder.encode(token.value).length : token.value.length;
if (multilen >= uintBoundaries[0]) {
if (multilen < uintBoundaries[1]) {
outp += ` ${ slc(1, 1) }`;
} else if (multilen < uintBoundaries[2]) {
outp += ` ${ slc(1, 2) }`;
} else if (multilen < uintBoundaries[3]) {
outp += ` ${ slc(1, 4) }`;
} else if (multilen < uintBoundaries[4]) {
outp += ` ${ slc(1, 8) }`;
}
}
break;
default:
outp += ` ${ slc(1, vLength) }`;
break;
}
outp = outp.padEnd(width / 2, ' ');
outp += `# ${ margin }${ token.type.name }`;
if (token.type.name !== v) {
outp += `(${ v })`;
}
yield outp;
if (str) {
let asString = token.type.name === 'string';
margin += ' ';
let repr = asString ? utf8Encoder.encode(token.value) : token.value;
if (asString && token.byteValue !== undefined) {
if (repr.length !== token.byteValue.length) {
repr = token.byteValue;
asString = false;
}
}
const wh = (width / 2 - margin.length - 1) / 2;
let snip = 0;
while (repr.length - snip > 0) {
const piece = repr.slice(snip, snip + wh);
snip += piece.length;
const st = asString ? utf8Decoder.decode(piece) : piece.reduce((p, c) => {
if (c < 32 || c >= 127 && c < 161 || c === 173) {
return `${ p }\\x${ c.toString(16).padStart(2, '0') }`;
}
return `${ p }${ String.fromCharCode(c) }`;
}, '');
yield `${ margin }${ toHex(piece) }`.padEnd(width / 2, ' ') + `# ${ margin }"${ st }"`;
}
}
if (indent.length) {
indent[indent.length - 1]--;
}
if (!token.type.terminal) {
switch (token.type.name) {
case 'map':
indent.push(token.value * 2);
break;
case 'array':
indent.push(token.value);
break;
case 'tag':
indent.push(1);
break;
default:
throw new Error(`Unknown token type '${ token.type.name }'`);
}
}
while (indent.length && indent[indent.length - 1] <= 0) {
indent.pop();
}
pos += token.encodedLength;
}
}
function fromDiag(input) {
if (typeof input !== 'string') {
throw new TypeError('Expected string input');
}
input = input.replace(/#.*?$/mg, '').replace(/[\s\r\n]+/mg, '');
if (/[^a-f0-9]/i.test(input)) {
throw new TypeError('Input string was not CBOR diagnostic format');
}
return fromHex(input);
}
export {
tokensToDiagnostic,
fromDiag
};
+246
View File
@@ -0,0 +1,246 @@
import { is } from './is.js';
import {
Token,
Type
} from './token.js';
import { Bl } from './bl.js';
import { encodeErrPrefix } from './common.js';
import { quickEncodeToken } from './jump.js';
import { asU8A } from './byte-utils.js';
import { encodeUint } from './0uint.js';
import { encodeNegint } from './1negint.js';
import { encodeBytes } from './2bytes.js';
import { encodeString } from './3string.js';
import { encodeArray } from './4array.js';
import { encodeMap } from './5map.js';
import { encodeTag } from './6tag.js';
import { encodeFloat } from './7float.js';
const defaultEncodeOptions = {
float64: false,
mapSorter,
quickEncodeToken
};
export function makeCborEncoders() {
const encoders = [];
encoders[Type.uint.major] = encodeUint;
encoders[Type.negint.major] = encodeNegint;
encoders[Type.bytes.major] = encodeBytes;
encoders[Type.string.major] = encodeString;
encoders[Type.array.major] = encodeArray;
encoders[Type.map.major] = encodeMap;
encoders[Type.tag.major] = encodeTag;
encoders[Type.float.major] = encodeFloat;
return encoders;
}
const cborEncoders = makeCborEncoders();
const buf = new Bl();
class Ref {
constructor(obj, parent) {
this.obj = obj;
this.parent = parent;
}
includes(obj) {
let p = this;
do {
if (p.obj === obj) {
return true;
}
} while (p = p.parent);
return false;
}
static createCheck(stack, obj) {
if (stack && stack.includes(obj)) {
throw new Error(`${ encodeErrPrefix } object contains circular references`);
}
return new Ref(obj, stack);
}
}
const simpleTokens = {
null: new Token(Type.null, null),
undefined: new Token(Type.undefined, undefined),
true: new Token(Type.true, true),
false: new Token(Type.false, false),
emptyArray: new Token(Type.array, 0),
emptyMap: new Token(Type.map, 0)
};
const typeEncoders = {
number(obj, _typ, _options, _refStack) {
if (!Number.isInteger(obj) || !Number.isSafeInteger(obj)) {
return new Token(Type.float, obj);
} else if (obj >= 0) {
return new Token(Type.uint, obj);
} else {
return new Token(Type.negint, obj);
}
},
bigint(obj, _typ, _options, _refStack) {
if (obj >= BigInt(0)) {
return new Token(Type.uint, obj);
} else {
return new Token(Type.negint, obj);
}
},
Uint8Array(obj, _typ, _options, _refStack) {
return new Token(Type.bytes, obj);
},
string(obj, _typ, _options, _refStack) {
return new Token(Type.string, obj);
},
boolean(obj, _typ, _options, _refStack) {
return obj ? simpleTokens.true : simpleTokens.false;
},
null(_obj, _typ, _options, _refStack) {
return simpleTokens.null;
},
undefined(_obj, _typ, _options, _refStack) {
return simpleTokens.undefined;
},
ArrayBuffer(obj, _typ, _options, _refStack) {
return new Token(Type.bytes, new Uint8Array(obj));
},
DataView(obj, _typ, _options, _refStack) {
return new Token(Type.bytes, new Uint8Array(obj.buffer, obj.byteOffset, obj.byteLength));
},
Array(obj, _typ, options, refStack) {
if (!obj.length) {
if (options.addBreakTokens === true) {
return [
simpleTokens.emptyArray,
new Token(Type.break)
];
}
return simpleTokens.emptyArray;
}
refStack = Ref.createCheck(refStack, obj);
const entries = [];
let i = 0;
for (const e of obj) {
entries[i++] = objectToTokens(e, options, refStack);
}
if (options.addBreakTokens) {
return [
new Token(Type.array, obj.length),
entries,
new Token(Type.break)
];
}
return [
new Token(Type.array, obj.length),
entries
];
},
Object(obj, typ, options, refStack) {
const isMap = typ !== 'Object';
const keys = isMap ? obj.keys() : Object.keys(obj);
const length = isMap ? obj.size : keys.length;
if (!length) {
if (options.addBreakTokens === true) {
return [
simpleTokens.emptyMap,
new Token(Type.break)
];
}
return simpleTokens.emptyMap;
}
refStack = Ref.createCheck(refStack, obj);
const entries = [];
let i = 0;
for (const key of keys) {
entries[i++] = [
objectToTokens(key, options, refStack),
objectToTokens(isMap ? obj.get(key) : obj[key], options, refStack)
];
}
sortMapEntries(entries, options);
if (options.addBreakTokens) {
return [
new Token(Type.map, length),
entries,
new Token(Type.break)
];
}
return [
new Token(Type.map, length),
entries
];
}
};
typeEncoders.Map = typeEncoders.Object;
typeEncoders.Buffer = typeEncoders.Uint8Array;
for (const typ of 'Uint8Clamped Uint16 Uint32 Int8 Int16 Int32 BigUint64 BigInt64 Float32 Float64'.split(' ')) {
typeEncoders[`${ typ }Array`] = typeEncoders.DataView;
}
function objectToTokens(obj, options = {}, refStack) {
const typ = is(obj);
const customTypeEncoder = options && options.typeEncoders && options.typeEncoders[typ] || typeEncoders[typ];
if (typeof customTypeEncoder === 'function') {
const tokens = customTypeEncoder(obj, typ, options, refStack);
if (tokens != null) {
return tokens;
}
}
const typeEncoder = typeEncoders[typ];
if (!typeEncoder) {
throw new Error(`${ encodeErrPrefix } unsupported type: ${ typ }`);
}
return typeEncoder(obj, typ, options, refStack);
}
function sortMapEntries(entries, options) {
if (options.mapSorter) {
entries.sort(options.mapSorter);
}
}
function mapSorter(e1, e2) {
const keyToken1 = Array.isArray(e1[0]) ? e1[0][0] : e1[0];
const keyToken2 = Array.isArray(e2[0]) ? e2[0][0] : e2[0];
if (keyToken1.type !== keyToken2.type) {
return keyToken1.type.compare(keyToken2.type);
}
const major = keyToken1.type.major;
const tcmp = cborEncoders[major].compareTokens(keyToken1, keyToken2);
if (tcmp === 0) {
console.warn('WARNING: complex key types used, CBOR key sorting guarantees are gone');
}
return tcmp;
}
function tokensToEncoded(buf, tokens, encoders, options) {
if (Array.isArray(tokens)) {
for (const token of tokens) {
tokensToEncoded(buf, token, encoders, options);
}
} else {
encoders[tokens.type.major](buf, tokens, options);
}
}
function encodeCustom(data, encoders, options) {
const tokens = objectToTokens(data, options);
if (!Array.isArray(tokens) && options.quickEncodeToken) {
const quickBytes = options.quickEncodeToken(tokens);
if (quickBytes) {
return quickBytes;
}
const encoder = encoders[tokens.type.major];
if (encoder.encodedSize) {
const size = encoder.encodedSize(tokens, options);
const buf = new Bl(size);
encoder(buf, tokens, options);
if (buf.chunks.length !== 1) {
throw new Error(`Unexpected error: pre-calculated length for ${ tokens } was wrong`);
}
return asU8A(buf.chunks[0]);
}
}
buf.reset();
tokensToEncoded(buf, tokens, encoders, options);
return buf.toBytes(true);
}
function encode(data, options) {
options = Object.assign({}, defaultEncodeOptions, options);
return encodeCustom(data, cborEncoders, options);
}
export {
objectToTokens,
encode,
encodeCustom,
Ref
};
+81
View File
@@ -0,0 +1,81 @@
const typeofs = [
'string',
'number',
'bigint',
'symbol'
];
const objectTypeNames = [
'Function',
'Generator',
'AsyncGenerator',
'GeneratorFunction',
'AsyncGeneratorFunction',
'AsyncFunction',
'Observable',
'Array',
'Buffer',
'Object',
'RegExp',
'Date',
'Error',
'Map',
'Set',
'WeakMap',
'WeakSet',
'ArrayBuffer',
'SharedArrayBuffer',
'DataView',
'Promise',
'URL',
'HTMLElement',
'Int8Array',
'Uint8Array',
'Uint8ClampedArray',
'Int16Array',
'Uint16Array',
'Int32Array',
'Uint32Array',
'Float32Array',
'Float64Array',
'BigInt64Array',
'BigUint64Array'
];
export function is(value) {
if (value === null) {
return 'null';
}
if (value === undefined) {
return 'undefined';
}
if (value === true || value === false) {
return 'boolean';
}
const typeOf = typeof value;
if (typeofs.includes(typeOf)) {
return typeOf;
}
if (typeOf === 'function') {
return 'Function';
}
if (Array.isArray(value)) {
return 'Array';
}
if (isBuffer(value)) {
return 'Buffer';
}
const objectType = getObjectType(value);
if (objectType) {
return objectType;
}
return 'Object';
}
function isBuffer(value) {
return value && value.constructor && value.constructor.isBuffer && value.constructor.isBuffer.call(null, value);
}
function getObjectType(value) {
const objectTypeName = Object.prototype.toString.call(value).slice(8, -1);
if (objectTypeNames.includes(objectTypeName)) {
return objectTypeName;
}
return undefined;
}
+413
View File
@@ -0,0 +1,413 @@
import { decode as _decode } from '../decode.js';
import {
Token,
Type
} from '../token.js';
import { decodeCodePointsArray } from '../byte-utils.js';
import { decodeErrPrefix } from '../common.js';
class Tokenizer {
constructor(data, options = {}) {
this.pos = 0;
this.data = data;
this.options = options;
this.modeStack = ['value'];
this.lastToken = '';
}
done() {
return this.pos >= this.data.length;
}
ch() {
return this.data[this.pos];
}
currentMode() {
return this.modeStack[this.modeStack.length - 1];
}
skipWhitespace() {
let c = this.ch();
while (c === 32 || c === 9 || c === 13 || c === 10) {
c = this.data[++this.pos];
}
}
expect(str) {
if (this.data.length - this.pos < str.length) {
throw new Error(`${ decodeErrPrefix } unexpected end of input at position ${ this.pos }`);
}
for (let i = 0; i < str.length; i++) {
if (this.data[this.pos++] !== str[i]) {
throw new Error(`${ decodeErrPrefix } unexpected token at position ${ this.pos }, expected to find '${ String.fromCharCode(...str) }'`);
}
}
}
parseNumber() {
const startPos = this.pos;
let negative = false;
let float = false;
const swallow = chars => {
while (!this.done()) {
const ch = this.ch();
if (chars.includes(ch)) {
this.pos++;
} else {
break;
}
}
};
if (this.ch() === 45) {
negative = true;
this.pos++;
}
if (this.ch() === 48) {
this.pos++;
if (this.ch() === 46) {
this.pos++;
float = true;
} else {
return new Token(Type.uint, 0, this.pos - startPos);
}
}
swallow([
48,
49,
50,
51,
52,
53,
54,
55,
56,
57
]);
if (negative && this.pos === startPos + 1) {
throw new Error(`${ decodeErrPrefix } unexpected token at position ${ this.pos }`);
}
if (!this.done() && this.ch() === 46) {
if (float) {
throw new Error(`${ decodeErrPrefix } unexpected token at position ${ this.pos }`);
}
float = true;
this.pos++;
swallow([
48,
49,
50,
51,
52,
53,
54,
55,
56,
57
]);
}
if (!this.done() && (this.ch() === 101 || this.ch() === 69)) {
float = true;
this.pos++;
if (!this.done() && (this.ch() === 43 || this.ch() === 45)) {
this.pos++;
}
swallow([
48,
49,
50,
51,
52,
53,
54,
55,
56,
57
]);
}
const numStr = String.fromCharCode.apply(null, this.data.subarray(startPos, this.pos));
const num = parseFloat(numStr);
if (float) {
return new Token(Type.float, num, this.pos - startPos);
}
if (this.options.allowBigInt !== true || Number.isSafeInteger(num)) {
return new Token(num >= 0 ? Type.uint : Type.negint, num, this.pos - startPos);
}
return new Token(num >= 0 ? Type.uint : Type.negint, BigInt(numStr), this.pos - startPos);
}
parseString() {
if (this.ch() !== 34) {
throw new Error(`${ decodeErrPrefix } unexpected character at position ${ this.pos }; this shouldn't happen`);
}
this.pos++;
for (let i = this.pos, l = 0; i < this.data.length && l < 65536; i++, l++) {
const ch = this.data[i];
if (ch === 92 || ch < 32 || ch >= 128) {
break;
}
if (ch === 34) {
const str = String.fromCharCode.apply(null, this.data.subarray(this.pos, i));
this.pos = i + 1;
return new Token(Type.string, str, l);
}
}
const startPos = this.pos;
const chars = [];
const readu4 = () => {
if (this.pos + 4 >= this.data.length) {
throw new Error(`${ decodeErrPrefix } unexpected end of unicode escape sequence at position ${ this.pos }`);
}
let u4 = 0;
for (let i = 0; i < 4; i++) {
let ch = this.ch();
if (ch >= 48 && ch <= 57) {
ch -= 48;
} else if (ch >= 97 && ch <= 102) {
ch = ch - 97 + 10;
} else if (ch >= 65 && ch <= 70) {
ch = ch - 65 + 10;
} else {
throw new Error(`${ decodeErrPrefix } unexpected unicode escape character at position ${ this.pos }`);
}
u4 = u4 * 16 + ch;
this.pos++;
}
return u4;
};
const readUtf8Char = () => {
const firstByte = this.ch();
let codePoint = null;
let bytesPerSequence = firstByte > 239 ? 4 : firstByte > 223 ? 3 : firstByte > 191 ? 2 : 1;
if (this.pos + bytesPerSequence > this.data.length) {
throw new Error(`${ decodeErrPrefix } unexpected unicode sequence at position ${ this.pos }`);
}
let secondByte, thirdByte, fourthByte, tempCodePoint;
switch (bytesPerSequence) {
case 1:
if (firstByte < 128) {
codePoint = firstByte;
}
break;
case 2:
secondByte = this.data[this.pos + 1];
if ((secondByte & 192) === 128) {
tempCodePoint = (firstByte & 31) << 6 | secondByte & 63;
if (tempCodePoint > 127) {
codePoint = tempCodePoint;
}
}
break;
case 3:
secondByte = this.data[this.pos + 1];
thirdByte = this.data[this.pos + 2];
if ((secondByte & 192) === 128 && (thirdByte & 192) === 128) {
tempCodePoint = (firstByte & 15) << 12 | (secondByte & 63) << 6 | thirdByte & 63;
if (tempCodePoint > 2047 && (tempCodePoint < 55296 || tempCodePoint > 57343)) {
codePoint = tempCodePoint;
}
}
break;
case 4:
secondByte = this.data[this.pos + 1];
thirdByte = this.data[this.pos + 2];
fourthByte = this.data[this.pos + 3];
if ((secondByte & 192) === 128 && (thirdByte & 192) === 128 && (fourthByte & 192) === 128) {
tempCodePoint = (firstByte & 15) << 18 | (secondByte & 63) << 12 | (thirdByte & 63) << 6 | fourthByte & 63;
if (tempCodePoint > 65535 && tempCodePoint < 1114112) {
codePoint = tempCodePoint;
}
}
}
if (codePoint === null) {
codePoint = 65533;
bytesPerSequence = 1;
} else if (codePoint > 65535) {
codePoint -= 65536;
chars.push(codePoint >>> 10 & 1023 | 55296);
codePoint = 56320 | codePoint & 1023;
}
chars.push(codePoint);
this.pos += bytesPerSequence;
};
while (!this.done()) {
const ch = this.ch();
let ch1;
switch (ch) {
case 92:
this.pos++;
if (this.done()) {
throw new Error(`${ decodeErrPrefix } unexpected string termination at position ${ this.pos }`);
}
ch1 = this.ch();
this.pos++;
switch (ch1) {
case 34:
case 39:
case 92:
case 47:
chars.push(ch1);
break;
case 98:
chars.push(8);
break;
case 116:
chars.push(9);
break;
case 110:
chars.push(10);
break;
case 102:
chars.push(12);
break;
case 114:
chars.push(13);
break;
case 117:
chars.push(readu4());
break;
default:
throw new Error(`${ decodeErrPrefix } unexpected string escape character at position ${ this.pos }`);
}
break;
case 34:
this.pos++;
return new Token(Type.string, decodeCodePointsArray(chars), this.pos - startPos);
default:
if (ch < 32) {
throw new Error(`${ decodeErrPrefix } invalid control character at position ${ this.pos }`);
} else if (ch < 128) {
chars.push(ch);
this.pos++;
} else {
readUtf8Char();
}
}
}
throw new Error(`${ decodeErrPrefix } unexpected end of string at position ${ this.pos }`);
}
parseValue() {
switch (this.ch()) {
case 123:
this.modeStack.push('obj-start');
this.pos++;
return new Token(Type.map, Infinity, 1);
case 91:
this.modeStack.push('array-start');
this.pos++;
return new Token(Type.array, Infinity, 1);
case 34: {
return this.parseString();
}
case 110:
this.expect([
110,
117,
108,
108
]);
return new Token(Type.null, null, 4);
case 102:
this.expect([
102,
97,
108,
115,
101
]);
return new Token(Type.false, false, 5);
case 116:
this.expect([
116,
114,
117,
101
]);
return new Token(Type.true, true, 4);
case 45:
case 48:
case 49:
case 50:
case 51:
case 52:
case 53:
case 54:
case 55:
case 56:
case 57:
return this.parseNumber();
default:
throw new Error(`${ decodeErrPrefix } unexpected character at position ${ this.pos }`);
}
}
next() {
this.skipWhitespace();
switch (this.currentMode()) {
case 'value':
this.modeStack.pop();
return this.parseValue();
case 'array-value': {
this.modeStack.pop();
if (this.ch() === 93) {
this.pos++;
this.skipWhitespace();
return new Token(Type.break, undefined, 1);
}
if (this.ch() !== 44) {
throw new Error(`${ decodeErrPrefix } unexpected character at position ${ this.pos }, was expecting array delimiter but found '${ String.fromCharCode(this.ch()) }'`);
}
this.pos++;
this.modeStack.push('array-value');
this.skipWhitespace();
return this.parseValue();
}
case 'array-start': {
this.modeStack.pop();
if (this.ch() === 93) {
this.pos++;
this.skipWhitespace();
return new Token(Type.break, undefined, 1);
}
this.modeStack.push('array-value');
this.skipWhitespace();
return this.parseValue();
}
case 'obj-key':
if (this.ch() === 125) {
this.modeStack.pop();
this.pos++;
this.skipWhitespace();
return new Token(Type.break, undefined, 1);
}
if (this.ch() !== 44) {
throw new Error(`${ decodeErrPrefix } unexpected character at position ${ this.pos }, was expecting object delimiter but found '${ String.fromCharCode(this.ch()) }'`);
}
this.pos++;
this.skipWhitespace();
case 'obj-start': {
this.modeStack.pop();
if (this.ch() === 125) {
this.pos++;
this.skipWhitespace();
return new Token(Type.break, undefined, 1);
}
const token = this.parseString();
this.skipWhitespace();
if (this.ch() !== 58) {
throw new Error(`${ decodeErrPrefix } unexpected character at position ${ this.pos }, was expecting key/value delimiter ':' but found '${ String.fromCharCode(this.ch()) }'`);
}
this.pos++;
this.modeStack.push('obj-value');
return token;
}
case 'obj-value': {
this.modeStack.pop();
this.modeStack.push('obj-key');
this.skipWhitespace();
return this.parseValue();
}
default:
throw new Error(`${ decodeErrPrefix } unexpected parse state at position ${ this.pos }; this shouldn't happen`);
}
}
}
function decode(data, options) {
options = Object.assign({ tokenizer: new Tokenizer(data, options) }, options);
return _decode(data, options);
}
export {
decode,
Tokenizer
};
+160
View File
@@ -0,0 +1,160 @@
import { Type } from '../token.js';
import { encodeCustom } from '../encode.js';
import { encodeErrPrefix } from '../common.js';
import {
asU8A,
fromString
} from '../byte-utils.js';
class JSONEncoder extends Array {
constructor() {
super();
this.inRecursive = [];
}
prefix(buf) {
const recurs = this.inRecursive[this.inRecursive.length - 1];
if (recurs) {
if (recurs.type === Type.array) {
recurs.elements++;
if (recurs.elements !== 1) {
buf.push([44]);
}
}
if (recurs.type === Type.map) {
recurs.elements++;
if (recurs.elements !== 1) {
if (recurs.elements % 2 === 1) {
buf.push([44]);
} else {
buf.push([58]);
}
}
}
}
}
[Type.uint.major](buf, token) {
this.prefix(buf);
const is = String(token.value);
const isa = [];
for (let i = 0; i < is.length; i++) {
isa[i] = is.charCodeAt(i);
}
buf.push(isa);
}
[Type.negint.major](buf, token) {
this[Type.uint.major](buf, token);
}
[Type.bytes.major](_buf, _token) {
throw new Error(`${ encodeErrPrefix } unsupported type: Uint8Array`);
}
[Type.string.major](buf, token) {
this.prefix(buf);
const byts = fromString(JSON.stringify(token.value));
buf.push(byts.length > 32 ? asU8A(byts) : byts);
}
[Type.array.major](buf, _token) {
this.prefix(buf);
this.inRecursive.push({
type: Type.array,
elements: 0
});
buf.push([91]);
}
[Type.map.major](buf, _token) {
this.prefix(buf);
this.inRecursive.push({
type: Type.map,
elements: 0
});
buf.push([123]);
}
[Type.tag.major](_buf, _token) {
}
[Type.float.major](buf, token) {
if (token.type.name === 'break') {
const recurs = this.inRecursive.pop();
if (recurs) {
if (recurs.type === Type.array) {
buf.push([93]);
} else if (recurs.type === Type.map) {
buf.push([125]);
} else {
throw new Error('Unexpected recursive type; this should not happen!');
}
return;
}
throw new Error('Unexpected break; this should not happen!');
}
if (token.value === undefined) {
throw new Error(`${ encodeErrPrefix } unsupported type: undefined`);
}
this.prefix(buf);
if (token.type.name === 'true') {
buf.push([
116,
114,
117,
101
]);
return;
} else if (token.type.name === 'false') {
buf.push([
102,
97,
108,
115,
101
]);
return;
} else if (token.type.name === 'null') {
buf.push([
110,
117,
108,
108
]);
return;
}
const is = String(token.value);
const isa = [];
let dp = false;
for (let i = 0; i < is.length; i++) {
isa[i] = is.charCodeAt(i);
if (!dp && (isa[i] === 46 || isa[i] === 101 || isa[i] === 69)) {
dp = true;
}
}
if (!dp) {
isa.push(46);
isa.push(48);
}
buf.push(isa);
}
}
function mapSorter(e1, e2) {
if (Array.isArray(e1[0]) || Array.isArray(e2[0])) {
throw new Error(`${ encodeErrPrefix } complex map keys are not supported`);
}
const keyToken1 = e1[0];
const keyToken2 = e2[0];
if (keyToken1.type !== Type.string || keyToken2.type !== Type.string) {
throw new Error(`${ encodeErrPrefix } non-string map keys are not supported`);
}
if (keyToken1 < keyToken2) {
return -1;
}
if (keyToken1 > keyToken2) {
return 1;
}
throw new Error(`${ encodeErrPrefix } unexpected duplicate map keys, this is not supported`);
}
const defaultEncodeOptions = {
addBreakTokens: true,
mapSorter
};
function encode(data, options) {
options = Object.assign({}, defaultEncodeOptions, options);
return encodeCustom(data, new JSONEncoder(), options);
}
export {
encode
};
+10
View File
@@ -0,0 +1,10 @@
import { encode } from './encode.js';
import {
decode,
Tokenizer
} from './decode.js';
export {
encode,
decode,
Tokenizer
};
+168
View File
@@ -0,0 +1,168 @@
import {
Token,
Type
} from './token.js';
import * as uint from './0uint.js';
import * as negint from './1negint.js';
import * as bytes from './2bytes.js';
import * as string from './3string.js';
import * as array from './4array.js';
import * as map from './5map.js';
import * as tag from './6tag.js';
import * as float from './7float.js';
import { decodeErrPrefix } from './common.js';
import { fromArray } from './byte-utils.js';
function invalidMinor(data, pos, minor) {
throw new Error(`${ decodeErrPrefix } encountered invalid minor (${ minor }) for major ${ data[pos] >>> 5 }`);
}
function errorer(msg) {
return () => {
throw new Error(`${ decodeErrPrefix } ${ msg }`);
};
}
export const jump = [];
for (let i = 0; i <= 23; i++) {
jump[i] = invalidMinor;
}
jump[24] = uint.decodeUint8;
jump[25] = uint.decodeUint16;
jump[26] = uint.decodeUint32;
jump[27] = uint.decodeUint64;
jump[28] = invalidMinor;
jump[29] = invalidMinor;
jump[30] = invalidMinor;
jump[31] = invalidMinor;
for (let i = 32; i <= 55; i++) {
jump[i] = invalidMinor;
}
jump[56] = negint.decodeNegint8;
jump[57] = negint.decodeNegint16;
jump[58] = negint.decodeNegint32;
jump[59] = negint.decodeNegint64;
jump[60] = invalidMinor;
jump[61] = invalidMinor;
jump[62] = invalidMinor;
jump[63] = invalidMinor;
for (let i = 64; i <= 87; i++) {
jump[i] = bytes.decodeBytesCompact;
}
jump[88] = bytes.decodeBytes8;
jump[89] = bytes.decodeBytes16;
jump[90] = bytes.decodeBytes32;
jump[91] = bytes.decodeBytes64;
jump[92] = invalidMinor;
jump[93] = invalidMinor;
jump[94] = invalidMinor;
jump[95] = errorer('indefinite length bytes/strings are not supported');
for (let i = 96; i <= 119; i++) {
jump[i] = string.decodeStringCompact;
}
jump[120] = string.decodeString8;
jump[121] = string.decodeString16;
jump[122] = string.decodeString32;
jump[123] = string.decodeString64;
jump[124] = invalidMinor;
jump[125] = invalidMinor;
jump[126] = invalidMinor;
jump[127] = errorer('indefinite length bytes/strings are not supported');
for (let i = 128; i <= 151; i++) {
jump[i] = array.decodeArrayCompact;
}
jump[152] = array.decodeArray8;
jump[153] = array.decodeArray16;
jump[154] = array.decodeArray32;
jump[155] = array.decodeArray64;
jump[156] = invalidMinor;
jump[157] = invalidMinor;
jump[158] = invalidMinor;
jump[159] = array.decodeArrayIndefinite;
for (let i = 160; i <= 183; i++) {
jump[i] = map.decodeMapCompact;
}
jump[184] = map.decodeMap8;
jump[185] = map.decodeMap16;
jump[186] = map.decodeMap32;
jump[187] = map.decodeMap64;
jump[188] = invalidMinor;
jump[189] = invalidMinor;
jump[190] = invalidMinor;
jump[191] = map.decodeMapIndefinite;
for (let i = 192; i <= 215; i++) {
jump[i] = tag.decodeTagCompact;
}
jump[216] = tag.decodeTag8;
jump[217] = tag.decodeTag16;
jump[218] = tag.decodeTag32;
jump[219] = tag.decodeTag64;
jump[220] = invalidMinor;
jump[221] = invalidMinor;
jump[222] = invalidMinor;
jump[223] = invalidMinor;
for (let i = 224; i <= 243; i++) {
jump[i] = errorer('simple values are not supported');
}
jump[244] = invalidMinor;
jump[245] = invalidMinor;
jump[246] = invalidMinor;
jump[247] = float.decodeUndefined;
jump[248] = errorer('simple values are not supported');
jump[249] = float.decodeFloat16;
jump[250] = float.decodeFloat32;
jump[251] = float.decodeFloat64;
jump[252] = invalidMinor;
jump[253] = invalidMinor;
jump[254] = invalidMinor;
jump[255] = float.decodeBreak;
export const quick = [];
for (let i = 0; i < 24; i++) {
quick[i] = new Token(Type.uint, i, 1);
}
for (let i = -1; i >= -24; i--) {
quick[31 - i] = new Token(Type.negint, i, 1);
}
quick[64] = new Token(Type.bytes, new Uint8Array(0), 1);
quick[96] = new Token(Type.string, '', 1);
quick[128] = new Token(Type.array, 0, 1);
quick[160] = new Token(Type.map, 0, 1);
quick[244] = new Token(Type.false, false, 1);
quick[245] = new Token(Type.true, true, 1);
quick[246] = new Token(Type.null, null, 1);
export function quickEncodeToken(token) {
switch (token.type) {
case Type.false:
return fromArray([244]);
case Type.true:
return fromArray([245]);
case Type.null:
return fromArray([246]);
case Type.bytes:
if (!token.value.length) {
return fromArray([64]);
}
return;
case Type.string:
if (token.value === '') {
return fromArray([96]);
}
return;
case Type.array:
if (token.value === 0) {
return fromArray([128]);
}
return;
case Type.map:
if (token.value === 0) {
return fromArray([160]);
}
return;
case Type.uint:
if (token.value < 24) {
return fromArray([Number(token.value)]);
}
return;
case Type.negint:
if (token.value >= -24) {
return fromArray([31 - Number(token.value)]);
}
}
}
+31
View File
@@ -0,0 +1,31 @@
import {
makeCborEncoders,
objectToTokens
} from './encode.js';
import { quickEncodeToken } from './jump.js';
const cborEncoders = makeCborEncoders();
const defaultEncodeOptions = {
float64: false,
quickEncodeToken
};
export function encodedLength(data, options) {
options = Object.assign({}, defaultEncodeOptions, options);
options.mapSorter = undefined;
const tokens = objectToTokens(data, options);
return tokensToLength(tokens, cborEncoders, options);
}
export function tokensToLength(tokens, encoders = cborEncoders, options = defaultEncodeOptions) {
if (Array.isArray(tokens)) {
let len = 0;
for (const token of tokens) {
len += tokensToLength(token, encoders, options);
}
return len;
} else {
const encoder = encoders[tokens.type.major];
if (encoder.encodedSize === undefined || typeof encoder.encodedSize !== 'function') {
throw new Error(`Encoder for ${ tokens.type.name } does not have an encodedSize()`);
}
return encoder.encodedSize(tokens, options);
}
}
+43
View File
@@ -0,0 +1,43 @@
class Type {
constructor(major, name, terminal) {
this.major = major;
this.majorEncoded = major << 5;
this.name = name;
this.terminal = terminal;
}
toString() {
return `Type[${ this.major }].${ this.name }`;
}
compare(typ) {
return this.major < typ.major ? -1 : this.major > typ.major ? 1 : 0;
}
}
Type.uint = new Type(0, 'uint', true);
Type.negint = new Type(1, 'negint', true);
Type.bytes = new Type(2, 'bytes', true);
Type.string = new Type(3, 'string', true);
Type.array = new Type(4, 'array', false);
Type.map = new Type(5, 'map', false);
Type.tag = new Type(6, 'tag', false);
Type.float = new Type(7, 'float', true);
Type.false = new Type(7, 'false', true);
Type.true = new Type(7, 'true', true);
Type.null = new Type(7, 'null', true);
Type.undefined = new Type(7, 'undefined', true);
Type.break = new Type(7, 'break', true);
class Token {
constructor(type, value, encodedLength) {
this.type = type;
this.value = value;
this.encodedLength = encodedLength;
this.encodedBytes = undefined;
this.byteValue = undefined;
}
toString() {
return `Token[${ this.type }].${ this.value }`;
}
}
export {
Type,
Token
};
+640
View File
@@ -0,0 +1,640 @@
const fixtures = [
{
cbor: 'AA==',
hex: '00',
roundtrip: true,
decoded: 0
},
{
cbor: 'AQ==',
hex: '01',
roundtrip: true,
decoded: 1
},
{
cbor: 'Cg==',
hex: '0a',
roundtrip: true,
decoded: 10
},
{
cbor: 'Fw==',
hex: '17',
roundtrip: true,
decoded: 23
},
{
cbor: 'GBg=',
hex: '1818',
roundtrip: true,
decoded: 24
},
{
cbor: 'GBk=',
hex: '1819',
roundtrip: true,
decoded: 25
},
{
cbor: 'GGQ=',
hex: '1864',
roundtrip: true,
decoded: 100
},
{
cbor: 'GQPo',
hex: '1903e8',
roundtrip: true,
decoded: 1000
},
{
cbor: 'GgAPQkA=',
hex: '1a000f4240',
roundtrip: true,
decoded: 1000000
},
{
cbor: 'GwAAAOjUpRAA',
hex: '1b000000e8d4a51000',
roundtrip: true,
decoded: 1000000000000
},
{
cbor: 'G///////////',
hex: '1bffffffffffffffff',
roundtrip: true,
decoded: BigInt('18446744073709551615')
},
{
cbor: 'wkkBAAAAAAAAAAA=',
hex: 'c249010000000000000000',
roundtrip: true,
decoded: BigInt('18446744073709551616'),
noTagDecodeError: /tag not supported \(2\)/,
noTagEncodeError: /BigInt larger than allowable range/
},
{
cbor: 'O///////////',
hex: '3bffffffffffffffff',
roundtrip: true,
decoded: BigInt('-18446744073709551616')
},
{
cbor: 'w0kBAAAAAAAAAAA=',
hex: 'c349010000000000000000',
roundtrip: true,
decoded: BigInt('-18446744073709551617'),
noTagDecodeError: /tag not supported \(3\)/,
noTagEncodeError: /BigInt larger than allowable range/
},
{
cbor: 'IA==',
hex: '20',
roundtrip: true,
decoded: -1
},
{
cbor: 'KQ==',
hex: '29',
roundtrip: true,
decoded: -10
},
{
cbor: 'OGM=',
hex: '3863',
roundtrip: true,
decoded: -100
},
{
cbor: 'OQPn',
hex: '3903e7',
roundtrip: true,
decoded: -1000
},
{
cbor: '+QAA',
hex: 'f90000',
roundtrip: false,
decoded: 0
},
{
cbor: '+YAA',
hex: 'f98000',
roundtrip: false,
decoded: -0
},
{
cbor: '+TwA',
hex: 'f93c00',
roundtrip: false,
decoded: 1
},
{
cbor: '+z/xmZmZmZma',
hex: 'fb3ff199999999999a',
roundtrip: true,
decoded: 1.1
},
{
cbor: '+T4A',
hex: 'f93e00',
roundtrip: true,
decoded: 1.5
},
{
cbor: '+Xv/',
hex: 'f97bff',
roundtrip: false,
decoded: 65504
},
{
cbor: '+kfDUAA=',
hex: 'fa47c35000',
roundtrip: false,
decoded: 100000
},
{
cbor: '+n9///8=',
hex: 'fa7f7fffff',
roundtrip: true,
decoded: 3.4028234663852886e+38
},
{
cbor: '+3435DyIAHWc',
hex: 'fb7e37e43c8800759c',
roundtrip: true,
decoded: 1e+300
},
{
cbor: '+QAB',
hex: 'f90001',
roundtrip: true,
decoded: 5.960464477539063e-8
},
{
cbor: '+QQA',
hex: 'f90400',
roundtrip: true,
decoded: 0.00006103515625
},
{
cbor: '+cQA',
hex: 'f9c400',
roundtrip: false,
decoded: -4
},
{
cbor: '+8AQZmZmZmZm',
hex: 'fbc010666666666666',
roundtrip: true,
decoded: -4.1
},
{
cbor: '+XwA',
hex: 'f97c00',
roundtrip: true,
diagnostic: Infinity
},
{
cbor: '+X4A',
hex: 'f97e00',
roundtrip: true,
diagnostic: NaN
},
{
cbor: '+fwA',
hex: 'f9fc00',
roundtrip: true,
diagnostic: -Infinity
},
{
cbor: '+n+AAAA=',
hex: 'fa7f800000',
roundtrip: false,
diagnostic: Infinity
},
{
cbor: '+n/AAAA=',
hex: 'fa7fc00000',
roundtrip: false,
diagnostic: NaN
},
{
cbor: '+v+AAAA=',
hex: 'faff800000',
roundtrip: false,
diagnostic: -Infinity
},
{
cbor: '+3/wAAAAAAAA',
hex: 'fb7ff0000000000000',
roundtrip: false,
diagnostic: Infinity
},
{
cbor: '+3/4AAAAAAAA',
hex: 'fb7ff8000000000000',
roundtrip: false,
diagnostic: NaN
},
{
cbor: '+//wAAAAAAAA',
hex: 'fbfff0000000000000',
roundtrip: false,
diagnostic: -Infinity
},
{
cbor: '9A==',
hex: 'f4',
roundtrip: true,
decoded: false
},
{
cbor: '9Q==',
hex: 'f5',
roundtrip: true,
decoded: true
},
{
cbor: '9g==',
hex: 'f6',
roundtrip: true,
decoded: null
},
{
cbor: '9w==',
hex: 'f7',
roundtrip: true,
diagnostic: undefined
},
{
cbor: '8A==',
hex: 'f0',
roundtrip: true,
diagnostic: 'simple(16)',
error: /simple values are not supported/
},
{
cbor: '+Bg=',
hex: 'f818',
roundtrip: true,
diagnostic: 'simple(24)',
error: /simple values are not supported/
},
{
cbor: '+P8=',
hex: 'f8ff',
roundtrip: true,
diagnostic: 'simple(255)',
error: /simple values are not supported/
},
{
cbor: 'wHQyMDEzLTAzLTIxVDIwOjA0OjAwWg==',
hex: 'c074323031332d30332d32315432303a30343a30305a',
roundtrip: false,
diagnostic: '0("2013-03-21T20:04:00Z")'
},
{
cbor: 'wRpRS2ew',
hex: 'c11a514b67b0',
roundtrip: false,
diagnostic: '1(1363896240)'
},
{
cbor: 'wftB1FLZ7CAAAA==',
hex: 'c1fb41d452d9ec200000',
roundtrip: false,
diagnostic: '1(1363896240.5)'
},
{
cbor: '10QBAgME',
hex: 'd74401020304',
roundtrip: false,
diagnostic: '23(h\'01020304\')'
},
{
cbor: '2BhFZElFVEY=',
hex: 'd818456449455446',
roundtrip: false,
diagnostic: '24(h\'6449455446\')'
},
{
cbor: '2CB2aHR0cDovL3d3dy5leGFtcGxlLmNvbQ==',
hex: 'd82076687474703a2f2f7777772e6578616d706c652e636f6d',
roundtrip: false,
diagnostic: '32("http://www.example.com")'
},
{
cbor: 'QA==',
hex: '40',
roundtrip: true,
diagnostic: 'h\'\''
},
{
cbor: 'RAECAwQ=',
hex: '4401020304',
roundtrip: true,
diagnostic: 'h\'01020304\''
},
{
cbor: 'YA==',
hex: '60',
roundtrip: true,
decoded: ''
},
{
cbor: 'YWE=',
hex: '6161',
roundtrip: true,
decoded: 'a'
},
{
cbor: 'ZElFVEY=',
hex: '6449455446',
roundtrip: true,
decoded: 'IETF'
},
{
cbor: 'YiJc',
hex: '62225c',
roundtrip: true,
decoded: '"\\'
},
{
cbor: 'YsO8',
hex: '62c3bc',
roundtrip: true,
decoded: 'ü'
},
{
cbor: 'Y+awtA==',
hex: '63e6b0b4',
roundtrip: true,
decoded: '水'
},
{
cbor: 'ZPCQhZE=',
hex: '64f0908591',
roundtrip: true,
decoded: '\uD800\uDD51'
},
{
cbor: 'gA==',
hex: '80',
roundtrip: true,
decoded: []
},
{
cbor: 'gwECAw==',
hex: '83010203',
roundtrip: true,
decoded: [
1,
2,
3
]
},
{
cbor: 'gwGCAgOCBAU=',
hex: '8301820203820405',
roundtrip: true,
decoded: [
1,
[
2,
3
],
[
4,
5
]
]
},
{
cbor: 'mBkBAgMEBQYHCAkKCwwNDg8QERITFBUWFxgYGBk=',
hex: '98190102030405060708090a0b0c0d0e0f101112131415161718181819',
roundtrip: true,
decoded: [
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
16,
17,
18,
19,
20,
21,
22,
23,
24,
25
]
},
{
cbor: 'oA==',
hex: 'a0',
roundtrip: true,
decoded: {}
},
{
cbor: 'ogECAwQ=',
hex: 'a201020304',
roundtrip: true,
diagnostic: '{1: 2, 3: 4}',
error: /non-string keys not supported/
},
{
cbor: 'omFhAWFiggID',
hex: 'a26161016162820203',
roundtrip: true,
decoded: {
a: 1,
b: [
2,
3
]
}
},
{
cbor: 'gmFhoWFiYWM=',
hex: '826161a161626163',
roundtrip: true,
decoded: [
'a',
{ b: 'c' }
]
},
{
cbor: 'pWFhYUFhYmFCYWNhQ2FkYURhZWFF',
hex: 'a56161614161626142616361436164614461656145',
roundtrip: true,
decoded: {
a: 'A',
b: 'B',
c: 'C',
d: 'D',
e: 'E'
}
},
{
cbor: 'X0IBAkMDBAX/',
hex: '5f42010243030405ff',
roundtrip: false,
diagnostic: '(_ h\'0102\', h\'030405\')',
error: /indefinite length bytes\/strings are not supported/
},
{
cbor: 'f2VzdHJlYWRtaW5n/w==',
hex: '7f657374726561646d696e67ff',
roundtrip: false,
decoded: 'streaming',
error: /indefinite length bytes\/strings are not supported/
},
{
cbor: 'n/8=',
hex: '9fff',
roundtrip: false,
decoded: []
},
{
cbor: 'nwGCAgOfBAX//w==',
hex: '9f018202039f0405ffff',
roundtrip: false,
decoded: [
1,
[
2,
3
],
[
4,
5
]
]
},
{
cbor: 'nwGCAgOCBAX/',
hex: '9f01820203820405ff',
roundtrip: false,
decoded: [
1,
[
2,
3
],
[
4,
5
]
]
},
{
cbor: 'gwGCAgOfBAX/',
hex: '83018202039f0405ff',
roundtrip: false,
decoded: [
1,
[
2,
3
],
[
4,
5
]
]
},
{
cbor: 'gwGfAgP/ggQF',
hex: '83019f0203ff820405',
roundtrip: false,
decoded: [
1,
[
2,
3
],
[
4,
5
]
]
},
{
cbor: 'nwECAwQFBgcICQoLDA0ODxAREhMUFRYXGBgYGf8=',
hex: '9f0102030405060708090a0b0c0d0e0f101112131415161718181819ff',
roundtrip: false,
decoded: [
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
16,
17,
18,
19,
20,
21,
22,
23,
24,
25
]
},
{
cbor: 'v2FhAWFinwID//8=',
hex: 'bf61610161629f0203ffff',
roundtrip: false,
decoded: {
a: 1,
b: [
2,
3
]
}
},
{
cbor: 'gmFhv2FiYWP/',
hex: '826161bf61626163ff',
roundtrip: false,
decoded: [
'a',
{ b: 'c' }
]
},
{
cbor: 'v2NGdW71Y0FtdCH/',
hex: 'bf6346756ef563416d7421ff',
roundtrip: false,
decoded: {
Fun: true,
Amt: -2
}
}
];
export {
fixtures
};
+19
View File
@@ -0,0 +1,19 @@
import {
Token,
Type
} from '../lib/token.js';
export function dateDecoder(obj) {
if (typeof obj !== 'string') {
throw new Error('expected string for tag 1');
}
return new Date(obj);
}
export function dateEncoder(obj) {
if (!(obj instanceof Date)) {
throw new Error('expected Date for "Date" encoder');
}
return [
new Token(Type.tag, 0),
new Token(Type.string, obj.toISOString().replace(/\.000Z$/, 'Z'))
];
}
+339
View File
@@ -0,0 +1,339 @@
import chai from 'chai';
import { exec } from 'child_process';
import process from 'process';
import path from 'path';
import { platform } from 'os';
import { fileURLToPath } from 'url';
const {assert} = chai;
const fixture1JsonString = '{"a":1,"b":[2,3],"smile":"\uD83D\uDE00"}';
const fixture1JsonPrettyString = `{
"a": 1,
"b": [
2,
3
],
"smile": "😀"
}
`;
const fixture1HexString = 'a3616101616282020365736d696c6564f09f9880';
const fixture1Bin = fromHex(fixture1HexString);
const fixture1BinString = new TextDecoder().decode(fixture1Bin);
const fixture1DiagnosticString = `a3 # map(3)
61 # string(1)
61 # "a"
01 # uint(1)
61 # string(1)
62 # "b"
82 # array(2)
02 # uint(2)
03 # uint(3)
65 # string(5)
736d696c65 # "smile"
64 # string(2)
f09f9880 # "😀"
`;
const fixture2HexString = 'a4616101616282020363627566440102036165736d696c6564f09f9880';
const fixture2DiagnosticString = `a4 # map(4)
61 # string(1)
61 # "a"
01 # uint(1)
61 # string(1)
62 # "b"
82 # array(2)
02 # uint(2)
03 # uint(3)
63 # string(3)
627566 # "buf"
44 # bytes(4)
01020361 # "\\x01\\x02\\x03a"
65 # string(5)
736d696c65 # "smile"
64 # string(2)
f09f9880 # "😀"
`;
const binPath = path.join(path.dirname(fileURLToPath(import.meta.url)), '../lib/bin.js');
function fromHex(hex) {
return new Uint8Array(hex.split('').map((c, i, d) => i % 2 === 0 ? `0x${ c }${ d[i + 1] }` : '').filter(Boolean).map(e => parseInt(e, 16)));
}
async function execBin(cmd, stdin) {
return new Promise((resolve, reject) => {
const cp = exec(`"${ process.execPath }" "${ binPath }" ${ cmd }`, (err, stdout, stderr) => {
if (err) {
err.stdout = stdout;
err.stderr = stderr;
return reject(err);
}
resolve({
stdout,
stderr
});
});
if (stdin != null) {
cp.on('spawn', () => {
cp.stdin.write(stdin);
cp.stdin.end();
});
}
});
}
describe('Bin', () => {
it('usage', async () => {
try {
await execBin('');
assert.fail('should have errored');
} catch (e) {
assert.strictEqual(e.stdout, '');
assert.strictEqual(e.stderr, `Usage: cborg <command> <args>
Valid commands:
\tbin2diag [binary input]
\tbin2hex [binary input]
\tbin2json [--pretty] [binary input]
\tdiag2bin [diagnostic input]
\tdiag2hex [diagnostic input]
\tdiag2json [--pretty] [diagnostic input]
\thex2bin [hex input]
\thex2diag [hex input]
\thex2json [--pretty] [hex input]
\tjson2bin '[json input]'
\tjson2diag '[json input]'
\tjson2hex '[json input]'
Input may either be supplied as an argument or piped via stdin
`);
}
});
it('bad cmd', async () => {
try {
await execBin('blip');
assert.fail('should have errored');
} catch (e) {
assert.strictEqual(e.stdout, '');
assert.strictEqual(e.stderr, `Unknown command: 'blip'
Usage: cborg <command> <args>
Valid commands:
\tbin2diag [binary input]
\tbin2hex [binary input]
\tbin2json [--pretty] [binary input]
\tdiag2bin [diagnostic input]
\tdiag2hex [diagnostic input]
\tdiag2json [--pretty] [diagnostic input]
\thex2bin [hex input]
\thex2diag [hex input]
\thex2json [--pretty] [hex input]
\tjson2bin '[json input]'
\tjson2diag '[json input]'
\tjson2hex '[json input]'
Input may either be supplied as an argument or piped via stdin
`);
}
});
it('help', async () => {
const {stdout, stderr} = await execBin('help');
assert.strictEqual(stdout, '');
assert.strictEqual(stderr, `Usage: cborg <command> <args>
Valid commands:
\tbin2diag [binary input]
\tbin2hex [binary input]
\tbin2json [--pretty] [binary input]
\tdiag2bin [diagnostic input]
\tdiag2hex [diagnostic input]
\tdiag2json [--pretty] [diagnostic input]
\thex2bin [hex input]
\thex2diag [hex input]
\thex2json [--pretty] [hex input]
\tjson2bin '[json input]'
\tjson2diag '[json input]'
\tjson2hex '[json input]'
Input may either be supplied as an argument or piped via stdin
`);
});
it('bin2diag (stdin)', async () => {
const {stdout, stderr} = await execBin('bin2diag', fixture1Bin);
assert.strictEqual(stderr, '');
assert.strictEqual(stdout, fixture1DiagnosticString);
});
it('bin2hex (stdin)', async () => {
const {stdout, stderr} = await execBin('bin2hex', fixture1Bin);
assert.strictEqual(stderr, '');
assert.strictEqual(stdout, `${ fixture1HexString }\n`);
});
it('bin2json (stdin)', async () => {
const {stdout, stderr} = await execBin('bin2json', fixture1Bin);
assert.strictEqual(stderr, '');
assert.strictEqual(stdout, `${ fixture1JsonString }\n`);
});
it('bin2json pretty (stdin)', async () => {
const {stdout, stderr} = await execBin('bin2json --pretty', fixture1Bin);
assert.strictEqual(stderr, '');
assert.strictEqual(stdout, fixture1JsonPrettyString);
});
for (const stdin of [
true,
false
]) {
if (platform() !== 'win32' || stdin) {
it(`diag2bin${ stdin ? ' (stdin)' : '' }`, async () => {
const {stdout, stderr} = !stdin ? await execBin(`diag2bin '${ fixture1DiagnosticString }'`) : await execBin('diag2bin', fixture1DiagnosticString);
assert.strictEqual(stderr, '');
assert.strictEqual(stdout, fixture1BinString);
});
it(`diag2hex${ stdin ? ' (stdin)' : '' }`, async () => {
const {stdout, stderr} = !stdin ? await execBin(`diag2hex '${ fixture1DiagnosticString }'`) : await execBin('diag2hex', fixture1DiagnosticString);
assert.strictEqual(stderr, '');
assert.strictEqual(stdout, `${ fixture1HexString }\n`);
});
it(`diag2json${ stdin ? ' (stdin)' : '' }`, async () => {
const {stdout, stderr} = !stdin ? await execBin(`diag2json '${ fixture1DiagnosticString }'`) : await execBin('diag2json', fixture1DiagnosticString);
assert.strictEqual(stderr, '');
assert.strictEqual(stdout, `${ fixture1JsonString }\n`);
});
it(`diag2json pretty${ stdin ? ' (stdin)' : '' }`, async () => {
const {stdout, stderr} = !stdin ? await execBin(`diag2json --pretty '${ fixture1DiagnosticString }'`) : await execBin('diag2json --pretty', fixture1DiagnosticString);
assert.strictEqual(stderr, '');
assert.strictEqual(stdout, fixture1JsonPrettyString);
});
}
it(`hex2bin${ stdin ? ' (stdin)' : '' }`, async () => {
const {stdout, stderr} = !stdin ? await execBin(`hex2bin ${ fixture1HexString }`) : await execBin('hex2bin', fixture1HexString);
assert.strictEqual(stderr, '');
assert.strictEqual(stdout, fixture1BinString);
});
it(`hex2diag${ stdin ? ' (stdin)' : '' }`, async () => {
const {stdout, stderr} = !stdin ? await execBin(`hex2diag ${ fixture2HexString }`) : await execBin('hex2diag', fixture2HexString);
assert.strictEqual(stderr, '');
assert.strictEqual(stdout, fixture2DiagnosticString);
});
it(`hex2json${ stdin ? ' (stdin)' : '' }`, async () => {
const {stdout, stderr} = !stdin ? await execBin(`hex2json ${ fixture1HexString }`) : await execBin('hex2json', fixture1HexString);
assert.strictEqual(stderr, '');
assert.strictEqual(stdout, `${ fixture1JsonString }\n`);
});
it(`hex2json pretty${ stdin ? ' (stdin)' : '' }`, async () => {
const {stdout, stderr} = !stdin ? await execBin(`hex2json --pretty ${ fixture1HexString }`) : await execBin('hex2json --pretty', fixture1HexString);
assert.strictEqual(stderr, '');
assert.strictEqual(stdout, fixture1JsonPrettyString);
});
it(`json2bin${ stdin ? ' (stdin)' : '' }`, async () => {
const {stdout, stderr} = !stdin ? await execBin('json2bin "{\\"a\\":1,\\"b\\":[2,3],\\"smile\\":\\"\uD83D\uDE00\\"}"') : await execBin('json2bin', fixture1JsonString);
assert.strictEqual(stderr, '');
assert.strictEqual(stdout, fixture1BinString);
});
it(`json2diag${ stdin ? ' (stdin)' : '' }`, async () => {
const {stdout, stderr} = !stdin ? await execBin('json2diag "{\\"a\\":1,\\"b\\":[2,3],\\"smile\\":\\"\uD83D\uDE00\\"}"') : await execBin('json2diag', fixture1JsonString);
assert.strictEqual(stderr, '');
assert.strictEqual(stdout, fixture1DiagnosticString);
});
it(`json2hex${ stdin ? ' (stdin)' : '' }`, async () => {
const {stdout, stderr} = !stdin ? await execBin(`json2hex "${ fixture1JsonString.replace(/"/g, '\\"') }"`) : await execBin('json2hex', fixture1JsonString);
assert.strictEqual(stderr, '');
assert.strictEqual(stdout, `${ fixture1HexString }\n`);
});
}
it('diag indenting', async () => {
const {stdout, stderr} = await execBin('json2diag', '{"a":[],"b":{},"c":{"a":1,"b":{"a":{"a":{}}}},"d":{"a":{"a":{"a":1},"b":2,"c":[]}},"e":[[[[{"a":{}}]]]],"f":1}');
assert.strictEqual(stderr, '');
assert.strictEqual(stdout, `a6 # map(6)
61 # string(1)
61 # "a"
80 # array(0)
61 # string(1)
62 # "b"
a0 # map(0)
61 # string(1)
63 # "c"
a2 # map(2)
61 # string(1)
61 # "a"
01 # uint(1)
61 # string(1)
62 # "b"
a1 # map(1)
61 # string(1)
61 # "a"
a1 # map(1)
61 # string(1)
61 # "a"
a0 # map(0)
61 # string(1)
64 # "d"
a1 # map(1)
61 # string(1)
61 # "a"
a3 # map(3)
61 # string(1)
61 # "a"
a1 # map(1)
61 # string(1)
61 # "a"
01 # uint(1)
61 # string(1)
62 # "b"
02 # uint(2)
61 # string(1)
63 # "c"
80 # array(0)
61 # string(1)
65 # "e"
81 # array(1)
81 # array(1)
81 # array(1)
81 # array(1)
a1 # map(1)
61 # string(1)
61 # "a"
a0 # map(0)
61 # string(1)
66 # "f"
01 # uint(1)
`);
});
describe('diag length bytes', () => {
it('compact', async () => {
const {stdout, stderr} = await execBin('json2diag', '"aaaaaaaaaaaaaaaaaaaaaaa"');
assert.strictEqual(stderr, '');
assert.strictEqual(stdout, `77 # string(23)
6161616161616161616161616161616161616161616161 # "aaaaaaaaaaaaaaaaaaaaaaa"
`);
});
it('1-byte', async () => {
const {stdout, stderr} = await execBin('json2diag', '"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"');
assert.strictEqual(stderr, '');
assert.strictEqual(stdout, `78 23 # string(35)
6161616161616161616161616161616161616161616161 # "aaaaaaaaaaaaaaaaaaaaaaa"
616161616161616161616161 # "aaaaaaaaaaaa"
`);
});
it('2-byte', async () => {
const {stdout, stderr} = await execBin('json2diag', '"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"');
assert.strictEqual(stderr, '');
assert.strictEqual(stdout, `79 0100 # string(256)
6161616161616161616161616161616161616161616161 # "aaaaaaaaaaaaaaaaaaaaaaa"
6161616161616161616161616161616161616161616161 # "aaaaaaaaaaaaaaaaaaaaaaa"
6161616161616161616161616161616161616161616161 # "aaaaaaaaaaaaaaaaaaaaaaa"
6161616161616161616161616161616161616161616161 # "aaaaaaaaaaaaaaaaaaaaaaa"
6161616161616161616161616161616161616161616161 # "aaaaaaaaaaaaaaaaaaaaaaa"
6161616161616161616161616161616161616161616161 # "aaaaaaaaaaaaaaaaaaaaaaa"
6161616161616161616161616161616161616161616161 # "aaaaaaaaaaaaaaaaaaaaaaa"
6161616161616161616161616161616161616161616161 # "aaaaaaaaaaaaaaaaaaaaaaa"
6161616161616161616161616161616161616161616161 # "aaaaaaaaaaaaaaaaaaaaaaa"
6161616161616161616161616161616161616161616161 # "aaaaaaaaaaaaaaaaaaaaaaa"
6161616161616161616161616161616161616161616161 # "aaaaaaaaaaaaaaaaaaaaaaa"
616161 # "aaa"
`);
});
});
it('diag non-utf8 and non-printable ascii', async () => {
const input = '7864f55ff8f12508b63ef2bfeca7557ae90df6311a5ec1631b4a1fa843310bd9c3a710eaace5a1bdd72ad0bfe049771c11e756338bd93865e645f1adec9b9c99ef407fbd4fc6859e7904c5ad7dc9bd10a5cc16973d5b28ec1a6dd43d9f82f9f18c3d03418e35';
let {stdout, stderr} = await execBin(`hex2diag ${ input }`);
assert.strictEqual(stderr, '');
assert.strictEqual(stdout, `78 64 # string(86)
f55ff8f12508b63ef2bfeca7557ae90df6311a5ec1631b # "õ_øñ%\\x08¶>ò¿ì§Uzé\\x0dö1\\x1a^Ác\\x1b"
4a1fa843310bd9c3a710eaace5a1bdd72ad0bfe049771c # "J\\x1f¨C1\\x0bÙç\\x10ê¬å¡½×*пàIw\\x1c"
11e756338bd93865e645f1adec9b9c99ef407fbd4fc685 # "\\x11çV3\\x8bÙ8eæEñ\\xadì\\x9b\\x9c\\x99ï@\\x7f½OÆ\\x85"
9e7904c5ad7dc9bd10a5cc16973d5b28ec1a6dd43d9f82 # "\\x9ey\\x04Å\\xad}ɽ\\x10¥Ì\\x16\\x97=[(ì\\x1amÔ=\\x9f\\x82"
f9f18c3d03418e35 # "ùñ\\x8c=\\x03A\\x8e5"
`);
({stdout, stderr} = await execBin('diag2hex', stdout));
assert.strictEqual(stderr, '');
assert.strictEqual(stdout, `${ input }\n`);
});
});
+1
View File
@@ -0,0 +1 @@
import bin from '../lib/bin.js';
+155
View File
@@ -0,0 +1,155 @@
import chai from 'chai';
import {
decode,
encode
} from '../cborg.js';
import {
fromHex,
toHex
} from '../lib/byte-utils.js';
const {assert} = chai;
const fixtures = [
{
data: '00',
expected: 0,
type: 'uint8'
},
{
data: '02',
expected: 2,
type: 'uint8'
},
{
data: '18ff',
expected: 255,
type: 'uint8'
},
{
data: '1901f4',
expected: 500,
type: 'uint16'
},
{
data: '1900ff',
expected: 255,
type: 'uint16',
strict: false
},
{
data: '19ffff',
expected: 65535,
type: 'uint16'
},
{
data: '1a000000ff',
expected: 255,
type: 'uint32',
strict: false
},
{
data: '1a00010000',
expected: 65536,
type: 'uint32'
},
{
data: '1a000f4240',
expected: 1000000,
type: 'uint32'
},
{
data: '1aa5f702b3',
expected: 2784428723,
type: 'uint32'
},
{
data: '1b00000000000000ff',
expected: 255,
type: 'uint64',
strict: false
},
{
data: '1b0016db6db6db6db7',
expected: Number.MAX_SAFE_INTEGER / 1.4,
type: 'uint64'
},
{
data: '1b001fffffffffffff',
expected: Number.MAX_SAFE_INTEGER,
type: 'uint64'
},
{
data: '1ba5f702b3a5f702b3',
expected: BigInt('11959030306112471731'),
type: 'uint64'
},
{
data: '1bffffffffffffffff',
expected: BigInt('18446744073709551615'),
type: 'uint64'
}
];
describe('uint', () => {
describe('decode', () => {
for (const fixture of fixtures) {
const data = fromHex(fixture.data);
it(`should decode ${ fixture.type }=${ fixture.expected }`, () => {
assert.ok(decode(data) === fixture.expected, `decode ${ fixture.type } ${ decode(data) } != ${ fixture.expected }`);
if (fixture.strict === false) {
assert.throws(() => decode(data, { strict: true }), Error, 'CBOR decode error: integer encoded in more bytes than necessary (strict decode)');
} else {
assert.ok(decode(data, { strict: true }) === fixture.expected, `decode ${ fixture.type }`);
}
});
}
});
it('should throw error', () => {
assert.throws(() => decode(fromHex('1ca5f702b3a5f702b3')), Error, 'CBOR decode error: encountered invalid minor (28) for major 0');
assert.throws(() => decode(fromHex('1ba5f702b3a5f702')), Error, 'CBOR decode error: not enough data for type');
});
describe('encode', () => {
for (const fixture of fixtures) {
it(`should encode ${ fixture.type }=${ fixture.expected }`, () => {
if (fixture.strict === false) {
assert.notStrictEqual(toHex(encode(fixture.expected)), fixture.data, `encode ${ fixture.type } !strict`);
} else {
assert.strictEqual(toHex(encode(fixture.expected)), fixture.data, `encode ${ fixture.type }`);
}
});
}
});
describe('roundtrip', () => {
for (const fixture of fixtures) {
if (fixture.strict !== false) {
it(`should roundtrip ${ fixture.type }=${ fixture.expected }`, () => {
assert.ok(decode(encode(fixture.expected)) === fixture.expected, `roundtrip ${ fixture.type }`);
});
}
}
});
describe('toobig', () => {
it('bigger than 64-bit', () => {
assert.doesNotThrow(() => encode(BigInt('18446744073709551615')));
assert.throws(() => encode(BigInt('18446744073709551616')), /BigInt larger than allowable range/);
});
it('disallow BigInt', () => {
for (const fixture of fixtures) {
const data = fromHex(fixture.data);
if (!Number.isSafeInteger(fixture.expected)) {
assert.throws(() => decode(data, { allowBigInt: false }), /safe integer range/);
} else {
assert.ok(decode(data, { allowBigInt: false }) === fixture.expected, `decode ${ fixture.type }`);
}
}
});
});
describe('toosmall', () => {
for (const fixture of fixtures) {
if (fixture.strict !== false && typeof fixture.expected === 'number') {
const small = BigInt(fixture.expected);
it(`should encode ${ small }n`, () => {
assert.strictEqual(toHex(encode(BigInt(small))), fixture.data, `encode ${ small }`);
});
}
}
});
});
+149
View File
@@ -0,0 +1,149 @@
import chai from 'chai';
import {
decode,
encode
} from '../cborg.js';
import {
fromHex,
toHex
} from '../lib/byte-utils.js';
const {assert} = chai;
const fixtures = [
{
data: '20',
expected: -1,
type: 'negint8'
},
{
data: '22',
expected: -3,
type: 'negint8'
},
{
data: '3863',
expected: -100,
type: 'negint8'
},
{
data: '38ff',
expected: -256,
type: 'negint8'
},
{
data: '3900ff',
expected: -256,
type: 'negint16',
strict: false
},
{
data: '3901f4',
expected: -501,
type: 'negint16'
},
{
data: '3a000000ff',
expected: -256,
type: 'negint32',
strict: false
},
{
data: '3aa5f702b3',
expected: -2784428724,
type: 'negint32'
},
{
data: '3b00000000000000ff',
expected: -256,
type: 'negint32',
strict: false
},
{
data: '3b0016db6db6db6db7',
expected: Number.MIN_SAFE_INTEGER / 1.4 - 1,
type: 'negint64'
},
{
data: '3b001ffffffffffffe',
expected: Number.MIN_SAFE_INTEGER,
type: 'negint64'
},
{
data: '3b001fffffffffffff',
expected: BigInt('-9007199254740992'),
type: 'negint64'
},
{
data: '3b0020000000000000',
expected: BigInt('-9007199254740993'),
type: 'negint64'
},
{
data: '3ba5f702b3a5f702b3',
expected: BigInt('-11959030306112471732'),
type: 'negint64'
},
{
data: '3bffffffffffffffff',
expected: BigInt('-18446744073709551616'),
type: 'negint64'
}
];
describe('negint', () => {
describe('decode', () => {
for (const fixture of fixtures) {
const data = fromHex(fixture.data);
it(`should decode ${ fixture.type }=${ fixture.expected }`, () => {
assert.ok(decode(data) === fixture.expected, `decode ${ fixture.type } (${ decode(data) } != ${ fixture.expected })`);
if (fixture.strict === false) {
assert.throws(() => decode(data, { strict: true }), Error, 'CBOR decode error: integer encoded in more bytes than necessary (strict decode)');
} else {
assert.strictEqual(decode(data, { strict: true }), fixture.expected, `decode ${ fixture.type }`);
}
});
}
});
describe('encode', () => {
for (const fixture of fixtures) {
it(`should encode ${ fixture.type }=${ fixture.expected }`, () => {
if (fixture.strict === false) {
assert.notStrictEqual(toHex(encode(fixture.expected)), fixture.data, `encode ${ fixture.type } !strict`);
} else {
assert.strictEqual(toHex(encode(fixture.expected)), fixture.data, `encode ${ fixture.type }`);
}
});
}
});
describe('roundtrip', () => {
for (const fixture of fixtures) {
it(`should roundtrip ${ fixture.type }=${ fixture.expected }`, () => {
assert.ok(decode(encode(fixture.expected)) === fixture.expected, `roundtrip ${ fixture.type }`);
});
}
});
describe('toobig', () => {
it('bigger than 64-bit', () => {
assert.doesNotThrow(() => encode(BigInt('-18446744073709551616')));
assert.throws(() => encode(BigInt('-18446744073709551617')), /BigInt larger than allowable range/);
});
it('disallow BigInt', () => {
for (const fixture of fixtures) {
const data = fromHex(fixture.data);
if (!Number.isSafeInteger(fixture.expected)) {
assert.throws(() => decode(data, { allowBigInt: false }), /safe integer range/);
} else {
assert.ok(decode(data, { allowBigInt: false }) === fixture.expected, `decode ${ fixture.type }`);
}
}
});
});
describe('toosmall', () => {
for (const fixture of fixtures) {
if (fixture.strict !== false && typeof fixture.expected === 'number') {
const small = BigInt(fixture.expected);
it(`should encode ${ small }n`, () => {
assert.strictEqual(toHex(encode(BigInt(small))), fixture.data, `encode ${ small }`);
});
}
}
});
});
+252
View File
@@ -0,0 +1,252 @@
import chai from 'chai';
import {
decode,
encode
} from '../cborg.js';
import {
useBuffer,
fromHex,
toHex
} from '../lib/byte-utils.js';
const {assert} = chai;
const fixtures = [
{
data: '40',
expected: '',
type: 'bytes'
},
{
data: '41a1',
expected: 'a1',
type: 'bytes'
},
{
data: '5801a1',
expected: 'a1',
type: 'bytes',
strict: false
},
{
data: '58ff000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfe',
expected: '000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfe',
type: 'bytes',
label: 'long bytes, 8-bit length'
},
{
data: '5900ff000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfe',
expected: '000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfe',
type: 'bytes',
label: 'long bytes, 16-bit length',
strict: false
},
{
data: '5a000000ff000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfe',
expected: '000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfe',
type: 'bytes',
label: 'long bytes, 32-bit length',
strict: false
},
{
data: '5b00000000000000ff000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfe',
expected: '000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfe',
type: 'bytes',
label: 'long bytes, 64-bit length',
strict: false
}
];
(() => {
function rnd(length) {
return new Uint8Array(Array.from({ length }, () => Math.floor(Math.random() * 255)));
}
const expected16 = rnd(256);
fixtures.push({
data: new Uint8Array([
...fromHex('590100'),
...expected16
]),
expected: expected16,
type: 'bytes',
label: 'long bytes, 16-bit length strict-compat'
});
const expected32 = rnd(65536);
fixtures.push({
data: new Uint8Array([
...fromHex('5a00010000'),
...expected32
]),
expected: expected32,
type: 'bytes',
label: 'long bytes, 32-bit length strict-compat'
});
})();
describe('bytes', () => {
describe('decode', () => {
for (const fixture of fixtures) {
const data = fromHex(fixture.data);
it(`should decode ${ fixture.type }=${ fixture.label || fixture.expected }`, () => {
let actual = decode(data);
assert.strictEqual(toHex(actual), toHex(fromHex(fixture.expected)), `decode ${ fixture.type }`);
if (fixture.strict === false) {
assert.throws(() => decode(data, { strict: true }), Error, 'CBOR decode error: integer encoded in more bytes than necessary (strict decode)');
} else {
actual = decode(data, { strict: true });
assert.strictEqual(toHex(actual), toHex(fromHex(fixture.expected)), `decode ${ fixture.type } strict`);
}
});
it('should fail to decode very large length', () => {
assert.throws(() => decode(fromHex('5ba5f702b3a5f702b3000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfe')), /CBOR decode error: 64-bit integer bytes lengths not supported/);
});
}
});
describe('encode', () => {
for (const fixture of fixtures) {
if (fixture.data.length >= 100000000) {
it.skip(`(TODO) skipping encode of very large bytes ${ fixture.type }=${ fixture.label || fixture.expected }`, () => {
});
continue;
}
const data = fromHex(fixture.expected);
const expectedHex = toHex(fixture.data);
it(`should encode ${ fixture.type }=${ fixture.label || fixture.expected }`, () => {
if (fixture.unsafe) {
assert.throws(() => encode(data), Error, /^CBOR encode error: number too large to encode \(-\d+\)$/);
} else if (fixture.strict === false) {
assert.notStrictEqual(toHex(encode(data)), expectedHex, `encode ${ fixture.type } !strict`);
} else {
assert.strictEqual(toHex(encode(data)), expectedHex, `encode ${ fixture.type }`);
}
});
}
});
describe('typedarrays', () => {
const cases = [
{
obj: Uint8Array.from([
1,
2,
3
]),
hex: '43010203'
},
{
obj: Uint8ClampedArray.from([
1,
2,
3
]),
hex: '43010203'
},
{
obj: Uint16Array.from([
1,
2,
3
]),
hex: '46010002000300'
},
{
obj: Uint32Array.from([
1,
2,
3
]),
hex: '4c010000000200000003000000'
},
{
obj: Int8Array.from([
1,
2,
-3
]),
hex: '430102fd'
},
{
obj: Int16Array.from([
1,
2,
-3
]),
hex: '4601000200fdff'
},
{
obj: Int32Array.from([
1,
2,
-3
]),
hex: '4c0100000002000000fdffffff'
},
{
obj: Float32Array.from([
1,
2,
-3
]),
hex: '4c0000803f00000040000040c0'
},
{
obj: Float64Array.from([
1,
2,
-3
]),
hex: '5818000000000000f03f000000000000004000000000000008c0'
},
{
obj: BigUint64Array.from([
BigInt(1),
BigInt(2),
BigInt(3)
]),
hex: '5818010000000000000002000000000000000300000000000000'
},
{
obj: BigInt64Array.from([
BigInt(1),
BigInt(2),
BigInt(-3)
]),
hex: '581801000000000000000200000000000000fdffffffffffffff'
},
{
obj: new DataView(Uint8Array.from([
1,
2,
3
]).buffer),
hex: '43010203'
},
{
obj: Uint8Array.from([
1,
2,
3
]).buffer,
hex: '43010203'
}
];
for (const testCase of cases) {
it(testCase.obj.constructor.name, () => {
assert.equal(toHex(encode(testCase.obj)), testCase.hex);
const decoded = decode(fromHex(testCase.hex));
assert.instanceOf(decoded, Uint8Array);
assert.equal(toHex(decoded), toHex(testCase.obj));
});
}
});
if (useBuffer) {
describe('buffer', () => {
it('can encode Node.js Buffers', () => {
const obj = global.Buffer.from([
1,
2,
3
]);
assert.equal(toHex(encode(obj)), '43010203');
const decoded = decode(fromHex('43010203'));
assert.instanceOf(decoded, Uint8Array);
assert.equal(toHex(decoded), toHex(obj));
});
});
}
});
+141
View File
@@ -0,0 +1,141 @@
import chai from 'chai';
import {
decode,
encode
} from '../cborg.js';
import {
fromHex,
toHex
} from '../lib/byte-utils.js';
const {assert} = chai;
const fixtures = [
{
data: '60',
expected: '',
type: 'string'
},
{
data: '6161',
expected: 'a',
type: 'string'
},
{
data: '780161',
expected: 'a',
type: 'string',
strict: false
},
{
data: '6c48656c6c6f20776f726c6421',
expected: 'Hello world!',
type: 'string'
},
{
data: '6fc48c6175657320c39f76c49b746521',
expected: 'Čaues ßvěte!',
type: 'string'
},
{
data: '78964c6f72656d20697073756d20646f6c6f722073697420616d65742c20636f6e73656374657475722061646970697363696e6720656c69742e20446f6e6563206d692074656c6c75732c20696163756c6973206e656320766573746962756c756d20717569732c206665726d656e74756d206e6f6e2066656c69732e204d616563656e6173207574206a7573746f20706f73756572652e',
expected: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec mi tellus, iaculis nec vestibulum quis, fermentum non felis. Maecenas ut justo posuere.',
type: 'string',
label: 'long string, 8-bit length'
},
{
data: '7900964c6f72656d20697073756d20646f6c6f722073697420616d65742c20636f6e73656374657475722061646970697363696e6720656c69742e20446f6e6563206d692074656c6c75732c20696163756c6973206e656320766573746962756c756d20717569732c206665726d656e74756d206e6f6e2066656c69732e204d616563656e6173207574206a7573746f20706f73756572652e',
expected: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec mi tellus, iaculis nec vestibulum quis, fermentum non felis. Maecenas ut justo posuere.',
type: 'string',
label: 'long string, 16-bit length',
strict: false
},
{
data: '7a000000964c6f72656d20697073756d20646f6c6f722073697420616d65742c20636f6e73656374657475722061646970697363696e6720656c69742e20446f6e6563206d692074656c6c75732c20696163756c6973206e656320766573746962756c756d20717569732c206665726d656e74756d206e6f6e2066656c69732e204d616563656e6173207574206a7573746f20706f73756572652e',
expected: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec mi tellus, iaculis nec vestibulum quis, fermentum non felis. Maecenas ut justo posuere.',
type: 'string',
label: 'long string, 32-bit length',
strict: false
},
{
data: '7b00000000000000964c6f72656d20697073756d20646f6c6f722073697420616d65742c20636f6e73656374657475722061646970697363696e6720656c69742e20446f6e6563206d692074656c6c75732c20696163756c6973206e656320766573746962756c756d20717569732c206665726d656e74756d206e6f6e2066656c69732e204d616563656e6173207574206a7573746f20706f73756572652e',
expected: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec mi tellus, iaculis nec vestibulum quis, fermentum non felis. Maecenas ut justo posuere.',
type: 'string',
label: 'long string, 64-bit length',
strict: false
}
];
(() => {
function rnd(length) {
const sa = [];
let l = 0;
while (l < length) {
const ascii = length - l < 3;
const base = ascii ? 32 : 126976;
const max = ascii ? 126 : 130816;
const cc = Math.floor(Math.random() * (max - base)) + base;
const s = String.fromCharCode(cc);
l += new TextEncoder().encode(s).length;
sa.push(s);
}
return sa.join('');
}
const expected16 = rnd(256);
fixtures.push({
data: new Uint8Array([
...fromHex('790100'),
...new TextEncoder().encode(expected16)
]),
expected: expected16,
type: 'string',
label: 'long string, 16-bit length strict-compat'
});
const expected32 = rnd(65536);
fixtures.push({
data: new Uint8Array([
...fromHex('7a00010000'),
...new TextEncoder().encode(expected32)
]),
expected: expected32,
type: 'string',
label: 'long string, 32-bit length strict-compat'
});
})();
describe('string', () => {
describe('decode', () => {
for (const fixture of fixtures) {
const data = fromHex(fixture.data);
it(`should decode ${ fixture.type }=${ fixture.label || fixture.expected }`, () => {
let actual = decode(data);
assert.strictEqual(actual, fixture.expected, `decode ${ fixture.type }`);
if (fixture.strict === false) {
assert.throws(() => decode(data, { strict: true }), Error, 'CBOR decode error: integer encoded in more bytes than necessary (strict decode)');
} else {
actual = decode(data, { strict: true });
assert.strictEqual(actual, fixture.expected, `decode ${ fixture.type } strict`);
}
});
it('should fail to decode very large length', () => {
assert.throws(() => decode(fromHex('7ba5f702b3a5f702b34c6f72656d20697073756d20646f6c6f722073697420616d65742c20636f6e73656374657475722061646970697363696e6720656c69742e20446f6e6563206d692074656c6c75732c20696163756c6973206e656320766573746962756c756d20717569732c206665726d656e74756d206e6f6e2066656c69732e204d616563656e6173207574206a7573746f20706f73756572652e')), /CBOR decode error: 64-bit integer string lengths not supported/);
});
}
});
describe('encode', () => {
for (const fixture of fixtures) {
if (fixture.data.length >= 100000000) {
it.skip(`(TODO) skipping encode of very large string ${ fixture.type }=${ fixture.label || fixture.expected }`, () => {
});
continue;
}
const data = fixture.expected;
const expectedHex = toHex(fixture.data);
it(`should encode ${ fixture.type }=${ fixture.label || fixture.expected }`, () => {
if (fixture.unsafe) {
assert.throws(() => encode(data), Error, /^CBOR encode error: number too large to encode \(-\d+\)$/);
} else if (fixture.strict === false) {
assert.notStrictEqual(toHex(encode(data)), expectedHex, `encode ${ fixture.type } !strict`);
} else {
assert.strictEqual(toHex(encode(data)), expectedHex, `encode ${ fixture.type }`);
}
});
}
});
});
+197
View File
@@ -0,0 +1,197 @@
import chai from 'chai';
import {
decode,
encode
} from '../cborg.js';
import {
fromHex,
toHex
} from '../lib/byte-utils.js';
const {assert} = chai;
const fixtures = [
{
data: '80',
expected: [],
type: 'array empty'
},
{
data: '8102',
expected: [2],
type: 'array 1 compact uint'
},
{
data: '8118ff',
expected: [255],
type: 'array 1 uint8'
},
{
data: '811901f4',
expected: [500],
type: 'array 1 uint16'
},
{
data: '811a00010000',
expected: [65536],
type: 'array 1 uint32'
},
{
data: '811b00000000000000ff',
expected: [255],
type: 'array 1 uint64',
strict: false
},
{
data: '811b0016db6db6db6db7',
expected: [Number.MAX_SAFE_INTEGER / 1.4],
type: 'array 1 uint64'
},
{
data: '811b001fffffffffffff',
expected: [Number.MAX_SAFE_INTEGER],
type: 'array 1 uint64'
},
{
data: '8403040506',
expected: [
3,
4,
5,
6
],
type: 'array 4 ints'
},
{
data: '8c1b0016db6db6db6db71a000100001901f40200202238ff3aa5f702b33b0016db6db6db6db74261316fc48c6175657320c39f76c49b746521',
expected: [
Number.MAX_SAFE_INTEGER / 1.4,
65536,
500,
2,
0,
-1,
-3,
-256,
-2784428724,
Number.MIN_SAFE_INTEGER / 1.4 - 1,
new TextEncoder().encode('a1'),
'Čaues ßvěte!'
],
type: 'array mixed terminals',
label: '[]'
},
{
data: '8265617272617982626f66820582666e657374656482666172726179736121',
expected: [
'array',
[
'of',
[
5,
[
'nested',
[
'arrays',
'!'
]
]
]
]
],
type: 'array nested'
},
{
data: '980403040506',
expected: [
3,
4,
5,
6
],
type: 'array 4 ints, length8',
strict: false
},
{
data: '99000403040506',
expected: [
3,
4,
5,
6
],
type: 'array 4 ints, length16',
strict: false
},
{
data: '9a0000000403040506',
expected: [
3,
4,
5,
6
],
type: 'array 4 ints, length32',
strict: false
},
{
data: '9b000000000000000403040506',
expected: [
3,
4,
5,
6
],
type: 'array 4 ints, length64',
strict: false
}
];
describe('array', () => {
describe('decode', () => {
for (const fixture of fixtures) {
const data = fromHex(fixture.data);
it(`should decode ${ fixture.type }=${ fixture.label || fixture.expected }`, () => {
assert.deepStrictEqual(decode(data), fixture.expected, `decode ${ fixture.type }`);
if (fixture.strict === false) {
assert.throws(() => decode(data, { strict: true }), Error, 'CBOR decode error: integer encoded in more bytes than necessary (strict decode)');
} else {
assert.deepStrictEqual(decode(data, { strict: true }), fixture.expected, `decode ${ fixture.type }`);
}
});
it('should fail to decode very large length', () => {
assert.throws(() => decode(fromHex('9ba5f702b3a5f7020403040506')), /CBOR decode error: 64-bit integer array lengths not supported/);
});
}
});
describe('encode', () => {
for (const fixture of fixtures) {
it(`should encode ${ fixture.type }=${ fixture.label || fixture.expected }`, () => {
if (fixture.unsafe) {
assert.throws(encode.bind(null, fixture.expected), Error, /^CBOR encode error: number too large to encode \(\d+\)$/);
} else if (fixture.strict === false) {
assert.notDeepEqual(toHex(encode(fixture.expected)), fixture.data, `encode ${ fixture.type } !strict`);
} else {
assert.strictEqual(toHex(encode(fixture.expected)), fixture.data, `encode ${ fixture.type }`);
}
});
}
});
describe('roundtrip', () => {
for (const fixture of fixtures) {
if (!fixture.unsafe && fixture.strict !== false) {
it(`should roundtrip ${ fixture.type }=${ fixture.label || fixture.expected }`, () => {
assert.deepStrictEqual(decode(encode(fixture.expected)), fixture.expected, `roundtrip ${ fixture.type }`);
});
}
}
});
describe('specials', () => {
it('can decode indefinite length items', () => {
assert.deepStrictEqual(decode(fromHex('9f616f6174ff')), [
'o',
't'
]);
});
it('can switch off indefinite length support', () => {
assert.throws(() => decode(fromHex('9f616f6174ff'), { allowIndefinite: false }), /indefinite/);
});
});
});
+664
View File
@@ -0,0 +1,664 @@
import chai from 'chai';
import {
decode,
encode
} from '../cborg.js';
import {
fromHex,
toHex
} from '../lib/byte-utils.js';
const {assert} = chai;
const fixtures = [
{
data: 'a0',
expected: {},
type: 'map empty'
},
{
data: 'a0',
expected: new Map(),
type: 'map empty (useMaps)',
useMaps: true
},
{
data: 'a1616101',
expected: { a: 1 },
type: 'map 1 pair'
},
{
data: 'a161316161',
expected: { 1: 'a' },
type: 'map 1 pair (rev)'
},
{
data: 'a1016161',
expected: toMap([[
1,
'a'
]]),
type: 'map 1 pair (int key as Map w/ useMaps)',
useMaps: true
},
{
data: 'a243010203633132334302030463323334',
expected: toMap([
[
Uint8Array.from([
1,
2,
3
]),
'123'
],
[
Uint8Array.from([
2,
3,
4
]),
'234'
]
]),
type: 'map 2 pair (bytes keys Map w/ useMaps)',
useMaps: true
},
{
data: 'a1666f626a656374a16477697468a26134666e6573746564676f626a65637473a161216121',
expected: {
object: {
with: {
4: 'nested',
objects: { '!': '!' }
}
}
},
type: 'map nested'
},
{
data: 'a1666f626a656374a16477697468a204666e6573746564676f626a65637473a161216121',
expected: toMap([[
'object',
toMap([[
'with',
toMap([
[
4,
'nested'
],
[
'objects',
toMap([[
'!',
'!'
]])
]
])
]])
]]),
type: 'map nested w/ useMaps',
useMaps: true
},
{
data: 'ae636f6e651b0016db6db6db6db763736978206374656e3b0016db6db6db6db76374776f1a0001000064666976650064666f757202646e696e653aa5f702b365656967687438ff65736576656e226574687265651901f466656c6576656e426131667477656c76656fc48c6175657320c39f76c49b74652168666f75727465656ea4616664666f7572616f016174026274680368746869727465656e840203046466697665',
encode: {
one: Number.MAX_SAFE_INTEGER / 1.4,
two: 65536,
three: 500,
four: 2,
five: 0,
six: -1,
seven: -3,
eight: -256,
nine: -2784428724,
ten: Number.MIN_SAFE_INTEGER / 1.4 - 1,
eleven: new TextEncoder().encode('a1'),
twelve: 'Čaues ßvěte!',
thirteen: [
2,
3,
4,
'five'
],
fourteen: {
o: 1,
t: 2,
th: 3,
f: 'four'
}
},
expected: {
one: Number.MAX_SAFE_INTEGER / 1.4,
six: -1,
ten: Number.MIN_SAFE_INTEGER / 1.4 - 1,
two: 65536,
five: 0,
four: 2,
nine: -2784428724,
eight: -256,
seven: -3,
three: 500,
eleven: new TextEncoder().encode('a1'),
twelve: 'Čaues ßvěte!',
fourteen: {
f: 'four',
o: 1,
t: 2,
th: 3
},
thirteen: [
2,
3,
4,
'five'
]
},
type: 'map with complex entries',
label: '{}'
},
{
data: 'ad01636f6e65026374776f1901f46c666976652068756e647265641902586b7369782068756e647265641a00010000636269671b0016db6db6db6db76662696767657220696d696e7573206f6e6521696d696e75732074776f38ff781f6d696e75732074776f2068756e6472656420616e64206669667479207369783901f4781a6d696e757820666976652068756e6472656420616e64206f6e653901f5781a6d696e757820666976652068756e6472656420616e642074776f3aa5f702b367626967206e65673b0016db6db6db6db76a626967676572206e6567',
encode: toMap([
[
2,
'two'
],
[
1,
'one'
],
[
-2,
'minus two'
],
[
-1,
'minus one'
],
[
600,
'six hundred'
],
[
500,
'five hundred'
],
[
-256,
'minus two hundred and fifty six'
],
[
-502,
'minux five hundred and two'
],
[
-501,
'minux five hundred and one'
],
[
65536,
'big'
],
[
-2784428724,
'big neg'
],
[
6433713753386423,
'bigger'
],
[
-6433713753386424,
'bigger neg'
]
]),
expected: toMap([
[
1,
'one'
],
[
2,
'two'
],
[
500,
'five hundred'
],
[
600,
'six hundred'
],
[
65536,
'big'
],
[
6433713753386423,
'bigger'
],
[
-1,
'minus one'
],
[
-2,
'minus two'
],
[
-256,
'minus two hundred and fifty six'
],
[
-501,
'minux five hundred and one'
],
[
-502,
'minux five hundred and two'
],
[
-2784428724,
'big neg'
],
[
-6433713753386424,
'bigger neg'
]
]),
type: 'map with ints and negints',
useMaps: true
},
{
data: 'a44104636f6e65430102026374776f430102036574687265654301020464666f7572',
encode: toMap([
[
Uint8Array.from([
1,
2,
3
]),
'three'
],
[
Uint8Array.from([4]),
'one'
],
[
Uint8Array.from([
1,
2,
4
]),
'four'
],
[
Uint8Array.from([
1,
2,
2
]),
'two'
]
]),
expected: toMap([
[
Uint8Array.from([4]),
'one'
],
[
Uint8Array.from([
1,
2,
2
]),
'two'
],
[
Uint8Array.from([
1,
2,
3
]),
'three'
],
[
Uint8Array.from([
1,
2,
4
]),
'four'
]
]),
type: 'map with bytes keys',
useMaps: true
},
{
data: 'b801616101',
expected: { a: 1 },
type: 'map 1 pair, length8',
strict: false
},
{
data: 'b90001616101',
expected: { a: 1 },
type: 'map 1 pair, length16',
strict: false
},
{
data: 'ba00000001616101',
expected: { a: 1 },
type: 'map 1 pair, length32',
strict: false
},
{
data: 'bb0000000000000001616101',
expected: { a: 1 },
type: 'map 1 pair, length64',
strict: false
}
];
function toMap(arr) {
const m = new Map();
for (const [key, value] of arr) {
m.set(key, value);
}
return m;
}
function entries(map) {
function nest(a) {
for (const e of a) {
e[0] = entries(e[0]);
e[1] = entries(e[1]);
}
return a;
}
if (Object.getPrototypeOf(map) === Map.prototype) {
return nest([...map.entries()]);
}
if (typeof map === 'object') {
return nest([...Object.entries(map)]);
}
return map;
}
describe('map', () => {
describe('decode', () => {
for (const fixture of fixtures) {
const data = fromHex(fixture.data);
it(`should decode ${ fixture.type }=${ fixture.label || JSON.stringify(fixture.expected) }`, () => {
let options = fixture.useMaps ? { useMaps: true } : undefined;
const decoded = decode(data, options);
if (fixture.useMaps) {
assert.strictEqual(Object.getPrototypeOf(decoded), Map.prototype, 'is Map');
} else {
assert.isObject(decoded, 'is object');
}
assert.deepStrictEqual(entries(decoded), entries(fixture.expected), `decode ${ fixture.type }`);
options = Object.assign({ strict: true }, options);
if (fixture.strict === false) {
assert.throws(() => decode(data, options), Error, 'CBOR decode error: integer encoded in more bytes than necessary (strict decode)');
} else {
assert.deepStrictEqual(entries(decode(data, options)), entries(fixture.expected), `decode ${ fixture.type }`);
}
});
it('should fail to decode very large length', () => {
assert.throws(() => decode(fromHex('bba5f702b3a5f70201616101')), /CBOR decode error: 64-bit integer map lengths not supported/);
});
}
it('errors', () => {
assert.throws(() => decode(fromHex('a1016161')), /non-string keys not supported \(got number\)/);
});
});
describe('encode', () => {
for (const fixture of fixtures) {
it(`should encode ${ fixture.type }=${ fixture.label || JSON.stringify(fixture.expected) }`, () => {
const toEncode = fixture.encode || fixture.expected;
if (fixture.unsafe) {
assert.throws(encode.bind(null, toEncode), Error, /^CBOR encode error: number too large to encode \(\d+\)$/);
} else if (fixture.strict === false || fixture.roundtrip === false) {
assert.notDeepEqual(toHex(encode(toEncode)), fixture.data, `encode ${ fixture.type } !strict`);
} else {
assert.strictEqual(toHex(encode(toEncode)), fixture.data, `encode ${ fixture.type }`);
}
});
}
});
describe('roundtrip', () => {
for (const fixture of fixtures) {
if (!fixture.unsafe && fixture.strict !== false && fixture.roundtrip !== false) {
it(`should roundtrip ${ fixture.type }=${ fixture.label || JSON.stringify(fixture.expected) }`, () => {
const toEncode = fixture.encode || fixture.expected;
const options = fixture.useMaps ? { useMaps: true } : undefined;
const rt = decode(encode(toEncode), options);
if (fixture.useMaps) {
assert.strictEqual(Object.getPrototypeOf(rt), Map.prototype, 'is Map');
} else {
assert.isObject(rt, 'is object');
}
assert.deepStrictEqual(entries(rt), entries(fixture.expected), `roundtrip ${ fixture.type }`);
});
}
}
});
describe('specials', () => {
it('can decode indefinite length items', () => {
assert.deepStrictEqual(decode(fromHex('bf616f01617402ff')), {
o: 1,
t: 2
});
});
it('can switch off indefinite length support', () => {
assert.throws(() => decode(fromHex('bf616f01617402ff'), { allowIndefinite: false }), /indefinite/);
});
});
describe('sorting', () => {
it('sorts int map keys', () => {
assert.strictEqual(toHex(encode(new Map([
[
1,
1
],
[
2,
2
]
]))), 'a201010202');
assert.strictEqual(toHex(encode(new Map([
[
2,
1
],
[
1,
2
]
]))), 'a201020201');
});
it('sorts negint map keys', () => {
assert.strictEqual(toHex(encode(new Map([
[
-1,
1
],
[
-2,
2
]
]))), 'a220012102');
assert.strictEqual(toHex(encode(new Map([
[
-2,
1
],
[
-1,
2
]
]))), 'a220022101');
});
it('sorts bytes map keys', () => {
assert.strictEqual(toHex(encode(new Map([
[
Uint8Array.from([
1,
2
]),
1
],
[
Uint8Array.from([
2,
1
]),
2
]
]))), 'a24201020142020102');
assert.strictEqual(toHex(encode(new Map([
[
Uint8Array.from([
2,
1
]),
1
],
[
Uint8Array.from([
1,
2
]),
2
]
]))), 'a24201020242020101');
assert.strictEqual(toHex(encode(new Map([
[
Uint8Array.from([
1,
2
]),
1
],
[
Uint8Array.from([
2,
1
]),
2
],
[
Uint8Array.from([200]),
3
]
]))), 'a341c8034201020142020102');
});
it('sorts bytes map keys', () => {
assert.strictEqual(toHex(encode(new Map([
[
Uint8Array.from([
1,
2
]),
1
],
[
Uint8Array.from([
2,
1
]),
2
]
]))), 'a24201020142020102');
assert.strictEqual(toHex(encode(new Map([
[
Uint8Array.from([
2,
1
]),
1
],
[
Uint8Array.from([
1,
2
]),
2
]
]))), 'a24201020242020101');
assert.strictEqual(toHex(encode(new Map([
[
Uint8Array.from([
1,
2
]),
1
],
[
Uint8Array.from([
2,
1
]),
2
],
[
Uint8Array.from([200]),
3
]
]))), 'a341c8034201020142020102');
});
it('sorts array map keys (length only)', () => {
assert.strictEqual(toHex(encode(new Map([
[
[1],
1
],
[
[
1,
1
],
2
]
]))), 'a281010182010102');
assert.strictEqual(toHex(encode(new Map([
[
[
1,
1
],
1
],
[
[1],
2
]
]))), 'a281010282010101');
});
it('sorts map map keys (length only)', () => {
assert.strictEqual(toHex(encode(new Map([
[
{ a: 1 },
1
],
[
{
a: 1,
b: 1
},
2
]
]))), 'a2a161610101a261610161620102');
assert.strictEqual(toHex(encode(new Map([
[
{
a: 1,
b: 1
},
1
],
[
{ a: 1 },
2
]
]))), 'a2a161610102a261610161620101');
});
});
});
+78
View File
@@ -0,0 +1,78 @@
import chai from 'chai';
import {
Token,
Type
} from '../lib/token.js';
import {
decode,
encode
} from '../cborg.js';
import {
fromHex,
toHex
} from '../lib/byte-utils.js';
import {
dateDecoder,
dateEncoder
} from './common.js';
const {assert} = chai;
function Uint16ArrayDecoder(obj) {
if (typeof obj !== 'string') {
throw new Error('expected string for tag 23');
}
const u8a = fromHex(obj);
return new Uint16Array(u8a.buffer, u8a.byteOffset, u8a.length / 2);
}
function Uint16ArrayEncoder(obj) {
if (!(obj instanceof Uint16Array)) {
throw new Error('expected Uint16Array for "Uint16Array" encoder');
}
return [
new Token(Type.tag, 23),
new Token(Type.string, toHex(obj))
];
}
describe('tag', () => {
it('date', () => {
assert.throws(() => encode({ d: new Date() }), /unsupported type: Date/);
assert.equal(toHex(encode(new Date('2013-03-21T20:04:00Z'), { typeEncoders: { Date: dateEncoder } })), 'c074323031332d30332d32315432303a30343a30305a');
const decodedDate = decode(fromHex('c074323031332d30332d32315432303a30343a30305a'), { tags: { 0: dateDecoder } });
assert.instanceOf(decodedDate, Date);
assert.equal(decodedDate.toISOString(), new Date('2013-03-21T20:04:00Z').toISOString());
});
it('Uint16Array as hex/23 (overide existing type)', () => {
assert.equal(toHex(encode(Uint16Array.from([
1,
2,
3
]), { typeEncoders: { Uint16Array: Uint16ArrayEncoder } })), 'd76c303130303032303030333030');
const decoded = decode(fromHex('d76c303130303032303030333030'), { tags: { 23: Uint16ArrayDecoder } });
assert.instanceOf(decoded, Uint16Array);
assert.equal(toHex(decoded), toHex(Uint16Array.from([
1,
2,
3
])));
});
it('tag int too large', () => {
const verify = (hex, strict) => {
if (!strict) {
assert.throws(() => decode(fromHex(hex), {
tags: { 8: dateDecoder },
strict: true
}), /integer encoded in more bytes than necessary/);
}
const decodedDate = decode(fromHex(hex), {
tags: { 8: dateDecoder },
strict
});
assert.instanceOf(decodedDate, Date);
assert.equal(decodedDate.toISOString(), new Date('2013-03-21T20:04:00Z').toISOString());
};
verify('c874323031332d30332d32315432303a30343a30305a', true);
verify('d80874323031332d30332d32315432303a30343a30305a', false);
verify('d9000874323031332d30332d32315432303a30343a30305a', false);
verify('da0000000874323031332d30332d32315432303a30343a30305a', false);
verify('db000000000000000874323031332d30332d32315432303a30343a30305a', false);
});
});
+250
View File
@@ -0,0 +1,250 @@
import chai from 'chai';
import {
decode,
encode
} from '../cborg.js';
import {
fromHex,
toHex
} from '../lib/byte-utils.js';
const {assert} = chai;
const fixtures = [
{
data: '8601f5f4f6f720',
expected: [
1,
true,
false,
null,
undefined,
-1
],
type: 'array of float specials'
},
{
data: 'f93800',
expected: 0.5,
type: 'float16'
},
{
data: 'f9b800',
expected: -0.5,
type: 'float16'
},
{
data: 'fa33c00000',
expected: 8.940696716308594e-8,
type: 'float32'
},
{
data: 'fab3c00000',
expected: -8.940696716308594e-8,
type: 'float32'
},
{
data: 'fb3ff199999999999a',
expected: 1.1,
type: 'float64'
},
{
data: 'fbbff199999999999a',
expected: -1.1,
type: 'float64'
},
{
data: 'fb3ff1c71c71c71c72',
expected: 1.1111111111111112,
type: 'float64'
},
{
data: 'fb0000000000000002',
expected: 1e-323,
type: 'float64'
},
{
data: 'fb8000000000000002',
expected: -1e-323,
type: 'float64'
},
{
data: 'fb3fefffffffffffff',
expected: 0.9999999999999999,
type: 'float64'
},
{
data: 'fbbfefffffffffffff',
expected: -0.9999999999999999,
type: 'float64'
},
{
data: 'f97c00',
expected: Infinity,
type: 'Infinity'
},
{
data: 'fb7ff0000000000000',
expected: Infinity,
type: 'Infinity',
strict: false
},
{
data: 'f9fc00',
expected: -Infinity,
type: '-Infinity'
},
{
data: 'fbfff0000000000000',
expected: -Infinity,
type: '-Infinity',
strict: false
},
{
data: 'f97e00',
expected: NaN,
type: 'NaN'
},
{
data: 'f97ff8',
expected: NaN,
type: 'NaN',
strict: false
},
{
data: 'fa7ff80000',
expected: NaN,
type: 'NaN',
strict: false
},
{
data: 'fb7ff8000000000000',
expected: NaN,
type: 'NaN',
strict: false
},
{
data: 'fb7ff8cafedeadbeef',
expected: NaN,
type: 'NaN',
strict: false
},
{
data: 'fb40f4241a31a5a515',
expected: 82497.63712086187,
type: 'float64'
}
];
describe('float', () => {
describe('decode', () => {
for (const fixture of fixtures) {
const data = fromHex(fixture.data);
it(`should decode ${ fixture.type }=${ fixture.expected }`, () => {
assert.deepStrictEqual(decode(data), fixture.expected, `decode ${ fixture.type }`);
assert.deepStrictEqual(decode(data, { strict: true }), fixture.expected, `decode ${ fixture.type }`);
});
}
});
it('error', () => {
assert.throws(() => decode(fromHex('f80000')), Error, 'simple values are not supported');
assert.throws(() => decode(fromHex('f900')), Error, 'not enough data for float16');
assert.throws(() => decode(fromHex('fa0000')), Error, 'not enough data for float32');
assert.throws(() => decode(fromHex('fb00000000')), Error, 'not enough data for float64');
});
describe('encode', () => {
for (const fixture of fixtures) {
if (fixture.strict !== false) {
it(`should encode ${ fixture.type }=${ fixture.expected }`, () => {
assert.strictEqual(toHex(encode(fixture.expected)), fixture.data, `encode ${ fixture.type }`);
});
}
}
});
describe('encode float64', () => {
for (const fixture of fixtures) {
if (fixture.type.startsWith('float')) {
it(`should encode ${ fixture.type }=${ fixture.expected }`, () => {
const encoded = encode(fixture.expected, { float64: true });
assert.strictEqual(encoded.length, 9);
assert.strictEqual(encoded[0], 251);
assert.strictEqual(decode(encoded), fixture.expected, `encode float64 ${ fixture.type }`);
});
}
}
});
describe('roundtrip', () => {
for (const fixture of fixtures) {
if (!fixture.unsafe && fixture.strict !== false) {
it(`should roundtrip ${ fixture.type }=${ fixture.expected }`, () => {
assert.deepStrictEqual(decode(encode(fixture.expected)), fixture.expected, `roundtrip ${ fixture.type }`);
});
}
}
});
describe('specials', () => {
it('indefinite length switch fails on BREAK', () => {
assert.throws(() => decode(Uint8Array.from([
131,
1,
2,
255
])), /unexpected break to lengthed array/);
assert.throws(() => decode(Uint8Array.from([
131,
1,
2,
255
]), { allowIndefinite: false }), /indefinite/);
});
it('can switch off undefined support', () => {
assert.deepStrictEqual(decode(fromHex('f7')), undefined);
assert.throws(() => decode(fromHex('f7'), { allowUndefined: false }), /undefined/);
assert.deepStrictEqual(decode(fromHex('830102f7')), [
1,
2,
undefined
]);
assert.throws(() => decode(fromHex('830102f7'), { allowUndefined: false }), /undefined/);
});
it('can coerce undefined to null', () => {
assert.deepStrictEqual(decode(fromHex('f7'), { coerceUndefinedToNull: false }), undefined);
assert.deepStrictEqual(decode(fromHex('f7'), { coerceUndefinedToNull: true }), null);
assert.deepStrictEqual(decode(fromHex('830102f7'), { coerceUndefinedToNull: false }), [
1,
2,
undefined
]);
assert.deepStrictEqual(decode(fromHex('830102f7'), { coerceUndefinedToNull: true }), [
1,
2,
null
]);
});
it('can switch off Infinity support', () => {
assert.deepStrictEqual(decode(fromHex('830102f97c00')), [
1,
2,
Infinity
]);
assert.deepStrictEqual(decode(fromHex('830102f9fc00')), [
1,
2,
-Infinity
]);
assert.throws(() => decode(fromHex('830102f97c00'), { allowInfinity: false }), /Infinity/);
assert.throws(() => decode(fromHex('830102f9fc00'), { allowInfinity: false }), /Infinity/);
for (const fixture of fixtures.filter(f => f.type.endsWith('Infinity'))) {
assert.throws(() => decode(fromHex(fixture.data), { allowInfinity: false }), /Infinity/);
}
});
it('can switch off NaN support', () => {
assert.deepStrictEqual(decode(fromHex('830102f97e00')), [
1,
2,
NaN
]);
assert.throws(() => decode(fromHex('830102f97e00'), { allowNaN: false }), /NaN/);
for (const fixture of fixtures.filter(f => f.type === 'NaN')) {
assert.throws(() => decode(fromHex(fixture.data), { allowNaN: false }), /NaN/);
}
});
});
});
+84
View File
@@ -0,0 +1,84 @@
import chai from 'chai';
import { Bl } from '../lib/bl.js';
const {assert} = chai;
describe('Internal bytes list', () => {
describe('push', () => {
it('push bits', () => {
const bl = new Bl(10);
const expected = [];
for (let i = 0; i < 25; i++) {
bl.push([i + 1]);
expected.push(i + 1);
}
assert.deepEqual([...bl.toBytes()], expected);
});
for (let i = 4; i < 21; i++) {
it(`push Bl(${ i })`, () => {
const bl = new Bl(i);
const expected = [
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
100,
110,
120,
11,
12,
130,
13,
14,
15,
16,
17,
18,
19,
20,
21,
22,
23
];
for (let i = 0; i < 5; i++) {
bl.push([i + 1]);
}
bl.push(Uint8Array.from([
6,
7,
8,
9,
10
]));
bl.push([100]);
bl.push(Uint8Array.from([
110,
120
]));
bl.push(Uint8Array.from([
11,
12
]));
bl.push([130]);
bl.push(Uint8Array.from([
13,
14,
15,
16,
17,
18,
19,
20,
21,
22,
23
]));
assert.deepEqual([...bl.toBytes()], expected);
});
}
});
});
+95
View File
@@ -0,0 +1,95 @@
import chai from 'chai';
import {
decode,
encode
} from '../cborg.js';
import * as taglib from '../taglib.js';
import {
fromHex,
toHex
} from '../lib/byte-utils.js';
import { fixtures } from './appendix_a.js';
const {assert} = chai;
const tags = [];
const typeEncoders = {};
tags[0] = function (obj) {
if (typeof obj !== 'string') {
throw new Error('expected string for tag 1');
}
return `0("${ new Date(obj).toISOString().replace(/\.000Z$/, 'Z') }")`;
};
tags[1] = function (obj) {
if (typeof obj !== 'number') {
throw new Error('expected number for tag 1');
}
return `1(${ obj })`;
};
tags[2] = taglib.bigIntDecoder;
typeEncoders.bigint = taglib.bigIntEncoder;
tags[3] = taglib.bigNegIntDecoder;
tags[23] = function (obj) {
if (!(obj instanceof Uint8Array)) {
throw new Error('expected byte array for tag 23');
}
return `23(h'${ toHex(obj) }')`;
};
tags[24] = function (obj) {
return tags[23](obj).replace(/^23/, '24');
};
tags[32] = function (obj) {
if (typeof obj !== 'string') {
throw new Error('expected string for tag 32');
}
;
(() => new URL(obj))();
return `32("${ obj }")`;
};
describe('cbor/test-vectors', () => {
let i = 0;
for (const fixture of fixtures) {
const u8a = fromHex(fixture.hex);
let expected = fixture.decoded !== undefined ? fixture.decoded : fixture.diagnostic;
if (typeof expected === 'string' && expected.startsWith('h\'')) {
expected = fromHex(expected.replace(/(^h)'|('$)/g, ''));
}
it(`test vector #${ i }: ${ inspect(expected).replace(/\n\s*/g, '') }`, () => {
if (fixture.error) {
assert.throws(() => decode(u8a, { tags }), fixture.error);
} else {
if (fixture.noTagDecodeError) {
assert.throws(() => decode(u8a), fixture.noTagDecodeError);
}
let actual = decode(u8a, { tags });
if (typeof actual === 'bigint') {
actual = inspect(actual);
}
if (typeof expected === 'bigint') {
expected = inspect(expected);
}
assert.deepEqual(actual, expected);
if (fixture.roundtrip) {
if (fixture.noTagEncodeError) {
assert.throws(() => encode(decode(u8a, { tags })), fixture.noTagEncodeError);
}
const reencoded = encode(decode(u8a, { tags }), { typeEncoders });
assert.equal(toHex(reencoded), fixture.hex);
}
}
});
i++;
}
it.skip('encode w/ tags', () => {
});
});
function inspect(o) {
if (typeof o === 'string') {
return `'${ o }'`;
}
if (o instanceof Uint8Array) {
return `Uint8Array<${ o.join(',') }>`;
}
if (o == null || typeof o !== 'object') {
return String(o);
}
return JSON.stringify(o);
}
+69
View File
@@ -0,0 +1,69 @@
import chai from 'chai';
import { decode } from '../cborg.js';
import { fromHex } from '../lib/byte-utils.js';
const {assert} = chai;
describe('decode errors', () => {
it('not Uint8Array', () => {
for (const arg of [
true,
false,
null,
undefined,
'string',
{ obj: 'ect' },
{},
['array'],
[],
[
1,
2,
3
],
0,
100,
1.1,
-1,
Symbol.for('nope')
]) {
assert.throws(() => decode(arg), /CBOR decode error.*must be a Uint8Array/);
}
});
it('no data', () => {
assert.throws(() => decode(new Uint8Array('')), /CBOR decode error.*content/);
});
it('break only', () => {
assert.throws(() => decode(new Uint8Array([255])), /CBOR decode error.*break/);
});
it('not enough map entries (value)', () => {
assert.throws(() => decode(fromHex('a2616f016174')), /map.*not enough entries.*value/);
});
it('not enough map entries (key)', () => {
assert.throws(() => decode(fromHex('a2616f01')), /map.*not enough entries.*key/);
});
it('break in lengthed map', () => {
assert.throws(() => decode(fromHex('a2616f01ff740f')), /unexpected break to lengthed map/);
});
it('not enough array entries', () => {
assert.throws(() => decode(fromHex('82616f')), /array.*not enough entries/);
});
it('break in lengthed array', () => {
assert.throws(() => decode(fromHex('82ff')), /unexpected break to lengthed array/);
});
it('no such decoder', () => {
assert.throws(() => decode(fromHex('82ff')), /unexpected break to lengthed array/);
});
it('too many terminals', () => {
assert.throws(() => decode(fromHex('0101')), /too many terminals/);
});
it('rejectDuplicateMapKeys enabled on duplicate keys', () => {
assert.deepStrictEqual(decode(fromHex('a3636261720363666f6f0163666f6f02')), {
foo: 2,
bar: 3
});
assert.throws(() => decode(fromHex('a3636261720363666f6f0163666f6f02'), { rejectDuplicateMapKeys: true }), /CBOR decode error: found repeat map key "foo"/);
assert.throws(() => decode(fromHex('a3636261720363666f6f0163666f6f02'), {
useMaps: true,
rejectDuplicateMapKeys: true
}), /CBOR decode error: found repeat map key "foo"/);
});
});
+50
View File
@@ -0,0 +1,50 @@
import { garbage } from 'ipld-garbage';
import {
decode,
encode
} from '../cborg.js';
import chai from 'chai';
const {assert} = chai;
describe('Fuzz round-trip', () => {
it('random objects', function () {
this.timeout(5000);
for (let i = 0; i < 1000; i++) {
const obj = garbage(300, { weights: { CID: 0 } });
const byts = encode(obj);
const decoded = decode(byts);
assert.deepEqual(decoded, obj);
}
});
it('circular references error', () => {
let obj = {};
obj.obj = obj;
assert.throws(() => encode(obj), /circular references/);
obj = {
blip: [
1,
2,
{ blop: {} }
]
};
obj.blip[2].blop.boop = obj;
assert.throws(() => encode(obj), /circular references/);
obj = {
blip: [
1,
2,
{ blop: {} }
]
};
obj.blip[2].blop.boop = obj.blip;
assert.throws(() => encode(obj), /circular references/);
obj = {
blip: {},
bloop: {}
};
obj.bloop = obj.blip;
assert.doesNotThrow(() => encode(obj));
const arr = [];
arr[0] = arr;
assert.throws(() => encode(arr), /circular references/);
});
});
+279
View File
@@ -0,0 +1,279 @@
import { assert } from 'chai';
import {
decode,
encode
} from '../lib/json/json.js';
const toBytes = str => new TextEncoder().encode(str);
function verifyRoundTrip(obj, sorting) {
const encoded = new TextDecoder().decode(encode(obj, sorting === false ? { mapSorter: null } : undefined));
const json = JSON.stringify(obj);
assert.strictEqual(encoded, json);
const decoded = decode(toBytes(JSON.stringify(obj)));
assert.deepStrictEqual(decoded, obj);
}
function verifyEncodedForm(testCase) {
const obj = JSON.parse(testCase);
const encoded = encode(obj);
assert.strictEqual(new TextDecoder().decode(encoded), JSON.stringify(obj));
const decoded = decode(encoded);
assert.deepStrictEqual(decoded, obj);
const decoded2 = decode(toBytes(testCase));
assert.deepStrictEqual(decoded2, obj);
}
describe('json basics', () => {
it('can round-trip basic literals', () => {
const testCases = [
'null',
'true',
'false',
'0',
'9007199254740991',
'-9007199254740991',
JSON.stringify(Number.MAX_VALUE),
JSON.stringify(Number.MIN_VALUE)
];
for (const testCase of testCases) {
verifyEncodedForm(testCase);
}
assert.strictEqual(decode(toBytes('1E1')), 10);
assert.strictEqual(decode(toBytes('0.1e1')), 1);
assert.strictEqual(decode(toBytes('1e-1')), 0.1);
assert.strictEqual(decode(toBytes('1e+00')), 1);
assert.strictEqual(decode(toBytes('10.0')), 10);
assert.deepStrictEqual(decode(toBytes('[-10.0,1.0,0.0,100.0]')), [
-10,
1,
0,
100
]);
verifyRoundTrip(true);
verifyRoundTrip(false);
verifyRoundTrip(null);
verifyRoundTrip(100);
verifyRoundTrip(-100);
verifyRoundTrip(1.11);
verifyRoundTrip(-100.11111);
verifyRoundTrip(11100000000);
verifyRoundTrip(1.0011111e-18);
});
it('handles large integers as BigInt', () => {
const verify = (inp, str) => {
if (str === undefined) {
str = String(inp);
}
assert.strictEqual(decode(toBytes(str), { allowBigInt: true }), inp);
assert.strictEqual(decode(toBytes(str)), parseFloat(str));
};
verify(Number.MAX_SAFE_INTEGER);
verify(-Number.MAX_SAFE_INTEGER);
verify(BigInt('9007199254740992'));
verify(BigInt('9007199254740993'));
verify(BigInt('11959030306112471731'));
verify(BigInt('18446744073709551615'));
verify(BigInt('9223372036854775807'));
verify(BigInt('-9007199254740992'));
verify(BigInt('-9007199254740993'));
verify(BigInt('-9223372036854776000'));
verify(BigInt('-11959030306112471732'));
verify(BigInt('-18446744073709551616'));
verify(-9007199254740992, '-9007199254740992.0');
verify(-9223372036854776000, '-9223372036854776000.0');
verify(-18446744073709552000, '-18446744073709551616.0');
});
it('can round-trip string literals', () => {
const testCases = [
JSON.stringify(''),
JSON.stringify(' '),
JSON.stringify('"'),
JSON.stringify('\\'),
JSON.stringify('\b\f\n\r\t'),
JSON.stringify('"'),
JSON.stringify('&#34; %22 0x22 034 &#x22;'),
'"\uD83D\uDE00"'
];
for (const testCase of testCases) {
verifyEncodedForm(testCase);
}
assert.strictEqual(decode(toBytes('"/ & \\/"')), '/ & /');
verifyRoundTrip('this is a string');
verifyRoundTrip('this \uD834\uDD1E is a \u263A\u263A \u2663 string ̐ ̀\n\r');
verifyRoundTrip('');
verifyRoundTrip('foo\\bar\nbaz\tbop\rbing"bip\'bang');
});
it('can round-trip array literals', () => {
const testCases = [
'[]',
'[null]',
'[true, false]',
'[ \n 0,1, 2\n , 3,\n4] \n ',
'[-10.0, 1.0, 0.0, 100.0]',
'[["2 deep"]]'
];
for (const testCase of testCases) {
verifyEncodedForm(testCase);
}
verifyRoundTrip([
1,
2,
3,
'string',
true,
4
]);
verifyRoundTrip([
1,
2,
3,
'string',
true,
[
'and',
'a',
'nested',
'array',
true
],
4
]);
});
it('can round-trip object literals', () => {
const testCases = [
'{}',
'\n {\n "\\b"\n :\n""\n }\n ',
'{"":""}',
'{"1":{"2":0,"3":"deep"}}'
];
for (const testCase of testCases) {
verifyEncodedForm(testCase);
}
});
it('will sort map keys', () => {
const unsorted = {
one: 1,
two: 2,
three: 3.1,
str: 'string',
bool: true,
four: 4
};
verifyRoundTrip(unsorted, false);
assert.strictEqual(new TextDecoder().decode(encode(unsorted)), '{"bool":true,"four":4,"one":1,"str":"string","three":3.1,"two":2}');
});
it('can handle novel cases', () => {
assert.strictEqual(decode(toBytes('"this \\uD834\\uDD1E is a \\u263a\\u263a string"')), 'this \uD834\uDD1E is a \u263A\u263A string');
verifyRoundTrip({
one: 1,
two: 2,
three: 3.1,
str: 'string',
arr: [
'and',
'a',
'nested',
[],
'array',
[
true,
1
],
false
],
bool: true,
obj: {
nested: 'object',
a: [],
o: {}
},
four: 4
}, false);
verifyRoundTrip([
false,
[
{
'#nFzU': {},
'\\w>': -0.9441451951197325,
'\t\'': '\'JB+2Wg\tw"IrM*#e^L/d&4rrzUuwq(1mH6aVRredB&Bfs]S"KqK(Tz1Q"URBAfw',
'\n@FrfM': 'M[D]q&'
},
'J4>\'Xdc+u2$%',
4227406737130333
]
], false);
verifyRoundTrip([
0.12995619865708727,
-4973404279772543,
{
drG2: [true],
';#K^Qf>V': null,
'`2=': 'ecc<e/$+-.;U>Gr5RdZDJ\n5+:{=QHNN.tVVN~dX$FWFwu`6>"&=tW!*1*^\u263A)JFM1p|}&X.B|${*\\f@!w2\u263A+'
}
], false);
assert.strictEqual(`${ decode(encode(9007199254740991)) }`, '9007199254740991');
assert.strictEqual(`${ decode(encode(9007199254740992)) }`, '9007199254740992');
assert.strictEqual(`${ decode(encode(900719925474099100n)) }`, '900719925474099100');
});
it('should throw on bad types', () => {
assert.throws(() => encode(new Uint8Array([
1,
2
])), /CBOR encode error: unsupported type: Uint8Array/);
assert.throws(() => encode({
boop: new Uint8Array([
1,
2
])
}), /CBOR encode error: unsupported type: Uint8Array/);
assert.throws(() => encode(undefined), /CBOR encode error: unsupported type: undefined/);
assert.throws(() => encode(new Map([
[
1,
2
],
[
2,
3
]
])), /CBOR encode error: non-string map keys are not supported/);
assert.throws(() => encode(new Map([
[
[
'foo',
'bar'
],
2
],
[
[
'bar',
'foo'
],
3
]
])), /CBOR encode error: complex map keys are not supported/);
});
it('should throw on bad decode failure modes', () => {
assert.throws(() => decode(toBytes('{"a":1 & "b":2}')), 'CBOR decode error: unexpected character at position 7, was expecting object delimiter but found \'&\'');
assert.throws(() => decode(toBytes('{"a":1,"b"!2}')), 'CBOR decode error: unexpected character at position 10, was expecting key/value delimiter \':\' but found \'!\'');
assert.throws(() => decode(toBytes('[1,2&3]')), 'CBOR decode error: unexpected character at position 4, was expecting array delimiter but found \'&\'');
assert.throws(() => decode(toBytes('{"a":!}')), 'CBOR decode error: unexpected character at position 5');
assert.throws(() => decode(toBytes('"abc')), 'CBOR decode error: unexpected end of string at position 4');
assert.throws(() => decode(toBytes('"ab\\xc"')), 'CBOR decode error: unexpected string escape character at position 5');
assert.throws(() => decode(toBytes('"ab\x1Ec"')), 'CBOR decode error: invalid control character at position 3');
assert.throws(() => decode(toBytes('"ab\\')), 'CBOR decode error: unexpected string termination at position 4');
assert.throws(() => decode(toBytes('"\u263A').subarray(0, 3)), 'CBOR decode error: unexpected unicode sequence at position 1');
assert.throws(() => decode(toBytes('"\\uxyza"')), 'CBOR decode error: unexpected unicode escape character at position 3');
assert.throws(() => decode(toBytes('"\\u11"')), 'CBOR decode error: unexpected end of unicode escape sequence at position 3');
assert.throws(() => decode(toBytes('-boop')), 'CBOR decode error: unexpected token at position 1');
assert.throws(() => decode(toBytes('{"v":nope}')), 'CBOR decode error: unexpected token at position 7, expected to find \'null\'');
assert.throws(() => decode(toBytes('[n]')), 'CBOR decode error: unexpected end of input at position 1');
assert.throws(() => decode(toBytes('{"v":truu}')), 'CBOR decode error: unexpected token at position 9, expected to find \'true\'');
assert.throws(() => decode(toBytes('[tr]')), 'CBOR decode error: unexpected end of input at position 1');
assert.throws(() => decode(toBytes('{"v":flase}')), 'CBOR decode error: unexpected token at position 7, expected to find \'false\'');
assert.throws(() => decode(toBytes('[fa]')), 'CBOR decode error: unexpected end of input at position 1');
assert.throws(() => decode(toBytes('-0..1')), 'CBOR decode error: unexpected token at position 3');
});
it('should throw when rejectDuplicateMapKeys enabled on duplicate keys', () => {
assert.deepStrictEqual(decode(toBytes('{"foo":1,"foo":2}')), { foo: 2 });
assert.throws(() => decode(toBytes('{"foo":1,"foo":2}'), { rejectDuplicateMapKeys: true }), /CBOR decode error: found repeat map key "foo"/);
});
});
+55
View File
@@ -0,0 +1,55 @@
import chai from 'chai';
import { garbage } from 'ipld-garbage';
import { uintBoundaries } from '../lib/0uint.js';
import { encode } from '../cborg.js';
import { encodedLength } from '../lib/length.js';
import { dateEncoder } from './common.js';
const {assert} = chai;
function verifyLength(object, options) {
const len = encodedLength(object, options);
const encoded = encode(object, options);
const actual = encoded.length;
assert.strictEqual(actual, len, JSON.stringify(object));
}
describe('encodedLength', () => {
it('int boundaries', () => {
for (let ii = 0; ii < 4; ii++) {
verifyLength(uintBoundaries[ii]);
verifyLength(uintBoundaries[ii] - 1);
verifyLength(uintBoundaries[ii] + 1);
verifyLength(-1 * uintBoundaries[ii]);
verifyLength(-1 * uintBoundaries[ii] - 1);
verifyLength(-1 * uintBoundaries[ii] + 1);
}
});
it('tags', () => {
verifyLength({ date: new Date('2013-03-21T20:04:00Z') }, { typeEncoders: { Date: dateEncoder } });
});
it('floats', () => {
verifyLength(0.5);
verifyLength(0.5, { float64: true });
verifyLength(8.940696716308594e-8);
verifyLength(8.940696716308594e-8, { float64: true });
});
it('small garbage', function () {
this.timeout(10000);
for (let ii = 0; ii < 1000; ii++) {
const gbg = garbage(1 << 6, { weights: { CID: 0 } });
verifyLength(gbg);
}
});
it('medium garbage', function () {
this.timeout(10000);
for (let ii = 0; ii < 100; ii++) {
const gbg = garbage(1 << 16, { weights: { CID: 0 } });
verifyLength(gbg);
}
});
it('large garbage', function () {
this.timeout(10000);
for (let ii = 0; ii < 10; ii++) {
const gbg = garbage(1 << 20, { weights: { CID: 0 } });
verifyLength(gbg);
}
});
});
+4
View File
@@ -0,0 +1,4 @@
{
"type": "module",
"browser": {}
}
+37
View File
@@ -0,0 +1,37 @@
import {
Token,
Type
} from './cborg.js';
const neg1b = BigInt(-1);
const pos1b = BigInt(1);
const zerob = BigInt(0);
const eightb = BigInt(8);
export function bigIntDecoder(bytes) {
let bi = zerob;
for (let ii = 0; ii < bytes.length; ii++) {
bi = (bi << eightb) + BigInt(bytes[ii]);
}
return bi;
}
function fromBigInt(bi) {
const buf = [];
while (bi > 0) {
buf.unshift(Number(bi) & 255);
bi >>= eightb;
}
return Uint8Array.from(buf);
}
const maxSafeBigInt = BigInt('18446744073709551615');
const minSafeBigInt = BigInt('-18446744073709551616');
export function bigIntEncoder(obj) {
if (obj >= minSafeBigInt && obj <= maxSafeBigInt) {
return null;
}
return [
new Token(Type.tag, obj >= zerob ? 2 : 3),
new Token(Type.bytes, fromBigInt(obj >= zerob ? obj : obj * neg1b - pos1b))
];
}
export function bigNegIntDecoder(bytes) {
return neg1b - bigIntDecoder(bytes);
}