@ipld/car 3.2.2 → 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.
Files changed (59) hide show
  1. package/README.md +74 -2
  2. package/cjs/browser-test/common.js +75 -0
  3. package/cjs/browser-test/test-errors.js +55 -32
  4. package/cjs/browser-test/test-indexer.js +12 -0
  5. package/cjs/browser-test/test-reader.js +83 -0
  6. package/cjs/lib/decoder.js +70 -13
  7. package/cjs/lib/header-validator.js +29 -0
  8. package/cjs/lib/reader-browser.js +7 -7
  9. package/cjs/lib/writer-browser.js +1 -1
  10. package/cjs/node-test/common.js +75 -0
  11. package/cjs/node-test/test-errors.js +55 -32
  12. package/cjs/node-test/test-indexer.js +12 -0
  13. package/cjs/node-test/test-reader.js +83 -0
  14. package/esm/browser-test/common.js +76 -1
  15. package/esm/browser-test/test-errors.js +57 -33
  16. package/esm/browser-test/test-indexer.js +15 -0
  17. package/esm/browser-test/test-reader.js +90 -1
  18. package/esm/lib/decoder.js +69 -13
  19. package/esm/lib/header-validator.js +23 -0
  20. package/esm/lib/reader-browser.js +7 -8
  21. package/esm/lib/writer-browser.js +1 -1
  22. package/esm/node-test/common.js +76 -1
  23. package/esm/node-test/test-errors.js +57 -33
  24. package/esm/node-test/test-indexer.js +15 -0
  25. package/esm/node-test/test-reader.js +90 -1
  26. package/examples/car-to-fixture.js +1 -4
  27. package/examples/dump-index.js +24 -0
  28. package/examples/package.json +1 -1
  29. package/examples/test-examples.js +33 -0
  30. package/lib/coding.ts +17 -2
  31. package/lib/decoder.js +130 -14
  32. package/lib/header-validator.js +33 -0
  33. package/lib/header.ipldsch +6 -0
  34. package/lib/reader-browser.js +11 -11
  35. package/lib/writer-browser.js +1 -1
  36. package/package.json +7 -7
  37. package/test/_fixtures_to_js.mjs +24 -0
  38. package/test/common.js +49 -3
  39. package/test/go.carv2 +0 -0
  40. package/test/test-errors.js +52 -30
  41. package/test/test-indexer.js +24 -1
  42. package/test/test-reader.js +94 -1
  43. package/tsconfig.json +2 -1
  44. package/types/lib/coding.d.ts +14 -4
  45. package/types/lib/coding.d.ts.map +1 -1
  46. package/types/lib/decoder.d.ts +38 -2
  47. package/types/lib/decoder.d.ts.map +1 -1
  48. package/types/lib/header-validator.d.ts +2 -0
  49. package/types/lib/header-validator.d.ts.map +1 -0
  50. package/types/lib/reader-browser.d.ts +15 -7
  51. package/types/lib/reader-browser.d.ts.map +1 -1
  52. package/types/test/_fixtures_to_js.d.mts +3 -0
  53. package/types/test/_fixtures_to_js.d.mts.map +1 -0
  54. package/types/test/common.d.ts +13 -0
  55. package/types/test/common.d.ts.map +1 -1
  56. package/types/test/fixtures-expectations.d.ts +63 -0
  57. package/types/test/fixtures-expectations.d.ts.map +1 -0
  58. package/types/test/fixtures.d.ts +3 -0
  59. package/types/test/fixtures.d.ts.map +1 -0
@@ -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('0aa16776657273696f6e02');
30
- common.assert.strictEqual(multiformats.bytes.toHex(makeHeader({ version: 2 })), '0aa16776657273696f6e02');
31
- await common.assert.isRejected(reader.CarReader.fromBytes(buf2), Error, 'Invalid CAR version: 2');
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
- it('bad header', async () => {
34
- let buf2 = makeHeader({
35
- version: 1,
36
- roots: []
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
- await common.assert.isFulfilled(reader.CarReader.fromBytes(buf2));
39
- buf2 = makeHeader({ roots: [] });
40
- await common.assert.isRejected(reader.CarReader.fromBytes(buf2), Error, 'Invalid CAR version: undefined');
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
- await common.assert.isRejected(reader.CarReader.fromBytes(buf2), Error, 'Invalid CAR version: "1"');
46
- buf2 = makeHeader({ version: 1 });
47
- await common.assert.isRejected(reader.CarReader.fromBytes(buf2), Error, 'Invalid CAR header format');
48
- buf2 = makeHeader({
49
- version: 1,
50
- roots: {}
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
- await common.assert.isRejected(reader.CarReader.fromBytes(buf2), Error, 'Invalid CAR header format');
53
- buf2 = makeHeader({
54
- version: 1,
55
- roots: [],
56
- blip: true
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('0aa16776657273696f6e02');
30
- assert.strictEqual(bytes.toHex(makeHeader({ version: 2 })), '0aa16776657273696f6e02');
31
- await assert.isRejected(CarReader.fromBytes(buf2), Error, 'Invalid CAR version: 2');
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
- it('bad header', async () => {
34
- let buf2 = makeHeader({
35
- version: 1,
36
- roots: []
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
- await assert.isFulfilled(CarReader.fromBytes(buf2));
39
- buf2 = makeHeader({ roots: [] });
40
- await assert.isRejected(CarReader.fromBytes(buf2), Error, 'Invalid CAR version: undefined');
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
- await assert.isRejected(CarReader.fromBytes(buf2), Error, 'Invalid CAR version: "1"');
46
- buf2 = makeHeader({ version: 1 });
47
- await assert.isRejected(CarReader.fromBytes(buf2), Error, 'Invalid CAR header format');
48
- buf2 = makeHeader({
49
- version: 1,
50
- roots: {}
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
- await assert.isRejected(CarReader.fromBytes(buf2), Error, 'Invalid CAR header format');
53
- buf2 = makeHeader({
54
- version: 1,
55
- roots: [],
56
- blip: true
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
  });