@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.
- 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/package.json +1 -1
- 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 +7 -7
- 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/README.md
CHANGED
|
@@ -240,6 +240,12 @@ be directly fed to a
|
|
|
240
240
|
* [`async CarWriter.createAppender()`](#CarWriter__createAppender)
|
|
241
241
|
* [`async CarWriter.updateRootsInBytes(bytes, roots)`](#CarWriter__updateRootsInBytes)
|
|
242
242
|
* [`async CarWriter.updateRootsInFile(fd, roots)`](#CarWriter__updateRootsInFile)
|
|
243
|
+
* [`async decoder.readHeader(reader)`](#async__decoder__readHeader__reader__)
|
|
244
|
+
* [`async decoder.readBlockHead(reader)`](#async__decoder__readBlockHead__reader__)
|
|
245
|
+
* [`decoder.createDecoder(reader)`](#decoder__createDecoder__reader__)
|
|
246
|
+
* [`decoder.bytesReader(bytes)`](#decoder__bytesReader__bytes__)
|
|
247
|
+
* [`decoder.asyncIterableReader(asyncIterable)`](#decoder__asyncIterableReader__asyncIterable__)
|
|
248
|
+
* [`decoder.limitReader(reader, byteLimit)`](#decoder__limitReader__reader____byteLimit__)
|
|
243
249
|
|
|
244
250
|
<a name="CarReader"></a>
|
|
245
251
|
### `class CarReader`
|
|
@@ -247,7 +253,7 @@ be directly fed to a
|
|
|
247
253
|
Properties:
|
|
248
254
|
|
|
249
255
|
* `version` `(number)`: The version number of the CAR referenced by this
|
|
250
|
-
reader (should be `1`).
|
|
256
|
+
reader (should be `1` or `2`).
|
|
251
257
|
|
|
252
258
|
Provides blockstore-like access to a CAR.
|
|
253
259
|
|
|
@@ -748,7 +754,8 @@ upon successful modification.
|
|
|
748
754
|
|
|
749
755
|
Update the list of roots in the header of an existing CAR file. The first
|
|
750
756
|
argument must be a file descriptor for CAR file that is open in read and
|
|
751
|
-
write mode (not append).
|
|
757
|
+
write mode (not append), e.g. `fs.open` or `fs.promises.open` with `'r+'`
|
|
758
|
+
mode.
|
|
752
759
|
|
|
753
760
|
This operation is an _overwrite_, the total length of the CAR will not be
|
|
754
761
|
modified. A rejection will occur if the new header will not be the same
|
|
@@ -759,6 +766,71 @@ replaced encode as the same length as the new roots.
|
|
|
759
766
|
This function is **only available in Node.js** and not a browser
|
|
760
767
|
environment.
|
|
761
768
|
|
|
769
|
+
<a name="async__decoder__readHeader__reader__"></a>
|
|
770
|
+
### `async decoder.readHeader(reader)`
|
|
771
|
+
|
|
772
|
+
* `reader` `(BytesReader)`
|
|
773
|
+
* `strictVersion` `(number, optional)`
|
|
774
|
+
|
|
775
|
+
* Returns: `Promise<(CarHeader|CarV2Header)>`
|
|
776
|
+
|
|
777
|
+
Reads header data from a `BytesReader`. The header may either be in the form
|
|
778
|
+
of a `CarHeader` or `CarV2Header` depending on the CAR being read.
|
|
779
|
+
|
|
780
|
+
<a name="async__decoder__readBlockHead__reader__"></a>
|
|
781
|
+
### `async decoder.readBlockHead(reader)`
|
|
782
|
+
|
|
783
|
+
* `reader` `(BytesReader)`
|
|
784
|
+
|
|
785
|
+
* Returns: `Promise<BlockHeader>`
|
|
786
|
+
|
|
787
|
+
Reads the leading data of an individual block from CAR data from a
|
|
788
|
+
`BytesReader`. Returns a `BlockHeader` object which contains
|
|
789
|
+
`{ cid, length, blockLength }` which can be used to either index the block
|
|
790
|
+
or read the block binary data.
|
|
791
|
+
|
|
792
|
+
<a name="decoder__createDecoder__reader__"></a>
|
|
793
|
+
### `decoder.createDecoder(reader)`
|
|
794
|
+
|
|
795
|
+
* `reader` `(BytesReader)`
|
|
796
|
+
|
|
797
|
+
* Returns: `CarDecoder`
|
|
798
|
+
|
|
799
|
+
Creates a `CarDecoder` from a `BytesReader`. The `CarDecoder` is as async
|
|
800
|
+
interface that will consume the bytes from the `BytesReader` to yield a
|
|
801
|
+
`header()` and either `blocks()` or `blocksIndex()` data.
|
|
802
|
+
|
|
803
|
+
<a name="decoder__bytesReader__bytes__"></a>
|
|
804
|
+
### `decoder.bytesReader(bytes)`
|
|
805
|
+
|
|
806
|
+
* `bytes` `(Uint8Array)`
|
|
807
|
+
|
|
808
|
+
* Returns: `BytesReader`
|
|
809
|
+
|
|
810
|
+
Creates a `BytesReader` from a `Uint8Array`.
|
|
811
|
+
|
|
812
|
+
<a name="decoder__asyncIterableReader__asyncIterable__"></a>
|
|
813
|
+
### `decoder.asyncIterableReader(asyncIterable)`
|
|
814
|
+
|
|
815
|
+
* `asyncIterable` `(AsyncIterable<Uint8Array>)`
|
|
816
|
+
|
|
817
|
+
* Returns: `BytesReader`
|
|
818
|
+
|
|
819
|
+
Creates a `BytesReader` from an `AsyncIterable<Uint8Array>`, which allows for
|
|
820
|
+
consumption of CAR data from a streaming source.
|
|
821
|
+
|
|
822
|
+
<a name="decoder__limitReader__reader____byteLimit__"></a>
|
|
823
|
+
### `decoder.limitReader(reader, byteLimit)`
|
|
824
|
+
|
|
825
|
+
* `reader` `(BytesReader)`
|
|
826
|
+
* `byteLimit` `(number)`
|
|
827
|
+
|
|
828
|
+
* Returns: `BytesReader`
|
|
829
|
+
|
|
830
|
+
Wraps a `BytesReader` in a limiting `BytesReader` which limits maximum read
|
|
831
|
+
to `byteLimit` bytes. It _does not_ update `pos` of the original
|
|
832
|
+
`BytesReader`.
|
|
833
|
+
|
|
762
834
|
## License
|
|
763
835
|
|
|
764
836
|
Licensed under either of
|
|
@@ -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(readerBrowser.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(readerBrowser.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(readerBrowser.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(readerBrowser.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(readerBrowser.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(readerBrowser.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(readerBrowser.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(readerBrowser.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(readerBrowser.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(readerBrowser.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(readerBrowser.CarReader.fromBytes(buf2), Error, 'Invalid CAR version: 2 (expected 1)');
|
|
57
88
|
});
|
|
58
|
-
await common.assert.isRejected(readerBrowser.CarReader.fromBytes(buf2), Error, 'Invalid CAR header format');
|
|
59
|
-
buf2 = makeHeader([
|
|
60
|
-
1,
|
|
61
|
-
[]
|
|
62
|
-
]);
|
|
63
|
-
await common.assert.isRejected(readerBrowser.CarReader.fromBytes(buf2), Error, 'Invalid CAR header format');
|
|
64
|
-
buf2 = makeHeader(null);
|
|
65
|
-
await common.assert.isRejected(readerBrowser.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 readerBrowser = require('../lib/reader-browser.js');
|
|
4
4
|
var writerBrowser = require('../lib/writer-browser.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 = await readerBrowser.CarReader.fromBytes(common.goCarV2Bytes);
|
|
77
|
+
const roots = await reader.getRoots();
|
|
78
|
+
common.assert.strictEqual(roots.length, 1);
|
|
79
|
+
common.assert(common.goCarV2Roots[0].equals(roots[0]));
|
|
80
|
+
common.assert.strictEqual(reader.version, 2);
|
|
81
|
+
for (const {cid} of common.goCarV2Index) {
|
|
82
|
+
const block = await reader.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(readerBrowser.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 = await readerBrowser.CarReader.fromBytes(data);
|
|
270
|
+
let i = 0;
|
|
271
|
+
for await (const cid of reader.cids()) {
|
|
272
|
+
common.assert.strictEqual(cid.toString(), expectedCids[i++]);
|
|
273
|
+
}
|
|
274
|
+
common.assert.strictEqual(i, expectedCids.length);
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
});
|
|
195
278
|
});
|
package/cjs/lib/decoder.js
CHANGED
|
@@ -6,6 +6,7 @@ var varint = require('varint');
|
|
|
6
6
|
var cid = require('multiformats/cid');
|
|
7
7
|
var Digest = require('multiformats/hashes/digest');
|
|
8
8
|
var dagCbor = require('@ipld/dag-cbor');
|
|
9
|
+
var headerValidator = require('./header-validator.js');
|
|
9
10
|
|
|
10
11
|
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
11
12
|
|
|
@@ -35,13 +36,34 @@ const CIDV0_BYTES = {
|
|
|
35
36
|
LENGTH: 32,
|
|
36
37
|
DAG_PB: 112
|
|
37
38
|
};
|
|
39
|
+
const V2_HEADER_LENGTH = 16 + 8 + 8 + 8;
|
|
38
40
|
async function readVarint(reader) {
|
|
39
41
|
const bytes = await reader.upTo(8);
|
|
42
|
+
if (!bytes.length) {
|
|
43
|
+
throw new Error('Unexpected end of data');
|
|
44
|
+
}
|
|
40
45
|
const i = varint__default["default"].decode(bytes);
|
|
41
46
|
reader.seek(varint__default["default"].decode.bytes);
|
|
42
47
|
return i;
|
|
43
48
|
}
|
|
44
|
-
async function
|
|
49
|
+
async function readV2Header(reader) {
|
|
50
|
+
const bytes = await reader.exactly(V2_HEADER_LENGTH);
|
|
51
|
+
const dv = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
52
|
+
let offset = 0;
|
|
53
|
+
const header = {
|
|
54
|
+
version: 2,
|
|
55
|
+
characteristics: [
|
|
56
|
+
dv.getBigUint64(offset, true),
|
|
57
|
+
dv.getBigUint64(offset += 8, true)
|
|
58
|
+
],
|
|
59
|
+
dataOffset: Number(dv.getBigUint64(offset += 8, true)),
|
|
60
|
+
dataSize: Number(dv.getBigUint64(offset += 8, true)),
|
|
61
|
+
indexOffset: Number(dv.getBigUint64(offset += 8, true))
|
|
62
|
+
};
|
|
63
|
+
reader.seek(V2_HEADER_LENGTH);
|
|
64
|
+
return header;
|
|
65
|
+
}
|
|
66
|
+
async function readHeader(reader, strictVersion) {
|
|
45
67
|
const length = await readVarint(reader);
|
|
46
68
|
if (length === 0) {
|
|
47
69
|
throw new Error('Invalid CAR header (zero length)');
|
|
@@ -49,22 +71,23 @@ async function readHeader(reader) {
|
|
|
49
71
|
const header = await reader.exactly(length);
|
|
50
72
|
reader.seek(length);
|
|
51
73
|
const block = dagCbor.decode(header);
|
|
52
|
-
if (
|
|
74
|
+
if (!headerValidator.CarHeader(block)) {
|
|
53
75
|
throw new Error('Invalid CAR header format');
|
|
54
76
|
}
|
|
55
|
-
if (block.version !== 1) {
|
|
56
|
-
|
|
57
|
-
throw new Error(`Invalid CAR version: "${ block.version }"`);
|
|
58
|
-
}
|
|
59
|
-
throw new Error(`Invalid CAR version: ${ block.version }`);
|
|
77
|
+
if (block.version !== 1 && block.version !== 2 || strictVersion !== undefined && block.version !== strictVersion) {
|
|
78
|
+
throw new Error(`Invalid CAR version: ${ block.version }${ strictVersion !== undefined ? ` (expected ${ strictVersion })` : '' }`);
|
|
60
79
|
}
|
|
61
|
-
|
|
80
|
+
const hasRoots = Array.isArray(block.roots);
|
|
81
|
+
if (block.version === 1 && !hasRoots || block.version === 2 && hasRoots) {
|
|
62
82
|
throw new Error('Invalid CAR header format');
|
|
63
83
|
}
|
|
64
|
-
if (
|
|
65
|
-
|
|
84
|
+
if (block.version === 1) {
|
|
85
|
+
return block;
|
|
66
86
|
}
|
|
67
|
-
|
|
87
|
+
const v2Header = await readV2Header(reader);
|
|
88
|
+
reader.seek(v2Header.dataOffset - reader.pos);
|
|
89
|
+
const v1Header = await readHeader(reader, 1);
|
|
90
|
+
return Object.assign(v1Header, v2Header);
|
|
68
91
|
}
|
|
69
92
|
async function readMultihash(reader) {
|
|
70
93
|
const bytes = await reader.upTo(8);
|
|
@@ -102,7 +125,7 @@ async function readBlockHead(reader) {
|
|
|
102
125
|
}
|
|
103
126
|
length += reader.pos - start;
|
|
104
127
|
const cid = await readCid(reader);
|
|
105
|
-
const blockLength = length - (reader.pos - start);
|
|
128
|
+
const blockLength = length - Number(reader.pos - start);
|
|
106
129
|
return {
|
|
107
130
|
cid,
|
|
108
131
|
length,
|
|
@@ -132,7 +155,14 @@ async function readBlockIndex(reader) {
|
|
|
132
155
|
return index;
|
|
133
156
|
}
|
|
134
157
|
function createDecoder(reader) {
|
|
135
|
-
const headerPromise =
|
|
158
|
+
const headerPromise = (async () => {
|
|
159
|
+
const header = await readHeader(reader);
|
|
160
|
+
if (header.version === 2) {
|
|
161
|
+
const v1length = reader.pos - header.dataOffset;
|
|
162
|
+
reader = limitReader(reader, header.dataSize - v1length);
|
|
163
|
+
}
|
|
164
|
+
return header;
|
|
165
|
+
})();
|
|
136
166
|
return {
|
|
137
167
|
header: () => headerPromise,
|
|
138
168
|
async *blocks() {
|
|
@@ -235,10 +265,37 @@ function asyncIterableReader(asyncIterable) {
|
|
|
235
265
|
}
|
|
236
266
|
return chunkReader(readChunk);
|
|
237
267
|
}
|
|
268
|
+
function limitReader(reader, byteLimit) {
|
|
269
|
+
let bytesRead = 0;
|
|
270
|
+
return {
|
|
271
|
+
async upTo(length) {
|
|
272
|
+
let bytes = await reader.upTo(length);
|
|
273
|
+
if (bytes.length + bytesRead > byteLimit) {
|
|
274
|
+
bytes = bytes.subarray(0, byteLimit - bytesRead);
|
|
275
|
+
}
|
|
276
|
+
return bytes;
|
|
277
|
+
},
|
|
278
|
+
async exactly(length) {
|
|
279
|
+
const bytes = await reader.exactly(length);
|
|
280
|
+
if (bytes.length + bytesRead > byteLimit) {
|
|
281
|
+
throw new Error('Unexpected end of data');
|
|
282
|
+
}
|
|
283
|
+
return bytes;
|
|
284
|
+
},
|
|
285
|
+
seek(length) {
|
|
286
|
+
bytesRead += length;
|
|
287
|
+
reader.seek(length);
|
|
288
|
+
},
|
|
289
|
+
get pos() {
|
|
290
|
+
return reader.pos;
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
}
|
|
238
294
|
|
|
239
295
|
exports.asyncIterableReader = asyncIterableReader;
|
|
240
296
|
exports.bytesReader = bytesReader;
|
|
241
297
|
exports.chunkReader = chunkReader;
|
|
242
298
|
exports.createDecoder = createDecoder;
|
|
299
|
+
exports.limitReader = limitReader;
|
|
243
300
|
exports.readBlockHead = readBlockHead;
|
|
244
301
|
exports.readHeader = readHeader;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
const Kinds = {
|
|
6
|
+
Null: obj => obj === null,
|
|
7
|
+
Int: obj => Number.isInteger(obj),
|
|
8
|
+
Float: obj => typeof obj === 'number' && Number.isFinite(obj),
|
|
9
|
+
String: obj => typeof obj === 'string',
|
|
10
|
+
Bool: obj => typeof obj === 'boolean',
|
|
11
|
+
Bytes: obj => obj instanceof Uint8Array,
|
|
12
|
+
Link: obj => !Kinds.Null(obj) && typeof obj === 'object' && obj.asCID === obj,
|
|
13
|
+
List: obj => Array.isArray(obj),
|
|
14
|
+
Map: obj => !Kinds.Null(obj) && typeof obj === 'object' && obj.asCID !== obj && !Kinds.List(obj) && !Kinds.Bytes(obj)
|
|
15
|
+
};
|
|
16
|
+
const Types = {
|
|
17
|
+
Int: Kinds.Int,
|
|
18
|
+
'CarHeader > version': obj => Types.Int(obj),
|
|
19
|
+
'CarHeader > roots (anon) > valueType (anon)': Kinds.Link,
|
|
20
|
+
'CarHeader > roots (anon)': obj => Kinds.List(obj) && Array.prototype.every.call(obj, Types['CarHeader > roots (anon) > valueType (anon)']),
|
|
21
|
+
'CarHeader > roots': obj => Types['CarHeader > roots (anon)'](obj),
|
|
22
|
+
CarHeader: obj => {
|
|
23
|
+
const keys = obj && Object.keys(obj);
|
|
24
|
+
return Kinds.Map(obj) && ['version'].every(k => keys.includes(k)) && Object.entries(obj).every(([name, value]) => Types['CarHeader > ' + name] && Types['CarHeader > ' + name](value));
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
const CarHeader = Types.CarHeader;
|
|
28
|
+
|
|
29
|
+
exports.CarHeader = CarHeader;
|
|
@@ -5,17 +5,16 @@ Object.defineProperty(exports, '__esModule', { value: true });
|
|
|
5
5
|
var decoder = require('./decoder.js');
|
|
6
6
|
|
|
7
7
|
class CarReader {
|
|
8
|
-
constructor(
|
|
9
|
-
this.
|
|
10
|
-
this._roots = roots;
|
|
8
|
+
constructor(header, blocks) {
|
|
9
|
+
this._header = header;
|
|
11
10
|
this._blocks = blocks;
|
|
12
11
|
this._keys = blocks.map(b => b.cid.toString());
|
|
13
12
|
}
|
|
14
13
|
get version() {
|
|
15
|
-
return this.
|
|
14
|
+
return this._header.version;
|
|
16
15
|
}
|
|
17
16
|
async getRoots() {
|
|
18
|
-
return this.
|
|
17
|
+
return this._header.roots;
|
|
19
18
|
}
|
|
20
19
|
async has(key) {
|
|
21
20
|
return this._keys.indexOf(key.toString()) > -1;
|
|
@@ -49,14 +48,15 @@ class CarReader {
|
|
|
49
48
|
}
|
|
50
49
|
async function decodeReaderComplete(reader) {
|
|
51
50
|
const decoder$1 = decoder.createDecoder(reader);
|
|
52
|
-
const
|
|
51
|
+
const header = await decoder$1.header();
|
|
53
52
|
const blocks = [];
|
|
54
53
|
for await (const block of decoder$1.blocks()) {
|
|
55
54
|
blocks.push(block);
|
|
56
55
|
}
|
|
57
|
-
return new CarReader(
|
|
56
|
+
return new CarReader(header, blocks);
|
|
58
57
|
}
|
|
59
58
|
const __browser = true;
|
|
60
59
|
|
|
61
60
|
exports.CarReader = CarReader;
|
|
62
61
|
exports.__browser = __browser;
|
|
62
|
+
exports.decodeReaderComplete = decodeReaderComplete;
|
|
@@ -62,7 +62,7 @@ class CarWriter {
|
|
|
62
62
|
const reader = decoder.bytesReader(bytes);
|
|
63
63
|
await decoder.readHeader(reader);
|
|
64
64
|
const newHeader = encoder.createHeader(roots);
|
|
65
|
-
if (reader.pos !== newHeader.length) {
|
|
65
|
+
if (Number(reader.pos) !== newHeader.length) {
|
|
66
66
|
throw new Error(`updateRoots() can only overwrite a header of the same length (old header is ${ reader.pos } bytes, new header is ${ newHeader.length } bytes)`);
|
|
67
67
|
}
|
|
68
68
|
bytes.set(newHeader, 0);
|