hypercore 10.3.1 → 10.4.0

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
@@ -242,14 +242,19 @@ module.exports = class Hypercore extends EventEmitter {
242
242
  async _openFromExisting (from, opts) {
243
243
  await from.opening
244
244
 
245
- this._passCapabilities(from)
246
- this.sessions = from.sessions
245
+ // includes ourself as well, so the loop below also updates us
246
+ const sessions = this.sessions
247
+
248
+ for (const s of sessions) {
249
+ s.sessions = from.sessions
250
+ s.sessions.push(s)
251
+ s._passCapabilities(from)
252
+ }
253
+
247
254
  this.storage = from.storage
248
255
  this.replicator.findingPeers += this._findingPeers
249
256
 
250
257
  ensureEncryption(this, opts)
251
-
252
- this.sessions.push(this)
253
258
  }
254
259
 
255
260
  async _openSession (key, storage, opts) {
@@ -424,29 +429,30 @@ module.exports = class Hypercore extends EventEmitter {
424
429
  const protocolStream = Hypercore.createProtocolStream(isInitiator, opts)
425
430
  const noiseStream = protocolStream.noiseStream
426
431
  const protocol = noiseStream.userData
432
+ const useSession = !!opts.session
427
433
 
428
- this._attachToMuxer(protocol, opts)
434
+ this._attachToMuxer(protocol, useSession)
429
435
 
430
436
  return protocolStream
431
437
  }
432
438
 
433
- _attachToMuxer (mux, opts) {
434
- // If the user wants to, we can make this replication run in a session
435
- // that way the core wont close "under them" during replication
436
- if (opts.session) {
437
- const s = this.session()
438
- mux.stream.on('close', () => s.close().catch(noop))
439
- }
440
-
439
+ _attachToMuxer (mux, useSession) {
441
440
  if (this.opened) {
442
- this.replicator.attachTo(mux)
441
+ this._attachToMuxerOpened(mux, useSession)
443
442
  } else {
444
- 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))
445
444
  }
446
445
 
447
446
  return mux
448
447
  }
449
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
+
450
456
  get discoveryKey () {
451
457
  return this.replicator === null ? null : this.replicator.discoveryKey
452
458
  }
@@ -611,7 +617,7 @@ module.exports = class Hypercore extends EventEmitter {
611
617
  async info () {
612
618
  if (this.opened === false) await this.opening
613
619
 
614
- return Info.from(this.core, this.padding, this._snapshot)
620
+ return Info.from(this)
615
621
  }
616
622
 
617
623
  async update (opts) {
package/lib/bitfield.js CHANGED
@@ -1,99 +1,116 @@
1
1
  const BigSparseArray = require('big-sparse-array')
2
2
  const b4a = require('b4a')
3
- const bits = require('bits-to-bytes')
3
+ const quickbit = require('./compat').quickbit
4
4
 
5
- class FixedBitfield {
5
+ const BITS_PER_PAGE = 2097152
6
+ const BYTES_PER_PAGE = BITS_PER_PAGE / 8
7
+ const WORDS_PER_PAGE = BYTES_PER_PAGE / 4
8
+
9
+ class BitfieldPage {
6
10
  constructor (index, bitfield) {
7
11
  this.dirty = false
8
12
  this.index = index
9
13
  this.bitfield = bitfield
14
+ this.tree = quickbit.Index.from(this.bitfield)
10
15
  }
11
16
 
12
17
  get (index) {
13
- return bits.get(this.bitfield, index)
18
+ return quickbit.get(this.bitfield, index)
14
19
  }
15
20
 
16
21
  set (index, val) {
17
- return bits.set(this.bitfield, index, val)
22
+ if (quickbit.set(this.bitfield, index, val)) {
23
+ this.tree.update(index)
24
+ }
18
25
  }
19
26
 
20
27
  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
28
+ quickbit.fill(this.bitfield, val, start, start + length)
29
+
30
+ let i = Math.floor(start / 32)
31
+ const n = i + Math.ceil(length / 32)
32
+
33
+ while (i < n) this.tree.update(i++ * 32)
25
34
  }
26
35
 
27
- firstSet (position) {
28
- return bits.indexOf(this.bitfield, true, position)
36
+ findFirst (val, position) {
37
+ return quickbit.findFirst(this.bitfield, val, this.tree.skipFirst(!val, position))
29
38
  }
30
39
 
31
- lastSet (position) {
32
- return bits.lastIndexOf(this.bitfield, true, position)
40
+ findLast (val, position) {
41
+ return quickbit.findLast(this.bitfield, val, this.tree.skipLast(!val, position))
33
42
  }
34
43
  }
35
44
 
36
45
  module.exports = class Bitfield {
37
46
  constructor (storage, buf) {
38
- this.pageSize = 32768
39
- this.pages = new BigSparseArray()
40
47
  this.unflushed = []
41
48
  this.storage = storage
42
49
  this.resumed = !!(buf && buf.byteLength >= 4)
43
50
 
51
+ this._pages = new BigSparseArray()
52
+
44
53
  const all = this.resumed
45
54
  ? new Uint32Array(buf.buffer, buf.byteOffset, Math.floor(buf.byteLength / 4))
46
- : new Uint32Array(1024)
55
+ : new Uint32Array(WORDS_PER_PAGE)
47
56
 
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)
57
+ for (let i = 0; i < all.length; i += WORDS_PER_PAGE) {
58
+ const bitfield = ensureSize(all.subarray(i, i + (WORDS_PER_PAGE)), WORDS_PER_PAGE)
59
+ const page = new BitfieldPage(i / (WORDS_PER_PAGE), bitfield)
60
+ this._pages.set(page.index, page)
52
61
  }
53
62
  }
54
63
 
55
64
  get (index) {
56
- const j = index & (this.pageSize - 1)
57
- const i = (index - j) / this.pageSize
65
+ const j = index & (BITS_PER_PAGE - 1)
66
+ const i = (index - j) / BITS_PER_PAGE
58
67
 
59
- const p = this.pages.get(i)
68
+ const p = this._pages.get(i)
60
69
 
61
70
  return p ? p.get(j) : false
62
71
  }
63
72
 
64
73
  set (index, val) {
65
- const j = index & (this.pageSize - 1)
66
- const i = (index - j) / this.pageSize
74
+ const j = index & (BITS_PER_PAGE - 1)
75
+ const i = (index - j) / BITS_PER_PAGE
67
76
 
68
- let p = this.pages.get(i)
77
+ let p = this._pages.get(i)
69
78
 
70
79
  if (!p && val) {
71
- p = this.pages.set(i, new FixedBitfield(i, new Uint32Array(1024)))
80
+ p = this._pages.set(i, new BitfieldPage(i, new Uint32Array(WORDS_PER_PAGE)))
72
81
  }
73
82
 
74
- if (p && p.set(j, val) && !p.dirty) {
75
- p.dirty = true
76
- this.unflushed.push(p)
83
+ if (p) {
84
+ p.set(j, val)
85
+
86
+ if (!p.dirty) {
87
+ p.dirty = true
88
+ this.unflushed.push(p)
89
+ }
77
90
  }
78
91
  }
79
92
 
80
93
  setRange (start, length, val) {
81
- let j = start & (this.pageSize - 1)
82
- let i = (start - j) / this.pageSize
94
+ let j = start & (BITS_PER_PAGE - 1)
95
+ let i = (start - j) / BITS_PER_PAGE
83
96
 
84
97
  while (length > 0) {
85
- let p = this.pages.get(i)
98
+ let p = this._pages.get(i)
86
99
 
87
100
  if (!p && val) {
88
- p = this.pages.set(i, new FixedBitfield(i, new Uint32Array(1024)))
101
+ p = this._pages.set(i, new BitfieldPage(i, new Uint32Array(WORDS_PER_PAGE)))
89
102
  }
90
103
 
91
- const end = Math.min(j + length, this.pageSize)
104
+ const end = Math.min(j + length, BITS_PER_PAGE)
92
105
  const range = end - j
93
106
 
94
- if (p && p.setRange(j, range, val) && !p.dirty) {
95
- p.dirty = true
96
- this.unflushed.push(p)
107
+ if (p) {
108
+ p.setRange(j, range, val)
109
+
110
+ if (!p.dirty) {
111
+ p.dirty = true
112
+ this.unflushed.push(p)
113
+ }
97
114
  }
98
115
 
99
116
  j = 0
@@ -102,18 +119,18 @@ module.exports = class Bitfield {
102
119
  }
103
120
  }
104
121
 
105
- firstSet (position) {
106
- let j = position & (this.pageSize - 1)
107
- let i = (position - j) / this.pageSize
122
+ findFirst (val, position) {
123
+ let j = position & (BITS_PER_PAGE - 1)
124
+ let i = (position - j) / BITS_PER_PAGE
108
125
 
109
- while (i < this.pages.factor) {
110
- const p = this.pages.get(i)
126
+ while (i < this._pages.maxLength) {
127
+ const p = this._pages.get(i)
111
128
 
112
129
  if (p) {
113
- const index = p.firstSet(j)
130
+ const index = p.findFirst(val, j)
114
131
 
115
132
  if (index !== -1) {
116
- return i * this.pageSize + index
133
+ return i * BITS_PER_PAGE + index
117
134
  }
118
135
  }
119
136
 
@@ -124,33 +141,72 @@ module.exports = class Bitfield {
124
141
  return -1
125
142
  }
126
143
 
127
- lastSet (position) {
128
- let j = position & (this.pageSize - 1)
129
- let i = (position - j) / this.pageSize
144
+ firstSet (position) {
145
+ return this.findFirst(true, position)
146
+ }
147
+
148
+ firstUnset (position) {
149
+ return this.findFirst(false, position)
150
+ }
151
+
152
+ findLast (val, position) {
153
+ let j = position & (BITS_PER_PAGE - 1)
154
+ let i = (position - j) / BITS_PER_PAGE
130
155
 
131
156
  while (i >= 0) {
132
- const p = this.pages.get(i)
157
+ const p = this._pages.get(i)
133
158
 
134
159
  if (p) {
135
- const index = p.lastSet(j)
160
+ const index = p.findLast(val, j)
136
161
 
137
162
  if (index !== -1) {
138
- return i * this.pageSize + index
163
+ return i * BITS_PER_PAGE + index
139
164
  }
140
165
  }
141
166
 
142
- j = this.pageSize - 1
167
+ j = BITS_PER_PAGE - 1
143
168
  i--
144
169
  }
145
170
 
146
171
  return -1
147
172
  }
148
173
 
174
+ lastSet (position) {
175
+ return this.findLast(true, position)
176
+ }
177
+
178
+ lastUnset (position) {
179
+ return this.findLast(false, position)
180
+ }
181
+
182
+ * want (start, length) {
183
+ const j = start & (BITS_PER_PAGE - 1)
184
+ let i = (start - j) / BITS_PER_PAGE
185
+
186
+ while (length > 0) {
187
+ const p = this._pages.get(i)
188
+
189
+ if (p) {
190
+ // We always send at least 4 KiB worth of bitfield in a want, rounding
191
+ // to the nearest 4 KiB.
192
+ const end = ceilTo(clamp(length / 8, 4096, BYTES_PER_PAGE), 4096)
193
+
194
+ yield {
195
+ start: i * BITS_PER_PAGE,
196
+ bitfield: p.bitfield.subarray(0, end / 4)
197
+ }
198
+ }
199
+
200
+ i++
201
+ length -= BITS_PER_PAGE
202
+ }
203
+ }
204
+
149
205
  clear () {
150
206
  return new Promise((resolve, reject) => {
151
207
  this.storage.truncate(0, (err) => {
152
208
  if (err) return reject(err)
153
- this.pages = new BigSparseArray()
209
+ this._pages = new BigSparseArray()
154
210
  this.unflushed = []
155
211
  resolve()
156
212
  })
@@ -182,7 +238,7 @@ module.exports = class Bitfield {
182
238
  )
183
239
 
184
240
  page.dirty = false
185
- this.storage.write(page.index * 4096, buf, done)
241
+ this.storage.write(page.index * BYTES_PER_PAGE, buf, done)
186
242
  }
187
243
 
188
244
  function done (err) {
@@ -210,9 +266,19 @@ module.exports = class Bitfield {
210
266
  }
211
267
  }
212
268
 
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
269
+ function ensureSize (buffer, size) {
270
+ if (buffer.byteLength === size) return buffer
271
+ const copy = new Uint32Array(size)
272
+ copy.set(buffer, 0)
273
+ return copy
274
+ }
275
+
276
+ function clamp (n, min, max) {
277
+ return Math.min(Math.max(n, min), max)
278
+ }
279
+
280
+ function ceilTo (n, multiple = 1) {
281
+ const remainder = n % multiple
282
+ if (remainder === 0) return n
283
+ return n + multiple - remainder
218
284
  }
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/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.1",
3
+ "version": "10.4.0",
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",