hypercore 10.0.0-alpha.5 → 10.0.0-alpha.50
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 +68 -5
- package/index.js +594 -173
- package/lib/bitfield.js +9 -5
- package/lib/block-encryption.js +68 -0
- package/lib/block-store.js +3 -1
- package/lib/caps.js +32 -0
- package/lib/core.js +88 -27
- package/lib/errors.js +50 -0
- package/lib/merkle-tree.js +186 -113
- package/lib/messages.js +249 -168
- package/lib/oplog.js +4 -3
- package/lib/replicator.js +1385 -616
- package/lib/streams.js +56 -0
- package/package.json +18 -9
- package/.github/workflows/test-node.yml +0 -23
- package/CHANGELOG.md +0 -37
- package/UPGRADE.md +0 -9
- package/examples/announce.js +0 -19
- package/examples/basic.js +0 -10
- package/examples/http.js +0 -123
- package/examples/lookup.js +0 -20
- package/lib/extensions.js +0 -76
- package/lib/protocol.js +0 -524
- package/lib/random-iterator.js +0 -46
- package/test/basic.js +0 -78
- package/test/bitfield.js +0 -71
- package/test/core.js +0 -290
- package/test/encodings.js +0 -18
- package/test/extension.js +0 -71
- package/test/helpers/index.js +0 -23
- package/test/merkle-tree.js +0 -518
- package/test/mutex.js +0 -137
- package/test/oplog.js +0 -399
- package/test/preload.js +0 -72
- package/test/replicate.js +0 -333
- package/test/sessions.js +0 -173
- package/test/user-data.js +0 -47
package/index.js
CHANGED
|
@@ -3,15 +3,19 @@ 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')
|
|
6
7
|
const Xache = require('xache')
|
|
7
8
|
const NoiseSecretStream = require('@hyperswarm/secret-stream')
|
|
9
|
+
const Protomux = require('protomux')
|
|
8
10
|
const codecs = require('codecs')
|
|
9
11
|
|
|
10
12
|
const fsctl = requireMaybe('fsctl') || { lock: noop, sparse: noop }
|
|
11
13
|
|
|
12
14
|
const Replicator = require('./lib/replicator')
|
|
13
|
-
const Extensions = require('./lib/extensions')
|
|
14
15
|
const Core = require('./lib/core')
|
|
16
|
+
const BlockEncryption = require('./lib/block-encryption')
|
|
17
|
+
const { ReadStream, WriteStream } = require('./lib/streams')
|
|
18
|
+
const { BAD_ARGUMENT, SESSION_CLOSED, SESSION_NOT_WRITABLE, SNAPSHOT_NOT_AVAILABLE } = require('./lib/errors')
|
|
15
19
|
|
|
16
20
|
const promises = Symbol.for('hypercore.promises')
|
|
17
21
|
const inspect = Symbol.for('nodejs.util.inspect.custom')
|
|
@@ -28,14 +32,17 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
28
32
|
opts = key
|
|
29
33
|
key = null
|
|
30
34
|
}
|
|
35
|
+
|
|
31
36
|
if (key && typeof key === 'string') {
|
|
32
|
-
key =
|
|
33
|
-
}
|
|
34
|
-
if (key && key.byteLength !== 32) {
|
|
35
|
-
throw new Error('Hypercore key should be 32 bytes')
|
|
37
|
+
key = b4a.from(key, 'hex')
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
if (!opts) opts = {}
|
|
41
|
+
|
|
42
|
+
if (!opts.crypto && key && key.byteLength !== 32) {
|
|
43
|
+
throw BAD_ARGUMENT('Hypercore key should be 32 bytes')
|
|
44
|
+
}
|
|
45
|
+
|
|
39
46
|
if (!storage) storage = opts.storage
|
|
40
47
|
|
|
41
48
|
this[promises] = true
|
|
@@ -44,23 +51,34 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
44
51
|
this.crypto = opts.crypto || hypercoreCrypto
|
|
45
52
|
this.core = null
|
|
46
53
|
this.replicator = null
|
|
47
|
-
this.
|
|
54
|
+
this.encryption = null
|
|
55
|
+
this.extensions = new Map()
|
|
48
56
|
this.cache = opts.cache === true ? new Xache({ maxSize: 65536, maxAge: 0 }) : (opts.cache || null)
|
|
49
57
|
|
|
50
58
|
this.valueEncoding = null
|
|
59
|
+
this.encodeBatch = null
|
|
60
|
+
this.activeRequests = []
|
|
61
|
+
|
|
51
62
|
this.key = key || null
|
|
52
|
-
this.
|
|
63
|
+
this.keyPair = null
|
|
53
64
|
this.readable = true
|
|
54
65
|
this.writable = false
|
|
55
66
|
this.opened = false
|
|
56
67
|
this.closed = false
|
|
68
|
+
this.snapshotted = !!opts.snapshot
|
|
69
|
+
this.sparse = opts.sparse !== false
|
|
57
70
|
this.sessions = opts._sessions || [this]
|
|
58
|
-
this.
|
|
71
|
+
this.auth = opts.auth || null
|
|
59
72
|
this.autoClose = !!opts.autoClose
|
|
73
|
+
this.onwait = opts.onwait || null
|
|
60
74
|
|
|
61
75
|
this.closing = null
|
|
62
|
-
this.opening =
|
|
76
|
+
this.opening = this._openSession(key, storage, opts)
|
|
63
77
|
this.opening.catch(noop)
|
|
78
|
+
|
|
79
|
+
this._preappend = preappend.bind(this)
|
|
80
|
+
this._snapshot = null
|
|
81
|
+
this._findingPeers = 0
|
|
64
82
|
}
|
|
65
83
|
|
|
66
84
|
[inspect] (depth, opts) {
|
|
@@ -69,21 +87,78 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
69
87
|
while (indent.length < opts.indentationLvl) indent += ' '
|
|
70
88
|
}
|
|
71
89
|
|
|
90
|
+
let peers = ''
|
|
91
|
+
const min = Math.min(this.peers.length, 5)
|
|
92
|
+
|
|
93
|
+
for (let i = 0; i < min; i++) {
|
|
94
|
+
const peer = this.peers[i]
|
|
95
|
+
|
|
96
|
+
peers += indent + ' Peer(\n'
|
|
97
|
+
peers += indent + ' remotePublicKey: ' + opts.stylize(toHex(peer.remotePublicKey), 'string') + '\n'
|
|
98
|
+
peers += indent + ' remoteLength: ' + opts.stylize(peer.remoteLength, 'number') + '\n'
|
|
99
|
+
peers += indent + ' remoteFork: ' + opts.stylize(peer.remoteFork, 'number') + '\n'
|
|
100
|
+
peers += indent + ' remoteCanUpgrade: ' + opts.stylize(peer.remoteCanUpgrade, 'boolean') + '\n'
|
|
101
|
+
peers += indent + ' )' + '\n'
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (this.peers.length > 5) {
|
|
105
|
+
peers += indent + ' ... and ' + (this.peers.length - 5) + ' more\n'
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (peers) peers = '[\n' + peers + indent + ' ]'
|
|
109
|
+
else peers = '[ ' + opts.stylize(0, 'number') + ' ]'
|
|
110
|
+
|
|
72
111
|
return this.constructor.name + '(\n' +
|
|
73
|
-
indent + ' key: ' + opts.stylize(
|
|
112
|
+
indent + ' key: ' + opts.stylize(toHex(this.key), 'string') + '\n' +
|
|
74
113
|
indent + ' discoveryKey: ' + opts.stylize(toHex(this.discoveryKey), 'string') + '\n' +
|
|
75
114
|
indent + ' opened: ' + opts.stylize(this.opened, 'boolean') + '\n' +
|
|
115
|
+
indent + ' closed: ' + opts.stylize(this.closed, 'boolean') + '\n' +
|
|
116
|
+
indent + ' snapshotted: ' + opts.stylize(this.snapshotted, 'boolean') + '\n' +
|
|
117
|
+
indent + ' sparse: ' + opts.stylize(this.sparse, 'boolean') + '\n' +
|
|
76
118
|
indent + ' writable: ' + opts.stylize(this.writable, 'boolean') + '\n' +
|
|
77
|
-
indent + ' sessions: ' + opts.stylize(this.sessions.length, 'number') + '\n' +
|
|
78
|
-
indent + ' peers: [ ' + opts.stylize(this.peers.length, 'number') + ' ]\n' +
|
|
79
119
|
indent + ' length: ' + opts.stylize(this.length, 'number') + '\n' +
|
|
80
120
|
indent + ' byteLength: ' + opts.stylize(this.byteLength, 'number') + '\n' +
|
|
121
|
+
indent + ' fork: ' + opts.stylize(this.fork, 'number') + '\n' +
|
|
122
|
+
indent + ' sessions: [ ' + opts.stylize(this.sessions.length, 'number') + ' ]\n' +
|
|
123
|
+
indent + ' activeRequests: [ ' + opts.stylize(this.activeRequests.length, 'number') + ' ]\n' +
|
|
124
|
+
indent + ' peers: ' + peers + '\n' +
|
|
81
125
|
indent + ')'
|
|
82
126
|
}
|
|
83
127
|
|
|
84
|
-
static
|
|
85
|
-
|
|
86
|
-
|
|
128
|
+
static getProtocolMuxer (stream) {
|
|
129
|
+
return stream.noiseStream.userData
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
static createProtocolStream (isInitiator, opts = {}) {
|
|
133
|
+
let outerStream = Protomux.isProtomux(isInitiator)
|
|
134
|
+
? isInitiator.stream
|
|
135
|
+
: isStream(isInitiator)
|
|
136
|
+
? isInitiator
|
|
137
|
+
: opts.stream
|
|
138
|
+
|
|
139
|
+
let noiseStream = null
|
|
140
|
+
|
|
141
|
+
if (outerStream) {
|
|
142
|
+
noiseStream = outerStream.noiseStream
|
|
143
|
+
} else {
|
|
144
|
+
noiseStream = new NoiseSecretStream(isInitiator, null, opts)
|
|
145
|
+
outerStream = noiseStream.rawStream
|
|
146
|
+
}
|
|
147
|
+
if (!noiseStream) throw BAD_ARGUMENT('Invalid stream')
|
|
148
|
+
|
|
149
|
+
if (!noiseStream.userData) {
|
|
150
|
+
const protocol = new Protomux(noiseStream)
|
|
151
|
+
|
|
152
|
+
if (opts.ondiscoverykey) {
|
|
153
|
+
protocol.pair({ protocol: 'hypercore/alpha' }, opts.ondiscoverykey)
|
|
154
|
+
}
|
|
155
|
+
if (opts.keepAlive !== false) {
|
|
156
|
+
noiseStream.setKeepAlive(5000)
|
|
157
|
+
}
|
|
158
|
+
noiseStream.userData = protocol
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return outerStream
|
|
87
162
|
}
|
|
88
163
|
|
|
89
164
|
static defaultStorage (storage, opts = {}) {
|
|
@@ -98,45 +173,187 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
98
173
|
}
|
|
99
174
|
}
|
|
100
175
|
|
|
176
|
+
snapshot (opts) {
|
|
177
|
+
return this.session({ ...opts, snapshot: true })
|
|
178
|
+
}
|
|
179
|
+
|
|
101
180
|
session (opts = {}) {
|
|
102
181
|
if (this.closing) {
|
|
103
182
|
// This makes the closing logic alot easier. If this turns out to be a problem
|
|
104
183
|
// in practive, open an issue and we'll try to make a solution for it.
|
|
105
|
-
throw
|
|
184
|
+
throw SESSION_CLOSED('Cannot make sessions on a closing core')
|
|
106
185
|
}
|
|
107
186
|
|
|
187
|
+
const sparse = opts.sparse === false ? false : this.sparse
|
|
188
|
+
const onwait = opts.onwait === undefined ? this.onwait : opts.onwait
|
|
108
189
|
const Clz = opts.class || Hypercore
|
|
109
|
-
const keyPair = opts.keyPair && opts.keyPair.secretKey && { ...opts.keyPair }
|
|
110
|
-
|
|
111
|
-
// This only works if the hypercore was fully loaded,
|
|
112
|
-
// but we only do this to validate the keypair to help catch bugs so yolo
|
|
113
|
-
if (this.key && keyPair) keyPair.publicKey = this.key
|
|
114
|
-
|
|
115
190
|
const s = new Clz(this.storage, this.key, {
|
|
116
191
|
...opts,
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
extensions: this.extensions,
|
|
192
|
+
sparse,
|
|
193
|
+
onwait,
|
|
120
194
|
_opening: this.opening,
|
|
121
195
|
_sessions: this.sessions
|
|
122
196
|
})
|
|
123
197
|
|
|
124
|
-
s.
|
|
198
|
+
s._passCapabilities(this)
|
|
199
|
+
|
|
200
|
+
// Configure the cache unless explicitly disabled.
|
|
201
|
+
if (opts.cache !== false) {
|
|
202
|
+
s.cache = opts.cache === true || !opts.cache ? this.cache : opts.cache
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
ensureEncryption(s, opts)
|
|
206
|
+
|
|
125
207
|
this.sessions.push(s)
|
|
126
208
|
|
|
127
209
|
return s
|
|
128
210
|
}
|
|
129
211
|
|
|
130
|
-
|
|
131
|
-
if (!this.
|
|
212
|
+
_passCapabilities (o) {
|
|
213
|
+
if (!this.auth) this.auth = o.auth
|
|
214
|
+
|
|
132
215
|
this.crypto = o.crypto
|
|
133
|
-
this.opened = o.opened
|
|
134
216
|
this.key = o.key
|
|
135
|
-
this.discoveryKey = o.discoveryKey
|
|
136
217
|
this.core = o.core
|
|
137
218
|
this.replicator = o.replicator
|
|
138
|
-
this.
|
|
219
|
+
this.encryption = o.encryption
|
|
220
|
+
this.writable = !!(this.auth && this.auth.sign)
|
|
139
221
|
this.autoClose = o.autoClose
|
|
222
|
+
|
|
223
|
+
if (this.snapshotted && this.core && !this._snapshot) this._updateSnapshot()
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
async _openFromExisting (from, opts) {
|
|
227
|
+
await from.opening
|
|
228
|
+
|
|
229
|
+
this._passCapabilities(from)
|
|
230
|
+
this.sessions = from.sessions
|
|
231
|
+
this.storage = from.storage
|
|
232
|
+
this.replicator.findingPeers += this._findingPeers
|
|
233
|
+
|
|
234
|
+
ensureEncryption(this, opts)
|
|
235
|
+
|
|
236
|
+
this.sessions.push(this)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
async _openSession (key, storage, opts) {
|
|
240
|
+
const isFirst = !opts._opening
|
|
241
|
+
|
|
242
|
+
if (!isFirst) await opts._opening
|
|
243
|
+
if (opts.preload) opts = { ...opts, ...(await opts.preload()) }
|
|
244
|
+
|
|
245
|
+
const keyPair = (key && opts.keyPair)
|
|
246
|
+
? { ...opts.keyPair, publicKey: key }
|
|
247
|
+
: key
|
|
248
|
+
? { publicKey: key, secretKey: null }
|
|
249
|
+
: opts.keyPair
|
|
250
|
+
|
|
251
|
+
// This only works if the hypercore was fully loaded,
|
|
252
|
+
// but we only do this to validate the keypair to help catch bugs so yolo
|
|
253
|
+
if (this.key && keyPair) keyPair.publicKey = this.key
|
|
254
|
+
|
|
255
|
+
if (opts.auth) {
|
|
256
|
+
this.auth = opts.auth
|
|
257
|
+
} else if (opts.sign) {
|
|
258
|
+
this.auth = Core.createAuth(this.crypto, keyPair, opts)
|
|
259
|
+
} else if (keyPair && keyPair.secretKey) {
|
|
260
|
+
this.auth = Core.createAuth(this.crypto, keyPair)
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (isFirst) {
|
|
264
|
+
await this._openCapabilities(keyPair, storage, opts)
|
|
265
|
+
// Only the root session should pass capabilities to other sessions.
|
|
266
|
+
for (let i = 0; i < this.sessions.length; i++) {
|
|
267
|
+
const s = this.sessions[i]
|
|
268
|
+
if (s !== this) s._passCapabilities(this)
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (!this.auth) this.auth = this.core.defaultAuth
|
|
273
|
+
this.writable = !!this.auth.sign
|
|
274
|
+
|
|
275
|
+
if (opts.valueEncoding) {
|
|
276
|
+
this.valueEncoding = c.from(codecs(opts.valueEncoding))
|
|
277
|
+
}
|
|
278
|
+
if (opts.encodeBatch) {
|
|
279
|
+
this.encodeBatch = opts.encodeBatch
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Start continous replication if not in sparse mode.
|
|
283
|
+
if (!this.sparse) this.download({ start: 0, end: -1 })
|
|
284
|
+
|
|
285
|
+
// This is a hidden option that's only used by Corestore.
|
|
286
|
+
// It's required so that corestore can load a name from userData before 'ready' is emitted.
|
|
287
|
+
if (opts._preready) await opts._preready(this)
|
|
288
|
+
|
|
289
|
+
this.opened = true
|
|
290
|
+
this.emit('ready')
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
async _openCapabilities (keyPair, storage, opts) {
|
|
294
|
+
if (opts.from) return this._openFromExisting(opts.from, opts)
|
|
295
|
+
|
|
296
|
+
this.storage = Hypercore.defaultStorage(opts.storage || storage)
|
|
297
|
+
|
|
298
|
+
this.core = await Core.open(this.storage, {
|
|
299
|
+
force: opts.force,
|
|
300
|
+
createIfMissing: opts.createIfMissing,
|
|
301
|
+
overwrite: opts.overwrite,
|
|
302
|
+
keyPair,
|
|
303
|
+
crypto: this.crypto,
|
|
304
|
+
legacy: opts.legacy,
|
|
305
|
+
auth: opts.auth,
|
|
306
|
+
onupdate: this._oncoreupdate.bind(this),
|
|
307
|
+
oncontigupdate: this._oncorecontigupdate.bind(this)
|
|
308
|
+
})
|
|
309
|
+
|
|
310
|
+
if (opts.userData) {
|
|
311
|
+
for (const [key, value] of Object.entries(opts.userData)) {
|
|
312
|
+
await this.core.userData(key, value)
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
this.key = this.core.header.signer.publicKey
|
|
317
|
+
this.keyPair = this.core.header.signer
|
|
318
|
+
|
|
319
|
+
this.replicator = new Replicator(this.core, this.key, {
|
|
320
|
+
eagerUpdate: true,
|
|
321
|
+
allowFork: opts.allowFork !== false,
|
|
322
|
+
onpeerupdate: this._onpeerupdate.bind(this),
|
|
323
|
+
onupload: this._onupload.bind(this)
|
|
324
|
+
})
|
|
325
|
+
|
|
326
|
+
this.replicator.findingPeers += this._findingPeers
|
|
327
|
+
|
|
328
|
+
if (!this.encryption && opts.encryptionKey) {
|
|
329
|
+
this.encryption = new BlockEncryption(opts.encryptionKey, this.key)
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
_getSnapshot () {
|
|
334
|
+
if (this.sparse) {
|
|
335
|
+
return {
|
|
336
|
+
length: this.core.tree.length,
|
|
337
|
+
byteLength: this.core.tree.byteLength,
|
|
338
|
+
fork: this.core.tree.fork,
|
|
339
|
+
compatLength: this.core.tree.length
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
return {
|
|
344
|
+
length: this.core.header.contiguousLength,
|
|
345
|
+
byteLength: 0,
|
|
346
|
+
fork: this.core.tree.fork,
|
|
347
|
+
compatLength: this.core.header.contiguousLength
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
_updateSnapshot () {
|
|
352
|
+
const prev = this._snapshot
|
|
353
|
+
const next = this._snapshot = this._getSnapshot()
|
|
354
|
+
|
|
355
|
+
if (!prev) return true
|
|
356
|
+
return prev.length !== next.length || prev.fork !== next.fork
|
|
140
357
|
}
|
|
141
358
|
|
|
142
359
|
close () {
|
|
@@ -155,6 +372,20 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
155
372
|
this.readable = false
|
|
156
373
|
this.writable = false
|
|
157
374
|
this.closed = true
|
|
375
|
+
this.opened = false
|
|
376
|
+
|
|
377
|
+
const gc = []
|
|
378
|
+
for (const ext of this.extensions.values()) {
|
|
379
|
+
if (ext.session === this) gc.push(ext)
|
|
380
|
+
}
|
|
381
|
+
for (const ext of gc) ext.destroy()
|
|
382
|
+
|
|
383
|
+
if (this.replicator !== null) {
|
|
384
|
+
this.replicator.findingPeers -= this._findingPeers
|
|
385
|
+
this.replicator.clearRequests(this.activeRequests)
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
this._findingPeers = 0
|
|
158
389
|
|
|
159
390
|
if (this.sessions.length) {
|
|
160
391
|
// if this is the last session and we are auto closing, trigger that first to enforce error handling
|
|
@@ -170,41 +401,60 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
170
401
|
}
|
|
171
402
|
|
|
172
403
|
replicate (isInitiator, opts = {}) {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
let noiseStream = null
|
|
404
|
+
// Only limitation here is that ondiscoverykey doesn't work atm when passing a muxer directly,
|
|
405
|
+
// because it doesn't really make a lot of sense.
|
|
406
|
+
if (Protomux.isProtomux(isInitiator)) return this._attachToMuxer(isInitiator, opts)
|
|
177
407
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
outerStream = Hypercore.createProtocolStream(isInitiator, opts)
|
|
182
|
-
noiseStream = outerStream.noiseStream
|
|
183
|
-
}
|
|
184
|
-
if (!noiseStream) throw new Error('Invalid stream passed to replicate')
|
|
408
|
+
const protocolStream = Hypercore.createProtocolStream(isInitiator, opts)
|
|
409
|
+
const noiseStream = protocolStream.noiseStream
|
|
410
|
+
const protocol = noiseStream.userData
|
|
185
411
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
412
|
+
this._attachToMuxer(protocol, opts)
|
|
413
|
+
|
|
414
|
+
return protocolStream
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
_attachToMuxer (mux, opts) {
|
|
418
|
+
// If the user wants to, we can make this replication run in a session
|
|
419
|
+
// that way the core wont close "under them" during replication
|
|
420
|
+
if (opts.session) {
|
|
421
|
+
const s = this.session()
|
|
422
|
+
mux.stream.on('close', () => s.close().catch(noop))
|
|
190
423
|
}
|
|
191
424
|
|
|
192
|
-
const protocol = noiseStream.userData
|
|
193
425
|
if (this.opened) {
|
|
194
|
-
this.replicator.
|
|
426
|
+
this.replicator.attachTo(mux)
|
|
195
427
|
} else {
|
|
196
|
-
this.opening.then(() => this.replicator.
|
|
428
|
+
this.opening.then(() => this.replicator.attachTo(mux), mux.destroy.bind(mux))
|
|
197
429
|
}
|
|
198
430
|
|
|
199
|
-
return
|
|
431
|
+
return mux
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
get discoveryKey () {
|
|
435
|
+
return this.replicator === null ? null : this.replicator.discoveryKey
|
|
200
436
|
}
|
|
201
437
|
|
|
202
438
|
get length () {
|
|
203
|
-
|
|
439
|
+
if (this._snapshot) return this._snapshot.length
|
|
440
|
+
if (this.core === null) return 0
|
|
441
|
+
if (!this.sparse) return this.contiguousLength
|
|
442
|
+
return this.core.tree.length
|
|
204
443
|
}
|
|
205
444
|
|
|
206
445
|
get byteLength () {
|
|
207
|
-
|
|
446
|
+
if (this._snapshot) return this._snapshot.byteLength
|
|
447
|
+
if (this.core === null) return 0
|
|
448
|
+
if (!this.sparse) return this.contiguousByteLength
|
|
449
|
+
return this.core.tree.byteLength - (this.core.tree.length * this.padding)
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
get contiguousLength () {
|
|
453
|
+
return this.core === null ? 0 : this.core.header.contiguousLength
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
get contiguousByteLength () {
|
|
457
|
+
return 0
|
|
208
458
|
}
|
|
209
459
|
|
|
210
460
|
get fork () {
|
|
@@ -215,105 +465,88 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
215
465
|
return this.replicator === null ? [] : this.replicator.peers
|
|
216
466
|
}
|
|
217
467
|
|
|
218
|
-
|
|
219
|
-
return this.
|
|
468
|
+
get encryptionKey () {
|
|
469
|
+
return this.encryption && this.encryption.key
|
|
220
470
|
}
|
|
221
471
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
this.valueEncoding = opts.valueEncoding ? c.from(codecs(opts.valueEncoding)) : null
|
|
226
|
-
|
|
227
|
-
const keyPair = (key && opts.keyPair)
|
|
228
|
-
? { ...opts.keyPair, publicKey: key }
|
|
229
|
-
: key
|
|
230
|
-
? { publicKey: key, secretKey: null }
|
|
231
|
-
: opts.keyPair
|
|
232
|
-
|
|
233
|
-
if (opts.from) {
|
|
234
|
-
const from = opts.from
|
|
235
|
-
await from.opening
|
|
236
|
-
for (const [name, ext] of this.extensions) from.extensions.register(name, null, ext)
|
|
237
|
-
this._initSession(from)
|
|
238
|
-
this.extensions = from.extensions
|
|
239
|
-
this.sessions = from.sessions
|
|
240
|
-
this.storage = from.storage
|
|
241
|
-
if (!this.sign) this.sign = opts.sign || ((keyPair && keyPair.secretKey) ? Core.createSigner(this.crypto, keyPair) : null)
|
|
242
|
-
this.writable = !!this.sign
|
|
243
|
-
this.sessions.push(this)
|
|
244
|
-
return
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
if (!this.storage) this.storage = Hypercore.defaultStorage(opts.storage || storage)
|
|
248
|
-
|
|
249
|
-
this.core = await Core.open(this.storage, {
|
|
250
|
-
keyPair,
|
|
251
|
-
crypto: this.crypto,
|
|
252
|
-
onupdate: this._oncoreupdate.bind(this)
|
|
253
|
-
})
|
|
254
|
-
|
|
255
|
-
if (opts.userData) {
|
|
256
|
-
for (const [key, value] of Object.entries(opts.userData)) {
|
|
257
|
-
await this.core.userData(key, value)
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
this.replicator = new Replicator(this.core, {
|
|
262
|
-
onupdate: this._onpeerupdate.bind(this)
|
|
263
|
-
})
|
|
264
|
-
|
|
265
|
-
if (!this.sign) this.sign = opts.sign || this.core.defaultSign
|
|
266
|
-
|
|
267
|
-
this.discoveryKey = this.crypto.discoveryKey(this.core.header.signer.publicKey)
|
|
268
|
-
this.key = this.core.header.signer.publicKey
|
|
269
|
-
this.writable = !!this.sign
|
|
472
|
+
get padding () {
|
|
473
|
+
return this.encryption === null ? 0 : this.encryption.padding
|
|
474
|
+
}
|
|
270
475
|
|
|
271
|
-
|
|
272
|
-
this.
|
|
476
|
+
ready () {
|
|
477
|
+
return this.opening
|
|
478
|
+
}
|
|
273
479
|
|
|
274
|
-
|
|
480
|
+
_onupload (index, value, from) {
|
|
481
|
+
const byteLength = value.byteLength - this.padding
|
|
275
482
|
|
|
276
483
|
for (let i = 0; i < this.sessions.length; i++) {
|
|
277
|
-
|
|
278
|
-
if (s !== this) s._initSession(this)
|
|
279
|
-
s.emit('ready')
|
|
484
|
+
this.sessions[i].emit('upload', index, byteLength, from)
|
|
280
485
|
}
|
|
281
486
|
}
|
|
282
487
|
|
|
283
488
|
_oncoreupdate (status, bitfield, value, from) {
|
|
284
489
|
if (status !== 0) {
|
|
490
|
+
const truncated = (status & 0b10) !== 0
|
|
491
|
+
const appended = (status & 0b01) !== 0
|
|
492
|
+
|
|
493
|
+
if (truncated) {
|
|
494
|
+
this.replicator.ontruncate(bitfield.start)
|
|
495
|
+
}
|
|
496
|
+
|
|
285
497
|
for (let i = 0; i < this.sessions.length; i++) {
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
498
|
+
const s = this.sessions[i]
|
|
499
|
+
|
|
500
|
+
if (truncated) {
|
|
501
|
+
if (s.cache) s.cache.clear()
|
|
502
|
+
s.emit('truncate', bitfield.start, this.core.tree.fork)
|
|
503
|
+
// If snapshotted, make sure to update our compat so we can fail gets
|
|
504
|
+
if (s._snapshot && bitfield.start < s._snapshot.compatLength) s._snapshot.compatLength = bitfield.start
|
|
292
505
|
}
|
|
506
|
+
|
|
507
|
+
// For sparse sessions, immediately emit appends. Non-sparse sessions
|
|
508
|
+
// are handled separately and only emit appends when their contiguous
|
|
509
|
+
// length is updated.
|
|
510
|
+
if (appended && s.sparse) s.emit('append')
|
|
293
511
|
}
|
|
294
512
|
|
|
295
|
-
this.replicator.
|
|
513
|
+
this.replicator.onupgrade()
|
|
296
514
|
}
|
|
297
515
|
|
|
298
|
-
if (bitfield
|
|
299
|
-
|
|
300
|
-
this.replicator.broadcastBlock(bitfield.start + i)
|
|
301
|
-
}
|
|
516
|
+
if (bitfield) {
|
|
517
|
+
this.replicator.onhave(bitfield.start, bitfield.length, bitfield.drop)
|
|
302
518
|
}
|
|
303
519
|
|
|
304
520
|
if (value) {
|
|
521
|
+
const byteLength = value.byteLength - this.padding
|
|
522
|
+
|
|
305
523
|
for (let i = 0; i < this.sessions.length; i++) {
|
|
306
|
-
this.sessions[i].emit('download', bitfield.start,
|
|
524
|
+
this.sessions[i].emit('download', bitfield.start, byteLength, from)
|
|
307
525
|
}
|
|
308
526
|
}
|
|
309
527
|
}
|
|
310
528
|
|
|
529
|
+
_oncorecontigupdate () {
|
|
530
|
+
// For non-sparse sessions, emit appends only when the contiguous length is
|
|
531
|
+
// updated.
|
|
532
|
+
for (let i = 0; i < this.sessions.length; i++) {
|
|
533
|
+
const s = this.sessions[i]
|
|
534
|
+
|
|
535
|
+
if (!s.sparse) s.emit('append')
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
311
539
|
_onpeerupdate (added, peer) {
|
|
312
|
-
if (added) this.extensions.update(peer)
|
|
313
540
|
const name = added ? 'peer-add' : 'peer-remove'
|
|
314
541
|
|
|
315
542
|
for (let i = 0; i < this.sessions.length; i++) {
|
|
316
543
|
this.sessions[i].emit(name, peer)
|
|
544
|
+
|
|
545
|
+
if (added) {
|
|
546
|
+
for (const ext of this.sessions[i].extensions.values()) {
|
|
547
|
+
peer.extensions.set(ext.name, ext)
|
|
548
|
+
}
|
|
549
|
+
}
|
|
317
550
|
}
|
|
318
551
|
}
|
|
319
552
|
|
|
@@ -330,19 +563,67 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
330
563
|
return null
|
|
331
564
|
}
|
|
332
565
|
|
|
333
|
-
|
|
566
|
+
findingPeers () {
|
|
567
|
+
this._findingPeers++
|
|
568
|
+
if (this.replicator !== null && !this.closing) this.replicator.findingPeers++
|
|
569
|
+
|
|
570
|
+
let once = true
|
|
571
|
+
|
|
572
|
+
return () => {
|
|
573
|
+
if (this.closing || !once) return
|
|
574
|
+
once = false
|
|
575
|
+
this._findingPeers--
|
|
576
|
+
if (this.replicator !== null && --this.replicator.findingPeers === 0) {
|
|
577
|
+
this.replicator.updateAll()
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
async update (opts) {
|
|
334
583
|
if (this.opened === false) await this.opening
|
|
584
|
+
if (this.closing !== null) return false
|
|
585
|
+
|
|
335
586
|
// TODO: add an option where a writer can bootstrap it's state from the network also
|
|
336
|
-
if (this.writable)
|
|
337
|
-
|
|
587
|
+
if (this.writable) {
|
|
588
|
+
if (!this.snapshotted) return false
|
|
589
|
+
return this._updateSnapshot()
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
const activeRequests = (opts && opts.activeRequests) || this.activeRequests
|
|
593
|
+
const req = this.replicator.addUpgrade(activeRequests)
|
|
594
|
+
|
|
595
|
+
let upgraded = await req.promise
|
|
596
|
+
|
|
597
|
+
if (!this.sparse) {
|
|
598
|
+
// Download all available blocks in non-sparse mode
|
|
599
|
+
const start = this.length
|
|
600
|
+
const end = this.core.tree.length
|
|
601
|
+
const contig = this.contiguousLength
|
|
602
|
+
|
|
603
|
+
await this.download({ start, end, ifAvailable: true }).downloaded()
|
|
604
|
+
|
|
605
|
+
if (!upgraded) upgraded = this.contiguousLength !== contig
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
if (!upgraded) return false
|
|
609
|
+
if (this.snapshotted) return this._updateSnapshot()
|
|
610
|
+
return true
|
|
338
611
|
}
|
|
339
612
|
|
|
340
|
-
async seek (bytes) {
|
|
613
|
+
async seek (bytes, opts) {
|
|
341
614
|
if (this.opened === false) await this.opening
|
|
342
615
|
|
|
343
|
-
const s = this.core.tree.seek(bytes)
|
|
616
|
+
const s = this.core.tree.seek(bytes, this.padding)
|
|
617
|
+
|
|
618
|
+
const offset = await s.update()
|
|
619
|
+
if (offset) return offset
|
|
620
|
+
|
|
621
|
+
if (this.closing !== null) throw SESSION_CLOSED()
|
|
344
622
|
|
|
345
|
-
|
|
623
|
+
const activeRequests = (opts && opts.activeRequests) || this.activeRequests
|
|
624
|
+
const req = this.replicator.addSeek(activeRequests, s)
|
|
625
|
+
|
|
626
|
+
return req.promise
|
|
346
627
|
}
|
|
347
628
|
|
|
348
629
|
async has (index) {
|
|
@@ -353,41 +634,93 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
353
634
|
|
|
354
635
|
async get (index, opts) {
|
|
355
636
|
if (this.opened === false) await this.opening
|
|
356
|
-
|
|
357
|
-
if (
|
|
358
|
-
|
|
359
|
-
const
|
|
360
|
-
|
|
361
|
-
|
|
637
|
+
if (this.closing !== null) throw SESSION_CLOSED()
|
|
638
|
+
if (this._snapshot !== null && index >= this._snapshot.compatLength) throw SNAPSHOT_NOT_AVAILABLE()
|
|
639
|
+
|
|
640
|
+
const encoding = (opts && opts.valueEncoding && c.from(codecs(opts.valueEncoding))) || this.valueEncoding
|
|
641
|
+
|
|
642
|
+
let req = this.cache && this.cache.get(index)
|
|
643
|
+
if (!req) req = this._get(index, opts)
|
|
644
|
+
|
|
645
|
+
let block = await req
|
|
646
|
+
if (!block) return null
|
|
647
|
+
|
|
648
|
+
if (this.encryption) {
|
|
649
|
+
// Copy the block as it might be shared with other sessions.
|
|
650
|
+
block = b4a.from(block)
|
|
651
|
+
|
|
652
|
+
this.encryption.decrypt(index, block)
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
return this._decode(encoding, block)
|
|
362
656
|
}
|
|
363
657
|
|
|
364
658
|
async _get (index, opts) {
|
|
365
|
-
|
|
659
|
+
let req
|
|
366
660
|
|
|
367
|
-
if (this.core.bitfield.get(index))
|
|
368
|
-
|
|
661
|
+
if (this.core.bitfield.get(index)) {
|
|
662
|
+
req = this.core.blocks.get(index)
|
|
369
663
|
|
|
370
|
-
|
|
664
|
+
if (this.cache) this.cache.set(index, req)
|
|
665
|
+
} else {
|
|
666
|
+
if (opts && opts.wait === false) return null
|
|
667
|
+
if (opts && opts.onwait) opts.onwait(index, this)
|
|
668
|
+
else if (this.onwait) this.onwait(index, this)
|
|
669
|
+
|
|
670
|
+
const activeRequests = (opts && opts.activeRequests) || this.activeRequests
|
|
671
|
+
|
|
672
|
+
req = this._cacheOnResolve(
|
|
673
|
+
index,
|
|
674
|
+
this.replicator
|
|
675
|
+
.addBlock(activeRequests, index)
|
|
676
|
+
.promise,
|
|
677
|
+
this.core.tree.fork
|
|
678
|
+
)
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
return req
|
|
371
682
|
}
|
|
372
683
|
|
|
373
|
-
|
|
374
|
-
const
|
|
375
|
-
const end = typeof (range && range.end) === 'number' ? range.end : -1 // download all
|
|
376
|
-
const linear = !!(range && range.linear)
|
|
684
|
+
async _cacheOnResolve (index, req, fork) {
|
|
685
|
+
const block = await req
|
|
377
686
|
|
|
378
|
-
|
|
687
|
+
if (this.cache && fork === this.core.tree.fork) {
|
|
688
|
+
this.cache.set(index, Promise.resolve(block))
|
|
689
|
+
}
|
|
379
690
|
|
|
380
|
-
|
|
691
|
+
return block
|
|
692
|
+
}
|
|
381
693
|
|
|
382
|
-
|
|
383
|
-
|
|
694
|
+
createReadStream (opts) {
|
|
695
|
+
return new ReadStream(this, opts)
|
|
696
|
+
}
|
|
384
697
|
|
|
385
|
-
|
|
698
|
+
createWriteStream (opts) {
|
|
699
|
+
return new WriteStream(this, opts)
|
|
386
700
|
}
|
|
387
701
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
702
|
+
download (range) {
|
|
703
|
+
const reqP = this._download(range)
|
|
704
|
+
|
|
705
|
+
// do not crash in the background...
|
|
706
|
+
reqP.catch(noop)
|
|
707
|
+
|
|
708
|
+
// TODO: turn this into an actual object...
|
|
709
|
+
return {
|
|
710
|
+
async downloaded () {
|
|
711
|
+
const req = await reqP
|
|
712
|
+
return req.promise
|
|
713
|
+
},
|
|
714
|
+
destroy () {
|
|
715
|
+
reqP.then(req => req.context && req.context.detach(req), noop)
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
async _download (range) {
|
|
721
|
+
if (this.opened === false) await this.opening
|
|
722
|
+
const activeRequests = (range && range.activeRequests) || this.activeRequests
|
|
723
|
+
return this.replicator.addRange(activeRequests, range)
|
|
391
724
|
}
|
|
392
725
|
|
|
393
726
|
// TODO: get rid of this / deprecate it?
|
|
@@ -395,12 +728,17 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
395
728
|
range.destroy(null)
|
|
396
729
|
}
|
|
397
730
|
|
|
731
|
+
// TODO: get rid of this / deprecate it?
|
|
732
|
+
cancel (request) {
|
|
733
|
+
// Do nothing for now
|
|
734
|
+
}
|
|
735
|
+
|
|
398
736
|
async truncate (newLength = 0, fork = -1) {
|
|
399
737
|
if (this.opened === false) await this.opening
|
|
400
|
-
if (this.writable === false) throw
|
|
738
|
+
if (this.writable === false) throw SESSION_NOT_WRITABLE()
|
|
401
739
|
|
|
402
740
|
if (fork === -1) fork = this.core.tree.fork + 1
|
|
403
|
-
await this.core.truncate(newLength, fork, this.
|
|
741
|
+
await this.core.truncate(newLength, fork, this.auth)
|
|
404
742
|
|
|
405
743
|
// TODO: Should propagate from an event triggered by the oplog
|
|
406
744
|
this.replicator.updateAll()
|
|
@@ -408,42 +746,108 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
408
746
|
|
|
409
747
|
async append (blocks) {
|
|
410
748
|
if (this.opened === false) await this.opening
|
|
411
|
-
if (this.writable === false) throw
|
|
749
|
+
if (this.writable === false) throw SESSION_NOT_WRITABLE()
|
|
412
750
|
|
|
413
|
-
|
|
414
|
-
const buffers = new Array(blks.length)
|
|
751
|
+
blocks = Array.isArray(blocks) ? blocks : [blocks]
|
|
415
752
|
|
|
416
|
-
|
|
417
|
-
const blk = blks[i]
|
|
753
|
+
const preappend = this.encryption && this._preappend
|
|
418
754
|
|
|
419
|
-
|
|
420
|
-
? blk
|
|
421
|
-
: this.valueEncoding
|
|
422
|
-
? c.encode(this.valueEncoding, blk)
|
|
423
|
-
: Buffer.from(blk)
|
|
755
|
+
const buffers = this.encodeBatch !== null ? this.encodeBatch(blocks) : new Array(blocks.length)
|
|
424
756
|
|
|
425
|
-
|
|
757
|
+
if (this.encodeBatch === null) {
|
|
758
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
759
|
+
buffers[i] = this._encode(this.valueEncoding, blocks[i])
|
|
760
|
+
}
|
|
426
761
|
}
|
|
427
762
|
|
|
428
|
-
return await this.core.append(buffers, this.
|
|
763
|
+
return await this.core.append(buffers, this.auth, { preappend })
|
|
429
764
|
}
|
|
430
765
|
|
|
431
|
-
|
|
432
|
-
|
|
766
|
+
async treeHash (length) {
|
|
767
|
+
if (length === undefined) {
|
|
768
|
+
await this.ready()
|
|
769
|
+
length = this.core.length
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
const roots = await this.core.tree.getRoots(length)
|
|
773
|
+
return this.crypto.tree(roots)
|
|
433
774
|
}
|
|
434
775
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
776
|
+
registerExtension (name, handlers = {}) {
|
|
777
|
+
if (this.extensions.has(name)) {
|
|
778
|
+
const ext = this.extensions.get(name)
|
|
779
|
+
ext.handlers = handlers
|
|
780
|
+
ext.encoding = c.from(codecs(handlers.encoding) || c.buffer)
|
|
781
|
+
ext.session = this
|
|
782
|
+
return ext
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
const ext = {
|
|
786
|
+
name,
|
|
787
|
+
handlers,
|
|
788
|
+
encoding: c.from(codecs(handlers.encoding) || c.buffer),
|
|
789
|
+
session: this,
|
|
790
|
+
send (message, peer) {
|
|
791
|
+
const buffer = c.encode(this.encoding, message)
|
|
792
|
+
peer.extension(name, buffer)
|
|
793
|
+
},
|
|
794
|
+
broadcast (message) {
|
|
795
|
+
const buffer = c.encode(this.encoding, message)
|
|
796
|
+
for (const peer of this.session.peers) {
|
|
797
|
+
peer.extension(name, buffer)
|
|
798
|
+
}
|
|
799
|
+
},
|
|
800
|
+
destroy () {
|
|
801
|
+
for (const peer of this.session.peers) {
|
|
802
|
+
if (peer.extensions.get(name) === ext) peer.extensions.delete(name)
|
|
803
|
+
}
|
|
804
|
+
this.session.extensions.delete(name)
|
|
805
|
+
},
|
|
806
|
+
_onmessage (state, peer) {
|
|
807
|
+
const m = this.encoding.decode(state)
|
|
808
|
+
if (this.handlers.onmessage) this.handlers.onmessage(m, peer)
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
this.extensions.set(name, ext)
|
|
813
|
+
for (const peer of this.peers) {
|
|
814
|
+
peer.extensions.set(name, ext)
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
return ext
|
|
438
818
|
}
|
|
439
|
-
}
|
|
440
819
|
|
|
441
|
-
|
|
820
|
+
_encode (enc, val) {
|
|
821
|
+
const state = { start: this.padding, end: this.padding, buffer: null }
|
|
442
822
|
|
|
443
|
-
|
|
444
|
-
|
|
823
|
+
if (b4a.isBuffer(val)) {
|
|
824
|
+
if (state.start === 0) return val
|
|
825
|
+
state.end += val.byteLength
|
|
826
|
+
} else if (enc) {
|
|
827
|
+
enc.preencode(state, val)
|
|
828
|
+
} else {
|
|
829
|
+
val = b4a.from(val)
|
|
830
|
+
if (state.start === 0) return val
|
|
831
|
+
state.end += val.byteLength
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
state.buffer = b4a.allocUnsafe(state.end)
|
|
835
|
+
|
|
836
|
+
if (enc) enc.encode(state, val)
|
|
837
|
+
else state.buffer.set(val, state.start)
|
|
838
|
+
|
|
839
|
+
return state.buffer
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
_decode (enc, block) {
|
|
843
|
+
if (this.padding) block = block.subarray(this.padding)
|
|
844
|
+
if (enc) return c.decode(enc, block)
|
|
845
|
+
return block
|
|
846
|
+
}
|
|
445
847
|
}
|
|
446
848
|
|
|
849
|
+
function noop () {}
|
|
850
|
+
|
|
447
851
|
function isStream (s) {
|
|
448
852
|
return typeof s === 'object' && s && typeof s.pipe === 'function'
|
|
449
853
|
}
|
|
@@ -457,5 +861,22 @@ function requireMaybe (name) {
|
|
|
457
861
|
}
|
|
458
862
|
|
|
459
863
|
function toHex (buf) {
|
|
460
|
-
return buf &&
|
|
864
|
+
return buf && b4a.toString(buf, 'hex')
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
function preappend (blocks) {
|
|
868
|
+
const offset = this.core.tree.length
|
|
869
|
+
const fork = this.core.tree.fork
|
|
870
|
+
|
|
871
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
872
|
+
this.encryption.encrypt(offset + i, blocks[i], fork)
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
function ensureEncryption (core, opts) {
|
|
877
|
+
if (!opts.encryptionKey) return
|
|
878
|
+
// Only override the block encryption if its either not already set or if
|
|
879
|
+
// the caller provided a different key.
|
|
880
|
+
if (core.encryption && b4a.equals(core.encryption.key, opts.encryptionKey)) return
|
|
881
|
+
core.encryption = new BlockEncryption(opts.encryptionKey, core.key)
|
|
461
882
|
}
|