hypercore 10.0.0-alpha.2 → 10.0.0-alpha.23

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, opts)
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,128 @@ 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
+ createIfMissing: opts.createIfMissing,
235
+ overwrite: opts.overwrite,
236
+ keyPair,
237
+ crypto: this.crypto,
238
+ onupdate: this._oncoreupdate.bind(this)
239
+ })
240
+
241
+ if (opts.userData) {
242
+ for (const [key, value] of Object.entries(opts.userData)) {
243
+ await this.core.userData(key, value)
244
+ }
245
+ }
246
+
247
+ this.replicator = new Replicator(this.core, {
248
+ onupdate: this._onpeerupdate.bind(this)
249
+ })
250
+
251
+ this.discoveryKey = this.crypto.discoveryKey(this.core.header.signer.publicKey)
252
+ this.key = this.core.header.signer.publicKey
253
+
254
+ if (!this.encryption && opts.encryptionKey) {
255
+ this.encryption = new BlockEncryption(opts.encryptionKey, this.key)
256
+ }
257
+
258
+ this.extensions.attach(this.replicator)
259
+ }
260
+
140
261
  close () {
141
262
  if (this.closing) return this.closing
142
263
  this.closing = this._close()
@@ -168,33 +289,17 @@ module.exports = class Hypercore extends EventEmitter {
168
289
  }
169
290
 
170
291
  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
-
292
+ const protocolStream = Hypercore.createProtocolStream(isInitiator, opts)
293
+ const noiseStream = protocolStream.noiseStream
190
294
  const protocol = noiseStream.userData
295
+
191
296
  if (this.opened) {
192
297
  this.replicator.joinProtocol(protocol, this.key, this.discoveryKey)
193
298
  } else {
194
299
  this.opening.then(() => this.replicator.joinProtocol(protocol, this.key, this.discoveryKey), protocol.destroy.bind(protocol))
195
300
  }
196
301
 
197
- return outerStream
302
+ return protocolStream
198
303
  }
199
304
 
200
305
  get length () {
@@ -202,7 +307,7 @@ module.exports = class Hypercore extends EventEmitter {
202
307
  }
203
308
 
204
309
  get byteLength () {
205
- return this.core === null ? 0 : this.core.tree.byteLength
310
+ return this.core === null ? 0 : this.core.tree.byteLength - (this.core.tree.length * this.padding)
206
311
  }
207
312
 
208
313
  get fork () {
@@ -213,76 +318,28 @@ module.exports = class Hypercore extends EventEmitter {
213
318
  return this.replicator === null ? [] : this.replicator.peers
214
319
  }
215
320
 
216
- ready () {
217
- return this.opening
321
+ get encryptionKey () {
322
+ return this.encryption && this.encryption.key
218
323
  }
219
324
 
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)
325
+ get padding () {
326
+ return this.encryption === null ? 0 : this.encryption.padding
327
+ }
273
328
 
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
- }
329
+ ready () {
330
+ return this.opening
279
331
  }
280
332
 
281
333
  _oncoreupdate (status, bitfield, value, from) {
282
334
  if (status !== 0) {
283
335
  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')
336
+ if ((status & 0b10) !== 0) {
337
+ if (this.cache) this.cache.clear()
338
+ this.sessions[i].emit('truncate', this.core.tree.fork)
339
+ }
340
+ if ((status & 0b01) !== 0) {
341
+ this.sessions[i].emit('append')
342
+ }
286
343
  }
287
344
 
288
345
  this.replicator.broadcastInfo()
@@ -295,8 +352,10 @@ module.exports = class Hypercore extends EventEmitter {
295
352
  }
296
353
 
297
354
  if (value) {
355
+ const byteLength = value.byteLength - this.padding
356
+
298
357
  for (let i = 0; i < this.sessions.length; i++) {
299
- this.sessions[i].emit('download', bitfield.start, value, from)
358
+ this.sessions[i].emit('download', bitfield.start, byteLength, from)
300
359
  }
301
360
  }
302
361
  }
@@ -333,7 +392,7 @@ module.exports = class Hypercore extends EventEmitter {
333
392
  async seek (bytes) {
334
393
  if (this.opened === false) await this.opening
335
394
 
336
- const s = this.core.tree.seek(bytes)
395
+ const s = this.core.tree.seek(bytes, this.padding)
337
396
 
338
397
  return (await s.update()) || this.replicator.requestSeek(s)
339
398
  }
@@ -346,22 +405,61 @@ module.exports = class Hypercore extends EventEmitter {
346
405
 
347
406
  async get (index, opts) {
348
407
  if (this.opened === false) await this.opening
408
+ const c = this.cache && this.cache.get(index)
409
+ if (c) return c
410
+ const fork = this.core.tree.fork
411
+ const b = await this._get(index, opts)
412
+ if (this.cache && fork === this.core.tree.fork && b) this.cache.set(index, b)
413
+ return b
414
+ }
415
+
416
+ async _get (index, opts) {
349
417
  const encoding = (opts && opts.valueEncoding && c.from(codecs(opts.valueEncoding))) || this.valueEncoding
350
418
 
351
- if (this.core.bitfield.get(index)) return decode(encoding, await this.core.blocks.get(index))
352
- if (opts && opts.onwait) opts.onwait(index)
419
+ let block
353
420
 
354
- return decode(encoding, await this.replicator.requestBlock(index))
421
+ if (this.core.bitfield.get(index)) {
422
+ block = await this.core.blocks.get(index)
423
+ } else {
424
+ if (opts && opts.wait === false) return null
425
+ if (opts && opts.onwait) opts.onwait(index)
426
+ block = await this.replicator.requestBlock(index)
427
+ }
428
+
429
+ if (this.encryption) this.encryption.decrypt(index, block)
430
+ return this._decode(encoding, block)
431
+ }
432
+
433
+ createReadStream (opts) {
434
+ return new ReadStream(this, opts)
435
+ }
436
+
437
+ createWriteStream (opts) {
438
+ return new WriteStream(this, opts)
355
439
  }
356
440
 
357
441
  download (range) {
358
- const start = (range && range.start) || 0
359
- const end = typeof (range && range.end) === 'number' ? range.end : -1 // download all
360
442
  const linear = !!(range && range.linear)
361
443
 
362
- // TODO: support range.blocks
444
+ let start
445
+ let end
446
+ let filter
447
+
448
+ if (range && range.blocks) {
449
+ const blocks = range.blocks instanceof Set
450
+ ? range.blocks
451
+ : new Set(range.blocks)
452
+
453
+ start = range.start || (blocks.size ? min(range.blocks) : 0)
454
+ end = range.end || (blocks.size ? max(range.blocks) + 1 : 0)
455
+
456
+ filter = (i) => blocks.has(i)
457
+ } else {
458
+ start = (range && range.start) || 0
459
+ end = typeof (range && range.end) === 'number' ? range.end : -1 // download all
460
+ }
363
461
 
364
- const r = Replicator.createRange(start, end, linear)
462
+ const r = Replicator.createRange(start, end, filter, linear)
365
463
 
366
464
  if (this.opened) this.replicator.addRange(r)
367
465
  else this.opening.then(() => this.replicator.addRange(r), noop)
@@ -394,22 +492,19 @@ module.exports = class Hypercore extends EventEmitter {
394
492
  if (this.opened === false) await this.opening
395
493
  if (this.writable === false) throw new Error('Core is not writable')
396
494
 
397
- const blks = Array.isArray(blocks) ? blocks : [blocks]
398
- const buffers = new Array(blks.length)
495
+ blocks = Array.isArray(blocks) ? blocks : [blocks]
399
496
 
400
- for (let i = 0; i < blks.length; i++) {
401
- const blk = blks[i]
497
+ const preappend = this.encryption && this._preappend
402
498
 
403
- const buf = Buffer.isBuffer(blk)
404
- ? blk
405
- : this.valueEncoding
406
- ? c.encode(this.valueEncoding, blk)
407
- : Buffer.from(blk)
499
+ const buffers = this.encodeBatch !== null ? this.encodeBatch(blocks) : new Array(blocks.length)
408
500
 
409
- buffers[i] = buf
501
+ if (this.encodeBatch === null) {
502
+ for (let i = 0; i < blocks.length; i++) {
503
+ buffers[i] = this._encode(this.valueEncoding, blocks[i])
504
+ }
410
505
  }
411
506
 
412
- return await this.core.append(buffers, this.sign)
507
+ return await this.core.append(buffers, this.sign, { preappend })
413
508
  }
414
509
 
415
510
  registerExtension (name, handlers) {
@@ -420,14 +515,38 @@ module.exports = class Hypercore extends EventEmitter {
420
515
  onextensionupdate () {
421
516
  if (this.replicator !== null) this.replicator.broadcastOptions()
422
517
  }
423
- }
424
518
 
425
- function noop () {}
519
+ _encode (enc, val) {
520
+ const state = { start: this.padding, end: this.padding, buffer: null }
521
+
522
+ if (b4a.isBuffer(val)) {
523
+ if (state.start === 0) return val
524
+ state.end += val.byteLength
525
+ } else if (enc) {
526
+ enc.preencode(state, val)
527
+ } else {
528
+ val = b4a.from(val)
529
+ if (state.start === 0) return val
530
+ state.end += val.byteLength
531
+ }
426
532
 
427
- function decode (enc, buf) {
428
- return enc ? c.decode(enc, buf) : buf
533
+ state.buffer = b4a.allocUnsafe(state.end)
534
+
535
+ if (enc) enc.encode(state, val)
536
+ else state.buffer.set(val, state.start)
537
+
538
+ return state.buffer
539
+ }
540
+
541
+ _decode (enc, block) {
542
+ block = block.subarray(this.padding)
543
+ if (enc) return c.decode(enc, block)
544
+ return block
545
+ }
429
546
  }
430
547
 
548
+ function noop () {}
549
+
431
550
  function isStream (s) {
432
551
  return typeof s === 'object' && s && typeof s.pipe === 'function'
433
552
  }
@@ -441,5 +560,27 @@ function requireMaybe (name) {
441
560
  }
442
561
 
443
562
  function toHex (buf) {
444
- return buf && buf.toString('hex')
563
+ return buf && b4a.toString(buf, 'hex')
564
+ }
565
+
566
+ function reduce (iter, fn, acc) {
567
+ for (const item of iter) acc = fn(acc, item)
568
+ return acc
569
+ }
570
+
571
+ function min (arr) {
572
+ return reduce(arr, (a, b) => Math.min(a, b), Infinity)
573
+ }
574
+
575
+ function max (arr) {
576
+ return reduce(arr, (a, b) => Math.max(a, b), -Infinity)
577
+ }
578
+
579
+ function preappend (blocks) {
580
+ const offset = this.core.tree.length
581
+ const fork = this.core.tree.fork
582
+
583
+ for (let i = 0; i < blocks.length; i++) {
584
+ this.encryption.encrypt(offset + i, blocks[i], fork)
585
+ }
445
586
  }
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) {