@ipld/car 3.2.4 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +74 -2
- package/cjs/browser-test/common.js +75 -0
- package/cjs/browser-test/test-errors.js +55 -32
- package/cjs/browser-test/test-indexer.js +12 -0
- package/cjs/browser-test/test-reader.js +83 -0
- package/cjs/lib/decoder.js +70 -13
- package/cjs/lib/header-validator.js +29 -0
- package/cjs/lib/reader-browser.js +7 -7
- package/cjs/lib/writer-browser.js +1 -1
- package/cjs/node-test/common.js +75 -0
- package/cjs/node-test/test-errors.js +55 -32
- package/cjs/node-test/test-indexer.js +12 -0
- package/cjs/node-test/test-reader.js +83 -0
- package/esm/browser-test/common.js +76 -1
- package/esm/browser-test/test-errors.js +57 -33
- package/esm/browser-test/test-indexer.js +15 -0
- package/esm/browser-test/test-reader.js +90 -1
- package/esm/lib/decoder.js +69 -13
- package/esm/lib/header-validator.js +23 -0
- package/esm/lib/reader-browser.js +7 -8
- package/esm/lib/writer-browser.js +1 -1
- package/esm/node-test/common.js +76 -1
- package/esm/node-test/test-errors.js +57 -33
- package/esm/node-test/test-indexer.js +15 -0
- package/esm/node-test/test-reader.js +90 -1
- package/examples/car-to-fixture.js +1 -4
- package/examples/dump-index.js +24 -0
- package/examples/test-examples.js +33 -0
- package/lib/coding.ts +17 -2
- package/lib/decoder.js +130 -14
- package/lib/header-validator.js +33 -0
- package/lib/header.ipldsch +6 -0
- package/lib/reader-browser.js +11 -11
- package/lib/writer-browser.js +1 -1
- package/package.json +4 -4
- package/test/_fixtures_to_js.mjs +24 -0
- package/test/common.js +49 -3
- package/test/go.carv2 +0 -0
- package/test/test-errors.js +52 -30
- package/test/test-indexer.js +24 -1
- package/test/test-reader.js +94 -1
- package/tsconfig.json +2 -1
- package/types/lib/coding.d.ts +14 -4
- package/types/lib/coding.d.ts.map +1 -1
- package/types/lib/decoder.d.ts +38 -2
- package/types/lib/decoder.d.ts.map +1 -1
- package/types/lib/header-validator.d.ts +2 -0
- package/types/lib/header-validator.d.ts.map +1 -0
- package/types/lib/reader-browser.d.ts +15 -7
- package/types/lib/reader-browser.d.ts.map +1 -1
- package/types/test/_fixtures_to_js.d.mts +3 -0
- package/types/test/_fixtures_to_js.d.mts.map +1 -0
- package/types/test/common.d.ts +13 -0
- package/types/test/common.d.ts.map +1 -1
- package/types/test/fixtures-expectations.d.ts +63 -0
- package/types/test/fixtures-expectations.d.ts.map +1 -0
- package/types/test/fixtures.d.ts +3 -0
- package/types/test/fixtures.d.ts.map +1 -0
package/cjs/node-test/common.js
CHANGED
|
@@ -214,12 +214,87 @@ const goCarIndex = [
|
|
|
214
214
|
blockLength: 18
|
|
215
215
|
}
|
|
216
216
|
];
|
|
217
|
+
const goCarV2Bytes = multiformats.bytes.fromHex('0aa16776657273696f6e02000000000000000000000000000000003300000000000000c001000000000000f30100000000000038a265726f6f747381d82a5823001220fb16f5083412ef1371d031ed4aa239903d84efdadf1ba3cd678e6475b1a232f86776657273696f6e01511220fb16f5083412ef1371d031ed4aa239903d84efdadf1ba3cd678e6475b1a232f8122d0a221220d9c0d5376d26f1931f7ad52d7acc00fc1090d2edb0808bf61eeb0a152826f6261204f09f8da418a40185011220d9c0d5376d26f1931f7ad52d7acc00fc1090d2edb0808bf61eeb0a152826f62612310a221220d745b7757f5b4593eeab7820306c7bc64eb496a7410a0d07df7a34ffec4b97f1120962617272656c657965183a122e0a2401551220a2e1c40da1ae335d4dffe729eb4d5ca23b74b9e51fc535f4a804a261080c294d1204f09f90a11807581220d745b7757f5b4593eeab7820306c7bc64eb496a7410a0d07df7a34ffec4b97f112340a2401551220b474a99a2705e23cf905a484ec6d14ef58b56bbe62e9292783466ec363b5072d120a666973686d6f6e67657218042801551220b474a99a2705e23cf905a484ec6d14ef58b56bbe62e9292783466ec363b5072d666973682b01551220a2e1c40da1ae335d4dffe729eb4d5ca23b74b9e51fc535f4a804a261080c294d6c6f62737465720100000028000000c800000000000000a2e1c40da1ae335d4dffe729eb4d5ca23b74b9e51fc535f4a804a261080c294d9401000000000000b474a99a2705e23cf905a484ec6d14ef58b56bbe62e9292783466ec363b5072d6b01000000000000d745b7757f5b4593eeab7820306c7bc64eb496a7410a0d07df7a34ffec4b97f11201000000000000d9c0d5376d26f1931f7ad52d7acc00fc1090d2edb0808bf61eeb0a152826f6268b00000000000000fb16f5083412ef1371d031ed4aa239903d84efdadf1ba3cd678e6475b1a232f83900000000000000');
|
|
218
|
+
const goCarV2Roots = [multiformats.CID.parse('QmfEoLyB5NndqeKieExd1rtJzTduQUPEV8TwAYcUiy3H5Z')];
|
|
219
|
+
const goCarV2Index = [
|
|
220
|
+
{
|
|
221
|
+
blockLength: 47,
|
|
222
|
+
blockOffset: 143,
|
|
223
|
+
cid: multiformats.CID.parse('QmfEoLyB5NndqeKieExd1rtJzTduQUPEV8TwAYcUiy3H5Z'),
|
|
224
|
+
length: 82,
|
|
225
|
+
offset: 108
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
blockLength: 99,
|
|
229
|
+
blockOffset: 226,
|
|
230
|
+
cid: multiformats.CID.parse('QmczfirA7VEH7YVvKPTPoU69XM3qY4DC39nnTsWd4K3SkM'),
|
|
231
|
+
length: 135,
|
|
232
|
+
offset: 190
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
blockLength: 54,
|
|
236
|
+
blockOffset: 360,
|
|
237
|
+
cid: multiformats.CID.parse('Qmcpz2FHJD7VAhg1fxFXdYJKePtkx1BsHuCrAgWVnaHMTE'),
|
|
238
|
+
length: 89,
|
|
239
|
+
offset: 325
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
blockLength: 4,
|
|
243
|
+
blockOffset: 451,
|
|
244
|
+
cid: multiformats.CID.parse('bafkreifuosuzujyf4i6psbneqtwg2fhplc2wxptc5euspa2gn3bwhnihfu'),
|
|
245
|
+
length: 41,
|
|
246
|
+
offset: 414
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
blockLength: 7,
|
|
250
|
+
blockOffset: 492,
|
|
251
|
+
cid: multiformats.CID.parse('bafkreifc4hca3inognou377hfhvu2xfchn2ltzi7yu27jkaeujqqqdbjju'),
|
|
252
|
+
length: 44,
|
|
253
|
+
offset: 455
|
|
254
|
+
}
|
|
255
|
+
];
|
|
256
|
+
const goCarV2Contents = {
|
|
257
|
+
QmfEoLyB5NndqeKieExd1rtJzTduQUPEV8TwAYcUiy3H5Z: {
|
|
258
|
+
Links: [{
|
|
259
|
+
Hash: multiformats.CID.parse('QmczfirA7VEH7YVvKPTPoU69XM3qY4DC39nnTsWd4K3SkM'),
|
|
260
|
+
Name: '\uD83C\uDF64',
|
|
261
|
+
Tsize: 164
|
|
262
|
+
}]
|
|
263
|
+
},
|
|
264
|
+
QmczfirA7VEH7YVvKPTPoU69XM3qY4DC39nnTsWd4K3SkM: {
|
|
265
|
+
Links: [
|
|
266
|
+
{
|
|
267
|
+
Hash: multiformats.CID.parse('Qmcpz2FHJD7VAhg1fxFXdYJKePtkx1BsHuCrAgWVnaHMTE'),
|
|
268
|
+
Name: 'barreleye',
|
|
269
|
+
Tsize: 58
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
Hash: multiformats.CID.parse('bafkreifc4hca3inognou377hfhvu2xfchn2ltzi7yu27jkaeujqqqdbjju'),
|
|
273
|
+
Name: '\uD83D\uDC21',
|
|
274
|
+
Tsize: 7
|
|
275
|
+
}
|
|
276
|
+
]
|
|
277
|
+
},
|
|
278
|
+
Qmcpz2FHJD7VAhg1fxFXdYJKePtkx1BsHuCrAgWVnaHMTE: {
|
|
279
|
+
Links: [{
|
|
280
|
+
Hash: multiformats.CID.parse('bafkreifuosuzujyf4i6psbneqtwg2fhplc2wxptc5euspa2gn3bwhnihfu'),
|
|
281
|
+
Name: 'fishmonger',
|
|
282
|
+
Tsize: 4
|
|
283
|
+
}]
|
|
284
|
+
},
|
|
285
|
+
bafkreifuosuzujyf4i6psbneqtwg2fhplc2wxptc5euspa2gn3bwhnihfu: 'fish',
|
|
286
|
+
bafkreifc4hca3inognou377hfhvu2xfchn2ltzi7yu27jkaeujqqqdbjju: 'lobster'
|
|
287
|
+
};
|
|
217
288
|
|
|
218
289
|
exports.assert = assert;
|
|
219
290
|
exports.carBytes = carBytes;
|
|
220
291
|
exports.goCarBytes = goCarBytes;
|
|
221
292
|
exports.goCarIndex = goCarIndex;
|
|
222
293
|
exports.goCarRoots = goCarRoots;
|
|
294
|
+
exports.goCarV2Bytes = goCarV2Bytes;
|
|
295
|
+
exports.goCarV2Contents = goCarV2Contents;
|
|
296
|
+
exports.goCarV2Index = goCarV2Index;
|
|
297
|
+
exports.goCarV2Roots = goCarV2Roots;
|
|
223
298
|
exports.makeData = makeData;
|
|
224
299
|
exports.makeIterable = makeIterable;
|
|
225
300
|
exports.rndCid = rndCid;
|
|
@@ -26,42 +26,65 @@ describe('Misc errors', () => {
|
|
|
26
26
|
});
|
|
27
27
|
});
|
|
28
28
|
it('bad version', async () => {
|
|
29
|
-
const buf2 = multiformats.bytes.fromHex('
|
|
30
|
-
common.assert.strictEqual(multiformats.bytes.toHex(makeHeader({ version:
|
|
31
|
-
await common.assert.isRejected(reader.CarReader.fromBytes(buf2), Error, 'Invalid CAR version:
|
|
29
|
+
const buf2 = multiformats.bytes.fromHex('0aa16776657273696f6e03');
|
|
30
|
+
common.assert.strictEqual(multiformats.bytes.toHex(makeHeader({ version: 3 })), '0aa16776657273696f6e03');
|
|
31
|
+
await common.assert.isRejected(reader.CarReader.fromBytes(buf2), Error, 'Invalid CAR version: 3');
|
|
32
32
|
});
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
describe('bad header', async () => {
|
|
34
|
+
it('sanity check', async () => {
|
|
35
|
+
const buf2 = makeHeader({
|
|
36
|
+
version: 1,
|
|
37
|
+
roots: []
|
|
38
|
+
});
|
|
39
|
+
await common.assert.isFulfilled(reader.CarReader.fromBytes(buf2));
|
|
37
40
|
});
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
buf2 = makeHeader({
|
|
42
|
-
version: '1',
|
|
43
|
-
roots: []
|
|
41
|
+
it('no \'version\' array', async () => {
|
|
42
|
+
const buf2 = makeHeader({ roots: [] });
|
|
43
|
+
await common.assert.isRejected(reader.CarReader.fromBytes(buf2), Error, 'Invalid CAR header format');
|
|
44
44
|
});
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
45
|
+
it('bad \'version\' type', async () => {
|
|
46
|
+
const buf2 = makeHeader({
|
|
47
|
+
version: '1',
|
|
48
|
+
roots: []
|
|
49
|
+
});
|
|
50
|
+
await common.assert.isRejected(reader.CarReader.fromBytes(buf2), Error, 'Invalid CAR header format');
|
|
51
51
|
});
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
52
|
+
it('no \'roots\' array', async () => {
|
|
53
|
+
const buf2 = makeHeader({ version: 1 });
|
|
54
|
+
await common.assert.isRejected(reader.CarReader.fromBytes(buf2), Error, 'Invalid CAR header format');
|
|
55
|
+
});
|
|
56
|
+
it('bad \'roots\' type', async () => {
|
|
57
|
+
const buf2 = makeHeader({
|
|
58
|
+
version: 1,
|
|
59
|
+
roots: {}
|
|
60
|
+
});
|
|
61
|
+
await common.assert.isRejected(reader.CarReader.fromBytes(buf2), Error, 'Invalid CAR header format');
|
|
62
|
+
});
|
|
63
|
+
it('extraneous properties', async () => {
|
|
64
|
+
const buf2 = makeHeader({
|
|
65
|
+
version: 1,
|
|
66
|
+
roots: [],
|
|
67
|
+
blip: true
|
|
68
|
+
});
|
|
69
|
+
await common.assert.isRejected(reader.CarReader.fromBytes(buf2), Error, 'Invalid CAR header format');
|
|
70
|
+
});
|
|
71
|
+
it('not an object', async () => {
|
|
72
|
+
const buf2 = makeHeader([
|
|
73
|
+
1,
|
|
74
|
+
[]
|
|
75
|
+
]);
|
|
76
|
+
await common.assert.isRejected(reader.CarReader.fromBytes(buf2), Error, 'Invalid CAR header format');
|
|
77
|
+
});
|
|
78
|
+
it('not an object', async () => {
|
|
79
|
+
const buf2 = makeHeader(null);
|
|
80
|
+
await common.assert.isRejected(reader.CarReader.fromBytes(buf2), Error, 'Invalid CAR header format');
|
|
81
|
+
});
|
|
82
|
+
it('recursive v2 header', async () => {
|
|
83
|
+
const v2Header = common.goCarV2Bytes.slice(0, 51);
|
|
84
|
+
const buf2 = new Uint8Array(51 * 2);
|
|
85
|
+
buf2.set(v2Header, 0);
|
|
86
|
+
buf2.set(v2Header, 51);
|
|
87
|
+
await common.assert.isRejected(reader.CarReader.fromBytes(buf2), Error, 'Invalid CAR version: 2 (expected 1)');
|
|
57
88
|
});
|
|
58
|
-
await common.assert.isRejected(reader.CarReader.fromBytes(buf2), Error, 'Invalid CAR header format');
|
|
59
|
-
buf2 = makeHeader([
|
|
60
|
-
1,
|
|
61
|
-
[]
|
|
62
|
-
]);
|
|
63
|
-
await common.assert.isRejected(reader.CarReader.fromBytes(buf2), Error, 'Invalid CAR header format');
|
|
64
|
-
buf2 = makeHeader(null);
|
|
65
|
-
await common.assert.isRejected(reader.CarReader.fromBytes(buf2), Error, 'Invalid CAR header format');
|
|
66
89
|
});
|
|
67
90
|
});
|
|
@@ -15,6 +15,18 @@ describe('CarIndexer fromBytes()', () => {
|
|
|
15
15
|
}
|
|
16
16
|
common.assert.deepStrictEqual(indexData, common.goCarIndex);
|
|
17
17
|
});
|
|
18
|
+
it('v2 complete', async () => {
|
|
19
|
+
const indexer$1 = await indexer.CarIndexer.fromBytes(common.goCarV2Bytes);
|
|
20
|
+
const roots = await indexer$1.getRoots();
|
|
21
|
+
common.assert.strictEqual(roots.length, 1);
|
|
22
|
+
common.assert(common.goCarV2Roots[0].equals(roots[0]));
|
|
23
|
+
common.assert.strictEqual(indexer$1.version, 2);
|
|
24
|
+
const indexData = [];
|
|
25
|
+
for await (const index of indexer$1) {
|
|
26
|
+
indexData.push(index);
|
|
27
|
+
}
|
|
28
|
+
common.assert.deepStrictEqual(indexData, common.goCarV2Index);
|
|
29
|
+
});
|
|
18
30
|
it('bad argument', async () => {
|
|
19
31
|
for (const arg of [
|
|
20
32
|
true,
|
|
@@ -2,11 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
var reader = require('../lib/reader.js');
|
|
4
4
|
var writer = require('../lib/writer.js');
|
|
5
|
+
var decoder = require('../lib/decoder.js');
|
|
5
6
|
var Block = require('multiformats/block');
|
|
6
7
|
var sha2 = require('multiformats/hashes/sha2');
|
|
7
8
|
var raw = require('multiformats/codecs/raw');
|
|
9
|
+
var base64 = require('multiformats/bases/base64');
|
|
10
|
+
var dagPb = require('@ipld/dag-pb');
|
|
8
11
|
var common = require('./common.js');
|
|
9
12
|
var verifyStoreReader = require('./verify-store-reader.js');
|
|
13
|
+
var fixtures = require('./fixtures.js');
|
|
14
|
+
var fixturesExpectations = require('./fixtures-expectations.js');
|
|
10
15
|
|
|
11
16
|
function _interopNamespace(e) {
|
|
12
17
|
if (e && e.__esModule) return e;
|
|
@@ -28,6 +33,7 @@ function _interopNamespace(e) {
|
|
|
28
33
|
|
|
29
34
|
var Block__namespace = /*#__PURE__*/_interopNamespace(Block);
|
|
30
35
|
var raw__namespace = /*#__PURE__*/_interopNamespace(raw);
|
|
36
|
+
var dagPb__namespace = /*#__PURE__*/_interopNamespace(dagPb);
|
|
31
37
|
|
|
32
38
|
describe('CarReader fromBytes()', () => {
|
|
33
39
|
it('complete', async () => {
|
|
@@ -66,6 +72,29 @@ describe('CarReader fromBytes()', () => {
|
|
|
66
72
|
message: 'Unexpected end of data'
|
|
67
73
|
});
|
|
68
74
|
});
|
|
75
|
+
it('v2 complete', async () => {
|
|
76
|
+
const reader$1 = await reader.CarReader.fromBytes(common.goCarV2Bytes);
|
|
77
|
+
const roots = await reader$1.getRoots();
|
|
78
|
+
common.assert.strictEqual(roots.length, 1);
|
|
79
|
+
common.assert(common.goCarV2Roots[0].equals(roots[0]));
|
|
80
|
+
common.assert.strictEqual(reader$1.version, 2);
|
|
81
|
+
for (const {cid} of common.goCarV2Index) {
|
|
82
|
+
const block = await reader$1.get(cid);
|
|
83
|
+
common.assert.isDefined(block);
|
|
84
|
+
if (block) {
|
|
85
|
+
common.assert(cid.equals(block.cid));
|
|
86
|
+
let content;
|
|
87
|
+
if (cid.code === dagPb__namespace.code) {
|
|
88
|
+
content = dagPb__namespace.decode(block.bytes);
|
|
89
|
+
} else if (cid.code === 85) {
|
|
90
|
+
content = new TextDecoder().decode(block.bytes);
|
|
91
|
+
} else {
|
|
92
|
+
common.assert.fail('Unexpected codec');
|
|
93
|
+
}
|
|
94
|
+
common.assert.deepStrictEqual(content, common.goCarV2Contents[cid.toString()]);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
});
|
|
69
98
|
it('decode error - trailing null bytes', async () => {
|
|
70
99
|
const bytes = new Uint8Array(common.carBytes.length + 5);
|
|
71
100
|
bytes.set(common.carBytes);
|
|
@@ -192,4 +221,58 @@ describe('CarReader fromIterable()', () => {
|
|
|
192
221
|
message: 'Unexpected end of data'
|
|
193
222
|
});
|
|
194
223
|
});
|
|
224
|
+
it('v2 decode error - truncated', async () => {
|
|
225
|
+
const bytes = common.goCarV2Bytes.slice();
|
|
226
|
+
const dv = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
227
|
+
dv.setBigUint64(35, BigInt(448 - 10), true);
|
|
228
|
+
await common.assert.isRejected(reader.CarReader.fromIterable(common.makeIterable(bytes, 64)), {
|
|
229
|
+
name: 'Error',
|
|
230
|
+
message: 'Unexpected end of data'
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
describe('Shared fixtures', () => {
|
|
235
|
+
describe('Header', () => {
|
|
236
|
+
for (const [name, {
|
|
237
|
+
version: expectedVersion,
|
|
238
|
+
err: expectedError
|
|
239
|
+
}] of Object.entries(fixturesExpectations.expectations)) {
|
|
240
|
+
it(name, async () => {
|
|
241
|
+
const data = base64.base64.baseDecode(fixtures.data[name]);
|
|
242
|
+
let header;
|
|
243
|
+
try {
|
|
244
|
+
header = await decoder.readHeader(decoder.bytesReader(data));
|
|
245
|
+
} catch (err) {
|
|
246
|
+
if (expectedError != null) {
|
|
247
|
+
common.assert.equal(err.message, expectedError);
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
common.assert.ifError(err);
|
|
251
|
+
}
|
|
252
|
+
if (expectedError != null) {
|
|
253
|
+
common.assert.fail(`Expected error: ${ expectedError }`);
|
|
254
|
+
}
|
|
255
|
+
common.assert.isDefined(header, 'did not decode header');
|
|
256
|
+
if (expectedVersion != null && header != null) {
|
|
257
|
+
common.assert.strictEqual(header.version, expectedVersion);
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
describe('Contents', () => {
|
|
263
|
+
for (const [name, {cids: expectedCids}] of Object.entries(fixturesExpectations.expectations)) {
|
|
264
|
+
if (expectedCids == null) {
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
it(name, async () => {
|
|
268
|
+
const data = base64.base64.baseDecode(fixtures.data[name]);
|
|
269
|
+
const reader$1 = await reader.CarReader.fromBytes(data);
|
|
270
|
+
let i = 0;
|
|
271
|
+
for await (const cid of reader$1.cids()) {
|
|
272
|
+
common.assert.strictEqual(cid.toString(), expectedCids[i++]);
|
|
273
|
+
}
|
|
274
|
+
common.assert.strictEqual(i, expectedCids.length);
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
});
|
|
195
278
|
});
|
|
@@ -186,6 +186,77 @@ const goCarIndex = [
|
|
|
186
186
|
blockLength: 18
|
|
187
187
|
}
|
|
188
188
|
];
|
|
189
|
+
const goCarV2Bytes = bytes.fromHex('0aa16776657273696f6e02000000000000000000000000000000003300000000000000c001000000000000f30100000000000038a265726f6f747381d82a5823001220fb16f5083412ef1371d031ed4aa239903d84efdadf1ba3cd678e6475b1a232f86776657273696f6e01511220fb16f5083412ef1371d031ed4aa239903d84efdadf1ba3cd678e6475b1a232f8122d0a221220d9c0d5376d26f1931f7ad52d7acc00fc1090d2edb0808bf61eeb0a152826f6261204f09f8da418a40185011220d9c0d5376d26f1931f7ad52d7acc00fc1090d2edb0808bf61eeb0a152826f62612310a221220d745b7757f5b4593eeab7820306c7bc64eb496a7410a0d07df7a34ffec4b97f1120962617272656c657965183a122e0a2401551220a2e1c40da1ae335d4dffe729eb4d5ca23b74b9e51fc535f4a804a261080c294d1204f09f90a11807581220d745b7757f5b4593eeab7820306c7bc64eb496a7410a0d07df7a34ffec4b97f112340a2401551220b474a99a2705e23cf905a484ec6d14ef58b56bbe62e9292783466ec363b5072d120a666973686d6f6e67657218042801551220b474a99a2705e23cf905a484ec6d14ef58b56bbe62e9292783466ec363b5072d666973682b01551220a2e1c40da1ae335d4dffe729eb4d5ca23b74b9e51fc535f4a804a261080c294d6c6f62737465720100000028000000c800000000000000a2e1c40da1ae335d4dffe729eb4d5ca23b74b9e51fc535f4a804a261080c294d9401000000000000b474a99a2705e23cf905a484ec6d14ef58b56bbe62e9292783466ec363b5072d6b01000000000000d745b7757f5b4593eeab7820306c7bc64eb496a7410a0d07df7a34ffec4b97f11201000000000000d9c0d5376d26f1931f7ad52d7acc00fc1090d2edb0808bf61eeb0a152826f6268b00000000000000fb16f5083412ef1371d031ed4aa239903d84efdadf1ba3cd678e6475b1a232f83900000000000000');
|
|
190
|
+
const goCarV2Roots = [CID.parse('QmfEoLyB5NndqeKieExd1rtJzTduQUPEV8TwAYcUiy3H5Z')];
|
|
191
|
+
const goCarV2Index = [
|
|
192
|
+
{
|
|
193
|
+
blockLength: 47,
|
|
194
|
+
blockOffset: 143,
|
|
195
|
+
cid: CID.parse('QmfEoLyB5NndqeKieExd1rtJzTduQUPEV8TwAYcUiy3H5Z'),
|
|
196
|
+
length: 82,
|
|
197
|
+
offset: 108
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
blockLength: 99,
|
|
201
|
+
blockOffset: 226,
|
|
202
|
+
cid: CID.parse('QmczfirA7VEH7YVvKPTPoU69XM3qY4DC39nnTsWd4K3SkM'),
|
|
203
|
+
length: 135,
|
|
204
|
+
offset: 190
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
blockLength: 54,
|
|
208
|
+
blockOffset: 360,
|
|
209
|
+
cid: CID.parse('Qmcpz2FHJD7VAhg1fxFXdYJKePtkx1BsHuCrAgWVnaHMTE'),
|
|
210
|
+
length: 89,
|
|
211
|
+
offset: 325
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
blockLength: 4,
|
|
215
|
+
blockOffset: 451,
|
|
216
|
+
cid: CID.parse('bafkreifuosuzujyf4i6psbneqtwg2fhplc2wxptc5euspa2gn3bwhnihfu'),
|
|
217
|
+
length: 41,
|
|
218
|
+
offset: 414
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
blockLength: 7,
|
|
222
|
+
blockOffset: 492,
|
|
223
|
+
cid: CID.parse('bafkreifc4hca3inognou377hfhvu2xfchn2ltzi7yu27jkaeujqqqdbjju'),
|
|
224
|
+
length: 44,
|
|
225
|
+
offset: 455
|
|
226
|
+
}
|
|
227
|
+
];
|
|
228
|
+
const goCarV2Contents = {
|
|
229
|
+
QmfEoLyB5NndqeKieExd1rtJzTduQUPEV8TwAYcUiy3H5Z: {
|
|
230
|
+
Links: [{
|
|
231
|
+
Hash: CID.parse('QmczfirA7VEH7YVvKPTPoU69XM3qY4DC39nnTsWd4K3SkM'),
|
|
232
|
+
Name: '\uD83C\uDF64',
|
|
233
|
+
Tsize: 164
|
|
234
|
+
}]
|
|
235
|
+
},
|
|
236
|
+
QmczfirA7VEH7YVvKPTPoU69XM3qY4DC39nnTsWd4K3SkM: {
|
|
237
|
+
Links: [
|
|
238
|
+
{
|
|
239
|
+
Hash: CID.parse('Qmcpz2FHJD7VAhg1fxFXdYJKePtkx1BsHuCrAgWVnaHMTE'),
|
|
240
|
+
Name: 'barreleye',
|
|
241
|
+
Tsize: 58
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
Hash: CID.parse('bafkreifc4hca3inognou377hfhvu2xfchn2ltzi7yu27jkaeujqqqdbjju'),
|
|
245
|
+
Name: '\uD83D\uDC21',
|
|
246
|
+
Tsize: 7
|
|
247
|
+
}
|
|
248
|
+
]
|
|
249
|
+
},
|
|
250
|
+
Qmcpz2FHJD7VAhg1fxFXdYJKePtkx1BsHuCrAgWVnaHMTE: {
|
|
251
|
+
Links: [{
|
|
252
|
+
Hash: CID.parse('bafkreifuosuzujyf4i6psbneqtwg2fhplc2wxptc5euspa2gn3bwhnihfu'),
|
|
253
|
+
Name: 'fishmonger',
|
|
254
|
+
Tsize: 4
|
|
255
|
+
}]
|
|
256
|
+
},
|
|
257
|
+
bafkreifuosuzujyf4i6psbneqtwg2fhplc2wxptc5euspa2gn3bwhnihfu: 'fish',
|
|
258
|
+
bafkreifc4hca3inognou377hfhvu2xfchn2ltzi7yu27jkaeujqqqdbjju: 'lobster'
|
|
259
|
+
};
|
|
189
260
|
export {
|
|
190
261
|
toBlock,
|
|
191
262
|
assert,
|
|
@@ -195,5 +266,9 @@ export {
|
|
|
195
266
|
carBytes,
|
|
196
267
|
goCarBytes,
|
|
197
268
|
goCarRoots,
|
|
198
|
-
goCarIndex
|
|
269
|
+
goCarIndex,
|
|
270
|
+
goCarV2Bytes,
|
|
271
|
+
goCarV2Roots,
|
|
272
|
+
goCarV2Index,
|
|
273
|
+
goCarV2Contents
|
|
199
274
|
};
|
|
@@ -4,7 +4,8 @@ import { encode as vEncode } from 'varint';
|
|
|
4
4
|
import { CarReader } from '../lib/reader-browser.js';
|
|
5
5
|
import {
|
|
6
6
|
carBytes,
|
|
7
|
-
assert
|
|
7
|
+
assert,
|
|
8
|
+
goCarV2Bytes
|
|
8
9
|
} from './common.js';
|
|
9
10
|
function makeHeader(block) {
|
|
10
11
|
const u = cbEncode(block);
|
|
@@ -26,42 +27,65 @@ describe('Misc errors', () => {
|
|
|
26
27
|
});
|
|
27
28
|
});
|
|
28
29
|
it('bad version', async () => {
|
|
29
|
-
const buf2 = bytes.fromHex('
|
|
30
|
-
assert.strictEqual(bytes.toHex(makeHeader({ version:
|
|
31
|
-
await assert.isRejected(CarReader.fromBytes(buf2), Error, 'Invalid CAR version:
|
|
30
|
+
const buf2 = bytes.fromHex('0aa16776657273696f6e03');
|
|
31
|
+
assert.strictEqual(bytes.toHex(makeHeader({ version: 3 })), '0aa16776657273696f6e03');
|
|
32
|
+
await assert.isRejected(CarReader.fromBytes(buf2), Error, 'Invalid CAR version: 3');
|
|
32
33
|
});
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
describe('bad header', async () => {
|
|
35
|
+
it('sanity check', async () => {
|
|
36
|
+
const buf2 = makeHeader({
|
|
37
|
+
version: 1,
|
|
38
|
+
roots: []
|
|
39
|
+
});
|
|
40
|
+
await assert.isFulfilled(CarReader.fromBytes(buf2));
|
|
37
41
|
});
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
buf2 = makeHeader({
|
|
42
|
-
version: '1',
|
|
43
|
-
roots: []
|
|
42
|
+
it('no \'version\' array', async () => {
|
|
43
|
+
const buf2 = makeHeader({ roots: [] });
|
|
44
|
+
await assert.isRejected(CarReader.fromBytes(buf2), Error, 'Invalid CAR header format');
|
|
44
45
|
});
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
46
|
+
it('bad \'version\' type', async () => {
|
|
47
|
+
const buf2 = makeHeader({
|
|
48
|
+
version: '1',
|
|
49
|
+
roots: []
|
|
50
|
+
});
|
|
51
|
+
await assert.isRejected(CarReader.fromBytes(buf2), Error, 'Invalid CAR header format');
|
|
51
52
|
});
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
53
|
+
it('no \'roots\' array', async () => {
|
|
54
|
+
const buf2 = makeHeader({ version: 1 });
|
|
55
|
+
await assert.isRejected(CarReader.fromBytes(buf2), Error, 'Invalid CAR header format');
|
|
56
|
+
});
|
|
57
|
+
it('bad \'roots\' type', async () => {
|
|
58
|
+
const buf2 = makeHeader({
|
|
59
|
+
version: 1,
|
|
60
|
+
roots: {}
|
|
61
|
+
});
|
|
62
|
+
await assert.isRejected(CarReader.fromBytes(buf2), Error, 'Invalid CAR header format');
|
|
63
|
+
});
|
|
64
|
+
it('extraneous properties', async () => {
|
|
65
|
+
const buf2 = makeHeader({
|
|
66
|
+
version: 1,
|
|
67
|
+
roots: [],
|
|
68
|
+
blip: true
|
|
69
|
+
});
|
|
70
|
+
await assert.isRejected(CarReader.fromBytes(buf2), Error, 'Invalid CAR header format');
|
|
71
|
+
});
|
|
72
|
+
it('not an object', async () => {
|
|
73
|
+
const buf2 = makeHeader([
|
|
74
|
+
1,
|
|
75
|
+
[]
|
|
76
|
+
]);
|
|
77
|
+
await assert.isRejected(CarReader.fromBytes(buf2), Error, 'Invalid CAR header format');
|
|
78
|
+
});
|
|
79
|
+
it('not an object', async () => {
|
|
80
|
+
const buf2 = makeHeader(null);
|
|
81
|
+
await assert.isRejected(CarReader.fromBytes(buf2), Error, 'Invalid CAR header format');
|
|
82
|
+
});
|
|
83
|
+
it('recursive v2 header', async () => {
|
|
84
|
+
const v2Header = goCarV2Bytes.slice(0, 51);
|
|
85
|
+
const buf2 = new Uint8Array(51 * 2);
|
|
86
|
+
buf2.set(v2Header, 0);
|
|
87
|
+
buf2.set(v2Header, 51);
|
|
88
|
+
await assert.isRejected(CarReader.fromBytes(buf2), Error, 'Invalid CAR version: 2 (expected 1)');
|
|
57
89
|
});
|
|
58
|
-
await assert.isRejected(CarReader.fromBytes(buf2), Error, 'Invalid CAR header format');
|
|
59
|
-
buf2 = makeHeader([
|
|
60
|
-
1,
|
|
61
|
-
[]
|
|
62
|
-
]);
|
|
63
|
-
await assert.isRejected(CarReader.fromBytes(buf2), Error, 'Invalid CAR header format');
|
|
64
|
-
buf2 = makeHeader(null);
|
|
65
|
-
await assert.isRejected(CarReader.fromBytes(buf2), Error, 'Invalid CAR header format');
|
|
66
90
|
});
|
|
67
91
|
});
|
|
@@ -2,6 +2,9 @@ import { CarIndexer } from '../lib/indexer.js';
|
|
|
2
2
|
import {
|
|
3
3
|
goCarBytes,
|
|
4
4
|
goCarIndex,
|
|
5
|
+
goCarV2Bytes,
|
|
6
|
+
goCarV2Roots,
|
|
7
|
+
goCarV2Index,
|
|
5
8
|
makeIterable,
|
|
6
9
|
assert
|
|
7
10
|
} from './common.js';
|
|
@@ -17,6 +20,18 @@ describe('CarIndexer fromBytes()', () => {
|
|
|
17
20
|
}
|
|
18
21
|
assert.deepStrictEqual(indexData, goCarIndex);
|
|
19
22
|
});
|
|
23
|
+
it('v2 complete', async () => {
|
|
24
|
+
const indexer = await CarIndexer.fromBytes(goCarV2Bytes);
|
|
25
|
+
const roots = await indexer.getRoots();
|
|
26
|
+
assert.strictEqual(roots.length, 1);
|
|
27
|
+
assert(goCarV2Roots[0].equals(roots[0]));
|
|
28
|
+
assert.strictEqual(indexer.version, 2);
|
|
29
|
+
const indexData = [];
|
|
30
|
+
for await (const index of indexer) {
|
|
31
|
+
indexData.push(index);
|
|
32
|
+
}
|
|
33
|
+
assert.deepStrictEqual(indexData, goCarV2Index);
|
|
34
|
+
});
|
|
20
35
|
it('bad argument', async () => {
|
|
21
36
|
for (const arg of [
|
|
22
37
|
true,
|
|
@@ -1,12 +1,22 @@
|
|
|
1
1
|
import { CarReader } from '../lib/reader-browser.js';
|
|
2
2
|
import { CarWriter } from '../lib/writer-browser.js';
|
|
3
|
+
import {
|
|
4
|
+
bytesReader,
|
|
5
|
+
readHeader
|
|
6
|
+
} from '../lib/decoder.js';
|
|
3
7
|
import * as Block from 'multiformats/block';
|
|
4
8
|
import { sha256 } from 'multiformats/hashes/sha2';
|
|
5
9
|
import * as raw from 'multiformats/codecs/raw';
|
|
10
|
+
import { base64 } from 'multiformats/bases/base64';
|
|
11
|
+
import * as dagPb from '@ipld/dag-pb';
|
|
6
12
|
import {
|
|
7
13
|
carBytes,
|
|
8
14
|
makeIterable,
|
|
9
|
-
assert
|
|
15
|
+
assert,
|
|
16
|
+
goCarV2Bytes,
|
|
17
|
+
goCarV2Roots,
|
|
18
|
+
goCarV2Index,
|
|
19
|
+
goCarV2Contents
|
|
10
20
|
} from './common.js';
|
|
11
21
|
import {
|
|
12
22
|
verifyRoots,
|
|
@@ -15,6 +25,8 @@ import {
|
|
|
15
25
|
verifyBlocks,
|
|
16
26
|
verifyCids
|
|
17
27
|
} from './verify-store-reader.js';
|
|
28
|
+
import { data as fixtures } from './fixtures.js';
|
|
29
|
+
import { expectations as fixtureExpectations } from './fixtures-expectations.js';
|
|
18
30
|
describe('CarReader fromBytes()', () => {
|
|
19
31
|
it('complete', async () => {
|
|
20
32
|
const reader = await CarReader.fromBytes(carBytes);
|
|
@@ -52,6 +64,29 @@ describe('CarReader fromBytes()', () => {
|
|
|
52
64
|
message: 'Unexpected end of data'
|
|
53
65
|
});
|
|
54
66
|
});
|
|
67
|
+
it('v2 complete', async () => {
|
|
68
|
+
const reader = await CarReader.fromBytes(goCarV2Bytes);
|
|
69
|
+
const roots = await reader.getRoots();
|
|
70
|
+
assert.strictEqual(roots.length, 1);
|
|
71
|
+
assert(goCarV2Roots[0].equals(roots[0]));
|
|
72
|
+
assert.strictEqual(reader.version, 2);
|
|
73
|
+
for (const {cid} of goCarV2Index) {
|
|
74
|
+
const block = await reader.get(cid);
|
|
75
|
+
assert.isDefined(block);
|
|
76
|
+
if (block) {
|
|
77
|
+
assert(cid.equals(block.cid));
|
|
78
|
+
let content;
|
|
79
|
+
if (cid.code === dagPb.code) {
|
|
80
|
+
content = dagPb.decode(block.bytes);
|
|
81
|
+
} else if (cid.code === 85) {
|
|
82
|
+
content = new TextDecoder().decode(block.bytes);
|
|
83
|
+
} else {
|
|
84
|
+
assert.fail('Unexpected codec');
|
|
85
|
+
}
|
|
86
|
+
assert.deepStrictEqual(content, goCarV2Contents[cid.toString()]);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
});
|
|
55
90
|
it('decode error - trailing null bytes', async () => {
|
|
56
91
|
const bytes = new Uint8Array(carBytes.length + 5);
|
|
57
92
|
bytes.set(carBytes);
|
|
@@ -178,4 +213,58 @@ describe('CarReader fromIterable()', () => {
|
|
|
178
213
|
message: 'Unexpected end of data'
|
|
179
214
|
});
|
|
180
215
|
});
|
|
216
|
+
it('v2 decode error - truncated', async () => {
|
|
217
|
+
const bytes = goCarV2Bytes.slice();
|
|
218
|
+
const dv = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
219
|
+
dv.setBigUint64(35, BigInt(448 - 10), true);
|
|
220
|
+
await assert.isRejected(CarReader.fromIterable(makeIterable(bytes, 64)), {
|
|
221
|
+
name: 'Error',
|
|
222
|
+
message: 'Unexpected end of data'
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
describe('Shared fixtures', () => {
|
|
227
|
+
describe('Header', () => {
|
|
228
|
+
for (const [name, {
|
|
229
|
+
version: expectedVersion,
|
|
230
|
+
err: expectedError
|
|
231
|
+
}] of Object.entries(fixtureExpectations)) {
|
|
232
|
+
it(name, async () => {
|
|
233
|
+
const data = base64.baseDecode(fixtures[name]);
|
|
234
|
+
let header;
|
|
235
|
+
try {
|
|
236
|
+
header = await readHeader(bytesReader(data));
|
|
237
|
+
} catch (err) {
|
|
238
|
+
if (expectedError != null) {
|
|
239
|
+
assert.equal(err.message, expectedError);
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
assert.ifError(err);
|
|
243
|
+
}
|
|
244
|
+
if (expectedError != null) {
|
|
245
|
+
assert.fail(`Expected error: ${ expectedError }`);
|
|
246
|
+
}
|
|
247
|
+
assert.isDefined(header, 'did not decode header');
|
|
248
|
+
if (expectedVersion != null && header != null) {
|
|
249
|
+
assert.strictEqual(header.version, expectedVersion);
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
describe('Contents', () => {
|
|
255
|
+
for (const [name, {cids: expectedCids}] of Object.entries(fixtureExpectations)) {
|
|
256
|
+
if (expectedCids == null) {
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
it(name, async () => {
|
|
260
|
+
const data = base64.baseDecode(fixtures[name]);
|
|
261
|
+
const reader = await CarReader.fromBytes(data);
|
|
262
|
+
let i = 0;
|
|
263
|
+
for await (const cid of reader.cids()) {
|
|
264
|
+
assert.strictEqual(cid.toString(), expectedCids[i++]);
|
|
265
|
+
}
|
|
266
|
+
assert.strictEqual(i, expectedCids.length);
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
});
|
|
181
270
|
});
|