@ipld/car 3.2.3 → 4.1.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 (77) hide show
  1. package/README.md +183 -2
  2. package/api.ts +22 -4
  3. package/buffer-writer +1 -0
  4. package/cjs/browser-test/common.js +78 -3
  5. package/cjs/browser-test/node-test-large.js +8 -8
  6. package/cjs/browser-test/test-buffer-writer.js +330 -0
  7. package/cjs/browser-test/test-errors.js +57 -34
  8. package/cjs/browser-test/test-indexer.js +12 -0
  9. package/cjs/browser-test/test-reader.js +83 -0
  10. package/cjs/lib/buffer-writer.js +161 -0
  11. package/cjs/lib/decoder.js +72 -15
  12. package/cjs/lib/encoder.js +2 -2
  13. package/cjs/lib/header-validator.js +29 -0
  14. package/cjs/lib/reader-browser.js +7 -7
  15. package/cjs/lib/writer-browser.js +1 -1
  16. package/cjs/node-test/common.js +78 -3
  17. package/cjs/node-test/node-test-large.js +8 -8
  18. package/cjs/node-test/test-buffer-writer.js +330 -0
  19. package/cjs/node-test/test-errors.js +57 -34
  20. package/cjs/node-test/test-indexer.js +12 -0
  21. package/cjs/node-test/test-reader.js +83 -0
  22. package/esm/browser-test/common.js +76 -1
  23. package/esm/browser-test/test-buffer-writer.js +311 -0
  24. package/esm/browser-test/test-errors.js +57 -33
  25. package/esm/browser-test/test-indexer.js +15 -0
  26. package/esm/browser-test/test-reader.js +90 -1
  27. package/esm/lib/buffer-writer.js +126 -0
  28. package/esm/lib/decoder.js +69 -13
  29. package/esm/lib/header-validator.js +23 -0
  30. package/esm/lib/reader-browser.js +7 -8
  31. package/esm/lib/writer-browser.js +1 -1
  32. package/esm/node-test/common.js +76 -1
  33. package/esm/node-test/test-buffer-writer.js +311 -0
  34. package/esm/node-test/test-errors.js +57 -33
  35. package/esm/node-test/test-indexer.js +15 -0
  36. package/esm/node-test/test-reader.js +90 -1
  37. package/examples/car-to-fixture.js +1 -4
  38. package/examples/dump-index.js +24 -0
  39. package/examples/test-examples.js +33 -0
  40. package/lib/buffer-writer.js +286 -0
  41. package/lib/coding.ts +17 -2
  42. package/lib/decoder.js +130 -14
  43. package/lib/header-validator.js +33 -0
  44. package/lib/header.ipldsch +6 -0
  45. package/lib/reader-browser.js +11 -11
  46. package/lib/writer-browser.js +1 -1
  47. package/package.json +17 -7
  48. package/test/_fixtures_to_js.mjs +24 -0
  49. package/test/common.js +49 -3
  50. package/test/go.carv2 +0 -0
  51. package/test/test-buffer-writer.js +256 -0
  52. package/test/test-errors.js +52 -30
  53. package/test/test-indexer.js +24 -1
  54. package/test/test-reader.js +94 -1
  55. package/tsconfig.json +3 -1
  56. package/types/api.d.ts +16 -0
  57. package/types/api.d.ts.map +1 -1
  58. package/types/lib/buffer-writer.d.ts +86 -0
  59. package/types/lib/buffer-writer.d.ts.map +1 -0
  60. package/types/lib/coding.d.ts +14 -4
  61. package/types/lib/coding.d.ts.map +1 -1
  62. package/types/lib/decoder.d.ts +38 -2
  63. package/types/lib/decoder.d.ts.map +1 -1
  64. package/types/lib/header-validator.d.ts +2 -0
  65. package/types/lib/header-validator.d.ts.map +1 -0
  66. package/types/lib/reader-browser.d.ts +15 -7
  67. package/types/lib/reader-browser.d.ts.map +1 -1
  68. package/types/test/_fixtures_to_js.d.mts +3 -0
  69. package/types/test/_fixtures_to_js.d.mts.map +1 -0
  70. package/types/test/common.d.ts +13 -0
  71. package/types/test/common.d.ts.map +1 -1
  72. package/types/test/fixtures-expectations.d.ts +63 -0
  73. package/types/test/fixtures-expectations.d.ts.map +1 -0
  74. package/types/test/fixtures.d.ts +3 -0
  75. package/types/test/fixtures.d.ts.map +1 -0
  76. package/types/test/test-buffer-writer.d.ts +2 -0
  77. package/types/test/test-buffer-writer.d.ts.map +1 -0
@@ -2,18 +2,40 @@ import varint from 'varint';
2
2
  import { CID } from 'multiformats/cid';
3
3
  import * as Digest from 'multiformats/hashes/digest';
4
4
  import { decode as decodeDagCbor } from '@ipld/dag-cbor';
5
+ import { CarHeader as headerValidator } from './header-validator.js';
5
6
  const CIDV0_BYTES = {
6
7
  SHA2_256: 18,
7
8
  LENGTH: 32,
8
9
  DAG_PB: 112
9
10
  };
11
+ const V2_HEADER_LENGTH = 16 + 8 + 8 + 8;
10
12
  async function readVarint(reader) {
11
13
  const bytes = await reader.upTo(8);
14
+ if (!bytes.length) {
15
+ throw new Error('Unexpected end of data');
16
+ }
12
17
  const i = varint.decode(bytes);
13
18
  reader.seek(varint.decode.bytes);
14
19
  return i;
15
20
  }
16
- export async function readHeader(reader) {
21
+ async function readV2Header(reader) {
22
+ const bytes = await reader.exactly(V2_HEADER_LENGTH);
23
+ const dv = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
24
+ let offset = 0;
25
+ const header = {
26
+ version: 2,
27
+ characteristics: [
28
+ dv.getBigUint64(offset, true),
29
+ dv.getBigUint64(offset += 8, true)
30
+ ],
31
+ dataOffset: Number(dv.getBigUint64(offset += 8, true)),
32
+ dataSize: Number(dv.getBigUint64(offset += 8, true)),
33
+ indexOffset: Number(dv.getBigUint64(offset += 8, true))
34
+ };
35
+ reader.seek(V2_HEADER_LENGTH);
36
+ return header;
37
+ }
38
+ export async function readHeader(reader, strictVersion) {
17
39
  const length = await readVarint(reader);
18
40
  if (length === 0) {
19
41
  throw new Error('Invalid CAR header (zero length)');
@@ -21,22 +43,23 @@ export async function readHeader(reader) {
21
43
  const header = await reader.exactly(length);
22
44
  reader.seek(length);
23
45
  const block = decodeDagCbor(header);
24
- if (block == null || Array.isArray(block) || typeof block !== 'object') {
46
+ if (!headerValidator(block)) {
25
47
  throw new Error('Invalid CAR header format');
26
48
  }
27
- if (block.version !== 1) {
28
- if (typeof block.version === 'string') {
29
- throw new Error(`Invalid CAR version: "${ block.version }"`);
30
- }
31
- throw new Error(`Invalid CAR version: ${ block.version }`);
49
+ if (block.version !== 1 && block.version !== 2 || strictVersion !== undefined && block.version !== strictVersion) {
50
+ throw new Error(`Invalid CAR version: ${ block.version }${ strictVersion !== undefined ? ` (expected ${ strictVersion })` : '' }`);
32
51
  }
33
- if (!Array.isArray(block.roots)) {
52
+ const hasRoots = Array.isArray(block.roots);
53
+ if (block.version === 1 && !hasRoots || block.version === 2 && hasRoots) {
34
54
  throw new Error('Invalid CAR header format');
35
55
  }
36
- if (Object.keys(block).filter(p => p !== 'roots' && p !== 'version').length) {
37
- throw new Error('Invalid CAR header format');
56
+ if (block.version === 1) {
57
+ return block;
38
58
  }
39
- return block;
59
+ const v2Header = await readV2Header(reader);
60
+ reader.seek(v2Header.dataOffset - reader.pos);
61
+ const v1Header = await readHeader(reader, 1);
62
+ return Object.assign(v1Header, v2Header);
40
63
  }
41
64
  async function readMultihash(reader) {
42
65
  const bytes = await reader.upTo(8);
@@ -74,7 +97,7 @@ export async function readBlockHead(reader) {
74
97
  }
75
98
  length += reader.pos - start;
76
99
  const cid = await readCid(reader);
77
- const blockLength = length - (reader.pos - start);
100
+ const blockLength = length - Number(reader.pos - start);
78
101
  return {
79
102
  cid,
80
103
  length,
@@ -104,7 +127,14 @@ async function readBlockIndex(reader) {
104
127
  return index;
105
128
  }
106
129
  export function createDecoder(reader) {
107
- const headerPromise = readHeader(reader);
130
+ const headerPromise = (async () => {
131
+ const header = await readHeader(reader);
132
+ if (header.version === 2) {
133
+ const v1length = reader.pos - header.dataOffset;
134
+ reader = limitReader(reader, header.dataSize - v1length);
135
+ }
136
+ return header;
137
+ })();
108
138
  return {
109
139
  header: () => headerPromise,
110
140
  async *blocks() {
@@ -206,4 +236,30 @@ export function asyncIterableReader(asyncIterable) {
206
236
  return next.value;
207
237
  }
208
238
  return chunkReader(readChunk);
239
+ }
240
+ export function limitReader(reader, byteLimit) {
241
+ let bytesRead = 0;
242
+ return {
243
+ async upTo(length) {
244
+ let bytes = await reader.upTo(length);
245
+ if (bytes.length + bytesRead > byteLimit) {
246
+ bytes = bytes.subarray(0, byteLimit - bytesRead);
247
+ }
248
+ return bytes;
249
+ },
250
+ async exactly(length) {
251
+ const bytes = await reader.exactly(length);
252
+ if (bytes.length + bytesRead > byteLimit) {
253
+ throw new Error('Unexpected end of data');
254
+ }
255
+ return bytes;
256
+ },
257
+ seek(length) {
258
+ bytesRead += length;
259
+ reader.seek(length);
260
+ },
261
+ get pos() {
262
+ return reader.pos;
263
+ }
264
+ };
209
265
  }
@@ -0,0 +1,23 @@
1
+ const Kinds = {
2
+ Null: obj => obj === null,
3
+ Int: obj => Number.isInteger(obj),
4
+ Float: obj => typeof obj === 'number' && Number.isFinite(obj),
5
+ String: obj => typeof obj === 'string',
6
+ Bool: obj => typeof obj === 'boolean',
7
+ Bytes: obj => obj instanceof Uint8Array,
8
+ Link: obj => !Kinds.Null(obj) && typeof obj === 'object' && obj.asCID === obj,
9
+ List: obj => Array.isArray(obj),
10
+ Map: obj => !Kinds.Null(obj) && typeof obj === 'object' && obj.asCID !== obj && !Kinds.List(obj) && !Kinds.Bytes(obj)
11
+ };
12
+ const Types = {
13
+ Int: Kinds.Int,
14
+ 'CarHeader > version': obj => Types.Int(obj),
15
+ 'CarHeader > roots (anon) > valueType (anon)': Kinds.Link,
16
+ 'CarHeader > roots (anon)': obj => Kinds.List(obj) && Array.prototype.every.call(obj, Types['CarHeader > roots (anon) > valueType (anon)']),
17
+ 'CarHeader > roots': obj => Types['CarHeader > roots (anon)'](obj),
18
+ CarHeader: obj => {
19
+ const keys = obj && Object.keys(obj);
20
+ return Kinds.Map(obj) && ['version'].every(k => keys.includes(k)) && Object.entries(obj).every(([name, value]) => Types['CarHeader > ' + name] && Types['CarHeader > ' + name](value));
21
+ }
22
+ };
23
+ export const CarHeader = Types.CarHeader;
@@ -4,17 +4,16 @@ import {
4
4
  createDecoder
5
5
  } from './decoder.js';
6
6
  export class CarReader {
7
- constructor(version, roots, blocks) {
8
- this._version = version;
9
- this._roots = roots;
7
+ constructor(header, blocks) {
8
+ this._header = header;
10
9
  this._blocks = blocks;
11
10
  this._keys = blocks.map(b => b.cid.toString());
12
11
  }
13
12
  get version() {
14
- return this._version;
13
+ return this._header.version;
15
14
  }
16
15
  async getRoots() {
17
- return this._roots;
16
+ return this._header.roots;
18
17
  }
19
18
  async has(key) {
20
19
  return this._keys.indexOf(key.toString()) > -1;
@@ -46,13 +45,13 @@ export class CarReader {
46
45
  return decodeReaderComplete(asyncIterableReader(asyncIterable));
47
46
  }
48
47
  }
49
- async function decodeReaderComplete(reader) {
48
+ export async function decodeReaderComplete(reader) {
50
49
  const decoder = createDecoder(reader);
51
- const {version, roots} = await decoder.header();
50
+ const header = await decoder.header();
52
51
  const blocks = [];
53
52
  for await (const block of decoder.blocks()) {
54
53
  blocks.push(block);
55
54
  }
56
- return new CarReader(version, roots, blocks);
55
+ return new CarReader(header, blocks);
57
56
  }
58
57
  export const __browser = true;
@@ -63,7 +63,7 @@ export class CarWriter {
63
63
  const reader = bytesReader(bytes);
64
64
  await readHeader(reader);
65
65
  const newHeader = createHeader(roots);
66
- if (reader.pos !== newHeader.length) {
66
+ if (Number(reader.pos) !== newHeader.length) {
67
67
  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)`);
68
68
  }
69
69
  bytes.set(newHeader, 0);
@@ -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
  };
@@ -0,0 +1,311 @@
1
+ import * as CarBufferWriter from '../lib/buffer-writer.js';
2
+ import { CarReader } from '../lib/reader.js';
3
+ import { createHeader } from '../lib/encoder.js';
4
+ import { assert } from './common.js';
5
+ import {
6
+ CID,
7
+ varint
8
+ } from 'multiformats';
9
+ import * as CBOR from '@ipld/dag-cbor';
10
+ import {
11
+ sha256,
12
+ sha512
13
+ } from 'multiformats/hashes/sha2';
14
+ import { identity } from 'multiformats/hashes/identity';
15
+ import * as Raw from 'multiformats/codecs/raw';
16
+ import * as Block from 'multiformats/block';
17
+ describe('CarBufferWriter', () => {
18
+ const cid = CID.parse('bafkreifuosuzujyf4i6psbneqtwg2fhplc2wxptc5euspa2gn3bwhnihfu');
19
+ describe('calculateHeaderLength', async () => {
20
+ for (const count of [
21
+ 0,
22
+ 1,
23
+ 10,
24
+ 18,
25
+ 24,
26
+ 48,
27
+ 124,
28
+ 255,
29
+ 258,
30
+ 65536 - 1,
31
+ 65536
32
+ ]) {
33
+ it(`calculateHeaderLength(new Array(${ count }).fill(36))`, () => {
34
+ const roots = new Array(count).fill(cid);
35
+ const sizes = new Array(count).fill(cid.bytes.byteLength);
36
+ assert.deepEqual(CarBufferWriter.calculateHeaderLength(sizes), createHeader(roots).byteLength);
37
+ });
38
+ it(`calculateHeaderLength(new Array(${ count }).fill(36))`, () => {
39
+ const roots = new Array(count).fill(cid);
40
+ const rootLengths = roots.map(c => c.bytes.byteLength);
41
+ assert.deepEqual(CarBufferWriter.calculateHeaderLength(rootLengths), createHeader(roots).byteLength);
42
+ });
43
+ }
44
+ it('estimate on large CIDs', () => {
45
+ const largeCID = CID.parse(`bafkqbbac${ 'a'.repeat(416) }`);
46
+ assert.equal(CarBufferWriter.calculateHeaderLength([
47
+ cid.bytes.byteLength,
48
+ largeCID.bytes.byteLength
49
+ ]), createHeader([
50
+ cid,
51
+ largeCID
52
+ ]).byteLength);
53
+ });
54
+ it('estimate on large CIDs 2', () => {
55
+ const largeCID = CID.createV1(Raw.code, identity.digest(new Uint8Array(512).fill(1)));
56
+ assert.equal(CarBufferWriter.calculateHeaderLength([
57
+ cid.bytes.byteLength,
58
+ largeCID.bytes.byteLength
59
+ ]), createHeader([
60
+ cid,
61
+ largeCID
62
+ ]).byteLength);
63
+ });
64
+ });
65
+ describe('writer', () => {
66
+ it('estimate header and write blocks', async () => {
67
+ const headerSize = CarBufferWriter.estimateHeaderLength(1);
68
+ const dataSize = 256;
69
+ const buffer = new ArrayBuffer(headerSize + dataSize);
70
+ const writer = CarBufferWriter.createWriter(buffer, { headerSize });
71
+ const b1 = await Block.encode({
72
+ value: { hello: 'world' },
73
+ codec: CBOR,
74
+ hasher: sha256
75
+ });
76
+ writer.write(b1);
77
+ const b2 = await Block.encode({
78
+ value: { bye: 'world' },
79
+ codec: CBOR,
80
+ hasher: sha256
81
+ });
82
+ writer.write(b2);
83
+ writer.addRoot(b1.cid);
84
+ const bytes = writer.close();
85
+ const reader = await CarReader.fromBytes(bytes);
86
+ assert.deepEqual(await reader.getRoots(), [b1.cid]);
87
+ assert.deepEqual(reader._blocks, [
88
+ {
89
+ cid: b1.cid,
90
+ bytes: b1.bytes
91
+ },
92
+ {
93
+ cid: b2.cid,
94
+ bytes: b2.bytes
95
+ }
96
+ ]);
97
+ });
98
+ it('overestimate header', async () => {
99
+ const headerSize = CarBufferWriter.estimateHeaderLength(2);
100
+ const dataSize = 256;
101
+ const buffer = new ArrayBuffer(headerSize + dataSize);
102
+ const writer = CarBufferWriter.createWriter(buffer, { headerSize });
103
+ const b1 = await Block.encode({
104
+ value: { hello: 'world' },
105
+ codec: CBOR,
106
+ hasher: sha256
107
+ });
108
+ writer.write(b1);
109
+ const b2 = await Block.encode({
110
+ value: { bye: 'world' },
111
+ codec: CBOR,
112
+ hasher: sha256
113
+ });
114
+ writer.write(b2);
115
+ writer.addRoot(b1.cid);
116
+ assert.throws(() => writer.close(), /Header size was overestimate/);
117
+ const bytes = writer.close({ resize: true });
118
+ const reader = await CarReader.fromBytes(bytes);
119
+ assert.deepEqual(await reader.getRoots(), [b1.cid]);
120
+ assert.deepEqual(reader._blocks, [
121
+ {
122
+ cid: b1.cid,
123
+ bytes: b1.bytes
124
+ },
125
+ {
126
+ cid: b2.cid,
127
+ bytes: b2.bytes
128
+ }
129
+ ]);
130
+ });
131
+ it('underestimate header', async () => {
132
+ const headerSize = CarBufferWriter.estimateHeaderLength(2);
133
+ const dataSize = 300;
134
+ const buffer = new ArrayBuffer(headerSize + dataSize);
135
+ const writer = CarBufferWriter.createWriter(buffer, { headerSize });
136
+ const b1 = await Block.encode({
137
+ value: { hello: 'world' },
138
+ codec: CBOR,
139
+ hasher: sha256
140
+ });
141
+ writer.write(b1);
142
+ writer.addRoot(b1.cid);
143
+ const b2 = await Block.encode({
144
+ value: { bye: 'world' },
145
+ codec: CBOR,
146
+ hasher: sha512
147
+ });
148
+ writer.write(b2);
149
+ assert.throws(() => writer.addRoot(b2.cid), /has no capacity/);
150
+ writer.addRoot(b2.cid, { resize: true });
151
+ const bytes = writer.close();
152
+ const reader = await CarReader.fromBytes(bytes);
153
+ assert.deepEqual(await reader.getRoots(), [
154
+ b1.cid,
155
+ b2.cid
156
+ ]);
157
+ assert.deepEqual(reader._blocks, [
158
+ {
159
+ cid: b1.cid,
160
+ bytes: b1.bytes
161
+ },
162
+ {
163
+ cid: b2.cid,
164
+ bytes: b2.bytes
165
+ }
166
+ ]);
167
+ });
168
+ });
169
+ it('has no space for the root', async () => {
170
+ const headerSize = CarBufferWriter.estimateHeaderLength(1);
171
+ const dataSize = 100;
172
+ const buffer = new ArrayBuffer(headerSize + dataSize);
173
+ const writer = CarBufferWriter.createWriter(buffer, { headerSize });
174
+ const b1 = await Block.encode({
175
+ value: { hello: 'world' },
176
+ codec: CBOR,
177
+ hasher: sha256
178
+ });
179
+ writer.write(b1);
180
+ writer.addRoot(b1.cid);
181
+ const b2 = await Block.encode({
182
+ value: { bye: 'world' },
183
+ codec: CBOR,
184
+ hasher: sha256
185
+ });
186
+ writer.write(b2);
187
+ assert.throws(() => writer.addRoot(b2.cid), /Buffer has no capacity for a new root/);
188
+ assert.throws(() => writer.addRoot(b2.cid, { resize: true }), /Buffer has no capacity for a new root/);
189
+ const bytes = writer.close();
190
+ const reader = await CarReader.fromBytes(bytes);
191
+ assert.deepEqual(await reader.getRoots(), [b1.cid]);
192
+ assert.deepEqual(reader._blocks, [
193
+ {
194
+ cid: b1.cid,
195
+ bytes: b1.bytes
196
+ },
197
+ {
198
+ cid: b2.cid,
199
+ bytes: b2.bytes
200
+ }
201
+ ]);
202
+ });
203
+ it('has no space for the block', async () => {
204
+ const headerSize = CarBufferWriter.estimateHeaderLength(1);
205
+ const dataSize = 58;
206
+ const buffer = new ArrayBuffer(headerSize + dataSize);
207
+ const writer = CarBufferWriter.createWriter(buffer, { headerSize });
208
+ const b1 = await Block.encode({
209
+ value: { hello: 'world' },
210
+ codec: CBOR,
211
+ hasher: sha256
212
+ });
213
+ writer.write(b1);
214
+ writer.addRoot(b1.cid);
215
+ const b2 = await Block.encode({
216
+ value: { bye: 'world' },
217
+ codec: CBOR,
218
+ hasher: sha256
219
+ });
220
+ assert.throws(() => writer.write(b2), /Buffer has no capacity for this block/);
221
+ const bytes = writer.close();
222
+ const reader = await CarReader.fromBytes(bytes);
223
+ assert.deepEqual(await reader.getRoots(), [b1.cid]);
224
+ assert.deepEqual(reader._blocks, [{
225
+ cid: b1.cid,
226
+ bytes: b1.bytes
227
+ }]);
228
+ });
229
+ it('provide roots', async () => {
230
+ const b1 = await Block.encode({
231
+ value: { hello: 'world' },
232
+ codec: CBOR,
233
+ hasher: sha256
234
+ });
235
+ const b2 = await Block.encode({
236
+ value: { bye: 'world' },
237
+ codec: CBOR,
238
+ hasher: sha512
239
+ });
240
+ const buffer = new ArrayBuffer(300);
241
+ const writer = CarBufferWriter.createWriter(buffer, {
242
+ roots: [
243
+ b1.cid,
244
+ b2.cid
245
+ ]
246
+ });
247
+ writer.write(b1);
248
+ writer.write(b2);
249
+ const bytes = writer.close();
250
+ const reader = await CarReader.fromBytes(bytes);
251
+ assert.deepEqual(await reader.getRoots(), [
252
+ b1.cid,
253
+ b2.cid
254
+ ]);
255
+ assert.deepEqual(reader._blocks, [
256
+ {
257
+ cid: b1.cid,
258
+ bytes: b1.bytes
259
+ },
260
+ {
261
+ cid: b2.cid,
262
+ bytes: b2.bytes
263
+ }
264
+ ]);
265
+ });
266
+ it('provide large CID root', async () => {
267
+ const bytes = new Uint8Array(512).fill(1);
268
+ const b1 = await Block.encode({
269
+ value: { hello: 'world' },
270
+ codec: CBOR,
271
+ hasher: sha256
272
+ });
273
+ const b2 = {
274
+ cid: CID.createV1(Raw.code, identity.digest(bytes)),
275
+ bytes
276
+ };
277
+ const headerSize = CBOR.encode({
278
+ version: 1,
279
+ roots: [
280
+ b1.cid,
281
+ b2.cid
282
+ ]
283
+ }).byteLength;
284
+ const bodySize = CarBufferWriter.blockLength(b1) + CarBufferWriter.blockLength(b2);
285
+ const varintSize = varint.encodingLength(headerSize);
286
+ const writer = CarBufferWriter.createWriter(new ArrayBuffer(varintSize + headerSize + bodySize), {
287
+ roots: [
288
+ b1.cid,
289
+ b2.cid
290
+ ]
291
+ });
292
+ writer.write(b1);
293
+ writer.write(b2);
294
+ const car = writer.close();
295
+ const reader = await CarReader.fromBytes(car);
296
+ assert.deepEqual(await reader.getRoots(), [
297
+ b1.cid,
298
+ b2.cid
299
+ ]);
300
+ assert.deepEqual(reader._blocks, [
301
+ {
302
+ cid: b1.cid,
303
+ bytes: b1.bytes
304
+ },
305
+ {
306
+ cid: b2.cid,
307
+ bytes: b2.bytes
308
+ }
309
+ ]);
310
+ });
311
+ });