@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
@@ -0,0 +1,256 @@
1
+ /* eslint-env mocha */
2
+
3
+ import * as CarBufferWriter from '@ipld/car/buffer-writer'
4
+ import { CarReader } from '@ipld/car/reader'
5
+ import { createHeader } from '../lib/encoder.js'
6
+ import { assert } from './common.js'
7
+ import { CID, varint } from 'multiformats'
8
+ import * as CBOR from '@ipld/dag-cbor'
9
+ import { sha256, sha512 } from 'multiformats/hashes/sha2'
10
+ import { identity } from 'multiformats/hashes/identity'
11
+ import * as Raw from 'multiformats/codecs/raw'
12
+ import * as Block from 'multiformats/block'
13
+
14
+ describe('CarBufferWriter', () => {
15
+ const cid = CID.parse('bafkreifuosuzujyf4i6psbneqtwg2fhplc2wxptc5euspa2gn3bwhnihfu')
16
+ describe('calculateHeaderLength', async () => {
17
+ for (const count of [0, 1, 10, 18, 24, 48, 124, 255, 258, 65536 - 1, 65536]) {
18
+ it(`calculateHeaderLength(new Array(${count}).fill(36))`, () => {
19
+ const roots = new Array(count).fill(cid)
20
+ const sizes = new Array(count).fill(cid.bytes.byteLength)
21
+ assert.deepEqual(
22
+ CarBufferWriter.calculateHeaderLength(sizes),
23
+ createHeader(roots).byteLength
24
+ )
25
+ })
26
+ it(`calculateHeaderLength(new Array(${count}).fill(36))`, () => {
27
+ const roots = new Array(count).fill(cid)
28
+ const rootLengths = roots.map((c) => c.bytes.byteLength)
29
+ assert.deepEqual(CarBufferWriter.calculateHeaderLength(rootLengths), createHeader(roots).byteLength)
30
+ })
31
+ }
32
+ it('estimate on large CIDs', () => {
33
+ const largeCID = CID.parse(`bafkqbbac${'a'.repeat(416)}`)
34
+ assert.equal(
35
+ CarBufferWriter.calculateHeaderLength([
36
+ cid.bytes.byteLength,
37
+ largeCID.bytes.byteLength
38
+ ]),
39
+ createHeader([
40
+ cid,
41
+ largeCID
42
+ ]).byteLength
43
+ )
44
+ })
45
+
46
+ it('estimate on large CIDs 2', () => {
47
+ const largeCID = CID.createV1(Raw.code, identity.digest(new Uint8Array(512).fill(1)))
48
+ assert.equal(
49
+ CarBufferWriter.calculateHeaderLength([
50
+ cid.bytes.byteLength,
51
+ largeCID.bytes.byteLength
52
+ ]),
53
+ createHeader([cid, largeCID]).byteLength
54
+ )
55
+ })
56
+ })
57
+
58
+ describe('writer', () => {
59
+ it('estimate header and write blocks', async () => {
60
+ const headerSize = CarBufferWriter.estimateHeaderLength(1)
61
+ const dataSize = 256
62
+ const buffer = new ArrayBuffer(headerSize + dataSize)
63
+ const writer = CarBufferWriter.createWriter(buffer, { headerSize })
64
+ const b1 = await Block.encode({
65
+ value: { hello: 'world' },
66
+ codec: CBOR,
67
+ hasher: sha256
68
+ })
69
+
70
+ writer.write(b1)
71
+
72
+ const b2 = await Block.encode({
73
+ value: { bye: 'world' },
74
+ codec: CBOR,
75
+ hasher: sha256
76
+ })
77
+ writer.write(b2)
78
+
79
+ writer.addRoot(b1.cid)
80
+ const bytes = writer.close()
81
+
82
+ const reader = await CarReader.fromBytes(bytes)
83
+ assert.deepEqual(await reader.getRoots(), [b1.cid])
84
+ assert.deepEqual(reader._blocks, [{ cid: b1.cid, bytes: b1.bytes }, { cid: b2.cid, bytes: b2.bytes }])
85
+ })
86
+
87
+ it('overestimate header', async () => {
88
+ const headerSize = CarBufferWriter.estimateHeaderLength(2)
89
+ const dataSize = 256
90
+ const buffer = new ArrayBuffer(headerSize + dataSize)
91
+ const writer = CarBufferWriter.createWriter(buffer, { headerSize })
92
+ const b1 = await Block.encode({
93
+ value: { hello: 'world' },
94
+ codec: CBOR,
95
+ hasher: sha256
96
+ })
97
+
98
+ writer.write(b1)
99
+
100
+ const b2 = await Block.encode({
101
+ value: { bye: 'world' },
102
+ codec: CBOR,
103
+ hasher: sha256
104
+ })
105
+ writer.write(b2)
106
+
107
+ writer.addRoot(b1.cid)
108
+ assert.throws(() => writer.close(), /Header size was overestimate/)
109
+ const bytes = writer.close({ resize: true })
110
+
111
+ const reader = await CarReader.fromBytes(bytes)
112
+ assert.deepEqual(await reader.getRoots(), [b1.cid])
113
+ assert.deepEqual(reader._blocks, [{ cid: b1.cid, bytes: b1.bytes }, { cid: b2.cid, bytes: b2.bytes }])
114
+ })
115
+
116
+ it('underestimate header', async () => {
117
+ const headerSize = CarBufferWriter.estimateHeaderLength(2)
118
+ const dataSize = 300
119
+ const buffer = new ArrayBuffer(headerSize + dataSize)
120
+ const writer = CarBufferWriter.createWriter(buffer, { headerSize })
121
+ const b1 = await Block.encode({
122
+ value: { hello: 'world' },
123
+ codec: CBOR,
124
+ hasher: sha256
125
+ })
126
+
127
+ writer.write(b1)
128
+ writer.addRoot(b1.cid)
129
+
130
+ const b2 = await Block.encode({
131
+ value: { bye: 'world' },
132
+ codec: CBOR,
133
+ hasher: sha512
134
+ })
135
+ writer.write(b2)
136
+ assert.throws(() => writer.addRoot(b2.cid), /has no capacity/)
137
+ writer.addRoot(b2.cid, { resize: true })
138
+
139
+ const bytes = writer.close()
140
+
141
+ const reader = await CarReader.fromBytes(bytes)
142
+ assert.deepEqual(await reader.getRoots(), [b1.cid, b2.cid])
143
+ assert.deepEqual(reader._blocks, [{ cid: b1.cid, bytes: b1.bytes }, { cid: b2.cid, bytes: b2.bytes }])
144
+ })
145
+ })
146
+
147
+ it('has no space for the root', async () => {
148
+ const headerSize = CarBufferWriter.estimateHeaderLength(1)
149
+ const dataSize = 100
150
+ const buffer = new ArrayBuffer(headerSize + dataSize)
151
+ const writer = CarBufferWriter.createWriter(buffer, { headerSize })
152
+ const b1 = await Block.encode({
153
+ value: { hello: 'world' },
154
+ codec: CBOR,
155
+ hasher: sha256
156
+ })
157
+
158
+ writer.write(b1)
159
+ writer.addRoot(b1.cid)
160
+
161
+ const b2 = await Block.encode({
162
+ value: { bye: 'world' },
163
+ codec: CBOR,
164
+ hasher: sha256
165
+ })
166
+ writer.write(b2)
167
+ assert.throws(() => writer.addRoot(b2.cid), /Buffer has no capacity for a new root/)
168
+ assert.throws(() => writer.addRoot(b2.cid, { resize: true }), /Buffer has no capacity for a new root/)
169
+
170
+ const bytes = writer.close()
171
+
172
+ const reader = await CarReader.fromBytes(bytes)
173
+ assert.deepEqual(await reader.getRoots(), [b1.cid])
174
+ assert.deepEqual(reader._blocks, [{ cid: b1.cid, bytes: b1.bytes }, { cid: b2.cid, bytes: b2.bytes }])
175
+ })
176
+
177
+ it('has no space for the block', async () => {
178
+ const headerSize = CarBufferWriter.estimateHeaderLength(1)
179
+ const dataSize = 58
180
+ const buffer = new ArrayBuffer(headerSize + dataSize)
181
+ const writer = CarBufferWriter.createWriter(buffer, { headerSize })
182
+ const b1 = await Block.encode({
183
+ value: { hello: 'world' },
184
+ codec: CBOR,
185
+ hasher: sha256
186
+ })
187
+
188
+ writer.write(b1)
189
+ writer.addRoot(b1.cid)
190
+
191
+ const b2 = await Block.encode({
192
+ value: { bye: 'world' },
193
+ codec: CBOR,
194
+ hasher: sha256
195
+ })
196
+ assert.throws(() => writer.write(b2), /Buffer has no capacity for this block/)
197
+
198
+ const bytes = writer.close()
199
+
200
+ const reader = await CarReader.fromBytes(bytes)
201
+ assert.deepEqual(await reader.getRoots(), [b1.cid])
202
+ assert.deepEqual(reader._blocks, [{ cid: b1.cid, bytes: b1.bytes }])
203
+ })
204
+
205
+ it('provide roots', async () => {
206
+ const b1 = await Block.encode({
207
+ value: { hello: 'world' },
208
+ codec: CBOR,
209
+ hasher: sha256
210
+ })
211
+ const b2 = await Block.encode({
212
+ value: { bye: 'world' },
213
+ codec: CBOR,
214
+ hasher: sha512
215
+ })
216
+
217
+ const buffer = new ArrayBuffer(300)
218
+ const writer = CarBufferWriter.createWriter(buffer, { roots: [b1.cid, b2.cid] })
219
+
220
+ writer.write(b1)
221
+ writer.write(b2)
222
+
223
+ const bytes = writer.close()
224
+
225
+ const reader = await CarReader.fromBytes(bytes)
226
+ assert.deepEqual(await reader.getRoots(), [b1.cid, b2.cid])
227
+ assert.deepEqual(reader._blocks, [{ cid: b1.cid, bytes: b1.bytes }, { cid: b2.cid, bytes: b2.bytes }])
228
+ })
229
+
230
+ it('provide large CID root', async () => {
231
+ const bytes = new Uint8Array(512).fill(1)
232
+ const b1 = await Block.encode({
233
+ value: { hello: 'world' },
234
+ codec: CBOR,
235
+ hasher: sha256
236
+ })
237
+
238
+ const b2 = {
239
+ cid: CID.createV1(Raw.code, identity.digest(bytes)),
240
+ bytes
241
+ }
242
+
243
+ const headerSize = CBOR.encode({ version: 1, roots: [b1.cid, b2.cid] }).byteLength
244
+ const bodySize = CarBufferWriter.blockLength(b1) + CarBufferWriter.blockLength(b2)
245
+ const varintSize = varint.encodingLength(headerSize)
246
+
247
+ const writer = CarBufferWriter.createWriter(new ArrayBuffer(varintSize + headerSize + bodySize), { roots: [b1.cid, b2.cid] })
248
+
249
+ writer.write(b1)
250
+ writer.write(b2)
251
+ const car = writer.close()
252
+ const reader = await CarReader.fromBytes(car)
253
+ assert.deepEqual(await reader.getRoots(), [b1.cid, b2.cid])
254
+ assert.deepEqual(reader._blocks, [{ cid: b1.cid, bytes: b1.bytes }, { cid: b2.cid, bytes: b2.bytes }])
255
+ })
256
+ })
@@ -3,7 +3,7 @@ import { bytes } from 'multiformats'
3
3
  import { encode as cbEncode } from '@ipld/dag-cbor'
4
4
  import { encode as vEncode } from 'varint'
5
5
  import { CarReader } from '@ipld/car/reader'
6
- import { carBytes, assert } from './common.js'
6
+ import { carBytes, assert, goCarV2Bytes } from './common.js'
7
7
 
8
8
  /**
9
9
  * @param {any} block
@@ -34,43 +34,65 @@ describe('Misc errors', () => {
34
34
 
35
35
  it('bad version', async () => {
36
36
  // quick sanity check that makeHeader() works properly!
37
- const buf2 = bytes.fromHex('0aa16776657273696f6e02')
38
- // {version:2} - fixed string, likely to be used by CARv2 to escape header parsing rules
39
- assert.strictEqual(bytes.toHex(makeHeader({ version: 2 })), '0aa16776657273696f6e02')
40
- await assert.isRejected(CarReader.fromBytes(buf2), Error, 'Invalid CAR version: 2')
37
+ const buf2 = bytes.fromHex('0aa16776657273696f6e03')
38
+ assert.strictEqual(bytes.toHex(makeHeader({ version: 3 })), '0aa16776657273696f6e03')
39
+ // {version:3}
40
+ await assert.isRejected(CarReader.fromBytes(buf2), Error, 'Invalid CAR version: 3')
41
41
  })
42
42
 
43
- it('bad header', async () => {
44
- // sanity check, this should be fine
45
- let buf2 = makeHeader({ version: 1, roots: [] })
46
- await assert.isFulfilled(CarReader.fromBytes(buf2))
43
+ describe('bad header', async () => {
44
+ it('sanity check', async () => {
45
+ // sanity check, this should be fine
46
+ const buf2 = makeHeader({ version: 1, roots: [] })
47
+ await assert.isFulfilled(CarReader.fromBytes(buf2))
48
+ })
47
49
 
48
- // no 'version' array
49
- buf2 = makeHeader({ roots: [] })
50
- await assert.isRejected(CarReader.fromBytes(buf2), Error, 'Invalid CAR version: undefined')
50
+ it('no \'version\' array', async () => {
51
+ const buf2 = makeHeader({ roots: [] })
52
+ await assert.isRejected(CarReader.fromBytes(buf2), Error, 'Invalid CAR header format')
53
+ })
51
54
 
52
- // bad 'version' type
53
- buf2 = makeHeader({ version: '1', roots: [] })
54
- await assert.isRejected(CarReader.fromBytes(buf2), Error, 'Invalid CAR version: "1"')
55
+ it('bad \'version\' type', async () => {
56
+ const buf2 = makeHeader({ version: '1', roots: [] })
57
+ await assert.isRejected(CarReader.fromBytes(buf2), Error, 'Invalid CAR header format')
58
+ })
59
+
60
+ it('no \'roots\' array', async () => {
61
+ const buf2 = makeHeader({ version: 1 })
62
+ await assert.isRejected(CarReader.fromBytes(buf2), Error, 'Invalid CAR header format')
63
+ })
55
64
 
56
- // no 'roots' array
57
- buf2 = makeHeader({ version: 1 })
58
- await assert.isRejected(CarReader.fromBytes(buf2), Error, 'Invalid CAR header format')
65
+ it('bad \'roots\' type', async () => {
66
+ const buf2 = makeHeader({ version: 1, roots: {} })
67
+ await assert.isRejected(CarReader.fromBytes(buf2), Error, 'Invalid CAR header format')
68
+ })
59
69
 
60
- // bad 'roots' type
61
- buf2 = makeHeader({ version: 1, roots: {} })
62
- await assert.isRejected(CarReader.fromBytes(buf2), Error, 'Invalid CAR header format')
70
+ it('extraneous properties', async () => {
71
+ const buf2 = makeHeader({ version: 1, roots: [], blip: true })
72
+ await assert.isRejected(CarReader.fromBytes(buf2), Error, 'Invalid CAR header format')
73
+ })
63
74
 
64
- // extraneous properties
65
- buf2 = makeHeader({ version: 1, roots: [], blip: true })
66
- await assert.isRejected(CarReader.fromBytes(buf2), Error, 'Invalid CAR header format')
75
+ it('not an object', async () => {
76
+ const buf2 = makeHeader([1, []])
77
+ await assert.isRejected(CarReader.fromBytes(buf2), Error, 'Invalid CAR header format')
78
+ })
67
79
 
68
- // not an object
69
- buf2 = makeHeader([1, []])
70
- await assert.isRejected(CarReader.fromBytes(buf2), Error, 'Invalid CAR header format')
80
+ it('not an object', async () => {
81
+ const buf2 = makeHeader(null)
82
+ await assert.isRejected(CarReader.fromBytes(buf2), Error, 'Invalid CAR header format')
83
+ })
71
84
 
72
- // not an object
73
- buf2 = makeHeader(null)
74
- await assert.isRejected(CarReader.fromBytes(buf2), Error, 'Invalid CAR header format')
85
+ it('recursive v2 header', async () => {
86
+ // first 51 bytes are the carv2 header:
87
+ // 11b prefix, 16b characteristics, 8b data offset, 8b data size, 8b index offset
88
+ const v2Header = goCarV2Bytes.slice(0, 51)
89
+ // parser should expect to get a carv1 header at the data offset, but it uses the same
90
+ // code to check the carv2 header so let's make sure it doesn't allow recursive carv2
91
+ // headers
92
+ const buf2 = new Uint8Array(51 * 2)
93
+ buf2.set(v2Header, 0)
94
+ buf2.set(v2Header, 51)
95
+ await assert.isRejected(CarReader.fromBytes(buf2), Error, 'Invalid CAR version: 2 (expected 1)')
96
+ })
75
97
  })
76
98
  })
@@ -1,7 +1,15 @@
1
1
  /* eslint-env mocha */
2
2
 
3
3
  import { CarIndexer } from '@ipld/car/indexer'
4
- import { goCarBytes, goCarIndex, makeIterable, assert } from './common.js'
4
+ import {
5
+ goCarBytes,
6
+ goCarIndex,
7
+ goCarV2Bytes,
8
+ goCarV2Roots,
9
+ goCarV2Index,
10
+ makeIterable,
11
+ assert
12
+ } from './common.js'
5
13
  import { verifyRoots } from './verify-store-reader.js'
6
14
 
7
15
  describe('CarIndexer fromBytes()', () => {
@@ -18,6 +26,21 @@ describe('CarIndexer fromBytes()', () => {
18
26
  assert.deepStrictEqual(indexData, goCarIndex)
19
27
  })
20
28
 
29
+ it('v2 complete', async () => {
30
+ const indexer = await CarIndexer.fromBytes(goCarV2Bytes)
31
+ const roots = await indexer.getRoots()
32
+ assert.strictEqual(roots.length, 1)
33
+ assert(goCarV2Roots[0].equals(roots[0]))
34
+ assert.strictEqual(indexer.version, 2)
35
+
36
+ const indexData = []
37
+ for await (const index of indexer) {
38
+ indexData.push(index)
39
+ }
40
+
41
+ assert.deepStrictEqual(indexData, goCarV2Index)
42
+ })
43
+
21
44
  it('bad argument', async () => {
22
45
  for (const arg of [true, false, null, undefined, 'string', 100, { obj: 'nope' }]) {
23
46
  // @ts-ignore
@@ -2,10 +2,21 @@
2
2
 
3
3
  import { CarReader } from '@ipld/car/reader'
4
4
  import { CarWriter } from '@ipld/car/writer'
5
+ import { bytesReader, readHeader } from '@ipld/car/decoder'
5
6
  import * as Block from 'multiformats/block'
6
7
  import { sha256 } from 'multiformats/hashes/sha2'
7
8
  import * as raw from 'multiformats/codecs/raw'
8
- import { carBytes, makeIterable, assert } from './common.js'
9
+ import { base64 } from 'multiformats/bases/base64'
10
+ import * as dagPb from '@ipld/dag-pb'
11
+ import {
12
+ carBytes,
13
+ makeIterable,
14
+ assert,
15
+ goCarV2Bytes,
16
+ goCarV2Roots,
17
+ goCarV2Index,
18
+ goCarV2Contents
19
+ } from './common.js'
9
20
  import {
10
21
  verifyRoots,
11
22
  verifyHas,
@@ -13,6 +24,8 @@ import {
13
24
  verifyBlocks,
14
25
  verifyCids
15
26
  } from './verify-store-reader.js'
27
+ import { data as fixtures } from './fixtures.js'
28
+ import { expectations as fixtureExpectations } from './fixtures-expectations.js'
16
29
 
17
30
  describe('CarReader fromBytes()', () => {
18
31
  it('complete', async () => {
@@ -48,6 +61,30 @@ describe('CarReader fromBytes()', () => {
48
61
  })
49
62
  })
50
63
 
64
+ it('v2 complete', async () => {
65
+ const reader = await CarReader.fromBytes(goCarV2Bytes)
66
+ const roots = await reader.getRoots()
67
+ assert.strictEqual(roots.length, 1)
68
+ assert(goCarV2Roots[0].equals(roots[0]))
69
+ assert.strictEqual(reader.version, 2)
70
+ for (const { cid } of goCarV2Index) {
71
+ const block = await reader.get(cid)
72
+ assert.isDefined(block)
73
+ if (block) {
74
+ assert(cid.equals(block.cid))
75
+ let content
76
+ if (cid.code === dagPb.code) {
77
+ content = dagPb.decode(block.bytes)
78
+ } else if (cid.code === 85) { // raw
79
+ content = new TextDecoder().decode(block.bytes)
80
+ } else {
81
+ assert.fail('Unexpected codec')
82
+ }
83
+ assert.deepStrictEqual(content, goCarV2Contents[cid.toString()])
84
+ }
85
+ }
86
+ })
87
+
51
88
  it('decode error - trailing null bytes', async () => {
52
89
  const bytes = new Uint8Array(carBytes.length + 5)
53
90
  bytes.set(carBytes)
@@ -149,4 +186,60 @@ describe('CarReader fromIterable()', () => {
149
186
  message: 'Unexpected end of data'
150
187
  })
151
188
  })
189
+
190
+ it('v2 decode error - truncated', async () => {
191
+ const bytes = goCarV2Bytes.slice()
192
+ const dv = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength)
193
+ // dataSize is an 64-bit uint at byte offset 35 from the begining, we're shortening it
194
+ // by 10 to simulate a premature end of CARv1 content
195
+ dv.setBigUint64(35, BigInt(448 - 10), true)
196
+ await assert.isRejected(CarReader.fromIterable(makeIterable(bytes, 64)), {
197
+ name: 'Error',
198
+ message: 'Unexpected end of data'
199
+ })
200
+ })
201
+ })
202
+
203
+ describe('Shared fixtures', () => {
204
+ describe('Header', () => {
205
+ for (const [name, { version: expectedVersion, err: expectedError }] of Object.entries(fixtureExpectations)) {
206
+ it(name, async () => {
207
+ const data = base64.baseDecode(fixtures[name])
208
+ let header
209
+ try {
210
+ header = await readHeader(bytesReader(data))
211
+ } catch (err) {
212
+ if (expectedError != null) {
213
+ assert.equal(err.message, expectedError)
214
+ return
215
+ }
216
+ assert.ifError(err)
217
+ }
218
+ if (expectedError != null) {
219
+ assert.fail(`Expected error: ${expectedError}`)
220
+ }
221
+ assert.isDefined(header, 'did not decode header')
222
+ if (expectedVersion != null && header != null) {
223
+ assert.strictEqual(header.version, expectedVersion)
224
+ }
225
+ })
226
+ }
227
+ })
228
+
229
+ describe('Contents', () => {
230
+ for (const [name, { cids: expectedCids }] of Object.entries(fixtureExpectations)) {
231
+ if (expectedCids == null) {
232
+ continue
233
+ }
234
+ it(name, async () => {
235
+ const data = base64.baseDecode(fixtures[name])
236
+ const reader = await CarReader.fromBytes(data)
237
+ let i = 0
238
+ for await (const cid of reader.cids()) {
239
+ assert.strictEqual(cid.toString(), expectedCids[i++])
240
+ }
241
+ assert.strictEqual(i, expectedCids.length)
242
+ })
243
+ }
244
+ })
152
245
  })
package/tsconfig.json CHANGED
@@ -31,10 +31,12 @@
31
31
  "paths": {
32
32
  "@ipld/car": [ "car.js", "car-browser.js", "lib/" ],
33
33
  "@ipld/car/writer": [ "./lib/writer.js" ],
34
+ "@ipld/car/buffer-writer": ["./lib/buffer-writer.js"],
34
35
  "@ipld/car/reader": [ "./lib/reader.js" ],
35
36
  "@ipld/car/indexed-reader": [ "./lib/indexed-reader.js" ],
36
37
  "@ipld/car/iterator": [ "./lib/iterator.js" ],
37
- "@ipld/car/indexer": [ "./lib/indexer.js" ]
38
+ "@ipld/car/indexer": [ "./lib/indexer.js" ],
39
+ "@ipld/car/decoder": [ "./lib/decoder.js" ],
38
40
  }
39
41
  },
40
42
  "exclude": [
package/types/api.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { CID } from 'multiformats/cid';
2
+ export type { CID };
2
3
  export declare type Block = {
3
4
  cid: CID;
4
5
  bytes: Uint8Array;
@@ -30,6 +31,21 @@ export interface BlockWriter {
30
31
  put(block: Block): Promise<void>;
31
32
  close(): Promise<void>;
32
33
  }
34
+ export interface CarBufferWriter {
35
+ addRoot(root: CID, options?: {
36
+ resize?: boolean;
37
+ }): CarBufferWriter;
38
+ write(block: Block): CarBufferWriter;
39
+ close(options?: {
40
+ resize?: boolean;
41
+ }): Uint8Array;
42
+ }
43
+ export interface CarBufferWriterOptions {
44
+ roots?: CID[];
45
+ byteOffset?: number;
46
+ byteLength?: number;
47
+ headerSize?: number;
48
+ }
33
49
  export interface WriterChannel {
34
50
  writer: BlockWriter;
35
51
  out: AsyncIterable<Uint8Array>;
@@ -1 +1 @@
1
- {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../api.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,kBAAkB,CAAA;AAItC,oBAAY,KAAK,GAAG;IAAE,GAAG,EAAE,GAAG,CAAC;IAAC,KAAK,EAAE,UAAU,CAAA;CAAE,CAAA;AAEnD,oBAAY,WAAW,GAAG;IACxB,GAAG,EAAE,GAAG,CAAC;IACT,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAA;CACpB,CAAA;AAED,oBAAY,UAAU,GAAG,WAAW,GAAG;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAA;CACpB,CAAA;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAA;IACf,QAAQ,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC,CAAA;CAC3B;AAED,MAAM,WAAW,aAAc,SAAQ,aAAa,CAAC,KAAK,CAAC;CAAG;AAE9D,MAAM,WAAW,WAAY,SAAQ,aAAa,CAAC,GAAG,CAAC;CAAG;AAE1D,MAAM,WAAW,WAAW;IAC1B,GAAG,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IAC/B,GAAG,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,KAAK,GAAG,SAAS,CAAC,CAAA;IACzC,MAAM,IAAI,aAAa,CAAA;IACvB,IAAI,IAAI,WAAW,CAAA;CACpB;AAED,MAAM,WAAW,WAAW;IAC1B,GAAG,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAChC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CACvB;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,WAAW,CAAA;IACnB,GAAG,EAAE,aAAa,CAAC,UAAU,CAAC,CAAA;CAC/B;AAED,MAAM,WAAW,SAAU,SAAQ,WAAW,EAAE,WAAW;CAAG"}
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../api.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,kBAAkB,CAAA;AAEtC,YAAY,EAAE,GAAG,EAAE,CAAA;AAGnB,oBAAY,KAAK,GAAG;IAClB,GAAG,EAAE,GAAG,CAAA;IACR,KAAK,EAAE,UAAU,CAAA;CAClB,CAAA;AAED,oBAAY,WAAW,GAAG;IACxB,GAAG,EAAE,GAAG,CAAA;IACR,MAAM,EAAE,MAAM,CAAA;IACd,WAAW,EAAE,MAAM,CAAA;CACpB,CAAA;AAED,oBAAY,UAAU,GAAG,WAAW,GAAG;IACrC,MAAM,EAAE,MAAM,CAAA;IACd,WAAW,EAAE,MAAM,CAAA;CACpB,CAAA;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAA;IACf,QAAQ,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC,CAAA;CAC3B;AAED,MAAM,WAAW,aAAc,SAAQ,aAAa,CAAC,KAAK,CAAC;CAAG;AAE9D,MAAM,WAAW,WAAY,SAAQ,aAAa,CAAC,GAAG,CAAC;CAAG;AAE1D,MAAM,WAAW,WAAW;IAC1B,GAAG,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IAC/B,GAAG,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,KAAK,GAAG,SAAS,CAAC,CAAA;IACzC,MAAM,IAAI,aAAa,CAAA;IACvB,IAAI,IAAI,WAAW,CAAA;CACpB;AAED,MAAM,WAAW,WAAW;IAC1B,GAAG,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAChC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CACvB;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,CAAC,IAAI,EAAC,GAAG,EAAE,OAAO,CAAC,EAAC;QAAE,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE,GAAE,eAAe,CAAA;IAChE,KAAK,CAAC,KAAK,EAAE,KAAK,GAAG,eAAe,CAAA;IACpC,KAAK,CAAC,OAAO,CAAC,EAAC;QAAE,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,UAAU,CAAA;CACjD;AAED,MAAM,WAAW,sBAAsB;IACrC,KAAK,CAAC,EAAE,GAAG,EAAE,CAAA;IACb,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,UAAU,CAAC,EAAE,MAAM,CAAA;IAEnB,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,WAAW,CAAA;IACnB,GAAG,EAAE,aAAa,CAAC,UAAU,CAAC,CAAA;CAC/B;AAED,MAAM,WAAW,SAAU,SAAQ,WAAW,EAAE,WAAW;CAAG"}
@@ -0,0 +1,86 @@
1
+ export function addRoot(writer: CarBufferWriter, root: CID, { resize }?: {
2
+ resize?: boolean | undefined;
3
+ } | undefined): void;
4
+ export function blockLength({ cid, bytes }: Block): number;
5
+ export function addBlock(writer: CarBufferWriter, { cid, bytes }: Block): void;
6
+ export function close(writer: CarBufferWriter, { resize }?: {
7
+ resize?: boolean | undefined;
8
+ } | undefined): Uint8Array;
9
+ export function resizeHeader(writer: CarBufferWriter, byteLength: number): void;
10
+ export function calculateHeaderLength(rootLengths: number[]): number;
11
+ export function headerLength({ roots }: {
12
+ roots: CID[];
13
+ }): number;
14
+ export function estimateHeaderLength(rootCount: number, rootByteLength?: number | undefined): number;
15
+ export function createWriter(buffer: ArrayBuffer, { roots, byteOffset, byteLength, headerSize }?: {
16
+ roots?: import("multiformats/cid").CID[] | undefined;
17
+ byteOffset?: number | undefined;
18
+ byteLength?: number | undefined;
19
+ headerSize?: number | undefined;
20
+ } | undefined): CarBufferWriter;
21
+ export type CID = import('../api').CID;
22
+ export type Block = import('../api').Block;
23
+ export type Writer = import('../api').CarBufferWriter;
24
+ export type Options = import('../api').CarBufferWriterOptions;
25
+ export type CarEncoder = import('./coding').CarEncoder;
26
+ /**
27
+ * @typedef {import('../api').CID} CID
28
+ * @typedef {import('../api').Block} Block
29
+ * @typedef {import('../api').CarBufferWriter} Writer
30
+ * @typedef {import('../api').CarBufferWriterOptions} Options
31
+ * @typedef {import('./coding').CarEncoder} CarEncoder
32
+ */
33
+ /**
34
+ * A simple CAR writer that writes to a pre-allocated buffer.
35
+ *
36
+ * @class
37
+ * @name CarBufferWriter
38
+ * @implements {Writer}
39
+ */
40
+ declare class CarBufferWriter implements Writer {
41
+ /**
42
+ * @param {Uint8Array} bytes
43
+ * @param {number} headerSize
44
+ */
45
+ constructor(bytes: Uint8Array, headerSize: number);
46
+ /** @readonly */
47
+ readonly bytes: Uint8Array;
48
+ byteOffset: number;
49
+ /**
50
+ * @readonly
51
+ * @type {CID[]}
52
+ */
53
+ readonly roots: CID[];
54
+ headerSize: number;
55
+ /**
56
+ * Add a root to this writer, to be used to create a header when the CAR is
57
+ * finalized with {@link CarBufferWriter.close `close()`}
58
+ *
59
+ * @param {CID} root
60
+ * @param {{resize?:boolean}} [options]
61
+ * @returns {CarBufferWriter}
62
+ */
63
+ addRoot(root: CID, options?: {
64
+ resize?: boolean | undefined;
65
+ } | undefined): CarBufferWriter;
66
+ /**
67
+ * Write a `Block` (a `{ cid:CID, bytes:Uint8Array }` pair) to the archive.
68
+ * Throws if there is not enough capacity.
69
+ *
70
+ * @param {Block} block A `{ cid:CID, bytes:Uint8Array }` pair.
71
+ * @returns {CarBufferWriter}
72
+ */
73
+ write(block: Block): CarBufferWriter;
74
+ /**
75
+ * Finalize the CAR and return it as a `Uint8Array`.
76
+ *
77
+ * @param {object} [options]
78
+ * @param {boolean} [options.resize]
79
+ * @returns {Uint8Array}
80
+ */
81
+ close(options?: {
82
+ resize?: boolean | undefined;
83
+ } | undefined): Uint8Array;
84
+ }
85
+ export {};
86
+ //# sourceMappingURL=buffer-writer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"buffer-writer.d.ts","sourceRoot":"","sources":["../../lib/buffer-writer.js"],"names":[],"mappings":"AAgFO,gCAJI,eAAe,QACf,GAAG;;qBA0Bb;AAUM,4CAHI,KAAK,GACH,MAAM,CAKlB;AAMM,iCAHI,eAAe,kBACf,KAAK,QAYf;AAOM,8BAJI,eAAe;;2BA2BzB;AAMM,qCAHI,eAAe,cACf,MAAM,QAShB;AAqCM,mDAHI,MAAM,EAAE,GACN,MAAM,CAWlB;AAUM;IAHmB,KAAK,EAApB,GAAG,EAAE;IACH,MAAM,CAG4C;AAYxD,gDAJI,MAAM,wCAEJ,MAAM,CAG+C;AAuB3D,qCARI,WAAW;;;;;gBAMT,eAAe,CAmB3B;kBAvRY,OAAO,QAAQ,EAAE,GAAG;oBACpB,OAAO,QAAQ,EAAE,KAAK;qBACtB,OAAO,QAAQ,EAAE,eAAe;sBAChC,OAAO,QAAQ,EAAE,sBAAsB;yBACvC,OAAO,UAAU,EAAE,UAAU;AAL1C;;;;;;GAMG;AAEH;;;;;;GAMG;AACH;IACE;;;OAGG;IACH,mBAHW,UAAU,cACV,MAAM,EAahB;IAVC,gBAAgB;IAChB,2BAAkB;IAClB,mBAA4B;IAE5B;;;OAGG;IACH,gBAFU,GAAG,EAAE,CAEA;IACf,mBAA4B;IAG9B;;;;;;;OAOG;IACH,cAJW,GAAG;;oBAED,eAAe,CAK3B;IAED;;;;;;OAMG;IACH,aAHW,KAAK,GACH,eAAe,CAK3B;IAED;;;;;;OAMG;IACH;;oBAFa,UAAU,CAItB;CACF"}