@ipld/car 5.0.3 → 5.1.1

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.
@@ -0,0 +1,150 @@
1
+ import * as BufferDecoder from './buffer-decoder.js'
2
+
3
+ /**
4
+ * @typedef {import('multiformats').CID} CID
5
+ * @typedef {import('./api').Block} Block
6
+ * @typedef {import('./api').CarBufferReader} ICarBufferReader
7
+ * @typedef {import('./coding').CarHeader} CarHeader
8
+ * @typedef {import('./coding').CarV2Header} CarV2Header
9
+ */
10
+
11
+ /**
12
+ * Provides blockstore-like access to a CAR.
13
+ *
14
+ * Implements the `RootsBufferReader` interface:
15
+ * {@link ICarBufferReader.getRoots `getRoots()`}. And the `BlockBufferReader` interface:
16
+ * {@link ICarBufferReader.get `get()`}, {@link ICarBufferReader.has `has()`},
17
+ * {@link ICarBufferReader.blocks `blocks()`} and
18
+ * {@link ICarBufferReader.cids `cids()`}.
19
+ *
20
+ * Load this class with either `import { CarBufferReader } from '@ipld/car/buffer-reader'`
21
+ * (`const { CarBufferReader } = require('@ipld/car/buffer-reader')`). Or
22
+ * `import { CarBufferReader } from '@ipld/car'` (`const { CarBufferReader } = require('@ipld/car')`).
23
+ * The former will likely result in smaller bundle sizes where this is
24
+ * important.
25
+ *
26
+ * @name CarBufferReader
27
+ * @class
28
+ * @implements {ICarBufferReader}
29
+ * @property {number} version The version number of the CAR referenced by this
30
+ * reader (should be `1` or `2`).
31
+ */
32
+ export class CarBufferReader {
33
+ /**
34
+ * @constructs CarBufferReader
35
+ * @param {CarHeader|CarV2Header} header
36
+ * @param {Block[]} blocks
37
+ */
38
+ constructor (header, blocks) {
39
+ this._header = header
40
+ this._blocks = blocks
41
+ this._cids = undefined
42
+ }
43
+
44
+ /**
45
+ * @property version
46
+ * @memberof CarBufferReader
47
+ * @instance
48
+ */
49
+ get version () {
50
+ return this._header.version
51
+ }
52
+
53
+ /**
54
+ * Get the list of roots defined by the CAR referenced by this reader. May be
55
+ * zero or more `CID`s.
56
+ *
57
+ * @function
58
+ * @memberof CarBufferReader
59
+ * @instance
60
+ * @returns {CID[]}
61
+ */
62
+ getRoots () {
63
+ return this._header.roots
64
+ /* c8 ignore next 2 */
65
+ // Node.js 12 c8 bug
66
+ }
67
+
68
+ /**
69
+ * Check whether a given `CID` exists within the CAR referenced by this
70
+ * reader.
71
+ *
72
+ * @function
73
+ * @memberof CarBufferReader
74
+ * @instance
75
+ * @param {CID} key
76
+ * @returns {boolean}
77
+ */
78
+ has (key) {
79
+ return this._blocks.some(b => b.cid.equals(key))
80
+ /* c8 ignore next 2 */
81
+ // Node.js 12 c8 bug
82
+ }
83
+
84
+ /**
85
+ * Fetch a `Block` (a `{ cid:CID, bytes:Uint8Array }` pair) from the CAR
86
+ * referenced by this reader matching the provided `CID`. In the case where
87
+ * the provided `CID` doesn't exist within the CAR, `undefined` will be
88
+ * returned.
89
+ *
90
+ * @function
91
+ * @memberof CarBufferReader
92
+ * @instance
93
+ * @param {CID} key
94
+ * @returns {Block | undefined}
95
+ */
96
+ get (key) {
97
+ return this._blocks.find(b => b.cid.equals(key))
98
+ /* c8 ignore next 2 */
99
+ // Node.js 12 c8 bug
100
+ }
101
+
102
+ /**
103
+ * Returns a `Block[]` of the `Block`s (`{ cid:CID, bytes:Uint8Array }` pairs) contained within
104
+ * the CAR referenced by this reader.
105
+ *
106
+ * @function
107
+ * @memberof CarBufferReader
108
+ * @instance
109
+ * @returns {Block[]}
110
+ */
111
+ blocks () {
112
+ return this._blocks
113
+ }
114
+
115
+ /**
116
+ * Returns a `CID[]` of the `CID`s contained within the CAR referenced by this reader.
117
+ *
118
+ * @function
119
+ * @memberof CarBufferReader
120
+ * @instance
121
+ * @returns {CID[]}
122
+ */
123
+ cids () {
124
+ if (!this._cids) {
125
+ this._cids = this._blocks.map(b => b.cid)
126
+ }
127
+ return this._cids
128
+ }
129
+
130
+ /**
131
+ * Instantiate a {@link CarBufferReader} from a `Uint8Array` blob. This performs a
132
+ * decode fully in memory and maintains the decoded state in memory for full
133
+ * access to the data via the `CarReader` API.
134
+ *
135
+ * @static
136
+ * @memberof CarBufferReader
137
+ * @param {Uint8Array} bytes
138
+ * @returns {CarBufferReader}
139
+ */
140
+ static fromBytes (bytes) {
141
+ if (!(bytes instanceof Uint8Array)) {
142
+ throw new TypeError('fromBytes() requires a Uint8Array')
143
+ }
144
+
145
+ const { header, blocks } = BufferDecoder.fromBytes(bytes)
146
+ return new CarBufferReader(header, blocks)
147
+ }
148
+ }
149
+
150
+ export const __browser = true
@@ -0,0 +1,53 @@
1
+ import fs from 'fs'
2
+ import { CarBufferReader as BrowserCarBufferReader } from './buffer-reader-browser.js'
3
+
4
+ /**
5
+ * @typedef {import('./api').Block} Block
6
+ * @typedef {import('./api').BlockIndex} BlockIndex
7
+ * @typedef {import('./api').CarBufferReader} ICarBufferReader
8
+ */
9
+
10
+ const fsread = fs.readSync
11
+
12
+ /**
13
+ * @class
14
+ * @implements {ICarBufferReader}
15
+ */
16
+ export class CarBufferReader extends BrowserCarBufferReader {
17
+ /**
18
+ * Reads a block directly from a file descriptor for an open CAR file. This
19
+ * function is **only available in Node.js** and not a browser environment.
20
+ *
21
+ * This function can be used in connection with {@link CarIndexer} which emits
22
+ * the `BlockIndex` objects that are required by this function.
23
+ *
24
+ * The user is responsible for opening and closing the file used in this call.
25
+ *
26
+ * @static
27
+ * @memberof CarBufferReader
28
+ * @param {number} fd - A file descriptor from the
29
+ * Node.js `fs` module. An integer, from `fs.open()`.
30
+ * @param {BlockIndex} blockIndex - An index pointing to the location of the
31
+ * Block required. This `BlockIndex` should take the form:
32
+ * `{cid:CID, blockLength:number, blockOffset:number}`.
33
+ * @returns {Block} A `{ cid:CID, bytes:Uint8Array }` pair.
34
+ */
35
+ static readRaw (fd, blockIndex) {
36
+ const { cid, blockLength, blockOffset } = blockIndex
37
+ const bytes = new Uint8Array(blockLength)
38
+ let read
39
+ if (typeof fd === 'number') {
40
+ read = fsread(fd, bytes, 0, blockLength, blockOffset)
41
+ } else {
42
+ throw new TypeError('Bad fd')
43
+ }
44
+ if (read !== blockLength) {
45
+ throw new Error(`Failed to read entire block (${read} instead of ${blockLength})`)
46
+ }
47
+ return { cid, bytes }
48
+ /* c8 ignore next 2 */
49
+ // Node.js 12 c8 bug
50
+ }
51
+ }
52
+
53
+ export const __browser = false
package/src/coding.ts CHANGED
@@ -45,12 +45,22 @@ export interface CarDecoder {
45
45
  blocksIndex: () => AsyncGenerator<BlockIndex>
46
46
  }
47
47
 
48
- export interface BytesReader {
48
+ export interface Seekable {
49
+ seek: (length: number) => void
50
+ }
51
+
52
+ export interface BytesReader extends Seekable {
49
53
  upTo: (length: number) => Promise<Uint8Array>
50
54
 
51
- exactly: (length: number) => Promise<Uint8Array>
55
+ exactly: (length: number, seek?: boolean) => Promise<Uint8Array>
52
56
 
53
- seek: (length: number) => void
57
+ pos: number
58
+ }
59
+
60
+ export interface BytesBufferReader extends Seekable{
61
+ upTo: (length: number) => Uint8Array
62
+
63
+ exactly: (length: number, seek?: boolean) => Uint8Array
54
64
 
55
65
  pos: number
56
66
  }
@@ -0,0 +1,86 @@
1
+ import varint from 'varint'
2
+
3
+ export const CIDV0_BYTES = {
4
+ SHA2_256: 0x12,
5
+ LENGTH: 0x20,
6
+ DAG_PB: 0x70
7
+ }
8
+
9
+ export const V2_HEADER_LENGTH = /* characteristics */ 16 /* v1 offset */ + 8 /* v1 size */ + 8 /* index offset */ + 8
10
+
11
+ /**
12
+ * Decodes varint and seeks the buffer
13
+ *
14
+ * ```js
15
+ * // needs bytes to be read first
16
+ * const bytes = reader.upTo(8) // maybe async
17
+ * ```
18
+ *
19
+ * @param {Uint8Array} bytes
20
+ * @param {import('./coding').Seekable} seeker
21
+ * @returns {number}
22
+ */
23
+ export function decodeVarint (bytes, seeker) {
24
+ if (!bytes.length) {
25
+ throw new Error('Unexpected end of data')
26
+ }
27
+ const i = varint.decode(bytes)
28
+ seeker.seek(/** @type {number} */(varint.decode.bytes))
29
+ return i
30
+ /* c8 ignore next 2 */
31
+ // Node.js 12 c8 bug
32
+ }
33
+
34
+ /**
35
+ * Decode v2 header
36
+ *
37
+ * ```js
38
+ * // needs bytes to be read first
39
+ * const bytes = reader.exactly(V2_HEADER_LENGTH, true) // maybe async
40
+ * ```
41
+ *
42
+ * @param {Uint8Array} bytes
43
+ * @returns {import('./coding').CarV2FixedHeader}
44
+ */
45
+ export function decodeV2Header (bytes) {
46
+ const dv = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength)
47
+ let offset = 0
48
+ const header = {
49
+ version: 2,
50
+ /** @type {[bigint, bigint]} */
51
+ characteristics: [
52
+ dv.getBigUint64(offset, true),
53
+ dv.getBigUint64(offset += 8, true)
54
+ ],
55
+ dataOffset: Number(dv.getBigUint64(offset += 8, true)),
56
+ dataSize: Number(dv.getBigUint64(offset += 8, true)),
57
+ indexOffset: Number(dv.getBigUint64(offset += 8, true))
58
+ }
59
+ return header
60
+ /* c8 ignore next 2 */
61
+ // Node.js 12 c8 bug
62
+ }
63
+
64
+ /**
65
+ * Checks the length of the multihash to be read afterwards
66
+ *
67
+ * ```js
68
+ * // needs bytes to be read first
69
+ * const bytes = reader.upTo(8) // maybe async
70
+ * ```
71
+ *
72
+ * @param {Uint8Array} bytes
73
+ */
74
+ export function getMultihashLength (bytes) {
75
+ // | code | length | .... |
76
+ // where both code and length are varints, so we have to decode
77
+ // them first before we can know total length
78
+
79
+ varint.decode(bytes) // code
80
+ const codeLength = /** @type {number} */(varint.decode.bytes)
81
+ const length = varint.decode(bytes.subarray(varint.decode.bytes))
82
+ const lengthLength = /** @type {number} */(varint.decode.bytes)
83
+ const mhLength = codeLength + lengthLength + length
84
+
85
+ return mhLength
86
+ }
package/src/decoder.js CHANGED
@@ -1,8 +1,8 @@
1
- import varint from 'varint'
2
1
  import { CID } from 'multiformats/cid'
3
2
  import * as Digest from 'multiformats/hashes/digest'
4
3
  import { decode as decodeDagCbor } from '@ipld/dag-cbor'
5
4
  import { CarHeader as headerValidator } from './header-validator.js'
5
+ import { CIDV0_BYTES, decodeV2Header, decodeVarint, getMultihashLength, V2_HEADER_LENGTH } from './decoder-common.js'
6
6
 
7
7
  /**
8
8
  * @typedef {import('./api').Block} Block
@@ -15,56 +15,6 @@ import { CarHeader as headerValidator } from './header-validator.js'
15
15
  * @typedef {import('./coding').CarDecoder} CarDecoder
16
16
  */
17
17
 
18
- const CIDV0_BYTES = {
19
- SHA2_256: 0x12,
20
- LENGTH: 0x20,
21
- DAG_PB: 0x70
22
- }
23
-
24
- const V2_HEADER_LENGTH = /* characteristics */ 16 /* v1 offset */ + 8 /* v1 size */ + 8 /* index offset */ + 8
25
-
26
- /**
27
- * @param {BytesReader} reader
28
- * @returns {Promise<number>}
29
- */
30
- async function readVarint (reader) {
31
- const bytes = await reader.upTo(8)
32
- if (!bytes.length) {
33
- throw new Error('Unexpected end of data')
34
- }
35
- const i = varint.decode(bytes)
36
- reader.seek(/** @type {number} */(varint.decode.bytes))
37
- return i
38
- /* c8 ignore next 2 */
39
- // Node.js 12 c8 bug
40
- }
41
-
42
- /**
43
- * @param {BytesReader} reader
44
- * @returns {Promise<CarV2FixedHeader>}
45
- */
46
- async function readV2Header (reader) {
47
- /** @type {Uint8Array} */
48
- const bytes = await reader.exactly(V2_HEADER_LENGTH)
49
- const dv = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength)
50
- let offset = 0
51
- const header = {
52
- version: 2,
53
- /** @type {[bigint, bigint]} */
54
- characteristics: [
55
- dv.getBigUint64(offset, true),
56
- dv.getBigUint64(offset += 8, true)
57
- ],
58
- dataOffset: Number(dv.getBigUint64(offset += 8, true)),
59
- dataSize: Number(dv.getBigUint64(offset += 8, true)),
60
- indexOffset: Number(dv.getBigUint64(offset += 8, true))
61
- }
62
- reader.seek(V2_HEADER_LENGTH)
63
- return header
64
- /* c8 ignore next 2 */
65
- // Node.js 12 c8 bug
66
- }
67
-
68
18
  /**
69
19
  * Reads header data from a `BytesReader`. The header may either be in the form
70
20
  * of a `CarHeader` or `CarV2Header` depending on the CAR being read.
@@ -75,12 +25,11 @@ async function readV2Header (reader) {
75
25
  * @returns {Promise<CarHeader|CarV2Header>}
76
26
  */
77
27
  export async function readHeader (reader, strictVersion) {
78
- const length = await readVarint(reader)
28
+ const length = decodeVarint(await reader.upTo(8), reader)
79
29
  if (length === 0) {
80
30
  throw new Error('Invalid CAR header (zero length)')
81
31
  }
82
- const header = await reader.exactly(length)
83
- reader.seek(length)
32
+ const header = await reader.exactly(length, true)
84
33
  const block = decodeDagCbor(header)
85
34
  if (!headerValidator(block)) {
86
35
  throw new Error('Invalid CAR header format')
@@ -98,7 +47,7 @@ export async function readHeader (reader, strictVersion) {
98
47
  return block
99
48
  }
100
49
  // version 2
101
- const v2Header = await readV2Header(reader)
50
+ const v2Header = decodeV2Header(await reader.exactly(V2_HEADER_LENGTH, true))
102
51
  reader.seek(v2Header.dataOffset - reader.pos)
103
52
  const v1Header = await readHeader(reader, 1)
104
53
  return Object.assign(v1Header, v2Header)
@@ -106,48 +55,25 @@ export async function readHeader (reader, strictVersion) {
106
55
  // Node.js 12 c8 bug
107
56
  }
108
57
 
109
- /**
110
- * @param {BytesReader} reader
111
- * @returns {Promise<Uint8Array>}
112
- */
113
- async function readMultihash (reader) {
114
- // | code | length | .... |
115
- // where both code and length are varints, so we have to decode
116
- // them first before we can know total length
117
-
118
- const bytes = await reader.upTo(8)
119
- varint.decode(bytes) // code
120
- const codeLength = /** @type {number} */(varint.decode.bytes)
121
- const length = varint.decode(bytes.subarray(varint.decode.bytes))
122
- const lengthLength = /** @type {number} */(varint.decode.bytes)
123
- const mhLength = codeLength + lengthLength + length
124
- const multihash = await reader.exactly(mhLength)
125
- reader.seek(mhLength)
126
- return multihash
127
- /* c8 ignore next 2 */
128
- // Node.js 12 c8 bug
129
- }
130
-
131
58
  /**
132
59
  * @param {BytesReader} reader
133
60
  * @returns {Promise<CID>}
134
61
  */
135
62
  async function readCid (reader) {
136
- const first = await reader.exactly(2)
63
+ const first = await reader.exactly(2, false)
137
64
  if (first[0] === CIDV0_BYTES.SHA2_256 && first[1] === CIDV0_BYTES.LENGTH) {
138
65
  // cidv0 32-byte sha2-256
139
- const bytes = await reader.exactly(34)
140
- reader.seek(34)
66
+ const bytes = await reader.exactly(34, true)
141
67
  const multihash = Digest.decode(bytes)
142
68
  return CID.create(0, CIDV0_BYTES.DAG_PB, multihash)
143
69
  }
144
70
 
145
- const version = await readVarint(reader)
71
+ const version = decodeVarint(await reader.upTo(8), reader)
146
72
  if (version !== 1) {
147
73
  throw new Error(`Unexpected CID version (${version})`)
148
74
  }
149
- const codec = await readVarint(reader)
150
- const bytes = await readMultihash(reader)
75
+ const codec = decodeVarint(await reader.upTo(8), reader)
76
+ const bytes = await reader.exactly(getMultihashLength(await reader.upTo(8)), true)
151
77
  const multihash = Digest.decode(bytes)
152
78
  return CID.create(version, codec, multihash)
153
79
  /* c8 ignore next 2 */
@@ -168,7 +94,7 @@ export async function readBlockHead (reader) {
168
94
  // length includes a CID + Binary, where CID has a variable length
169
95
  // we have to deal with
170
96
  const start = reader.pos
171
- let length = await readVarint(reader)
97
+ let length = decodeVarint(await reader.upTo(8), reader)
172
98
  if (length === 0) {
173
99
  throw new Error('Invalid CAR section (zero length)')
174
100
  }
@@ -187,8 +113,7 @@ export async function readBlockHead (reader) {
187
113
  */
188
114
  async function readBlock (reader) {
189
115
  const { cid, blockLength } = await readBlockHead(reader)
190
- const bytes = await reader.exactly(blockLength)
191
- reader.seek(blockLength)
116
+ const bytes = await reader.exactly(blockLength, true)
192
117
  return { bytes, cid }
193
118
  /* c8 ignore next 2 */
194
119
  // Node.js 12 c8 bug
@@ -261,16 +186,21 @@ export function bytesReader (bytes) {
261
186
  /** @type {BytesReader} */
262
187
  return {
263
188
  async upTo (length) {
264
- return bytes.subarray(pos, pos + Math.min(length, bytes.length - pos))
189
+ const out = bytes.subarray(pos, pos + Math.min(length, bytes.length - pos))
265
190
  /* c8 ignore next 2 */
191
+ return out
266
192
  // Node.js 12 c8 bug
267
193
  },
268
194
 
269
- async exactly (length) {
195
+ async exactly (length, seek = false) {
270
196
  if (length > bytes.length - pos) {
271
197
  throw new Error('Unexpected end of data')
272
198
  }
273
- return bytes.subarray(pos, pos + length)
199
+ const out = bytes.subarray(pos, pos + length)
200
+ if (seek) {
201
+ pos += length
202
+ }
203
+ return out
274
204
  /* c8 ignore next 2 */
275
205
  // Node.js 12 c8 bug
276
206
  },
@@ -340,14 +270,19 @@ export function chunkReader (readChunk /*, closer */) {
340
270
  // Node.js 12 c8 bug
341
271
  },
342
272
 
343
- async exactly (length) {
273
+ async exactly (length, seek = false) {
344
274
  if (currentChunk.length - offset < length) {
345
275
  await read(length)
346
276
  }
347
277
  if (currentChunk.length - offset < length) {
348
278
  throw new Error('Unexpected end of data')
349
279
  }
350
- return currentChunk.subarray(offset, offset + length)
280
+ const out = currentChunk.subarray(offset, offset + length)
281
+ if (seek) {
282
+ pos += length
283
+ offset += length
284
+ }
285
+ return out
351
286
  /* c8 ignore next 2 */
352
287
  // Node.js 12 c8 bug
353
288
  },
@@ -412,11 +347,14 @@ export function limitReader (reader, byteLimit) {
412
347
  // Node.js 12 c8 bug
413
348
  },
414
349
 
415
- async exactly (length) {
416
- const bytes = await reader.exactly(length)
350
+ async exactly (length, seek = false) {
351
+ const bytes = await reader.exactly(length, seek)
417
352
  if (bytes.length + bytesRead > byteLimit) {
418
353
  throw new Error('Unexpected end of data')
419
354
  }
355
+ if (seek) {
356
+ bytesRead += length
357
+ }
420
358
  return bytes
421
359
  /* c8 ignore next 2 */
422
360
  // Node.js 12 c8 bug
@@ -3,6 +3,8 @@ import { CarIndexer } from './indexer.js'
3
3
  import { CarBlockIterator, CarCIDIterator } from './iterator.js'
4
4
  import { CarWriter } from './writer-browser.js'
5
5
  import { CarIndexedReader } from './indexed-reader-browser.js'
6
+ import { CarBufferReader } from './buffer-reader.js'
7
+ import * as CarBufferWriter from './buffer-writer.js'
6
8
 
7
9
  export {
8
10
  CarReader,
@@ -10,5 +12,7 @@ export {
10
12
  CarBlockIterator,
11
13
  CarCIDIterator,
12
14
  CarWriter,
13
- CarIndexedReader
15
+ CarIndexedReader,
16
+ CarBufferReader,
17
+ CarBufferWriter
14
18
  }
package/src/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { CarReader } from './reader.js'
2
+ import { CarBufferReader } from './buffer-reader.js'
2
3
  import { CarIndexer } from './indexer.js'
3
4
  import { CarBlockIterator, CarCIDIterator } from './iterator.js'
4
5
  import { CarWriter } from './writer.js'
@@ -7,6 +8,7 @@ import * as CarBufferWriter from './buffer-writer.js'
7
8
 
8
9
  export {
9
10
  CarReader,
11
+ CarBufferReader,
10
12
  CarIndexer,
11
13
  CarBlockIterator,
12
14
  CarCIDIterator,