hypercore 10.3.2 → 10.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.js CHANGED
@@ -429,29 +429,30 @@ module.exports = class Hypercore extends EventEmitter {
429
429
  const protocolStream = Hypercore.createProtocolStream(isInitiator, opts)
430
430
  const noiseStream = protocolStream.noiseStream
431
431
  const protocol = noiseStream.userData
432
+ const useSession = !!opts.session
432
433
 
433
- this._attachToMuxer(protocol, opts)
434
+ this._attachToMuxer(protocol, useSession)
434
435
 
435
436
  return protocolStream
436
437
  }
437
438
 
438
- _attachToMuxer (mux, opts) {
439
- // If the user wants to, we can make this replication run in a session
440
- // that way the core wont close "under them" during replication
441
- if (opts.session) {
442
- const s = this.session()
443
- mux.stream.on('close', () => s.close().catch(noop))
444
- }
445
-
439
+ _attachToMuxer (mux, useSession) {
446
440
  if (this.opened) {
447
- this.replicator.attachTo(mux)
441
+ this._attachToMuxerOpened(mux, useSession)
448
442
  } else {
449
- this.opening.then(() => this.replicator.attachTo(mux), mux.destroy.bind(mux))
443
+ this.opening.then(this._attachToMuxerOpened.bind(this, mux, useSession), mux.destroy.bind(mux))
450
444
  }
451
445
 
452
446
  return mux
453
447
  }
454
448
 
449
+ _attachToMuxerOpened (mux, useSession) {
450
+ // If the user wants to, we can make this replication run in a session
451
+ // that way the core wont close "under them" during replication
452
+ const session = useSession ? this.session() : null
453
+ this.replicator.attachTo(mux, session)
454
+ }
455
+
455
456
  get discoveryKey () {
456
457
  return this.replicator === null ? null : this.replicator.discoveryKey
457
458
  }
@@ -616,7 +617,7 @@ module.exports = class Hypercore extends EventEmitter {
616
617
  async info () {
617
618
  if (this.opened === false) await this.opening
618
619
 
619
- return Info.from(this.core, this.padding, this._snapshot)
620
+ return Info.from(this)
620
621
  }
621
622
 
622
623
  async update (opts) {
package/lib/bitfield.js CHANGED
@@ -1,99 +1,244 @@
1
1
  const BigSparseArray = require('big-sparse-array')
2
2
  const b4a = require('b4a')
3
- const bits = require('bits-to-bytes')
4
-
5
- class FixedBitfield {
6
- constructor (index, bitfield) {
3
+ const quickbit = require('./compat').quickbit
4
+
5
+ const BITS_PER_PAGE = 32768
6
+ const BYTES_PER_PAGE = BITS_PER_PAGE / 8
7
+ const WORDS_PER_PAGE = BYTES_PER_PAGE / 4
8
+ const BITS_PER_SEGMENT = 2097152
9
+ const BYTES_PER_SEGMENT = BITS_PER_SEGMENT / 8
10
+ const WORDS_PER_SEGMENT = BYTES_PER_SEGMENT / 4
11
+ const INITIAL_WORDS_PER_SEGMENT = 1024
12
+ const PAGES_PER_SEGMENT = BITS_PER_SEGMENT / BITS_PER_PAGE
13
+ const SEGMENT_GROWTH_FACTOR = 4
14
+
15
+ class BitfieldPage {
16
+ constructor (index, segment) {
7
17
  this.dirty = false
8
18
  this.index = index
9
- this.bitfield = bitfield
19
+ this.bitfield = null
20
+ this.segment = segment
21
+
22
+ segment.add(this)
23
+ }
24
+
25
+ get tree () {
26
+ return this.segment.tree
10
27
  }
11
28
 
12
29
  get (index) {
13
- return bits.get(this.bitfield, index)
30
+ return quickbit.get(this.bitfield, index)
14
31
  }
15
32
 
16
33
  set (index, val) {
17
- return bits.set(this.bitfield, index, val)
34
+ if (quickbit.set(this.bitfield, index, val)) {
35
+ this.tree.update(this.offset * 8 + index)
36
+ }
18
37
  }
19
38
 
20
39
  setRange (start, length, val) {
21
- // Using fill instead of setRange is ~2 orders of magnitude faster, but does
22
- // have the downside of not being able to tell if any bits actually changed.
23
- bits.fill(this.bitfield, val, start, start + length)
24
- return true
40
+ quickbit.fill(this.bitfield, val, start, start + length)
41
+
42
+ let i = Math.floor(start / 32)
43
+ const n = i + Math.ceil(length / 32)
44
+
45
+ while (i < n) this.tree.update(this.offset * 8 + i++ * 32)
25
46
  }
26
47
 
27
- firstSet (position) {
28
- return bits.indexOf(this.bitfield, true, position)
48
+ findFirst (val, position) {
49
+ return quickbit.findFirst(this.bitfield, val, position)
29
50
  }
30
51
 
31
- lastSet (position) {
32
- return bits.lastIndexOf(this.bitfield, true, position)
52
+ findLast (val, position) {
53
+ return quickbit.findLast(this.bitfield, val, position)
54
+ }
55
+ }
56
+
57
+ class BitfieldSegment {
58
+ constructor (index, bitfield) {
59
+ this.index = index
60
+ this.offset = index * BYTES_PER_SEGMENT
61
+ this.tree = quickbit.Index.from(bitfield)
62
+ this.pages = new Array(PAGES_PER_SEGMENT)
63
+ }
64
+
65
+ get bitfield () {
66
+ return this.tree.field
67
+ }
68
+
69
+ add (page) {
70
+ const i = page.index - this.index * PAGES_PER_SEGMENT
71
+ this.pages[i] = page
72
+
73
+ const start = i * WORDS_PER_PAGE
74
+ const end = start + WORDS_PER_PAGE
75
+
76
+ if (end >= this.bitfield.length) this.reallocate(end)
77
+
78
+ page.bitfield = this.bitfield.subarray(start, end)
79
+ }
80
+
81
+ reallocate (length) {
82
+ let target = this.bitfield.length
83
+ while (target < length) target *= SEGMENT_GROWTH_FACTOR
84
+
85
+ const bitfield = new Uint32Array(target)
86
+ bitfield.set(this.bitfield)
87
+
88
+ this.tree = quickbit.Index.from(bitfield)
89
+
90
+ for (let i = 0; i < this.pages.length; i++) {
91
+ const page = this.pages[i]
92
+ if (!page) continue
93
+
94
+ const start = i * WORDS_PER_PAGE
95
+ const end = start + WORDS_PER_PAGE
96
+
97
+ page.bitfield = bitfield.subarray(start, end)
98
+ }
99
+ }
100
+
101
+ findFirst (val, position) {
102
+ position = this.tree.skipFirst(!val, position)
103
+
104
+ const j = position & (BITS_PER_PAGE - 1)
105
+ const i = (position - j) / BITS_PER_PAGE
106
+
107
+ if (i >= PAGES_PER_SEGMENT) return -1
108
+
109
+ const p = this.pages[i]
110
+
111
+ if (p) {
112
+ const index = p.findFirst(val, j)
113
+
114
+ if (index !== -1) {
115
+ return i * BITS_PER_PAGE + index
116
+ }
117
+ }
118
+
119
+ return -1
120
+ }
121
+
122
+ findLast (val, position) {
123
+ position = this.tree.skipLast(!val, position)
124
+
125
+ const j = position & (BITS_PER_PAGE - 1)
126
+ const i = (position - j) / BITS_PER_PAGE
127
+
128
+ if (i >= PAGES_PER_SEGMENT) return -1
129
+
130
+ const p = this.pages[i]
131
+
132
+ if (p) {
133
+ const index = p.findLast(val, j)
134
+
135
+ if (index !== -1) {
136
+ return i * BITS_PER_PAGE + index
137
+ }
138
+ }
139
+
140
+ return -1
33
141
  }
34
142
  }
35
143
 
36
144
  module.exports = class Bitfield {
37
- constructor (storage, buf) {
38
- this.pageSize = 32768
39
- this.pages = new BigSparseArray()
145
+ constructor (storage, buffer) {
40
146
  this.unflushed = []
41
147
  this.storage = storage
42
- this.resumed = !!(buf && buf.byteLength >= 4)
148
+ this.resumed = !!(buffer && buffer.byteLength >= 4)
149
+
150
+ this._pages = new BigSparseArray()
151
+ this._segments = new BigSparseArray()
152
+
153
+ const view = this.resumed
154
+ ? new Uint32Array(
155
+ buffer.buffer,
156
+ buffer.byteOffset,
157
+ Math.floor(buffer.byteLength / 4)
158
+ )
159
+ : new Uint32Array(INITIAL_WORDS_PER_SEGMENT)
160
+
161
+ for (let i = 0; i < view.length; i += WORDS_PER_SEGMENT) {
162
+ let bitfield = view.subarray(i, i + (WORDS_PER_SEGMENT))
163
+ let length = WORDS_PER_SEGMENT
164
+
165
+ if (i === 0) {
166
+ length = INITIAL_WORDS_PER_SEGMENT
167
+ while (length < bitfield.length) length *= SEGMENT_GROWTH_FACTOR
168
+ }
43
169
 
44
- const all = this.resumed
45
- ? new Uint32Array(buf.buffer, buf.byteOffset, Math.floor(buf.byteLength / 4))
46
- : new Uint32Array(1024)
170
+ if (bitfield.length !== length) {
171
+ const copy = new Uint32Array(length)
172
+ copy.set(bitfield, 0)
173
+ bitfield = copy
174
+ }
175
+
176
+ const segment = new BitfieldSegment(i / (WORDS_PER_SEGMENT), bitfield)
177
+ this._segments.set(segment.index, segment)
47
178
 
48
- for (let i = 0; i < all.length; i += 1024) {
49
- const bitfield = ensureSize(all.subarray(i, i + 1024), 1024)
50
- const page = new FixedBitfield(i / 1024, bitfield)
51
- this.pages.set(page.index, page)
179
+ for (let j = 0; j < bitfield.length; j += WORDS_PER_PAGE) {
180
+ const page = new BitfieldPage((i + j) / WORDS_PER_PAGE, segment)
181
+ this._pages.set(page.index, page)
182
+ }
52
183
  }
53
184
  }
54
185
 
55
186
  get (index) {
56
- const j = index & (this.pageSize - 1)
57
- const i = (index - j) / this.pageSize
187
+ const j = index & (BITS_PER_PAGE - 1)
188
+ const i = (index - j) / BITS_PER_PAGE
58
189
 
59
- const p = this.pages.get(i)
190
+ const p = this._pages.get(i)
60
191
 
61
192
  return p ? p.get(j) : false
62
193
  }
63
194
 
64
195
  set (index, val) {
65
- const j = index & (this.pageSize - 1)
66
- const i = (index - j) / this.pageSize
196
+ const j = index & (BITS_PER_PAGE - 1)
197
+ const i = (index - j) / BITS_PER_PAGE
67
198
 
68
- let p = this.pages.get(i)
199
+ let p = this._pages.get(i)
69
200
 
70
201
  if (!p && val) {
71
- p = this.pages.set(i, new FixedBitfield(i, new Uint32Array(1024)))
202
+ const k = Math.floor(i / PAGES_PER_SEGMENT)
203
+ const s = this._segments.get(k) || this._segments.set(k, new BitfieldSegment(k, new Uint32Array(k === 0 ? INITIAL_WORDS_PER_SEGMENT : WORDS_PER_SEGMENT)))
204
+
205
+ p = this._pages.set(i, new BitfieldPage(i, s))
72
206
  }
73
207
 
74
- if (p && p.set(j, val) && !p.dirty) {
75
- p.dirty = true
76
- this.unflushed.push(p)
208
+ if (p) {
209
+ p.set(j, val)
210
+
211
+ if (!p.dirty) {
212
+ p.dirty = true
213
+ this.unflushed.push(p)
214
+ }
77
215
  }
78
216
  }
79
217
 
80
218
  setRange (start, length, val) {
81
- let j = start & (this.pageSize - 1)
82
- let i = (start - j) / this.pageSize
219
+ let j = start & (BITS_PER_PAGE - 1)
220
+ let i = (start - j) / BITS_PER_PAGE
83
221
 
84
222
  while (length > 0) {
85
- let p = this.pages.get(i)
223
+ let p = this._pages.get(i)
86
224
 
87
225
  if (!p && val) {
88
- p = this.pages.set(i, new FixedBitfield(i, new Uint32Array(1024)))
226
+ const k = Math.floor(i / PAGES_PER_SEGMENT)
227
+ const s = this._segments.get(k) || this._segments.set(k, new BitfieldSegment(k, new Uint32Array(k === 0 ? INITIAL_WORDS_PER_SEGMENT : WORDS_PER_SEGMENT)))
228
+
229
+ p = this._pages.set(i, new BitfieldPage(i, s))
89
230
  }
90
231
 
91
- const end = Math.min(j + length, this.pageSize)
232
+ const end = Math.min(j + length, BITS_PER_PAGE)
92
233
  const range = end - j
93
234
 
94
- if (p && p.setRange(j, range, val) && !p.dirty) {
95
- p.dirty = true
96
- this.unflushed.push(p)
235
+ if (p) {
236
+ p.setRange(j, range, val)
237
+
238
+ if (!p.dirty) {
239
+ p.dirty = true
240
+ this.unflushed.push(p)
241
+ }
97
242
  }
98
243
 
99
244
  j = 0
@@ -102,18 +247,18 @@ module.exports = class Bitfield {
102
247
  }
103
248
  }
104
249
 
105
- firstSet (position) {
106
- let j = position & (this.pageSize - 1)
107
- let i = (position - j) / this.pageSize
250
+ findFirst (val, position) {
251
+ let j = position & (BITS_PER_SEGMENT - 1)
252
+ let i = (position - j) / BITS_PER_SEGMENT
108
253
 
109
- while (i < this.pages.factor) {
110
- const p = this.pages.get(i)
254
+ while (i < this._segments.maxLength) {
255
+ const s = this._segments.get(i)
111
256
 
112
- if (p) {
113
- const index = p.firstSet(j)
257
+ if (s) {
258
+ const index = s.findFirst(val, j)
114
259
 
115
260
  if (index !== -1) {
116
- return i * this.pageSize + index
261
+ return i * BITS_PER_SEGMENT + index
117
262
  }
118
263
  }
119
264
 
@@ -124,33 +269,72 @@ module.exports = class Bitfield {
124
269
  return -1
125
270
  }
126
271
 
127
- lastSet (position) {
128
- let j = position & (this.pageSize - 1)
129
- let i = (position - j) / this.pageSize
272
+ firstSet (position) {
273
+ return this.findFirst(true, position)
274
+ }
275
+
276
+ firstUnset (position) {
277
+ return this.findFirst(false, position)
278
+ }
279
+
280
+ findLast (val, position) {
281
+ let j = position & (BITS_PER_SEGMENT - 1)
282
+ let i = (position - j) / BITS_PER_SEGMENT
130
283
 
131
284
  while (i >= 0) {
132
- const p = this.pages.get(i)
285
+ const s = this._segments.get(i)
133
286
 
134
- if (p) {
135
- const index = p.lastSet(j)
287
+ if (s) {
288
+ const index = s.findLast(val, j)
136
289
 
137
290
  if (index !== -1) {
138
- return i * this.pageSize + index
291
+ return i * BITS_PER_SEGMENT + index
139
292
  }
140
293
  }
141
294
 
142
- j = this.pageSize - 1
295
+ j = BITS_PER_SEGMENT - 1
143
296
  i--
144
297
  }
145
298
 
146
299
  return -1
147
300
  }
148
301
 
302
+ lastSet (position) {
303
+ return this.findLast(true, position)
304
+ }
305
+
306
+ lastUnset (position) {
307
+ return this.findLast(false, position)
308
+ }
309
+
310
+ * want (start, length) {
311
+ const j = start & (BITS_PER_SEGMENT - 1)
312
+ let i = (start - j) / BITS_PER_SEGMENT
313
+
314
+ while (length > 0) {
315
+ const s = this._segments.get(i)
316
+
317
+ if (s) {
318
+ // We always send at least 4 KiB worth of bitfield in a want, rounding
319
+ // to the nearest 4 KiB.
320
+ const end = ceilTo(clamp(length / 8, 4096, BYTES_PER_SEGMENT), 4096)
321
+
322
+ yield {
323
+ start: i * BITS_PER_SEGMENT,
324
+ bitfield: s.bitfield.subarray(0, end / 4)
325
+ }
326
+ }
327
+
328
+ i++
329
+ length -= BITS_PER_SEGMENT
330
+ }
331
+ }
332
+
149
333
  clear () {
150
334
  return new Promise((resolve, reject) => {
151
335
  this.storage.truncate(0, (err) => {
152
336
  if (err) return reject(err)
153
- this.pages = new BigSparseArray()
337
+ this._pages = new BigSparseArray()
154
338
  this.unflushed = []
155
339
  resolve()
156
340
  })
@@ -182,7 +366,7 @@ module.exports = class Bitfield {
182
366
  )
183
367
 
184
368
  page.dirty = false
185
- this.storage.write(page.index * 4096, buf, done)
369
+ this.storage.write(page.index * BYTES_PER_PAGE, buf, done)
186
370
  }
187
371
 
188
372
  function done (err) {
@@ -195,12 +379,13 @@ module.exports = class Bitfield {
195
379
  })
196
380
  }
197
381
 
198
- static open (storage) {
382
+ static open (storage, tree = null) {
199
383
  return new Promise((resolve, reject) => {
200
384
  storage.stat((err, st) => {
201
385
  if (err) return resolve(new Bitfield(storage, null))
202
- const size = st.size - (st.size & 3)
386
+ let size = st.size - (st.size & 3)
203
387
  if (!size) return resolve(new Bitfield(storage, null))
388
+ if (tree) size = Math.min(size, ceilTo(tree.length / 8, 4096))
204
389
  storage.read(0, size, (err, data) => {
205
390
  if (err) return reject(err)
206
391
  resolve(new Bitfield(storage, data))
@@ -210,9 +395,12 @@ module.exports = class Bitfield {
210
395
  }
211
396
  }
212
397
 
213
- function ensureSize (uint32, size) {
214
- if (uint32.byteLength === size) return uint32
215
- const a = new Uint32Array(1024)
216
- a.set(uint32, 0)
217
- return a
398
+ function clamp (n, min, max) {
399
+ return Math.min(Math.max(n, min), max)
400
+ }
401
+
402
+ function ceilTo (n, multiple = 1) {
403
+ const remainder = n % multiple
404
+ if (remainder === 0) return n
405
+ return n + multiple - remainder
218
406
  }
package/lib/compat.js ADDED
@@ -0,0 +1,11 @@
1
+ // Export the appropriate version of `quickbit-universal` as the plain import
2
+ // may resolve to an older version in some environments
3
+ let quickbit = require('quickbit-universal')
4
+ if (
5
+ typeof quickbit.findFirst !== 'function' ||
6
+ typeof quickbit.findLast !== 'function'
7
+ ) {
8
+ // This should always load the fallback from the locally installed version
9
+ quickbit = require('quickbit-universal/fallback')
10
+ }
11
+ exports.quickbit = quickbit
package/lib/core.js CHANGED
@@ -108,7 +108,7 @@ module.exports = class Core {
108
108
  }
109
109
 
110
110
  const tree = await MerkleTree.open(treeFile, { crypto, ...header.tree })
111
- const bitfield = await Bitfield.open(bitfieldFile)
111
+ const bitfield = await Bitfield.open(bitfieldFile, tree)
112
112
  const blocks = new BlockStore(dataFile, tree)
113
113
 
114
114
  if (overwrite) {
package/lib/info.js CHANGED
@@ -1,24 +1,23 @@
1
1
  module.exports = class Info {
2
2
  constructor (opts = {}) {
3
+ this.key = opts.key
4
+ this.discoveryKey = opts.discoveryKey
3
5
  this.length = opts.length || 0
4
6
  this.contiguousLength = opts.contiguousLength || 0
5
7
  this.byteLength = opts.byteLength || 0
8
+ this.fork = opts.fork || 0
6
9
  this.padding = opts.padding || 0
7
10
  }
8
11
 
9
- static async from (core, padding, snapshot) {
12
+ static async from (session) {
10
13
  return new Info({
11
- key: core.key,
12
- discoveryKey: core.discoveryKey,
13
- length: snapshot
14
- ? snapshot.length
15
- : core.tree.length,
16
- contiguousLength: core.header.contiguousLength,
17
- byteLength: snapshot
18
- ? snapshot.byteLength
19
- : (core.tree.byteLength - (core.tree.length * padding)),
20
- fork: core.tree.fork,
21
- padding
14
+ key: session.key,
15
+ discoveryKey: session.discoveryKey,
16
+ length: session.length,
17
+ contiguousLength: session.contiguousLength,
18
+ byteLength: session.byteLength,
19
+ fork: session.fork,
20
+ padding: session.padding
22
21
  })
23
22
  }
24
23
  }
package/lib/oplog.js CHANGED
@@ -1,6 +1,6 @@
1
1
  const cenc = require('compact-encoding')
2
2
  const b4a = require('b4a')
3
- const crc32 = require('crc32-universal')
3
+ const { crc32 } = require('crc-universal')
4
4
 
5
5
  module.exports = class Oplog {
6
6
  constructor (storage, { pageSize = 4096, headerEncoding = cenc.raw, entryEncoding = cenc.raw } = {}) {
@@ -1,45 +1,275 @@
1
1
  const BigSparseArray = require('big-sparse-array')
2
- const bits = require('bits-to-bytes')
2
+ const quickbit = require('./compat').quickbit
3
+
4
+ const BITS_PER_PAGE = 32768
5
+ const BYTES_PER_PAGE = BITS_PER_PAGE / 8
6
+ const WORDS_PER_PAGE = BYTES_PER_PAGE / 4
7
+ const BITS_PER_SEGMENT = 2097152
8
+ const BYTES_PER_SEGMENT = BITS_PER_SEGMENT / 8
9
+ const PAGES_PER_SEGMENT = BITS_PER_SEGMENT / BITS_PER_PAGE
10
+
11
+ class RemoteBitfieldPage {
12
+ constructor (index, bitfield, segment) {
13
+ this.index = index
14
+ this.offset = index * BYTES_PER_PAGE - segment.offset
15
+ this.bitfield = bitfield
16
+ this.segment = segment
17
+
18
+ segment.add(this)
19
+ }
20
+
21
+ get tree () {
22
+ return this.segment.tree
23
+ }
24
+
25
+ get (index) {
26
+ return quickbit.get(this.bitfield, index)
27
+ }
28
+
29
+ set (index, val) {
30
+ if (quickbit.set(this.bitfield, index, val)) {
31
+ this.tree.update(this.offset * 8 + index)
32
+ }
33
+ }
34
+
35
+ setRange (start, length, val) {
36
+ quickbit.fill(this.bitfield, val, start, start + length)
37
+
38
+ let i = Math.floor(start / 32)
39
+ const n = i + Math.ceil(length / 32)
40
+
41
+ while (i < n) this.tree.update(this.offset * 8 + i++ * 32)
42
+ }
43
+
44
+ findFirst (val, position) {
45
+ return quickbit.findFirst(this.bitfield, val, position)
46
+ }
47
+
48
+ findLast (val, position) {
49
+ return quickbit.findLast(this.bitfield, val, position)
50
+ }
51
+
52
+ insert (start, bitfield) {
53
+ this.bitfield.set(bitfield, start / 32)
54
+ }
55
+ }
56
+
57
+ class RemoteBitfieldSegment {
58
+ constructor (index) {
59
+ this.index = index
60
+ this.offset = index * BYTES_PER_SEGMENT
61
+ this.tree = quickbit.Index.from([])
62
+ this.pages = new Array(PAGES_PER_SEGMENT)
63
+ }
64
+
65
+ get chunks () {
66
+ return this.tree.chunks
67
+ }
68
+
69
+ add (page) {
70
+ this.pages[page.index - this.index * PAGES_PER_SEGMENT] = page
71
+
72
+ const chunk = { field: page.bitfield, offset: page.offset }
73
+
74
+ this.chunks.push(chunk)
75
+
76
+ for (let i = this.chunks.length - 2; i >= 0; i--) {
77
+ const prev = this.chunks[i]
78
+ if (prev.offset <= chunk.offset) break
79
+ this.chunks[i] = chunk
80
+ this.chunks[i + 1] = prev
81
+ }
82
+ }
83
+
84
+ findFirst (val, position) {
85
+ position = this.tree.skipFirst(!val, position)
86
+
87
+ const j = position & (BITS_PER_PAGE - 1)
88
+ const i = (position - j) / BITS_PER_PAGE
89
+
90
+ if (i >= PAGES_PER_SEGMENT) return -1
91
+
92
+ const p = this.pages[i]
93
+
94
+ if (p) {
95
+ const index = p.findFirst(val, j)
96
+
97
+ if (index !== -1) {
98
+ return i * BITS_PER_PAGE + index
99
+ }
100
+ }
101
+
102
+ return -1
103
+ }
104
+
105
+ findLast (val, position) {
106
+ position = this.tree.skipLast(!val, position)
107
+
108
+ const j = position & (BITS_PER_PAGE - 1)
109
+ const i = (position - j) / BITS_PER_PAGE
110
+
111
+ if (i >= PAGES_PER_SEGMENT) return -1
112
+
113
+ const p = this.pages[i]
114
+
115
+ if (p) {
116
+ const index = p.findLast(val, j)
117
+
118
+ if (index !== -1) {
119
+ return i * BITS_PER_PAGE + index
120
+ }
121
+ }
122
+
123
+ return -1
124
+ }
125
+ }
3
126
 
4
127
  module.exports = class RemoteBitfield {
5
128
  constructor () {
6
- this.pageSize = 32768
7
- this.pages = new BigSparseArray()
129
+ this._pages = new BigSparseArray()
130
+ this._segments = new BigSparseArray()
8
131
  }
9
132
 
10
133
  get (index) {
11
- const j = index & (this.pageSize - 1)
12
- const i = (index - j) / this.pageSize
134
+ const j = index & (BITS_PER_PAGE - 1)
135
+ const i = (index - j) / BITS_PER_PAGE
13
136
 
14
- const p = this.pages.get(i)
137
+ const p = this._pages.get(i)
15
138
 
16
- return p ? bits.get(p, j) : false
139
+ return p ? p.get(j) : false
17
140
  }
18
141
 
19
142
  set (index, val) {
20
- const j = index & (this.pageSize - 1)
21
- const i = (index - j) / this.pageSize
143
+ const j = index & (BITS_PER_PAGE - 1)
144
+ const i = (index - j) / BITS_PER_PAGE
22
145
 
23
- const p = this.pages.get(i) || this.pages.set(i, new Uint32Array(1024))
146
+ let p = this._pages.get(i)
24
147
 
25
- bits.set(p, j, val)
148
+ if (!p && val) {
149
+ const k = Math.floor(i / PAGES_PER_SEGMENT)
150
+ const s = this._segments.get(k) || this._segments.set(k, new RemoteBitfieldSegment(k))
151
+
152
+ p = this._pages.set(i, new RemoteBitfieldPage(i, new Uint32Array(WORDS_PER_PAGE), s))
153
+ }
154
+
155
+ if (p) p.set(j, val)
26
156
  }
27
157
 
28
158
  setRange (start, length, val) {
29
- let j = start & (this.pageSize - 1)
30
- let i = (start - j) / this.pageSize
159
+ let j = start & (BITS_PER_PAGE - 1)
160
+ let i = (start - j) / BITS_PER_PAGE
31
161
 
32
162
  while (length > 0) {
33
- const p = this.pages.get(i) || this.pages.set(i, new Uint32Array(1024))
163
+ let p = this._pages.get(i)
164
+
165
+ if (!p && val) {
166
+ const k = Math.floor(i / PAGES_PER_SEGMENT)
167
+ const s = this._segments.get(k) || this._segments.set(k, new RemoteBitfieldSegment(k))
168
+
169
+ p = this._pages.set(i, new RemoteBitfieldPage(i, new Uint32Array(WORDS_PER_PAGE), s))
170
+ }
34
171
 
35
- const end = Math.min(j + length, this.pageSize)
172
+ const end = Math.min(j + length, BITS_PER_PAGE)
36
173
  const range = end - j
37
174
 
38
- bits.fill(p, val, j, end)
175
+ if (p) p.setRange(j, range, val)
39
176
 
40
177
  j = 0
41
178
  i++
42
179
  length -= range
43
180
  }
44
181
  }
182
+
183
+ findFirst (val, position) {
184
+ let j = position & (BITS_PER_SEGMENT - 1)
185
+ let i = (position - j) / BITS_PER_SEGMENT
186
+
187
+ while (i < this._segments.maxLength) {
188
+ const s = this._segments.get(i)
189
+
190
+ if (s) {
191
+ const index = s.findFirst(val, j)
192
+
193
+ if (index !== -1) {
194
+ return i * BITS_PER_SEGMENT + index
195
+ }
196
+ }
197
+
198
+ j = 0
199
+ i++
200
+ }
201
+
202
+ return -1
203
+ }
204
+
205
+ firstSet (position) {
206
+ return this.findFirst(true, position)
207
+ }
208
+
209
+ firstUnset (position) {
210
+ return this.findFirst(false, position)
211
+ }
212
+
213
+ findLast (val, position) {
214
+ let j = position & (BITS_PER_SEGMENT - 1)
215
+ let i = (position - j) / BITS_PER_SEGMENT
216
+
217
+ while (i >= 0) {
218
+ const s = this._segments.get(i)
219
+
220
+ if (s) {
221
+ const index = s.findLast(val, j)
222
+
223
+ if (index !== -1) {
224
+ return i * BITS_PER_SEGMENT + index
225
+ }
226
+ }
227
+
228
+ j = BITS_PER_SEGMENT - 1
229
+ i--
230
+ }
231
+
232
+ return -1
233
+ }
234
+
235
+ lastSet (position) {
236
+ return this.findLast(true, position)
237
+ }
238
+
239
+ lastUnset (position) {
240
+ return this.findLast(false, position)
241
+ }
242
+
243
+ insert (start, bitfield) {
244
+ if (start % 32 !== 0) return false
245
+
246
+ let length = bitfield.byteLength * 8
247
+
248
+ let j = start & (BITS_PER_PAGE - 1)
249
+ let i = (start - j) / BITS_PER_PAGE
250
+
251
+ while (length > 0) {
252
+ let p = this._pages.get(i)
253
+
254
+ if (!p) {
255
+ const k = Math.floor(i / PAGES_PER_SEGMENT)
256
+ const s = this._segments.get(k) || this._segments.set(k, new RemoteBitfieldSegment(k))
257
+
258
+ p = this._pages.set(i, new RemoteBitfieldPage(i, new Uint32Array(WORDS_PER_PAGE), s))
259
+ }
260
+
261
+ const end = Math.min(j + length, BITS_PER_PAGE)
262
+ const range = end - j
263
+
264
+ p.insert(j, bitfield.subarray(0, range / 32))
265
+
266
+ bitfield = bitfield.subarray(range / 32)
267
+
268
+ j = 0
269
+ i++
270
+ length -= range
271
+ }
272
+
273
+ return true
274
+ }
45
275
  }
package/lib/replicator.js CHANGED
@@ -243,13 +243,15 @@ class BlockTracker {
243
243
  }
244
244
 
245
245
  class Peer {
246
- constructor (replicator, protomux, channel) {
246
+ constructor (replicator, protomux, channel, session) {
247
247
  this.core = replicator.core
248
248
  this.replicator = replicator
249
249
  this.stream = protomux.stream
250
250
  this.protomux = protomux
251
251
  this.remotePublicKey = this.stream.remotePublicKey
252
252
 
253
+ this.session = session
254
+
253
255
  this.channel = channel
254
256
  this.channel.userData = this
255
257
 
@@ -376,6 +378,8 @@ class Peer {
376
378
  }
377
379
 
378
380
  onclose (isRemote) {
381
+ if (this.session) this.session.close().catch(noop)
382
+
379
383
  if (this.remoteOpened === false) {
380
384
  this.replicator._ifAvailable--
381
385
  this.replicator.updateAll()
@@ -570,17 +574,9 @@ class Peer {
570
574
  }
571
575
 
572
576
  onbitfield ({ start, bitfield }) {
573
- // TODO: tweak this to be more generic
574
-
575
- if (bitfield.length < 1024) {
576
- const buf = b4a.from(bitfield.buffer, bitfield.byteOffset, bitfield.byteLength)
577
- const bigger = b4a.concat([buf, b4a.alloc(4096 - buf.length)])
578
- bitfield = new Uint32Array(bigger.buffer, bigger.byteOffset, 1024)
577
+ if (this.remoteBitfield.insert(start, bitfield)) {
578
+ this._update()
579
579
  }
580
-
581
- this.remoteBitfield.pages.set(start / this.core.bitfield.pageSize, bitfield)
582
-
583
- this._update()
584
580
  }
585
581
 
586
582
  onrange ({ drop, start, length }) {
@@ -1219,8 +1215,7 @@ module.exports = class Replicator {
1219
1215
  for (let i = 0; i < this._ranges.length; i++) {
1220
1216
  const r = this._ranges[i]
1221
1217
 
1222
- while ((r.end === -1 || r.start < r.end) && this.core.bitfield.get(mapIndex(r.blocks, r.start)) === true) r.start++
1223
- while (r.start < r.end && this.core.bitfield.get(mapIndex(r.blocks, r.end - 1)) === true) r.end--
1218
+ clampRange(this.core, r)
1224
1219
 
1225
1220
  if (r.end !== -1 && r.start >= r.end) {
1226
1221
  this._resolveRangeRequest(r, i--)
@@ -1332,18 +1327,8 @@ module.exports = class Replicator {
1332
1327
 
1333
1328
  peer.protomux.cork()
1334
1329
 
1335
- let i = Math.floor(start / this.core.bitfield.pageSize)
1336
- const n = Math.ceil((start + length) / this.core.bitfield.pageSize)
1337
-
1338
- for (; i < n; i++) {
1339
- const p = this.core.bitfield.pages.get(i)
1340
-
1341
- if (p) {
1342
- peer.wireBitfield.send({
1343
- start: i * this.core.bitfield.pageSize,
1344
- bitfield: p.bitfield
1345
- })
1346
- }
1330
+ for (const msg of this.core.bitfield.want(start, length)) {
1331
+ peer.wireBitfield.send(msg)
1347
1332
  }
1348
1333
 
1349
1334
  peer.protomux.uncork()
@@ -1525,21 +1510,21 @@ module.exports = class Replicator {
1525
1510
  this._maybeResolveIfAvailableRanges()
1526
1511
  }
1527
1512
 
1528
- attachTo (protomux) {
1529
- const makePeer = this._makePeer.bind(this, protomux)
1513
+ attachTo (protomux, session) {
1514
+ const makePeer = this._makePeer.bind(this, protomux, session)
1530
1515
 
1531
1516
  protomux.pair({ protocol: 'hypercore/alpha', id: this.discoveryKey }, makePeer)
1532
-
1533
1517
  this._ifAvailable++
1534
1518
  protomux.stream.opened.then((opened) => {
1535
1519
  this._ifAvailable--
1536
1520
  if (opened) makePeer()
1521
+ else if (session) session.close().catch(noop)
1537
1522
  this._checkUpgradeIfAvailable()
1538
1523
  })
1539
1524
  }
1540
1525
 
1541
- _makePeer (protomux) {
1542
- if (protomux.opened({ protocol: 'hypercore/alpha', id: this.discoveryKey })) return false
1526
+ _makePeer (protomux, session) {
1527
+ if (protomux.opened({ protocol: 'hypercore/alpha', id: this.discoveryKey })) return onnochannel()
1543
1528
 
1544
1529
  const channel = protomux.createChannel({
1545
1530
  userData: null,
@@ -1563,9 +1548,9 @@ module.exports = class Replicator {
1563
1548
  onclose: onwireclose
1564
1549
  })
1565
1550
 
1566
- if (channel === null) return false
1551
+ if (channel === null) return onnochannel()
1567
1552
 
1568
- const peer = new Peer(this, protomux, channel)
1553
+ const peer = new Peer(this, protomux, channel, session)
1569
1554
  const stream = protomux.stream
1570
1555
 
1571
1556
  peer.channel.open({
@@ -1573,6 +1558,11 @@ module.exports = class Replicator {
1573
1558
  })
1574
1559
 
1575
1560
  return true
1561
+
1562
+ function onnochannel () {
1563
+ if (session) session.close().catch(noop)
1564
+ return false
1565
+ }
1576
1566
  }
1577
1567
  }
1578
1568
 
@@ -1592,16 +1582,34 @@ function removeInflight (inf, req) {
1592
1582
  return true
1593
1583
  }
1594
1584
 
1595
- function mapIndex (blocks, index) {
1596
- return blocks === null ? index : blocks[index]
1597
- }
1598
-
1599
1585
  function noop () {}
1600
1586
 
1601
1587
  function toLength (start, end) {
1602
1588
  return end === -1 ? -1 : (end < start ? 0 : end - start)
1603
1589
  }
1604
1590
 
1591
+ function clampRange (core, r) {
1592
+ if (r.blocks === null) {
1593
+ const start = core.bitfield.firstUnset(r.start)
1594
+
1595
+ if (r.end === -1) {
1596
+ r.start = start === -1 ? core.tree.length : start
1597
+ } else {
1598
+ const end = core.bitfield.lastUnset(r.end - 1) + 1
1599
+
1600
+ if (start === -1) {
1601
+ r.start = r.end
1602
+ } else {
1603
+ r.start = start
1604
+ r.end = end
1605
+ }
1606
+ }
1607
+ } else {
1608
+ while (r.start < r.end && core.bitfield.get(r.blocks[r.start])) r.start++
1609
+ while (r.start < r.end && core.bitfield.get(r.blocks[r.end - 1])) r.end--
1610
+ }
1611
+ }
1612
+
1605
1613
  function onwireopen (m, c) {
1606
1614
  return c.userData.onopen(m)
1607
1615
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hypercore",
3
- "version": "10.3.2",
3
+ "version": "10.4.1",
4
4
  "description": "Hypercore is a secure, distributed append-only log",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -36,14 +36,14 @@
36
36
  "@hyperswarm/secret-stream": "^6.0.0",
37
37
  "b4a": "^1.1.0",
38
38
  "big-sparse-array": "^1.0.2",
39
- "bits-to-bytes": "^1.3.0",
40
39
  "compact-encoding": "^2.11.0",
41
- "crc32-universal": "^1.0.1",
40
+ "crc-universal": "^1.0.2",
42
41
  "events": "^3.3.0",
43
42
  "flat-tree": "^1.9.0",
44
43
  "hypercore-crypto": "^3.2.1",
45
44
  "is-options": "^1.0.1",
46
45
  "protomux": "^3.4.0",
46
+ "quickbit-universal": "^2.0.3",
47
47
  "random-access-file": "^4.0.0",
48
48
  "random-array-iterator": "^1.0.0",
49
49
  "safety-catch": "^1.0.1",