hypercore 10.0.0-alpha.2 → 10.0.0-alpha.20

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 CHANGED
@@ -63,12 +63,16 @@ Note that `tree`, `data`, and `bitfield` are normally heavily sparse files.
63
63
  createIfMissing: true, // create a new Hypercore key pair if none was present in storage
64
64
  overwrite: false, // overwrite any old Hypercore that might already exist
65
65
  valueEncoding: 'json' | 'utf-8' | 'binary', // defaults to binary
66
- keyPair: kp // optionally pass the public key and secret key as a key pair
66
+ encodeBatch: batch => { ... }, // optionally apply an encoding to complete batches
67
+ keyPair: kp, // optionally pass the public key and secret key as a key pair
68
+ encryptionKey: k // optionally pass an encryption key to enable block encryption
67
69
  }
68
70
  ```
69
71
 
70
72
  You can also set valueEncoding to any [abstract-encoding](https://github.com/mafintosh/abstract-encoding) or [compact-encoding](https://github.com/compact-encoding) instance.
71
73
 
74
+ valueEncodings will be applied to individually blocks, even if you append batches. If you want to control encoding at the batch-level, you can use the `encodeBatch` option, which is a function that takes a batch and returns a binary-encoded batch. If you provide a custom valueEncoding, it will not be applied prior to `encodeBatch`.
75
+
72
76
  #### `const seq = await core.append(block)`
73
77
 
74
78
  Append a block of data (or an array of blocks) to the core.
@@ -83,7 +87,7 @@ Options include
83
87
 
84
88
  ``` js
85
89
  {
86
- wait: true, // wait for index to be downloaded
90
+ wait: true, // wait for block to be downloaded
87
91
  onwait: () => {}, // hook that is called if the get is waiting for download
88
92
  timeout: 0, // wait at max some milliseconds (0 means no timeout)
89
93
  valueEncoding: 'json' | 'utf-8' | 'binary' // defaults to the core's valueEncoding
@@ -97,6 +101,19 @@ Truncate the core to a smaller length.
97
101
  Per default this will update the fork id of the core to `+ 1`, but you can set the fork id you prefer with the option.
98
102
  Note that the fork id should be monotonely incrementing.
99
103
 
104
+ #### `const stream = core.createReadStream([options])`
105
+
106
+ Make a read stream. Options include:
107
+
108
+ ``` js
109
+ {
110
+ start: 0,
111
+ end: core.length,
112
+ live: false,
113
+ snapshot: true // auto set end to core.length on open or update it on every read
114
+ }
115
+ ```
116
+
100
117
  #### `const range = core.download([range])`
101
118
 
102
119
  Download a range of data.
@@ -113,6 +130,7 @@ A range can have the following properties:
113
130
  {
114
131
  start: startIndex,
115
132
  end: nonInclusiveEndIndex,
133
+ blocks: [index1, index2, ...],
116
134
  linear: false // download range linearly and not randomly
117
135
  }
118
136
  ```
@@ -125,6 +143,12 @@ To download the full core continously (often referred to as non sparse mode) do
125
143
  core.download({ start: 0, end: -1 })
126
144
  ```
127
145
 
146
+ To downloaded a discrete range of blocks pass a list of indices.
147
+
148
+ ```js
149
+ core.download({ blocks: [4, 9, 7] });
150
+ ```
151
+
128
152
  To cancel downloading a range simply destroy the range instance.
129
153
 
130
154
  ``` js
@@ -195,6 +219,10 @@ In contrast to `core.key` this key does not allow you to verify the data but can
195
219
 
196
220
  Populated after `ready` has been emitted. Will be `null` before the event.
197
221
 
222
+ #### `core.encryptionKey`
223
+
224
+ Buffer containing the optional block encryption key of this core. Will be `null` unless block encryption is enabled.
225
+
198
226
  #### `core.length`
199
227
 
200
228
  How many blocks of data are available on this core?
@@ -213,6 +241,10 @@ What is the current fork id of this core?
213
241
 
214
242
  Populated after `ready` has been emitted. Will be `0` before the event.
215
243
 
244
+ #### `core.padding`
245
+
246
+ How much padding is applied to each block of this core? Will be `0` unless block encryption is enabled.
247
+
216
248
  #### `const stream = core.replicate(isInitiatorOrReplicationStream)`
217
249
 
218
250
  Create a replication stream. You should pipe this to another Hypercore instance.
package/index.js CHANGED
@@ -3,6 +3,8 @@ const raf = require('random-access-file')
3
3
  const isOptions = require('is-options')
4
4
  const hypercoreCrypto = require('hypercore-crypto')
5
5
  const c = require('compact-encoding')
6
+ const b4a = require('b4a')
7
+ const Xache = require('xache')
6
8
  const NoiseSecretStream = require('@hyperswarm/secret-stream')
7
9
  const codecs = require('codecs')
8
10
 
@@ -11,6 +13,8 @@ const fsctl = requireMaybe('fsctl') || { lock: noop, sparse: noop }
11
13
  const Replicator = require('./lib/replicator')
12
14
  const Extensions = require('./lib/extensions')
13
15
  const Core = require('./lib/core')
16
+ const BlockEncryption = require('./lib/block-encryption')
17
+ const { ReadStream, WriteStream } = require('./lib/streams')
14
18
 
15
19
  const promises = Symbol.for('hypercore.promises')
16
20
  const inspect = Symbol.for('nodejs.util.inspect.custom')
@@ -27,14 +31,17 @@ module.exports = class Hypercore extends EventEmitter {
27
31
  opts = key
28
32
  key = null
29
33
  }
34
+
30
35
  if (key && typeof key === 'string') {
31
- key = Buffer.from(key, 'hex')
36
+ key = b4a.from(key, 'hex')
32
37
  }
33
- if (key && key.byteLength !== 32) {
38
+
39
+ if (!opts) opts = {}
40
+
41
+ if (!opts.crypto && key && key.byteLength !== 32) {
34
42
  throw new Error('Hypercore key should be 32 bytes')
35
43
  }
36
44
 
37
- if (!opts) opts = {}
38
45
  if (!storage) storage = opts.storage
39
46
 
40
47
  this[promises] = true
@@ -43,9 +50,13 @@ module.exports = class Hypercore extends EventEmitter {
43
50
  this.crypto = opts.crypto || hypercoreCrypto
44
51
  this.core = null
45
52
  this.replicator = null
53
+ this.encryption = null
46
54
  this.extensions = opts.extensions || new Extensions()
55
+ this.cache = opts.cache === true ? new Xache({ maxSize: 65536, maxAge: 0 }) : (opts.cache || null)
47
56
 
48
57
  this.valueEncoding = null
58
+ this.encodeBatch = null
59
+
49
60
  this.key = key || null
50
61
  this.discoveryKey = null
51
62
  this.readable = true
@@ -57,8 +68,10 @@ module.exports = class Hypercore extends EventEmitter {
57
68
  this.autoClose = !!opts.autoClose
58
69
 
59
70
  this.closing = null
60
- this.opening = opts._opening || this._open(key, storage, opts)
71
+ this.opening = this._openSession(key, storage, opts)
61
72
  this.opening.catch(noop)
73
+
74
+ this._preappend = preappend.bind(this)
62
75
  }
63
76
 
64
77
  [inspect] (depth, opts) {
@@ -79,9 +92,28 @@ module.exports = class Hypercore extends EventEmitter {
79
92
  indent + ')'
80
93
  }
81
94
 
82
- static createProtocolStream (isInitiator, opts) {
83
- const noiseStream = new NoiseSecretStream(isInitiator, null, opts)
84
- return noiseStream.rawStream
95
+ static createProtocolStream (isInitiator, opts = {}) {
96
+ let outerStream = isStream(isInitiator)
97
+ ? isInitiator
98
+ : opts.stream
99
+ let noiseStream = null
100
+
101
+ if (outerStream) {
102
+ noiseStream = outerStream.noiseStream
103
+ } else {
104
+ noiseStream = new NoiseSecretStream(isInitiator, null, opts)
105
+ outerStream = noiseStream.rawStream
106
+ }
107
+ if (!noiseStream) throw new Error('Invalid stream')
108
+
109
+ if (!noiseStream.userData) {
110
+ const protocol = Replicator.createProtocol(noiseStream)
111
+ if (opts.keepAlive !== false) protocol.setKeepAlive(true)
112
+ noiseStream.userData = protocol
113
+ noiseStream.on('error', noop) // All noise errors already propagate through outerStream
114
+ }
115
+
116
+ return outerStream
85
117
  }
86
118
 
87
119
  static defaultStorage (storage, opts = {}) {
@@ -91,7 +123,7 @@ module.exports = class Hypercore extends EventEmitter {
91
123
  return function createFile (name) {
92
124
  const locked = name === toLock || name.endsWith('/' + toLock)
93
125
  const lock = locked ? fsctl.lock : null
94
- const sparse = locked ? null : fsctl.sparse
126
+ const sparse = locked ? null : null // fsctl.sparse, disable sparse on windows - seems to fail for some people. TODO: investigate
95
127
  return raf(name, { directory, lock, sparse })
96
128
  }
97
129
  }
@@ -104,39 +136,126 @@ module.exports = class Hypercore extends EventEmitter {
104
136
  }
105
137
 
106
138
  const Clz = opts.class || Hypercore
107
- const keyPair = opts.keyPair && opts.keyPair.secretKey && { ...opts.keyPair }
108
-
109
- // This only works if the hypercore was fully loaded,
110
- // but we only do this to validate the keypair to help catch bugs so yolo
111
- if (this.key && keyPair) keyPair.publicKey = this.key
112
-
113
139
  const s = new Clz(this.storage, this.key, {
114
140
  ...opts,
115
- sign: opts.sign || (keyPair && keyPair.secretKey && Core.createSigner(this.crypto, keyPair)) || this.sign,
116
- valueEncoding: this.valueEncoding,
117
141
  extensions: this.extensions,
118
142
  _opening: this.opening,
119
143
  _sessions: this.sessions
120
144
  })
121
145
 
122
- s._initSession(this)
146
+ s._passCapabilities(this)
123
147
  this.sessions.push(s)
124
148
 
125
149
  return s
126
150
  }
127
151
 
128
- _initSession (o) {
152
+ _passCapabilities (o) {
129
153
  if (!this.sign) this.sign = o.sign
130
154
  this.crypto = o.crypto
131
- this.opened = o.opened
132
155
  this.key = o.key
133
156
  this.discoveryKey = o.discoveryKey
134
157
  this.core = o.core
135
158
  this.replicator = o.replicator
159
+ this.encryption = o.encryption
136
160
  this.writable = !!this.sign
137
161
  this.autoClose = o.autoClose
138
162
  }
139
163
 
164
+ async _openFromExisting (from, opts) {
165
+ await from.opening
166
+
167
+ for (const [name, ext] of this.extensions) {
168
+ from.extensions.register(name, null, ext)
169
+ }
170
+
171
+ this._passCapabilities(from)
172
+ this.extensions = from.extensions
173
+ this.sessions = from.sessions
174
+ this.storage = from.storage
175
+
176
+ this.sessions.push(this)
177
+ }
178
+
179
+ async _openSession (key, storage, opts) {
180
+ const isFirst = !opts._opening
181
+
182
+ if (!isFirst) await opts._opening
183
+ if (opts.preload) opts = { ...opts, ...(await opts.preload()) }
184
+
185
+ const keyPair = (key && opts.keyPair)
186
+ ? { ...opts.keyPair, publicKey: key }
187
+ : key
188
+ ? { publicKey: key, secretKey: null }
189
+ : opts.keyPair
190
+
191
+ // This only works if the hypercore was fully loaded,
192
+ // but we only do this to validate the keypair to help catch bugs so yolo
193
+ if (this.key && keyPair) keyPair.publicKey = this.key
194
+
195
+ if (opts.sign) {
196
+ this.sign = opts.sign
197
+ } else if (keyPair && keyPair.secretKey) {
198
+ this.sign = Core.createSigner(this.crypto, keyPair)
199
+ }
200
+
201
+ if (isFirst) {
202
+ await this._openCapabilities(keyPair, storage, opts)
203
+ // Only the root session should pass capabilities to other sessions.
204
+ for (let i = 0; i < this.sessions.length; i++) {
205
+ const s = this.sessions[i]
206
+ if (s !== this) s._passCapabilities(this)
207
+ }
208
+ }
209
+
210
+ if (!this.sign) this.sign = this.core.defaultSign
211
+ this.writable = !!this.sign
212
+
213
+ if (opts.valueEncoding) {
214
+ this.valueEncoding = c.from(codecs(opts.valueEncoding))
215
+ }
216
+ if (opts.encodeBatch) {
217
+ this.encodeBatch = opts.encodeBatch
218
+ }
219
+
220
+ // This is a hidden option that's only used by Corestore.
221
+ // It's required so that corestore can load a name from userData before 'ready' is emitted.
222
+ if (opts._preready) await opts._preready(this)
223
+
224
+ this.opened = true
225
+ this.emit('ready')
226
+ }
227
+
228
+ async _openCapabilities (keyPair, storage, opts) {
229
+ if (opts.from) return this._openFromExisting(opts.from, opts)
230
+
231
+ this.storage = Hypercore.defaultStorage(opts.storage || storage)
232
+
233
+ this.core = await Core.open(this.storage, {
234
+ keyPair,
235
+ crypto: this.crypto,
236
+ onupdate: this._oncoreupdate.bind(this)
237
+ })
238
+
239
+ if (opts.userData) {
240
+ for (const [key, value] of Object.entries(opts.userData)) {
241
+ await this.core.userData(key, value)
242
+ }
243
+ }
244
+
245
+ this.replicator = new Replicator(this.core, {
246
+ onupdate: this._onpeerupdate.bind(this)
247
+ })
248
+
249
+ this.discoveryKey = this.crypto.discoveryKey(this.core.header.signer.publicKey)
250
+ this.key = this.core.header.signer.publicKey
251
+
252
+ if (!this.encryption && opts.encryptionKey) {
253
+ this.encryption = new BlockEncryption(opts.encryptionKey, this.key)
254
+ }
255
+
256
+ this.extensions.attach(this.replicator)
257
+ }
258
+
140
259
  close () {
141
260
  if (this.closing) return this.closing
142
261
  this.closing = this._close()
@@ -168,33 +287,17 @@ module.exports = class Hypercore extends EventEmitter {
168
287
  }
169
288
 
170
289
  replicate (isInitiator, opts = {}) {
171
- let outerStream = isStream(isInitiator)
172
- ? isInitiator
173
- : opts.stream
174
- let noiseStream = null
175
-
176
- if (outerStream) {
177
- noiseStream = outerStream.noiseStream
178
- } else {
179
- outerStream = Hypercore.createProtocolStream(isInitiator, opts)
180
- noiseStream = outerStream.noiseStream
181
- }
182
- if (!noiseStream) throw new Error('Invalid stream passed to replicate')
183
-
184
- if (!noiseStream.userData) {
185
- const protocol = Replicator.createProtocol(noiseStream)
186
- noiseStream.userData = protocol
187
- noiseStream.on('error', noop) // All noise errors already propagate through outerStream
188
- }
189
-
290
+ const protocolStream = Hypercore.createProtocolStream(isInitiator, opts)
291
+ const noiseStream = protocolStream.noiseStream
190
292
  const protocol = noiseStream.userData
293
+
191
294
  if (this.opened) {
192
295
  this.replicator.joinProtocol(protocol, this.key, this.discoveryKey)
193
296
  } else {
194
297
  this.opening.then(() => this.replicator.joinProtocol(protocol, this.key, this.discoveryKey), protocol.destroy.bind(protocol))
195
298
  }
196
299
 
197
- return outerStream
300
+ return protocolStream
198
301
  }
199
302
 
200
303
  get length () {
@@ -202,7 +305,7 @@ module.exports = class Hypercore extends EventEmitter {
202
305
  }
203
306
 
204
307
  get byteLength () {
205
- return this.core === null ? 0 : this.core.tree.byteLength
308
+ return this.core === null ? 0 : this.core.tree.byteLength - (this.core.tree.length * this.padding)
206
309
  }
207
310
 
208
311
  get fork () {
@@ -213,76 +316,28 @@ module.exports = class Hypercore extends EventEmitter {
213
316
  return this.replicator === null ? [] : this.replicator.peers
214
317
  }
215
318
 
216
- ready () {
217
- return this.opening
319
+ get encryptionKey () {
320
+ return this.encryption && this.encryption.key
218
321
  }
219
322
 
220
- async _open (key, storage, opts) {
221
- if (opts.preload) opts = { ...opts, ...(await opts.preload()) }
222
-
223
- this.valueEncoding = opts.valueEncoding ? c.from(codecs(opts.valueEncoding)) : null
224
-
225
- const keyPair = (key && opts.keyPair)
226
- ? { ...opts.keyPair, publicKey: key }
227
- : key
228
- ? { publicKey: key, secretKey: null }
229
- : opts.keyPair
230
-
231
- if (opts.from) {
232
- const from = opts.from
233
- await from.opening
234
- for (const [name, ext] of this.extensions) from.extensions.register(name, null, ext)
235
- this._initSession(from)
236
- this.extensions = from.extensions
237
- this.sessions = from.sessions
238
- this.storage = from.storage
239
- if (!this.sign) this.sign = opts.sign || ((keyPair && keyPair.secretKey) ? Core.createSigner(this.crypto, keyPair) : null)
240
- this.writable = !!this.sign
241
- this.sessions.push(this)
242
- return
243
- }
244
-
245
- if (!this.storage) this.storage = Hypercore.defaultStorage(opts.storage || storage)
246
-
247
- this.core = await Core.open(this.storage, {
248
- keyPair,
249
- crypto: this.crypto,
250
- onupdate: this._oncoreupdate.bind(this)
251
- })
252
-
253
- if (opts.userData) {
254
- for (const [key, value] of Object.entries(opts.userData)) {
255
- await this.core.userData(key, value)
256
- }
257
- }
258
-
259
- this.replicator = new Replicator(this.core, {
260
- onupdate: this._onpeerupdate.bind(this)
261
- })
262
-
263
- if (!this.sign) this.sign = opts.sign || this.core.defaultSign
264
-
265
- this.discoveryKey = this.crypto.discoveryKey(this.core.header.signer.publicKey)
266
- this.key = this.core.header.signer.publicKey
267
- this.writable = !!this.sign
268
-
269
- this.extensions.attach(this.replicator)
270
- this.opened = true
271
-
272
- if (opts.postload) await opts.postload(this)
323
+ get padding () {
324
+ return this.encryption === null ? 0 : this.encryption.padding
325
+ }
273
326
 
274
- for (let i = 0; i < this.sessions.length; i++) {
275
- const s = this.sessions[i]
276
- if (s !== this) s._initSession(this)
277
- s.emit('ready')
278
- }
327
+ ready () {
328
+ return this.opening
279
329
  }
280
330
 
281
331
  _oncoreupdate (status, bitfield, value, from) {
282
332
  if (status !== 0) {
283
333
  for (let i = 0; i < this.sessions.length; i++) {
284
- if ((status & 0b10) !== 0) this.sessions[i].emit('truncate', this.core.tree.fork)
285
- if ((status & 0b01) !== 0) this.sessions[i].emit('append')
334
+ if ((status & 0b10) !== 0) {
335
+ if (this.cache) this.cache.clear()
336
+ this.sessions[i].emit('truncate', this.core.tree.fork)
337
+ }
338
+ if ((status & 0b01) !== 0) {
339
+ this.sessions[i].emit('append')
340
+ }
286
341
  }
287
342
 
288
343
  this.replicator.broadcastInfo()
@@ -295,8 +350,10 @@ module.exports = class Hypercore extends EventEmitter {
295
350
  }
296
351
 
297
352
  if (value) {
353
+ const byteLength = value.byteLength - this.padding
354
+
298
355
  for (let i = 0; i < this.sessions.length; i++) {
299
- this.sessions[i].emit('download', bitfield.start, value, from)
356
+ this.sessions[i].emit('download', bitfield.start, byteLength, from)
300
357
  }
301
358
  }
302
359
  }
@@ -333,7 +390,7 @@ module.exports = class Hypercore extends EventEmitter {
333
390
  async seek (bytes) {
334
391
  if (this.opened === false) await this.opening
335
392
 
336
- const s = this.core.tree.seek(bytes)
393
+ const s = this.core.tree.seek(bytes, this.padding)
337
394
 
338
395
  return (await s.update()) || this.replicator.requestSeek(s)
339
396
  }
@@ -346,22 +403,61 @@ module.exports = class Hypercore extends EventEmitter {
346
403
 
347
404
  async get (index, opts) {
348
405
  if (this.opened === false) await this.opening
406
+ const c = this.cache && this.cache.get(index)
407
+ if (c) return c
408
+ const fork = this.core.tree.fork
409
+ const b = await this._get(index, opts)
410
+ if (this.cache && fork === this.core.tree.fork && b) this.cache.set(index, b)
411
+ return b
412
+ }
413
+
414
+ async _get (index, opts) {
349
415
  const encoding = (opts && opts.valueEncoding && c.from(codecs(opts.valueEncoding))) || this.valueEncoding
350
416
 
351
- if (this.core.bitfield.get(index)) return decode(encoding, await this.core.blocks.get(index))
352
- if (opts && opts.onwait) opts.onwait(index)
417
+ let block
353
418
 
354
- return decode(encoding, await this.replicator.requestBlock(index))
419
+ if (this.core.bitfield.get(index)) {
420
+ block = await this.core.blocks.get(index)
421
+ } else {
422
+ if (opts && opts.wait === false) return null
423
+ if (opts && opts.onwait) opts.onwait(index)
424
+ block = await this.replicator.requestBlock(index)
425
+ }
426
+
427
+ if (this.encryption) this.encryption.decrypt(index, block)
428
+ return this._decode(encoding, block)
429
+ }
430
+
431
+ createReadStream (opts) {
432
+ return new ReadStream(this, opts)
433
+ }
434
+
435
+ createWriteStream (opts) {
436
+ return new WriteStream(this, opts)
355
437
  }
356
438
 
357
439
  download (range) {
358
- const start = (range && range.start) || 0
359
- const end = typeof (range && range.end) === 'number' ? range.end : -1 // download all
360
440
  const linear = !!(range && range.linear)
361
441
 
362
- // TODO: support range.blocks
442
+ let start
443
+ let end
444
+ let filter
445
+
446
+ if (range && range.blocks) {
447
+ const blocks = range.blocks instanceof Set
448
+ ? range.blocks
449
+ : new Set(range.blocks)
450
+
451
+ start = range.start || (blocks.size ? min(range.blocks) : 0)
452
+ end = range.end || (blocks.size ? max(range.blocks) + 1 : 0)
453
+
454
+ filter = (i) => blocks.has(i)
455
+ } else {
456
+ start = (range && range.start) || 0
457
+ end = typeof (range && range.end) === 'number' ? range.end : -1 // download all
458
+ }
363
459
 
364
- const r = Replicator.createRange(start, end, linear)
460
+ const r = Replicator.createRange(start, end, filter, linear)
365
461
 
366
462
  if (this.opened) this.replicator.addRange(r)
367
463
  else this.opening.then(() => this.replicator.addRange(r), noop)
@@ -394,22 +490,19 @@ module.exports = class Hypercore extends EventEmitter {
394
490
  if (this.opened === false) await this.opening
395
491
  if (this.writable === false) throw new Error('Core is not writable')
396
492
 
397
- const blks = Array.isArray(blocks) ? blocks : [blocks]
398
- const buffers = new Array(blks.length)
493
+ blocks = Array.isArray(blocks) ? blocks : [blocks]
399
494
 
400
- for (let i = 0; i < blks.length; i++) {
401
- const blk = blks[i]
495
+ const preappend = this.encryption && this._preappend
402
496
 
403
- const buf = Buffer.isBuffer(blk)
404
- ? blk
405
- : this.valueEncoding
406
- ? c.encode(this.valueEncoding, blk)
407
- : Buffer.from(blk)
497
+ const buffers = this.encodeBatch !== null ? this.encodeBatch(blocks) : new Array(blocks.length)
408
498
 
409
- buffers[i] = buf
499
+ if (this.encodeBatch === null) {
500
+ for (let i = 0; i < blocks.length; i++) {
501
+ buffers[i] = this._encode(this.valueEncoding, blocks[i])
502
+ }
410
503
  }
411
504
 
412
- return await this.core.append(buffers, this.sign)
505
+ return await this.core.append(buffers, this.sign, { preappend })
413
506
  }
414
507
 
415
508
  registerExtension (name, handlers) {
@@ -420,14 +513,38 @@ module.exports = class Hypercore extends EventEmitter {
420
513
  onextensionupdate () {
421
514
  if (this.replicator !== null) this.replicator.broadcastOptions()
422
515
  }
423
- }
424
516
 
425
- function noop () {}
517
+ _encode (enc, val) {
518
+ const state = { start: this.padding, end: this.padding, buffer: null }
519
+
520
+ if (b4a.isBuffer(val)) {
521
+ if (state.start === 0) return val
522
+ state.end += val.byteLength
523
+ } else if (enc) {
524
+ enc.preencode(state, val)
525
+ } else {
526
+ val = b4a.from(val)
527
+ if (state.start === 0) return val
528
+ state.end += val.byteLength
529
+ }
426
530
 
427
- function decode (enc, buf) {
428
- return enc ? c.decode(enc, buf) : buf
531
+ state.buffer = b4a.allocUnsafe(state.end)
532
+
533
+ if (enc) enc.encode(state, val)
534
+ else state.buffer.set(val, state.start)
535
+
536
+ return state.buffer
537
+ }
538
+
539
+ _decode (enc, block) {
540
+ block = block.subarray(this.padding)
541
+ if (enc) return c.decode(enc, block)
542
+ return block
543
+ }
429
544
  }
430
545
 
546
+ function noop () {}
547
+
431
548
  function isStream (s) {
432
549
  return typeof s === 'object' && s && typeof s.pipe === 'function'
433
550
  }
@@ -441,5 +558,27 @@ function requireMaybe (name) {
441
558
  }
442
559
 
443
560
  function toHex (buf) {
444
- return buf && buf.toString('hex')
561
+ return buf && b4a.toString(buf, 'hex')
562
+ }
563
+
564
+ function reduce (iter, fn, acc) {
565
+ for (const item of iter) acc = fn(acc, item)
566
+ return acc
567
+ }
568
+
569
+ function min (arr) {
570
+ return reduce(arr, (a, b) => Math.min(a, b), Infinity)
571
+ }
572
+
573
+ function max (arr) {
574
+ return reduce(arr, (a, b) => Math.max(a, b), -Infinity)
575
+ }
576
+
577
+ function preappend (blocks) {
578
+ const offset = this.core.tree.length
579
+ const fork = this.core.tree.fork
580
+
581
+ for (let i = 0; i < blocks.length; i++) {
582
+ this.encryption.encrypt(offset + i, blocks[i], fork)
583
+ }
445
584
  }
package/lib/bitfield.js CHANGED
@@ -1,6 +1,7 @@
1
1
  // TODO: needs massive improvements obvs
2
2
 
3
3
  const BigSparseArray = require('big-sparse-array')
4
+ const b4a = require('b4a')
4
5
 
5
6
  class FixedBitfield {
6
7
  constructor (index, bitfield) {
@@ -116,9 +117,9 @@ module.exports = class Bitfield {
116
117
  let error = null
117
118
 
118
119
  for (const page of this.unflushed) {
119
- const b = Buffer.from(page.bitfield.buffer, page.bitfield.byteOffset, page.bitfield.byteLength)
120
+ const buf = b4a.from(page.bitfield.buffer, page.bitfield.byteOffset, page.bitfield.byteLength)
120
121
  page.dirty = false
121
- this.storage.write(page.index * 4096, b, done)
122
+ this.storage.write(page.index * 4096, buf, done)
122
123
  }
123
124
 
124
125
  function done (err) {