@libp2p/kad-dht 12.0.15 → 12.0.16
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/dist/index.min.js +4 -4
- package/dist/src/constants.d.ts.map +1 -1
- package/dist/src/constants.js +1 -1
- package/dist/src/constants.js.map +1 -1
- package/dist/src/content-routing/index.d.ts.map +1 -1
- package/dist/src/content-routing/index.js +3 -2
- package/dist/src/content-routing/index.js.map +1 -1
- package/dist/src/index.d.ts +34 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/network.d.ts +3 -0
- package/dist/src/network.d.ts.map +1 -1
- package/dist/src/network.js +33 -8
- package/dist/src/network.js.map +1 -1
- package/dist/src/peer-list/peer-distance-list.d.ts +13 -4
- package/dist/src/peer-list/peer-distance-list.d.ts.map +1 -1
- package/dist/src/peer-list/peer-distance-list.js +29 -21
- package/dist/src/peer-list/peer-distance-list.js.map +1 -1
- package/dist/src/peer-routing/index.d.ts +5 -5
- package/dist/src/peer-routing/index.d.ts.map +1 -1
- package/dist/src/peer-routing/index.js +15 -24
- package/dist/src/peer-routing/index.js.map +1 -1
- package/dist/src/query/manager.d.ts +3 -0
- package/dist/src/query/manager.d.ts.map +1 -1
- package/dist/src/query/manager.js +14 -5
- package/dist/src/query/manager.js.map +1 -1
- package/dist/src/query/query-path.d.ts +6 -6
- package/dist/src/query/query-path.d.ts.map +1 -1
- package/dist/src/query/query-path.js +32 -20
- package/dist/src/query/query-path.js.map +1 -1
- package/dist/src/routing-table/index.d.ts +11 -5
- package/dist/src/routing-table/index.d.ts.map +1 -1
- package/dist/src/routing-table/index.js +84 -42
- package/dist/src/routing-table/index.js.map +1 -1
- package/dist/src/routing-table/k-bucket.d.ts +80 -115
- package/dist/src/routing-table/k-bucket.d.ts.map +1 -1
- package/dist/src/routing-table/k-bucket.js +165 -311
- package/dist/src/routing-table/k-bucket.js.map +1 -1
- package/dist/src/routing-table/refresh.d.ts.map +1 -1
- package/dist/src/routing-table/refresh.js +9 -4
- package/dist/src/routing-table/refresh.js.map +1 -1
- package/package.json +8 -10
- package/src/constants.ts +1 -1
- package/src/content-routing/index.ts +3 -2
- package/src/index.ts +37 -1
- package/src/network.ts +38 -9
- package/src/peer-list/peer-distance-list.ts +36 -25
- package/src/peer-routing/index.ts +19 -28
- package/src/query/manager.ts +18 -5
- package/src/query/query-path.ts +46 -30
- package/src/routing-table/index.ts +100 -46
- package/src/routing-table/k-bucket.ts +214 -359
- package/src/routing-table/refresh.ts +10 -4
- package/dist/src/query/utils.d.ts +0 -6
- package/dist/src/query/utils.d.ts.map +0 -1
- package/dist/src/query/utils.js +0 -53
- package/dist/src/query/utils.js.map +0 -1
- package/src/query/utils.ts +0 -64
|
@@ -1,33 +1,9 @@
|
|
|
1
|
-
/*
|
|
2
|
-
index.js - Kademlia DHT K-bucket implementation as a binary tree.
|
|
3
|
-
|
|
4
|
-
The MIT License (MIT)
|
|
5
|
-
|
|
6
|
-
Copyright (c) 2013-2021 Tristan Slominski
|
|
7
|
-
|
|
8
|
-
Permission is hereby granted, free of charge, to any person
|
|
9
|
-
obtaining a copy of this software and associated documentation
|
|
10
|
-
files (the "Software"), to deal in the Software without
|
|
11
|
-
restriction, including without limitation the rights to use,
|
|
12
|
-
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
13
|
-
copies of the Software, and to permit persons to whom the
|
|
14
|
-
Software is furnished to do so, subject to the following
|
|
15
|
-
conditions:
|
|
16
|
-
|
|
17
|
-
The above copyright notice and this permission notice shall be
|
|
18
|
-
included in all copies or substantial portions of the Software.
|
|
19
|
-
|
|
20
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
21
|
-
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
|
22
|
-
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
23
|
-
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|
24
|
-
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
25
|
-
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
26
|
-
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
27
|
-
OTHER DEALINGS IN THE SOFTWARE.
|
|
28
|
-
*/
|
|
29
|
-
|
|
30
1
|
import { TypedEventEmitter } from '@libp2p/interface'
|
|
2
|
+
import map from 'it-map'
|
|
3
|
+
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
|
|
4
|
+
import { xor as uint8ArrayXor } from 'uint8arrays/xor'
|
|
5
|
+
import { PeerDistanceList } from '../peer-list/peer-distance-list.js'
|
|
6
|
+
import { KBUCKET_SIZE } from './index.js'
|
|
31
7
|
import type { PeerId } from '@libp2p/interface'
|
|
32
8
|
|
|
33
9
|
function arrayEquals (array1: Uint8Array, array2: Uint8Array): boolean {
|
|
@@ -45,233 +21,184 @@ function arrayEquals (array1: Uint8Array, array2: Uint8Array): boolean {
|
|
|
45
21
|
return true
|
|
46
22
|
}
|
|
47
23
|
|
|
48
|
-
function createNode (): Bucket {
|
|
49
|
-
// @ts-expect-error loose types
|
|
50
|
-
return { contacts: [], dontSplit: false, left: null, right: null }
|
|
51
|
-
}
|
|
52
|
-
|
|
53
24
|
function ensureInt8 (name: string, val?: Uint8Array): void {
|
|
54
25
|
if (!(val instanceof Uint8Array)) {
|
|
55
26
|
throw new TypeError(name + ' is not a Uint8Array')
|
|
56
27
|
}
|
|
57
|
-
}
|
|
58
28
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
29
|
+
if (val.byteLength !== 32) {
|
|
30
|
+
throw new TypeError(name + ' had incorrect length')
|
|
31
|
+
}
|
|
62
32
|
}
|
|
63
33
|
|
|
64
|
-
export interface
|
|
65
|
-
|
|
66
|
-
|
|
34
|
+
export interface PingEventDetails {
|
|
35
|
+
oldContacts: Peer[]
|
|
36
|
+
newContact: Peer
|
|
67
37
|
}
|
|
68
38
|
|
|
69
39
|
export interface KBucketEvents {
|
|
70
40
|
'ping': CustomEvent<PingEventDetails>
|
|
71
|
-
'added': CustomEvent<
|
|
72
|
-
'removed': CustomEvent<
|
|
73
|
-
'updated': CustomEvent<UpdatedEventDetails>
|
|
41
|
+
'added': CustomEvent<Peer>
|
|
42
|
+
'removed': CustomEvent<Peer>
|
|
74
43
|
}
|
|
75
44
|
|
|
76
45
|
export interface KBucketOptions {
|
|
77
46
|
/**
|
|
78
|
-
*
|
|
47
|
+
* The current peer. All subsequently added peers must have a KadID that is
|
|
48
|
+
* the same length as this peer.
|
|
79
49
|
*/
|
|
80
|
-
|
|
50
|
+
localPeer: Peer
|
|
81
51
|
|
|
82
52
|
/**
|
|
83
|
-
*
|
|
53
|
+
* How many bits of the key to use when forming the bucket trie. The larger
|
|
54
|
+
* this value, the deeper the tree will grow and the slower the lookups will
|
|
55
|
+
* be but the peers returned will be more specific to the key.
|
|
84
56
|
*/
|
|
85
|
-
|
|
57
|
+
prefixLength: number
|
|
86
58
|
|
|
87
59
|
/**
|
|
88
|
-
* The number of nodes
|
|
89
|
-
* full.
|
|
90
|
-
*
|
|
60
|
+
* The number of nodes that a max-depth k-bucket can contain before being
|
|
61
|
+
* full.
|
|
62
|
+
*
|
|
63
|
+
* @default 20
|
|
91
64
|
*/
|
|
92
|
-
|
|
65
|
+
kBucketSize?: number
|
|
93
66
|
|
|
94
67
|
/**
|
|
95
|
-
*
|
|
96
|
-
*
|
|
68
|
+
* The number of nodes that an intermediate k-bucket can contain before being
|
|
69
|
+
* split.
|
|
70
|
+
*
|
|
71
|
+
* @default kBucketSize
|
|
97
72
|
*/
|
|
98
|
-
|
|
73
|
+
splitThreshold?: number
|
|
99
74
|
|
|
100
75
|
/**
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
76
|
+
* The number of nodes to ping when a bucket that should not be split becomes
|
|
77
|
+
* full. KBucket will emit a `ping` event that contains `numberOfNodesToPing`
|
|
78
|
+
* nodes that have not been contacted the longest.
|
|
104
79
|
*/
|
|
105
|
-
|
|
80
|
+
numberOfNodesToPing?: number
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface Peer {
|
|
84
|
+
kadId: Uint8Array
|
|
85
|
+
peerId: PeerId
|
|
106
86
|
}
|
|
107
87
|
|
|
108
|
-
export interface
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
88
|
+
export interface LeafBucket {
|
|
89
|
+
prefix: string
|
|
90
|
+
depth: number
|
|
91
|
+
peers: Peer[]
|
|
112
92
|
}
|
|
113
93
|
|
|
114
|
-
export interface
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
dontSplit: boolean
|
|
94
|
+
export interface InternalBucket {
|
|
95
|
+
prefix: string
|
|
96
|
+
depth: number
|
|
118
97
|
left: Bucket
|
|
119
98
|
right: Bucket
|
|
120
99
|
}
|
|
121
100
|
|
|
101
|
+
export type Bucket = LeafBucket | InternalBucket
|
|
102
|
+
|
|
103
|
+
export function isLeafBucket (obj: any): obj is LeafBucket {
|
|
104
|
+
return Array.isArray(obj?.peers)
|
|
105
|
+
}
|
|
106
|
+
|
|
122
107
|
/**
|
|
123
|
-
* Implementation of a Kademlia DHT
|
|
124
|
-
*
|
|
108
|
+
* Implementation of a Kademlia DHT routing table as a prefix binary trie with
|
|
109
|
+
* configurable prefix length, bucket split threshold and size.
|
|
125
110
|
*/
|
|
126
111
|
export class KBucket extends TypedEventEmitter<KBucketEvents> {
|
|
127
|
-
public localNodeId: Uint8Array
|
|
128
112
|
public root: Bucket
|
|
129
|
-
|
|
113
|
+
public localPeer: Peer
|
|
114
|
+
private readonly prefixLength: number
|
|
115
|
+
private readonly splitThreshold: number
|
|
116
|
+
private readonly kBucketSize: number
|
|
130
117
|
private readonly numberOfNodesToPing: number
|
|
131
|
-
private readonly distance: (a: Uint8Array, b: Uint8Array) => number
|
|
132
|
-
private readonly arbiter: (incumbent: Contact, candidate: Contact) => Contact
|
|
133
118
|
|
|
134
119
|
constructor (options: KBucketOptions) {
|
|
135
120
|
super()
|
|
136
121
|
|
|
137
|
-
this.
|
|
138
|
-
this.
|
|
122
|
+
this.localPeer = options.localPeer
|
|
123
|
+
this.prefixLength = options.prefixLength
|
|
124
|
+
this.kBucketSize = options.kBucketSize ?? KBUCKET_SIZE
|
|
125
|
+
this.splitThreshold = options.splitThreshold ?? this.kBucketSize
|
|
139
126
|
this.numberOfNodesToPing = options.numberOfNodesToPing ?? 3
|
|
140
|
-
this.distance = options.distance ?? KBucket.distance
|
|
141
|
-
// use an arbiter from options or vectorClock arbiter by default
|
|
142
|
-
this.arbiter = options.arbiter ?? KBucket.arbiter
|
|
143
127
|
|
|
144
|
-
ensureInt8('
|
|
145
|
-
|
|
146
|
-
this.root = createNode()
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* Default arbiter function for contacts with the same id. Uses
|
|
151
|
-
* contact.vectorClock to select which contact to update the k-bucket with.
|
|
152
|
-
* Contact with larger vectorClock field will be selected. If vectorClock is
|
|
153
|
-
* the same, candidate will be selected.
|
|
154
|
-
*
|
|
155
|
-
* @param {object} incumbent - Contact currently stored in the k-bucket.
|
|
156
|
-
* @param {object} candidate - Contact being added to the k-bucket.
|
|
157
|
-
* @returns {object} Contact to updated the k-bucket with.
|
|
158
|
-
*/
|
|
159
|
-
static arbiter (incumbent: Contact, candidate: Contact): Contact {
|
|
160
|
-
return (incumbent.vectorClock ?? 0) > (candidate.vectorClock ?? 0) ? incumbent : candidate
|
|
161
|
-
}
|
|
128
|
+
ensureInt8('options.localPeer.kadId', options.localPeer.kadId)
|
|
162
129
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
* @param {Uint8Array} firstId - Uint8Array containing first id.
|
|
168
|
-
* @param {Uint8Array} secondId - Uint8Array containing second id.
|
|
169
|
-
* @returns {number} Integer The XOR distance between firstId and secondId.
|
|
170
|
-
*/
|
|
171
|
-
static distance (firstId: Uint8Array, secondId: Uint8Array): number {
|
|
172
|
-
let distance = 0
|
|
173
|
-
let i = 0
|
|
174
|
-
const min = Math.min(firstId.length, secondId.length)
|
|
175
|
-
const max = Math.max(firstId.length, secondId.length)
|
|
176
|
-
for (; i < min; ++i) {
|
|
177
|
-
distance = distance * 256 + (firstId[i] ^ secondId[i])
|
|
130
|
+
this.root = {
|
|
131
|
+
prefix: '',
|
|
132
|
+
depth: 0,
|
|
133
|
+
peers: []
|
|
178
134
|
}
|
|
179
|
-
for (; i < max; ++i) distance = distance * 256 + 255
|
|
180
|
-
return distance
|
|
181
135
|
}
|
|
182
136
|
|
|
183
137
|
/**
|
|
184
138
|
* Adds a contact to the k-bucket.
|
|
185
139
|
*
|
|
186
|
-
* @param {
|
|
140
|
+
* @param {Peer} peer - the contact object to add
|
|
187
141
|
*/
|
|
188
|
-
add (
|
|
189
|
-
ensureInt8('
|
|
142
|
+
add (peer: Peer): void {
|
|
143
|
+
ensureInt8('peer.kadId', peer?.kadId)
|
|
190
144
|
|
|
191
|
-
|
|
192
|
-
let node = this.root
|
|
193
|
-
|
|
194
|
-
while (node.contacts === null) {
|
|
195
|
-
// this is not a leaf node but an inner node with 'low' and 'high'
|
|
196
|
-
// branches; we will check the appropriate bit of the identifier and
|
|
197
|
-
// delegate to the appropriate node for further processing
|
|
198
|
-
node = this._determineNode(node, contact.id, bitIndex++)
|
|
199
|
-
}
|
|
145
|
+
const bucket = this._determineBucket(peer.kadId)
|
|
200
146
|
|
|
201
147
|
// check if the contact already exists
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
this._update(node, index, contact)
|
|
205
|
-
return this
|
|
148
|
+
if (this._indexOf(bucket, peer.kadId) > -1) {
|
|
149
|
+
return
|
|
206
150
|
}
|
|
207
151
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
152
|
+
// are there too many peers in the bucket and can we make the trie deeper?
|
|
153
|
+
if (bucket.peers.length === this.splitThreshold && bucket.depth < this.prefixLength) {
|
|
154
|
+
// split the bucket
|
|
155
|
+
this._split(bucket)
|
|
156
|
+
|
|
157
|
+
// try again
|
|
158
|
+
this.add(peer)
|
|
159
|
+
|
|
160
|
+
return
|
|
212
161
|
}
|
|
213
162
|
|
|
214
|
-
// the bucket
|
|
215
|
-
if (
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
// be added (this prevents DoS flodding with new invalid contacts)
|
|
221
|
-
this.safeDispatchEvent('ping', {
|
|
222
|
-
detail: {
|
|
223
|
-
oldContacts: node.contacts.slice(0, this.numberOfNodesToPing),
|
|
224
|
-
newContact: contact
|
|
225
|
-
}
|
|
226
|
-
})
|
|
227
|
-
return this
|
|
163
|
+
// is there space in the bucket?
|
|
164
|
+
if (bucket.peers.length < this.kBucketSize) {
|
|
165
|
+
bucket.peers.push(peer)
|
|
166
|
+
this.safeDispatchEvent('added', { detail: peer })
|
|
167
|
+
|
|
168
|
+
return
|
|
228
169
|
}
|
|
229
170
|
|
|
230
|
-
|
|
231
|
-
|
|
171
|
+
// we are at the bottom of the trie and the bucket is full so we can't add
|
|
172
|
+
// any more peers.
|
|
173
|
+
//
|
|
174
|
+
// instead ping the first this.numberOfNodesToPing in order to determine
|
|
175
|
+
// if they are still online.
|
|
176
|
+
//
|
|
177
|
+
// only add the new peer if one of the pinged nodes does not respond, this
|
|
178
|
+
// prevents DoS flooding with new invalid contacts.
|
|
179
|
+
this.safeDispatchEvent('ping', {
|
|
180
|
+
detail: {
|
|
181
|
+
oldContacts: bucket.peers.slice(0, this.numberOfNodesToPing),
|
|
182
|
+
newContact: peer
|
|
183
|
+
}
|
|
184
|
+
})
|
|
232
185
|
}
|
|
233
186
|
|
|
234
187
|
/**
|
|
235
|
-
* Get
|
|
188
|
+
* Get 0-n closest contacts to the provided node id. "Closest" here means:
|
|
236
189
|
* closest according to the XOR metric of the contact node id.
|
|
237
190
|
*
|
|
238
191
|
* @param {Uint8Array} id - Contact node id
|
|
239
|
-
* @
|
|
240
|
-
* @returns {Array} Array Maximum of n closest contacts to the node id
|
|
192
|
+
* @returns {Generator<Peer, void, undefined>} Array Maximum of n closest contacts to the node id
|
|
241
193
|
*/
|
|
242
|
-
closest (id: Uint8Array, n =
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
if ((!Number.isInteger(n) && n !== Infinity) || n <= 0) {
|
|
246
|
-
throw new TypeError('n is not positive number')
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
let contacts: Contact[] = []
|
|
194
|
+
* closest (id: Uint8Array, n: number = this.kBucketSize): Generator<PeerId, void, undefined> {
|
|
195
|
+
const list = new PeerDistanceList(id, n)
|
|
250
196
|
|
|
251
|
-
for (
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
if (node == null) {
|
|
255
|
-
continue
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
if (node.contacts === null) {
|
|
259
|
-
const detNode = this._determineNode(node, id, bitIndex++)
|
|
260
|
-
nodes.push(node.left === detNode ? node.right : node.left)
|
|
261
|
-
nodes.push(detNode)
|
|
262
|
-
} else {
|
|
263
|
-
contacts = contacts.concat(node.contacts)
|
|
264
|
-
}
|
|
197
|
+
for (const peer of this.toIterable()) {
|
|
198
|
+
list.addWitKadId({ id: peer.peerId, multiaddrs: [] }, peer.kadId)
|
|
265
199
|
}
|
|
266
200
|
|
|
267
|
-
|
|
268
|
-
.map(a => ({
|
|
269
|
-
distance: this.distance(a.id, id),
|
|
270
|
-
contact: a
|
|
271
|
-
}))
|
|
272
|
-
.sort((a, b) => a.distance - b.distance)
|
|
273
|
-
.slice(0, n)
|
|
274
|
-
.map(a => a.contact)
|
|
201
|
+
yield * map(list.peers, info => info.id)
|
|
275
202
|
}
|
|
276
203
|
|
|
277
204
|
/**
|
|
@@ -280,65 +207,25 @@ export class KBucket extends TypedEventEmitter<KBucketEvents> {
|
|
|
280
207
|
* @returns {number} The number of contacts held in the tree
|
|
281
208
|
*/
|
|
282
209
|
count (): number {
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
const node = nodes.pop()
|
|
287
|
-
|
|
288
|
-
if (node == null) {
|
|
289
|
-
continue
|
|
210
|
+
function countBucket (bucket: Bucket): number {
|
|
211
|
+
if (isLeafBucket(bucket)) {
|
|
212
|
+
return bucket.peers.length
|
|
290
213
|
}
|
|
291
214
|
|
|
292
|
-
|
|
293
|
-
nodes.push(node.right, node.left)
|
|
294
|
-
} else {
|
|
295
|
-
count += node.contacts.length
|
|
296
|
-
}
|
|
297
|
-
}
|
|
215
|
+
let count = 0
|
|
298
216
|
|
|
299
|
-
|
|
300
|
-
|
|
217
|
+
if (bucket.left != null) {
|
|
218
|
+
count += countBucket(bucket.left)
|
|
219
|
+
}
|
|
301
220
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
*
|
|
306
|
-
* @param {object} node - internal object that has 2 leafs: left and right
|
|
307
|
-
* @param {Uint8Array} id - Id to compare localNodeId with.
|
|
308
|
-
* @param {number} bitIndex - Integer (Default: 0) The bit index to which bit to check in the id Uint8Array.
|
|
309
|
-
* @returns {object} left leaf if id at bitIndex is 0, right leaf otherwise.
|
|
310
|
-
*/
|
|
311
|
-
_determineNode (node: any, id: Uint8Array, bitIndex: number): Bucket {
|
|
312
|
-
// **NOTE** remember that id is a Uint8Array and has granularity of
|
|
313
|
-
// bytes (8 bits), whereas the bitIndex is the _bit_ index (not byte)
|
|
314
|
-
|
|
315
|
-
// id's that are too short are put in low bucket (1 byte = 8 bits)
|
|
316
|
-
// (bitIndex >> 3) finds how many bytes the bitIndex describes
|
|
317
|
-
// bitIndex % 8 checks if we have extra bits beyond byte multiples
|
|
318
|
-
// if number of bytes is <= no. of bytes described by bitIndex and there
|
|
319
|
-
// are extra bits to consider, this means id has less bits than what
|
|
320
|
-
// bitIndex describes, id therefore is too short, and will be put in low
|
|
321
|
-
// bucket
|
|
322
|
-
const bytesDescribedByBitIndex = bitIndex >> 3
|
|
323
|
-
const bitIndexWithinByte = bitIndex % 8
|
|
324
|
-
if ((id.length <= bytesDescribedByBitIndex) && (bitIndexWithinByte !== 0)) {
|
|
325
|
-
return node.left
|
|
326
|
-
}
|
|
221
|
+
if (bucket.right != null) {
|
|
222
|
+
count += countBucket(bucket.right)
|
|
223
|
+
}
|
|
327
224
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
// byteUnderConsideration is an integer from 0 to 255 represented by 8 bits
|
|
331
|
-
// where 255 is 11111111 and 0 is 00000000
|
|
332
|
-
// in order to find out whether the bit at bitIndexWithinByte is set
|
|
333
|
-
// we construct (1 << (7 - bitIndexWithinByte)) which will consist
|
|
334
|
-
// of all bits being 0, with only one bit set to 1
|
|
335
|
-
// for example, if bitIndexWithinByte is 3, we will construct 00010000 by
|
|
336
|
-
// (1 << (7 - 3)) -> (1 << 4) -> 16
|
|
337
|
-
if ((byteUnderConsideration & (1 << (7 - bitIndexWithinByte))) !== 0) {
|
|
338
|
-
return node.right
|
|
225
|
+
return count
|
|
339
226
|
}
|
|
340
227
|
|
|
341
|
-
return
|
|
228
|
+
return countBucket(this.root)
|
|
342
229
|
}
|
|
343
230
|
|
|
344
231
|
/**
|
|
@@ -347,175 +234,143 @@ export class KBucket extends TypedEventEmitter<KBucketEvents> {
|
|
|
347
234
|
* contact if we have it or null if not. If this is an inner node, determine
|
|
348
235
|
* which branch of the tree to traverse and repeat.
|
|
349
236
|
*
|
|
350
|
-
* @param {Uint8Array}
|
|
351
|
-
* @returns {object |
|
|
352
|
-
*/
|
|
353
|
-
get (id: Uint8Array): Contact | undefined {
|
|
354
|
-
ensureInt8('id', id)
|
|
355
|
-
|
|
356
|
-
let bitIndex = 0
|
|
357
|
-
|
|
358
|
-
let node: Bucket = this.root
|
|
359
|
-
while (node.contacts === null) {
|
|
360
|
-
node = this._determineNode(node, id, bitIndex++)
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
// index of uses contact id for matching
|
|
364
|
-
const index = this._indexOf(node, id)
|
|
365
|
-
return index >= 0 ? node.contacts[index] : undefined
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
/**
|
|
369
|
-
* Returns the index of the contact with provided
|
|
370
|
-
* id if it exists, returns -1 otherwise.
|
|
371
|
-
*
|
|
372
|
-
* @param {object} node - internal object that has 2 leafs: left and right
|
|
373
|
-
* @param {Uint8Array} id - Contact node id.
|
|
374
|
-
* @returns {number} Integer Index of contact with provided id if it exists, -1 otherwise.
|
|
237
|
+
* @param {Uint8Array} kadId - The ID of the contact to fetch.
|
|
238
|
+
* @returns {object | undefined} The contact if available, otherwise null
|
|
375
239
|
*/
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
}
|
|
240
|
+
get (kadId: Uint8Array): Peer | undefined {
|
|
241
|
+
const bucket = this._determineBucket(kadId)
|
|
242
|
+
const index = this._indexOf(bucket, kadId)
|
|
380
243
|
|
|
381
|
-
return
|
|
244
|
+
return bucket.peers[index]
|
|
382
245
|
}
|
|
383
246
|
|
|
384
247
|
/**
|
|
385
248
|
* Removes contact with the provided id.
|
|
386
249
|
*
|
|
387
|
-
* @param {Uint8Array}
|
|
388
|
-
* @returns {object} The k-bucket itself
|
|
250
|
+
* @param {Uint8Array} kadId - The ID of the contact to remove
|
|
389
251
|
*/
|
|
390
|
-
remove (
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
let bitIndex = 0
|
|
394
|
-
let node = this.root
|
|
395
|
-
|
|
396
|
-
while (node.contacts === null) {
|
|
397
|
-
node = this._determineNode(node, id, bitIndex++)
|
|
398
|
-
}
|
|
252
|
+
remove (kadId: Uint8Array): void {
|
|
253
|
+
const bucket = this._determineBucket(kadId)
|
|
254
|
+
const index = this._indexOf(bucket, kadId)
|
|
399
255
|
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
const contact = node.contacts.splice(index, 1)[0]
|
|
256
|
+
if (index > -1) {
|
|
257
|
+
const peer = bucket.peers.splice(index, 1)[0]
|
|
403
258
|
this.safeDispatchEvent('removed', {
|
|
404
|
-
detail:
|
|
259
|
+
detail: peer
|
|
405
260
|
})
|
|
406
261
|
}
|
|
407
|
-
|
|
408
|
-
return this
|
|
409
262
|
}
|
|
410
263
|
|
|
411
264
|
/**
|
|
412
|
-
*
|
|
413
|
-
*
|
|
414
|
-
*
|
|
265
|
+
* Similar to `toArray()` but instead of buffering everything up into an
|
|
266
|
+
* array before returning it, yields contacts as they are encountered while
|
|
267
|
+
* walking the tree.
|
|
415
268
|
*
|
|
416
|
-
* @
|
|
417
|
-
* @param {number} bitIndex - the bitIndex to which byte to check in the Uint8Array for navigating the binary tree
|
|
269
|
+
* @returns {Iterable} All of the contacts in the tree, as an iterable
|
|
418
270
|
*/
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
271
|
+
* toIterable (): Generator<Peer, void, undefined> {
|
|
272
|
+
function * iterate (bucket: Bucket): Generator<Peer, void, undefined> {
|
|
273
|
+
if (isLeafBucket(bucket)) {
|
|
274
|
+
yield * bucket.peers
|
|
275
|
+
return
|
|
276
|
+
}
|
|
422
277
|
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
this._determineNode(node, contact.id, bitIndex).contacts.push(contact)
|
|
278
|
+
yield * iterate(bucket.left)
|
|
279
|
+
yield * iterate(bucket.right)
|
|
426
280
|
}
|
|
427
281
|
|
|
428
|
-
|
|
429
|
-
|
|
282
|
+
yield * iterate(this.root)
|
|
283
|
+
}
|
|
430
284
|
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
285
|
+
/**
|
|
286
|
+
* Default distance function. Finds the XOR distance between firstId and
|
|
287
|
+
* secondId.
|
|
288
|
+
*
|
|
289
|
+
* @param {Uint8Array} firstId - Uint8Array containing first id.
|
|
290
|
+
* @param {Uint8Array} secondId - Uint8Array containing second id.
|
|
291
|
+
* @returns {number} Integer The XOR distance between firstId and secondId.
|
|
292
|
+
*/
|
|
293
|
+
distance (firstId: Uint8Array, secondId: Uint8Array): bigint {
|
|
294
|
+
return BigInt('0x' + uint8ArrayToString(uint8ArrayXor(firstId, secondId), 'base16'))
|
|
437
295
|
}
|
|
438
296
|
|
|
439
297
|
/**
|
|
440
|
-
*
|
|
441
|
-
*
|
|
442
|
-
* return the union of the low and high branches (themselves also as arrays).
|
|
298
|
+
* Determines whether the id at the bitIndex is 0 or 1
|
|
299
|
+
* Return left leaf if `id` at `bitIndex` is 0, right leaf otherwise
|
|
443
300
|
*
|
|
444
|
-
* @
|
|
301
|
+
* @param {Uint8Array} kadId - Id to compare localNodeId with
|
|
302
|
+
* @returns {LeafBucket} left leaf if id at bitIndex is 0, right leaf otherwise.
|
|
445
303
|
*/
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
const node = nodes.pop()
|
|
304
|
+
private _determineBucket (kadId: Uint8Array): LeafBucket {
|
|
305
|
+
const bitString = uint8ArrayToString(kadId, 'base2')
|
|
306
|
+
const prefix = bitString.substring(0, this.prefixLength)
|
|
450
307
|
|
|
451
|
-
|
|
452
|
-
|
|
308
|
+
function findBucket (bucket: Bucket, bitIndex: number = 0): LeafBucket {
|
|
309
|
+
if (isLeafBucket(bucket)) {
|
|
310
|
+
return bucket
|
|
453
311
|
}
|
|
454
312
|
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
313
|
+
const bit = prefix[bitIndex]
|
|
314
|
+
|
|
315
|
+
if (bit === '0') {
|
|
316
|
+
return findBucket(bucket.left, bitIndex + 1)
|
|
459
317
|
}
|
|
318
|
+
|
|
319
|
+
return findBucket(bucket.right, bitIndex + 1)
|
|
460
320
|
}
|
|
461
|
-
|
|
321
|
+
|
|
322
|
+
return findBucket(this.root)
|
|
462
323
|
}
|
|
463
324
|
|
|
464
325
|
/**
|
|
465
|
-
*
|
|
466
|
-
*
|
|
467
|
-
* walking the tree.
|
|
326
|
+
* Returns the index of the contact with provided
|
|
327
|
+
* id if it exists, returns -1 otherwise.
|
|
468
328
|
*
|
|
469
|
-
* @
|
|
329
|
+
* @param {object} bucket - internal object that has 2 leafs: left and right
|
|
330
|
+
* @param {Uint8Array} kadId - KadId of peer
|
|
331
|
+
* @returns {number} Integer Index of contact with provided id if it exists, -1 otherwise.
|
|
470
332
|
*/
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
const node = nodes.pop()
|
|
474
|
-
|
|
475
|
-
if (node == null) {
|
|
476
|
-
continue
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
if (node.contacts === null) {
|
|
480
|
-
nodes.push(node.right, node.left)
|
|
481
|
-
} else {
|
|
482
|
-
yield * node.contacts
|
|
483
|
-
}
|
|
484
|
-
}
|
|
333
|
+
private _indexOf (bucket: LeafBucket, kadId: Uint8Array): number {
|
|
334
|
+
return bucket.peers.findIndex(peer => arrayEquals(peer.kadId, kadId))
|
|
485
335
|
}
|
|
486
336
|
|
|
487
337
|
/**
|
|
488
|
-
*
|
|
489
|
-
* If the selection is our old contact and the candidate is some new contact
|
|
490
|
-
* then the new contact is abandoned (not added).
|
|
491
|
-
* If the selection is our old contact and the candidate is our old contact
|
|
492
|
-
* then we are refreshing the contact and it is marked as most recently
|
|
493
|
-
* contacted (by being moved to the right/end of the bucket array).
|
|
494
|
-
* If the selection is our new contact, the old contact is removed and the new
|
|
495
|
-
* contact is marked as most recently contacted.
|
|
338
|
+
* Modify the bucket, turn it from a leaf bucket to an internal bucket
|
|
496
339
|
*
|
|
497
|
-
* @param {
|
|
498
|
-
* @param {number} index - the index in the bucket where contact exists (index has already been computed in a previous calculation)
|
|
499
|
-
* @param {object} contact - The contact object to update
|
|
340
|
+
* @param {any} bucket - bucket for splitting
|
|
500
341
|
*/
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
342
|
+
private _split (bucket: LeafBucket): void {
|
|
343
|
+
const depth = bucket.depth + 1
|
|
344
|
+
|
|
345
|
+
// create child buckets
|
|
346
|
+
const left: LeafBucket = {
|
|
347
|
+
prefix: '0',
|
|
348
|
+
depth,
|
|
349
|
+
peers: []
|
|
350
|
+
}
|
|
351
|
+
const right: LeafBucket = {
|
|
352
|
+
prefix: '1',
|
|
353
|
+
depth,
|
|
354
|
+
peers: []
|
|
505
355
|
}
|
|
506
356
|
|
|
507
|
-
|
|
508
|
-
const
|
|
509
|
-
|
|
510
|
-
// contact, then there is nothing to do
|
|
511
|
-
if (selection === incumbent && incumbent !== contact) return
|
|
357
|
+
// redistribute peers
|
|
358
|
+
for (const peer of bucket.peers) {
|
|
359
|
+
const bitString = uint8ArrayToString(peer.kadId, 'base2')
|
|
512
360
|
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
incumbent, selection
|
|
361
|
+
if (bitString[depth] === '0') {
|
|
362
|
+
left.peers.push(peer)
|
|
363
|
+
} else {
|
|
364
|
+
right.peers.push(peer)
|
|
518
365
|
}
|
|
519
|
-
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// convert leaf bucket to internal bucket
|
|
369
|
+
// @ts-expect-error peers is not a property of LeafBucket
|
|
370
|
+
delete bucket.peers
|
|
371
|
+
// @ts-expect-error left is not a property of LeafBucket
|
|
372
|
+
bucket.left = left
|
|
373
|
+
// @ts-expect-error right is not a property of LeafBucket
|
|
374
|
+
bucket.right = right
|
|
520
375
|
}
|
|
521
376
|
}
|