hypercore 9.12.0 → 10.0.0-alpha.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/.github/workflows/test-node.yml +3 -4
  2. package/README.md +131 -404
  3. package/__snapshots__/test/storage.js.snapshot.cjs +15 -0
  4. package/examples/announce.js +19 -0
  5. package/examples/basic.js +10 -0
  6. package/examples/http.js +123 -0
  7. package/examples/lookup.js +20 -0
  8. package/index.js +365 -1600
  9. package/lib/bitfield.js +113 -285
  10. package/lib/block-encryption.js +68 -0
  11. package/lib/block-store.js +58 -0
  12. package/lib/core.js +468 -0
  13. package/lib/extensions.js +76 -0
  14. package/lib/merkle-tree.js +1110 -0
  15. package/lib/messages.js +571 -0
  16. package/lib/mutex.js +39 -0
  17. package/lib/oplog.js +224 -0
  18. package/lib/protocol.js +525 -0
  19. package/lib/random-iterator.js +46 -0
  20. package/lib/remote-bitfield.js +24 -0
  21. package/lib/replicator.js +857 -0
  22. package/lib/streams.js +39 -0
  23. package/package.json +44 -45
  24. package/test/basic.js +59 -471
  25. package/test/bitfield.js +48 -133
  26. package/test/core.js +290 -0
  27. package/test/encodings.js +18 -0
  28. package/test/encryption.js +123 -0
  29. package/test/extension.js +71 -0
  30. package/test/helpers/index.js +23 -0
  31. package/test/merkle-tree.js +518 -0
  32. package/test/mutex.js +137 -0
  33. package/test/oplog.js +399 -0
  34. package/test/preload.js +72 -0
  35. package/test/replicate.js +227 -824
  36. package/test/sessions.js +173 -0
  37. package/test/storage.js +31 -0
  38. package/test/streams.js +39 -146
  39. package/test/user-data.js +47 -0
  40. package/bench/all.sh +0 -65
  41. package/bench/copy-64kb-blocks.js +0 -51
  42. package/bench/helpers/read-throttled.js +0 -27
  43. package/bench/helpers/read.js +0 -47
  44. package/bench/helpers/write.js +0 -29
  45. package/bench/read-16kb-blocks-proof-throttled.js +0 -1
  46. package/bench/read-16kb-blocks-proof.js +0 -1
  47. package/bench/read-16kb-blocks-throttled.js +0 -1
  48. package/bench/read-16kb-blocks.js +0 -1
  49. package/bench/read-512b-blocks.js +0 -1
  50. package/bench/read-64kb-blocks-linear-batch.js +0 -18
  51. package/bench/read-64kb-blocks-linear.js +0 -18
  52. package/bench/read-64kb-blocks-proof.js +0 -1
  53. package/bench/read-64kb-blocks.js +0 -1
  54. package/bench/replicate-16kb-blocks.js +0 -19
  55. package/bench/replicate-64kb-blocks.js +0 -19
  56. package/bench/write-16kb-blocks.js +0 -1
  57. package/bench/write-512b-blocks.js +0 -1
  58. package/bench/write-64kb-blocks-static.js +0 -1
  59. package/bench/write-64kb-blocks.js +0 -1
  60. package/example.js +0 -23
  61. package/lib/cache.js +0 -26
  62. package/lib/crypto.js +0 -5
  63. package/lib/replicate.js +0 -829
  64. package/lib/safe-buffer-equals.js +0 -6
  65. package/lib/storage.js +0 -421
  66. package/lib/tree-index.js +0 -183
  67. package/test/ack.js +0 -306
  68. package/test/audit.js +0 -36
  69. package/test/cache.js +0 -93
  70. package/test/compat.js +0 -209
  71. package/test/copy.js +0 -377
  72. package/test/default-storage.js +0 -51
  73. package/test/extensions.js +0 -137
  74. package/test/get.js +0 -64
  75. package/test/head.js +0 -65
  76. package/test/helpers/create-tracking-ram.js +0 -27
  77. package/test/helpers/create.js +0 -6
  78. package/test/helpers/replicate.js +0 -4
  79. package/test/seek.js +0 -234
  80. package/test/selections.js +0 -95
  81. package/test/set-uploading-downloading.js +0 -91
  82. package/test/stats.js +0 -77
  83. package/test/timeouts.js +0 -22
  84. package/test/tree-index.js +0 -841
  85. package/test/update.js +0 -156
  86. package/test/value-encoding.js +0 -52
@@ -0,0 +1,525 @@
1
+ const { uint, from: fromEncoding } = require('compact-encoding')
2
+ const b4a = require('b4a')
3
+ const safetyCatch = require('safety-catch')
4
+ const codecs = require('codecs')
5
+
6
+ const messages = require('./messages')
7
+
8
+ class Extension {
9
+ constructor (protocol, type, name, handlers) {
10
+ this.protocol = protocol
11
+ this.name = name
12
+ this.type = type
13
+ this.peers = new Set()
14
+ this.aliased = !!handlers.aliased
15
+ this.remoteSupports = false
16
+ this.destroyed = false
17
+
18
+ this.onerror = handlers.onerror || noop
19
+ this.onclose = handlers.onclose || noop
20
+ this.onmessage = handlers.onmessage || noop
21
+ this.onremotesupports = handlers.onremotesupports || noop
22
+
23
+ this.encoding = fromEncoding(codecs(handlers.encoding || 'binary'))
24
+ this.announce()
25
+ }
26
+
27
+ announce () {
28
+ if (this.destroyed) return
29
+
30
+ this.protocol.send(1, messages.extension, -1, { alias: this.type, name: this.name })
31
+ }
32
+
33
+ send (message) {
34
+ if (this.destroyed) return
35
+
36
+ return this._sendAlias(message, -1)
37
+ }
38
+
39
+ _sendAlias (message, alias) {
40
+ if (this.destroyed) return
41
+
42
+ if (this._remoteAliases) {
43
+ return this.protocol.send(this.type, this.encoding, alias, message)
44
+ }
45
+
46
+ this.protocol.cork()
47
+ this.announce()
48
+ this.protocol.send(this.type, this.encoding, alias, message)
49
+ this.protocol.uncork()
50
+
51
+ return false
52
+ }
53
+
54
+ _onremotesupports () {
55
+ if (this.destroyed) return
56
+
57
+ this.remoteSupports = true
58
+ this.onremotesupports(this)
59
+ for (const peer of this.peers) {
60
+ peer.onremotesupports(peer)
61
+ }
62
+ }
63
+
64
+ _onmessage (state) {
65
+ if (this.destroyed) return
66
+
67
+ if (!this.aliased) {
68
+ this.onmessage(this.encoding.decode(state))
69
+ return
70
+ }
71
+
72
+ const alias = uint.decode(state)
73
+ const m = this.encoding.decode(state)
74
+
75
+ for (const peer of this.peers) {
76
+ if (peer.alias === alias) {
77
+ peer.onmessage(m, peer.peer)
78
+ }
79
+ }
80
+ }
81
+
82
+ destroy () {
83
+ if (this.destroyed) return
84
+ this.destroyed = true
85
+ this.protocol.unregisterExtension(this.name)
86
+ this.onclose()
87
+ }
88
+ }
89
+
90
+ class CoreExtension {
91
+ constructor (ext, peer, name, handlers) {
92
+ this.extension = ext
93
+ this.peer = peer
94
+ this.name = name
95
+ this.alias = peer.alias
96
+ this.onmessage = handlers.onmessage || noop
97
+ this.onremotesupports = handlers.onremotesupports || noop
98
+ }
99
+
100
+ get remoteSupports () {
101
+ return this.extension.remoteSupports
102
+ }
103
+
104
+ announce () {
105
+ this.extension.announce()
106
+ }
107
+
108
+ send (message) {
109
+ return this.extension._sendAlias(message, this.peer.alias)
110
+ }
111
+
112
+ destroy () {
113
+ this.peer.extensions.delete(this.name)
114
+ this.extension.peers.delete(this)
115
+ }
116
+ }
117
+
118
+ class Peer {
119
+ constructor (protocol, alias, key, discoveryKey, handlers, state) {
120
+ this.protocol = protocol
121
+ this.handlers = handlers
122
+ this.key = key
123
+ this.discoveryKey = discoveryKey
124
+ this.alias = alias
125
+ this.remoteAlias = -1
126
+ this.resend = false
127
+ this.state = state
128
+ this.extensions = new Map()
129
+ this.destroyed = false
130
+
131
+ this._destroyer = this._safeDestroy.bind(this)
132
+ }
133
+
134
+ onmessage (type, state) {
135
+ const handlers = this.handlers
136
+
137
+ switch (type) {
138
+ case 4: {
139
+ this._catch(handlers.oninfo(messages.info.decode(state), this))
140
+ break
141
+ }
142
+
143
+ case 5: {
144
+ // options
145
+ break
146
+ }
147
+
148
+ case 6: {
149
+ // want
150
+ break
151
+ }
152
+
153
+ case 7: {
154
+ this._catch(handlers.onhave(messages.have.decode(state), this))
155
+ break
156
+ }
157
+
158
+ case 8: {
159
+ this._catch(handlers.onbitfield(messages.bitfield.decode(state), this))
160
+ break
161
+ }
162
+
163
+ case 9: {
164
+ this._catch(handlers.onrequest(messages.request.decode(state), this))
165
+ break
166
+ }
167
+
168
+ case 10: {
169
+ this._catch(handlers.ondata(messages.data.decode(state), this))
170
+ break
171
+ }
172
+ }
173
+
174
+ state.start = state.end
175
+ }
176
+
177
+ _catch (p) {
178
+ if (isPromise(p)) p.then(noop, this._destroyer)
179
+ }
180
+
181
+ registerExtension (name, handlers) {
182
+ if (this.extensions.has(name)) return this.extensions.get(name)
183
+ const ext = this.protocol.registerExtension(name, { aliased: true, encoding: handlers.encoding })
184
+ const coreExt = new CoreExtension(ext, this, name, handlers)
185
+ ext.peers.add(coreExt)
186
+ this.extensions.set(name, coreExt)
187
+ return coreExt
188
+ }
189
+
190
+ cork () {
191
+ this.protocol.cork()
192
+ }
193
+
194
+ uncork () {
195
+ this.protocol.uncork()
196
+ }
197
+
198
+ info (message) {
199
+ return this.protocol.send(4, messages.info, this.alias, message)
200
+ }
201
+
202
+ options (message) {
203
+ // TODO
204
+ // this._send(5, messages.info, this.alias, message)
205
+ }
206
+
207
+ want (message) {
208
+ // TODO
209
+ // this._send(6, messages.info, this.alias, message)
210
+ }
211
+
212
+ have (message) {
213
+ return this.protocol.send(7, messages.have, this.alias, message)
214
+ }
215
+
216
+ bitfield (message) {
217
+ return this.protocol.send(8, messages.bitfield, this.alias, message)
218
+ }
219
+
220
+ request (message) {
221
+ return this.protocol.send(9, messages.request, this.alias, message)
222
+ }
223
+
224
+ data (message) {
225
+ return this.protocol.send(10, messages.data, this.alias, message)
226
+ }
227
+
228
+ _safeDestroy (err) {
229
+ safetyCatch(err)
230
+ return this.destroy(err)
231
+ }
232
+
233
+ destroy (err) {
234
+ this.destroyed = true
235
+ return this.protocol.unregisterPeer(this, err)
236
+ }
237
+ }
238
+
239
+ module.exports = class Protocol {
240
+ constructor (noiseStream, handlers = {}) {
241
+ this.noiseStream = noiseStream
242
+
243
+ this.protocolVersion = handlers.protocolVersion || 0
244
+ this.userAgent = handlers.userAgent || ''
245
+ this.remoteUserAgent = ''
246
+ this.handlers = handlers
247
+
248
+ this._firstMessage = true
249
+ this._corks = 1
250
+ this._batch = []
251
+
252
+ this._localAliases = 0
253
+ this._remoteAliases = []
254
+ this._peers = new Map()
255
+
256
+ this._localExtensions = 128
257
+ this._remoteExtensions = []
258
+ this._extensions = new Map()
259
+
260
+ this._destroyer = this._safeDestroy.bind(this)
261
+ this.noiseStream.on('data', this.onmessage.bind(this))
262
+ this.noiseStream.on('end', this.noiseStream.end) // no half open
263
+ this.noiseStream.on('close', () => {
264
+ // TODO: If the stream was destroyed with an error, we probably want to forward it here
265
+ for (const peer of this._peers.values()) {
266
+ peer.destroy(null)
267
+ }
268
+ })
269
+
270
+ this._sendHandshake()
271
+ }
272
+
273
+ _sendHandshake () {
274
+ const m = { protocolVersion: this.protocolVersion, userAgent: this.userAgent }
275
+ const state = { start: 0, end: 0, buffer: null }
276
+
277
+ messages.handshake.preencode(state, m)
278
+ state.buffer = this.noiseStream.alloc(state.end)
279
+ messages.handshake.encode(state, m)
280
+ this.noiseStream.write(state.buffer)
281
+ }
282
+
283
+ isRegistered (discoveryKey) {
284
+ return this._peers.has(discoveryKey.toString('hex'))
285
+ }
286
+
287
+ registerPeer (key, discoveryKey, handlers = {}, state = null) {
288
+ const peer = new Peer(this, this._localAliases++, key, discoveryKey, handlers, state)
289
+ this._peers.set(b4a.toString(discoveryKey, 'hex'), peer)
290
+ this._announceCore(peer.alias, key, discoveryKey)
291
+ return peer
292
+ }
293
+
294
+ unregisterPeer (peer, err) {
295
+ this._peers.delete(b4a.toString(peer.discoveryKey, 'hex'))
296
+
297
+ if (peer.remoteAlias > -1) {
298
+ this._remoteAliases[peer.remoteAlias] = null
299
+ peer.remoteAlias = -1
300
+ }
301
+
302
+ peer.handlers.onunregister(peer, err)
303
+
304
+ if (err) this.noiseStream.destroy(err)
305
+ }
306
+
307
+ registerExtension (name, handlers) {
308
+ let ext = this._extensions.get(name)
309
+ if (ext) return ext
310
+ ext = new Extension(this, this._localExtensions++, name, handlers)
311
+ this._extensions.set(name, ext)
312
+ return ext
313
+ }
314
+
315
+ unregisterExtension (name) {
316
+ const ext = this._extensions.get(name)
317
+ if (!ext) return
318
+ if (!ext.destroyed) return ext.destroy()
319
+ this._extensions.delete(name)
320
+ this._remoteExtensions[ext.type - 128] = null
321
+ }
322
+
323
+ cork () {
324
+ if (++this._corks === 1) this._batch = []
325
+ }
326
+
327
+ uncork () {
328
+ if (--this._corks > 0) return
329
+
330
+ const batch = this._batch
331
+ this._batch = null
332
+
333
+ if (batch.length === 0) return
334
+
335
+ const state = { start: 0, end: 0, buffer: null }
336
+ const lens = new Array(batch.length)
337
+
338
+ uint.preencode(state, 0)
339
+ for (let i = 0; i < batch.length; i++) {
340
+ const [type, enc, dk, message] = batch[i]
341
+ const start = state.end
342
+ uint.preencode(state, type)
343
+ if (dk > -1) uint.preencode(state, dk)
344
+ enc.preencode(state, message)
345
+ uint.preencode(state, (lens[i] = state.end - start))
346
+ }
347
+
348
+ state.buffer = this.noiseStream.alloc(state.end)
349
+
350
+ uint.encode(state, 0)
351
+ for (let i = 0; i < batch.length; i++) {
352
+ const [type, enc, dk, message] = batch[i]
353
+ uint.encode(state, lens[i])
354
+ uint.encode(state, type)
355
+ if (dk > -1) uint.encode(state, dk)
356
+ enc.encode(state, message)
357
+ }
358
+
359
+ this.noiseStream.write(state.buffer)
360
+ }
361
+
362
+ onmessage (message) {
363
+ try {
364
+ this._decode(message)
365
+ } catch (err) {
366
+ this._safeDestroy(err)
367
+ }
368
+ }
369
+
370
+ _catch (p) {
371
+ if (isPromise(p)) p.then(noop, this._destroyer)
372
+ }
373
+
374
+ _announceCore (alias, key, discoveryKey) {
375
+ this.send(2, messages.core, -1, {
376
+ alias: alias,
377
+ discoveryKey: discoveryKey,
378
+ capability: b4a.alloc(32) // TODO
379
+ })
380
+ }
381
+
382
+ _decode (buffer) {
383
+ const state = { start: 0, end: buffer.length, buffer }
384
+
385
+ if (this._firstMessage === true) {
386
+ this._firstMessage = false
387
+ const { userAgent } = messages.handshake.decode(state)
388
+ this.remoteUserAgent = userAgent
389
+ this.uncork()
390
+ return
391
+ }
392
+
393
+ const type = uint.decode(state)
394
+
395
+ if (type === 0) { // batch
396
+ while (state.start < state.end) {
397
+ const len = uint.decode(state)
398
+ state.end = state.start + len
399
+ const type = uint.decode(state)
400
+ this._decodeMessage(type, state)
401
+ state.end = buffer.length
402
+ }
403
+ } else {
404
+ this._decodeMessage(type, state)
405
+ }
406
+ }
407
+
408
+ _decodeMessage (type, state) {
409
+ switch (type) {
410
+ case 1: return this._onextension(messages.extension.decode(state))
411
+ case 2: return this._oncore(messages.core.decode(state))
412
+ case 3: return this._onunknowncore(messages.unknownCore.decode(state))
413
+ }
414
+
415
+ if (type < 11) {
416
+ const remoteAlias = uint.decode(state)
417
+ const peer = this._remoteAliases[remoteAlias]
418
+ if (peer) peer.onmessage(type, state)
419
+ } else if (type >= 128) {
420
+ const ext = this._remoteExtensions[type - 128]
421
+ if (ext) ext._onmessage(state)
422
+ }
423
+
424
+ state.start = state.end
425
+ }
426
+
427
+ _onextension (m) {
428
+ const type = m.alias - 128
429
+ const ext = this._extensions.get(m.name)
430
+
431
+ if (type === this._remoteExtensions.length) {
432
+ this._remoteExtensions.push(null)
433
+ }
434
+
435
+ if (!ext) return
436
+
437
+ if (type < 0 || type >= this._remoteExtensions.length) {
438
+ this.destroy(new Error('Remote alias out of bounds'))
439
+ return
440
+ }
441
+
442
+ this._remoteExtensions[type] = ext
443
+ if (!ext.remoteSupports) ext._onremotesupports()
444
+ }
445
+
446
+ _oncore (m) {
447
+ const hex = b4a.toString(m.discoveryKey, 'hex')
448
+ const peer = this._peers.get(hex)
449
+
450
+ // allow one alloc
451
+ // TODO: if the remote allocs too many "holes", move to slower sparse firendly
452
+ // data structures such as a Map
453
+ if (m.alias === this._remoteAliases.length) this._remoteAliases.push(null)
454
+
455
+ if (peer) {
456
+ // TODO: check cap
457
+
458
+ if (m.alias >= this._remoteAliases.length) {
459
+ this.destroy(new Error('Remote alias out of bounds'))
460
+ return
461
+ }
462
+
463
+ this._remoteAliases[m.alias] = peer
464
+ peer.remoteAlias = m.alias
465
+ if (peer.resend) this._announceCore(peer.alias, peer.key, peer.discoveryKey)
466
+ this._catch(peer.handlers.oncore(m, peer))
467
+ return
468
+ }
469
+
470
+ const self = this
471
+ const p = this.handlers.ondiscoverykey ? this.handlers.ondiscoverykey(m.discoveryKey) : undefined
472
+
473
+ if (isPromise(p)) p.then(next, next)
474
+ else next()
475
+
476
+ function next () {
477
+ if (self._peers.has(hex)) return self._oncore(m)
478
+ self.send(3, messages.unknownCore, -1, { discoveryKey: m.discoveryKey })
479
+ }
480
+ }
481
+
482
+ _onunknowncore (m) {
483
+ const peer = this._peers.get(b4a.toString(m.discoveryKey, 'hex'))
484
+ if (!peer) return
485
+
486
+ peer.resend = true
487
+ this._catch(peer.handlers.onunknowncore(m, peer))
488
+ }
489
+
490
+ send (type, enc, dk, message) {
491
+ if (this._corks > 0) {
492
+ this._batch.push([type, enc, dk, message])
493
+ return false
494
+ }
495
+
496
+ const state = { start: 0, end: 0, buffer: null }
497
+
498
+ uint.preencode(state, type)
499
+ if (dk > -1) uint.preencode(state, dk)
500
+ enc.preencode(state, message)
501
+
502
+ state.buffer = this.noiseStream.alloc(state.end)
503
+
504
+ uint.encode(state, type)
505
+ if (dk > -1) uint.encode(state, dk)
506
+ enc.encode(state, message)
507
+
508
+ return this.noiseStream.write(state.buffer)
509
+ }
510
+
511
+ destroy (err) {
512
+ return this.noiseStream.destroy(err)
513
+ }
514
+
515
+ _safeDestroy (err) {
516
+ safetyCatch(err) // check if this was an accidental catch
517
+ this.destroy(err)
518
+ }
519
+ }
520
+
521
+ function noop () {}
522
+
523
+ function isPromise (p) {
524
+ return !!p && typeof p.then === 'function'
525
+ }
@@ -0,0 +1,46 @@
1
+ module.exports = class RandomIterator {
2
+ constructor (values) {
3
+ this._values = values
4
+ this._next = { value: null, done: false }
5
+ this._start = 0
6
+ this._end = values.length
7
+ }
8
+
9
+ next () {
10
+ if (this._start < this._values.length) {
11
+ if (this._start === this._end) this._end = this._values.length
12
+
13
+ const r = this._start + (Math.random() * (this._end - this._start)) | 0
14
+ const tmp = this._values[r]
15
+
16
+ this._values[r] = this._values[this._start]
17
+ this._next.value = this._values[this._start++] = tmp
18
+ } else {
19
+ this._next.done = true
20
+ }
21
+
22
+ return this._next
23
+ }
24
+
25
+ reset () {
26
+ this._start = 0
27
+ this._end = this._values.length
28
+ this._next.done = false
29
+ return this
30
+ }
31
+
32
+ requeue () {
33
+ if (this._start === this._end) {
34
+ this._start--
35
+ } else {
36
+ const top = this._values[--this._end]
37
+
38
+ this._values[this._end] = this._values[--this._start]
39
+ this._values[this._start] = top
40
+ }
41
+ }
42
+
43
+ [Symbol.iterator] () {
44
+ return this
45
+ }
46
+ }
@@ -0,0 +1,24 @@
1
+ const BigSparseArray = require('big-sparse-array')
2
+
3
+ module.exports = class RemoteBitfield {
4
+ constructor () {
5
+ this.pages = new BigSparseArray()
6
+ }
7
+
8
+ get (index) {
9
+ const r = index & 32767
10
+ const i = (index - r) / 32768
11
+ const p = this.pages.get(i)
12
+
13
+ return p ? (p[r >>> 5] & (1 << (r & 31))) !== 0 : false
14
+ }
15
+
16
+ set (index, val) {
17
+ const r = index & 32767
18
+ const i = (index - r) / 32768
19
+ const p = this.pages.get(i) || this.pages.set(i, new Uint32Array(1024))
20
+
21
+ if (val) p[r >>> 5] |= (1 << (r & 31))
22
+ else p[r >>> 5] &= ~(1 << (r & 31))
23
+ }
24
+ }