dht-rpc 5.0.4 → 6.0.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/README.md +17 -13
- package/index.js +22 -13
- package/lib/errors.js +10 -4
- package/lib/io.js +53 -43
- package/lib/peer.js +2 -1
- package/lib/query.js +3 -2
- package/package.json +6 -4
- package/test.js +3 -22
package/README.md
CHANGED
|
@@ -6,10 +6,6 @@ Make RPC calls over a [Kademlia](https://pdos.csail.mit.edu/~petar/papers/maymou
|
|
|
6
6
|
npm install dht-rpc
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
-
## NOTE: v5
|
|
10
|
-
|
|
11
|
-
Note that the latest release is v5. To see the v4 documentation/code go to https://github.com/mafintosh/dht-rpc/tree/v4
|
|
12
|
-
|
|
13
9
|
## Key Features
|
|
14
10
|
|
|
15
11
|
* Remote IP / firewall detection
|
|
@@ -116,12 +112,12 @@ Options include:
|
|
|
116
112
|
{
|
|
117
113
|
// A list of bootstrap nodes
|
|
118
114
|
bootstrap: [ 'bootstrap-node.com:24242', ... ],
|
|
119
|
-
// Optionally pass in your own UDP socket to use.
|
|
120
|
-
socket: udpSocket,
|
|
121
115
|
// Optionally pass in array of { host, port } to add to the routing table if you know any peers
|
|
122
116
|
nodes: [{ host, port }, ...],
|
|
123
117
|
// Optionally pass a port you prefer to bind to instead of a random one
|
|
124
|
-
|
|
118
|
+
port: 0,
|
|
119
|
+
// Optionally pass a UDX instance on which sockets will be created.
|
|
120
|
+
udx,
|
|
125
121
|
// dht-rpc will automatically detect if you are firewalled. If you know that you are not set this to false
|
|
126
122
|
firewalled: true
|
|
127
123
|
}
|
|
@@ -133,7 +129,7 @@ For the vast majority of use-cases you should always use adaptive mode to ensure
|
|
|
133
129
|
|
|
134
130
|
Your DHT routing id is `hash(publicIp + publicPort)` and will be autoconfigured internally.
|
|
135
131
|
|
|
136
|
-
#### `const node = DHT.
|
|
132
|
+
#### `const node = DHT.bootrapper(port, [options])`
|
|
137
133
|
|
|
138
134
|
Sugar for the options needed to run a bootstrap node, ie
|
|
139
135
|
|
|
@@ -144,7 +140,7 @@ Sugar for the options needed to run a bootstrap node, ie
|
|
|
144
140
|
}
|
|
145
141
|
```
|
|
146
142
|
|
|
147
|
-
Additionally since you'll want a known port for a bootstrap node it adds the
|
|
143
|
+
Additionally since you'll want a known port for a bootstrap node it adds the `port` option as a primary argument.
|
|
148
144
|
|
|
149
145
|
#### `await node.ready()`
|
|
150
146
|
|
|
@@ -165,7 +161,7 @@ Emitted when the routing table is fully bootstrapped. Emitted as a conveinience.
|
|
|
165
161
|
|
|
166
162
|
#### `node.on('listening')`
|
|
167
163
|
|
|
168
|
-
Emitted when the underlying
|
|
164
|
+
Emitted when the underlying UDX socket is listening. Emitted as a conveinience.
|
|
169
165
|
|
|
170
166
|
#### `node.on('persistent')`
|
|
171
167
|
|
|
@@ -179,6 +175,10 @@ you are on an open NAT.
|
|
|
179
175
|
Emitted when the node has detected that the computer has gone to sleep. If this happens,
|
|
180
176
|
it will switch from persistent mode to ephemeral again.
|
|
181
177
|
|
|
178
|
+
#### `node.on('network-change', interfaces)`
|
|
179
|
+
|
|
180
|
+
Emitted when the network interfaces of the computer change.
|
|
181
|
+
|
|
182
182
|
#### `node.refresh()`
|
|
183
183
|
|
|
184
184
|
Refresh the routing table by looking up a random node in the background.
|
|
@@ -201,12 +201,12 @@ Boolean indicated if your node is behind a firewall.
|
|
|
201
201
|
This is auto detected by having other node's trying to do a PING to you
|
|
202
202
|
without you contacting them first.
|
|
203
203
|
|
|
204
|
-
#### `const
|
|
204
|
+
#### `const addr = node.address()`
|
|
205
205
|
|
|
206
206
|
Get the local address of the UDP socket bound.
|
|
207
207
|
|
|
208
208
|
Note that if you are in ephemeral mode, this will return a different
|
|
209
|
-
port than the one you provided in the constructor (under
|
|
209
|
+
port than the one you provided in the constructor (under `port`), as ephemeral
|
|
210
210
|
mode always uses a random port.
|
|
211
211
|
|
|
212
212
|
#### `node.on('request', req)`
|
|
@@ -242,7 +242,7 @@ Options include:
|
|
|
242
242
|
```js
|
|
243
243
|
{
|
|
244
244
|
retry: true, // whether the request should retry on timeout
|
|
245
|
-
socket:
|
|
245
|
+
socket: udxSocket // request on this specific socket
|
|
246
246
|
}
|
|
247
247
|
```
|
|
248
248
|
|
|
@@ -309,6 +309,10 @@ nodes are stored in `stream.closestNodes` array.
|
|
|
309
309
|
|
|
310
310
|
If you want to access the closest replies to your provided target you can see those at `stream.closestReplies`.
|
|
311
311
|
|
|
312
|
+
#### `stream = node.findNode(target, [options])`
|
|
313
|
+
|
|
314
|
+
Find the node closest to the node with id `target`. Returns a stream encapsulating the query (see `node.query()`). `options` are the same as `node.query()`.
|
|
315
|
+
|
|
312
316
|
#### `node.destroy()`
|
|
313
317
|
|
|
314
318
|
Shutdown the DHT node.
|
package/index.js
CHANGED
|
@@ -2,16 +2,18 @@ const dns = require('dns')
|
|
|
2
2
|
const { EventEmitter } = require('events')
|
|
3
3
|
const Table = require('kademlia-routing-table')
|
|
4
4
|
const TOS = require('time-ordered-set')
|
|
5
|
+
const UDX = require('udx-native')
|
|
5
6
|
const sodium = require('sodium-universal')
|
|
6
7
|
const c = require('compact-encoding')
|
|
7
8
|
const NatSampler = require('nat-sampler')
|
|
9
|
+
const b4a = require('b4a')
|
|
8
10
|
const IO = require('./lib/io')
|
|
9
11
|
const Query = require('./lib/query')
|
|
10
12
|
const peer = require('./lib/peer')
|
|
11
13
|
const { UNKNOWN_COMMAND, INVALID_TOKEN } = require('./lib/errors')
|
|
12
14
|
const { PING, PING_NAT, FIND_NODE, DOWN_HINT } = require('./lib/commands')
|
|
13
15
|
|
|
14
|
-
const TMP =
|
|
16
|
+
const TMP = b4a.allocUnsafe(32)
|
|
15
17
|
const TICK_INTERVAL = 5000
|
|
16
18
|
const SLEEPING_INTERVAL = 3 * TICK_INTERVAL
|
|
17
19
|
const STABLE_TICKS = 240 // if nothing major bad happens in ~20mins we can consider this node stable (if nat is friendly)
|
|
@@ -27,7 +29,8 @@ class DHT extends EventEmitter {
|
|
|
27
29
|
this.bootstrapNodes = opts.bootstrap === false ? [] : (opts.bootstrap || []).map(parseNode)
|
|
28
30
|
this.table = new Table(opts.id || randomBytes(32))
|
|
29
31
|
this.nodes = new TOS()
|
|
30
|
-
this.
|
|
32
|
+
this.udx = opts.udx || new UDX()
|
|
33
|
+
this.io = new IO(this.table, this.udx, {
|
|
31
34
|
...opts,
|
|
32
35
|
onrequest: this._onrequest.bind(this),
|
|
33
36
|
onresponse: this._onresponse.bind(this),
|
|
@@ -42,7 +45,7 @@ class DHT extends EventEmitter {
|
|
|
42
45
|
this.destroyed = false
|
|
43
46
|
|
|
44
47
|
this._nat = new NatSampler()
|
|
45
|
-
this.
|
|
48
|
+
this._port = opts.port || 0
|
|
46
49
|
this._quickFirewall = opts.quickFirewall !== false
|
|
47
50
|
this._forcePersistent = opts.ephemeral === false
|
|
48
51
|
this._repinging = 0
|
|
@@ -59,13 +62,15 @@ class DHT extends EventEmitter {
|
|
|
59
62
|
|
|
60
63
|
this.table.on('row', this._onrow)
|
|
61
64
|
|
|
65
|
+
this.io.networkInterfaces.on('change', (interfaces) => this._onnetworkchange(interfaces))
|
|
66
|
+
|
|
62
67
|
if (opts.nodes) {
|
|
63
68
|
for (const node of opts.nodes) this.addNode(node)
|
|
64
69
|
}
|
|
65
70
|
}
|
|
66
71
|
|
|
67
|
-
static bootstrapper (
|
|
68
|
-
return new this({
|
|
72
|
+
static bootstrapper (port, opts) {
|
|
73
|
+
return new this({ port, firewalled: false, bootstrap: [], ...opts })
|
|
69
74
|
}
|
|
70
75
|
|
|
71
76
|
get id () {
|
|
@@ -194,7 +199,7 @@ class DHT extends EventEmitter {
|
|
|
194
199
|
if (!first) return
|
|
195
200
|
first = false
|
|
196
201
|
|
|
197
|
-
const value =
|
|
202
|
+
const value = b4a.allocUnsafe(2)
|
|
198
203
|
c.uint16.encode({ start: 0, end: 2, buffer: value }, self.io.serverSocket.address().port)
|
|
199
204
|
|
|
200
205
|
self._request(data.from, true, PING_NAT, null, value, () => { testNat = true }, noop)
|
|
@@ -269,7 +274,7 @@ class DHT extends EventEmitter {
|
|
|
269
274
|
}
|
|
270
275
|
|
|
271
276
|
_addNode (node) {
|
|
272
|
-
if (this.nodes.has(node) || node.id
|
|
277
|
+
if (this.nodes.has(node) || b4a.equals(node.id, this.table.id)) return
|
|
273
278
|
|
|
274
279
|
node.added = node.pinged = node.seen = this._tick
|
|
275
280
|
|
|
@@ -328,6 +333,10 @@ class DHT extends EventEmitter {
|
|
|
328
333
|
this._repingAndSwap(newNode, oldest)
|
|
329
334
|
}
|
|
330
335
|
|
|
336
|
+
_onnetworkchange (interfaces) {
|
|
337
|
+
this.emit('network-change', interfaces)
|
|
338
|
+
}
|
|
339
|
+
|
|
331
340
|
_repingAndSwap (newNode, oldNode) {
|
|
332
341
|
const self = this
|
|
333
342
|
const lastSeen = oldNode.seen
|
|
@@ -529,13 +538,13 @@ class DHT extends EventEmitter {
|
|
|
529
538
|
// as possible, vs blindly copying them over...
|
|
530
539
|
|
|
531
540
|
// all good! copy over the old routing table to the new one
|
|
532
|
-
if (!this.table.id
|
|
541
|
+
if (!b4a.equals(this.table.id, id)) {
|
|
533
542
|
const nodes = this.table.toArray()
|
|
534
543
|
|
|
535
544
|
this.table = this.io.table = new Table(id)
|
|
536
545
|
|
|
537
546
|
for (const node of nodes) {
|
|
538
|
-
if (
|
|
547
|
+
if (b4a.equals(node.id, id)) continue
|
|
539
548
|
if (!this.table.add(node)) this.nodes.remove(node)
|
|
540
549
|
}
|
|
541
550
|
|
|
@@ -586,7 +595,7 @@ class DHT extends EventEmitter {
|
|
|
586
595
|
if (nodes.length === 0) return true
|
|
587
596
|
|
|
588
597
|
const hosts = []
|
|
589
|
-
const value =
|
|
598
|
+
const value = b4a.allocUnsafe(2)
|
|
590
599
|
|
|
591
600
|
c.uint16.encode({ start: 0, end: 2, buffer: value }, this.io.serverSocket.address().port)
|
|
592
601
|
|
|
@@ -618,8 +627,8 @@ class DHT extends EventEmitter {
|
|
|
618
627
|
|
|
619
628
|
return false
|
|
620
629
|
|
|
621
|
-
function onmessage (_,
|
|
622
|
-
hosts.push(
|
|
630
|
+
function onmessage (_, { host }) {
|
|
631
|
+
hosts.push(host)
|
|
623
632
|
}
|
|
624
633
|
}
|
|
625
634
|
|
|
@@ -659,7 +668,7 @@ function parseNode (s) {
|
|
|
659
668
|
}
|
|
660
669
|
|
|
661
670
|
function randomBytes (n) {
|
|
662
|
-
const b =
|
|
671
|
+
const b = b4a.alloc(n)
|
|
663
672
|
sodium.randombytes_buf(b)
|
|
664
673
|
return b
|
|
665
674
|
}
|
package/lib/errors.js
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
exports.UNKNOWN_COMMAND = 1
|
|
2
2
|
exports.INVALID_TOKEN = 2
|
|
3
3
|
|
|
4
|
-
exports.
|
|
5
|
-
|
|
4
|
+
exports.createTimeoutError = () => {
|
|
5
|
+
const timeoutErr = new Error('Request timed out')
|
|
6
|
+
timeoutErr.code = 'ETIMEDOUT'
|
|
7
|
+
return timeoutErr
|
|
8
|
+
}
|
|
6
9
|
|
|
7
|
-
exports.
|
|
8
|
-
|
|
10
|
+
exports.createDestroyedError = () => {
|
|
11
|
+
const destroyErr = new Error('Request destroyed')
|
|
12
|
+
destroyErr.code = 'EDESTROYED'
|
|
13
|
+
return destroyErr
|
|
14
|
+
}
|
package/lib/io.js
CHANGED
|
@@ -1,25 +1,27 @@
|
|
|
1
1
|
const FIFO = require('fast-fifo')
|
|
2
2
|
const sodium = require('sodium-universal')
|
|
3
3
|
const c = require('compact-encoding')
|
|
4
|
-
const
|
|
4
|
+
const b4a = require('b4a')
|
|
5
5
|
const peer = require('./peer')
|
|
6
|
-
const
|
|
6
|
+
const errors = require('./errors')
|
|
7
7
|
|
|
8
8
|
const VERSION = 0b11
|
|
9
9
|
const RESPONSE_ID = (0b0001 << 4) | VERSION
|
|
10
10
|
const REQUEST_ID = (0b0000 << 4) | VERSION
|
|
11
|
-
const TMP =
|
|
11
|
+
const TMP = b4a.alloc(32)
|
|
12
12
|
const EMPTY_ARRAY = []
|
|
13
13
|
|
|
14
14
|
module.exports = class IO {
|
|
15
|
-
constructor (table, { maxWindow = 80,
|
|
15
|
+
constructor (table, udx, { maxWindow = 80, port = 0, firewalled = true, onrequest, onresponse = noop, ontimeout = noop } = {}) {
|
|
16
16
|
this.table = table
|
|
17
|
+
this.udx = udx
|
|
17
18
|
this.inflight = []
|
|
18
19
|
this.clientSocket = null
|
|
19
20
|
this.serverSocket = null
|
|
20
21
|
this.firewalled = firewalled !== false
|
|
21
22
|
this.ephemeral = true
|
|
22
23
|
this.congestion = new CongestionWindow(maxWindow)
|
|
24
|
+
this.networkInterfaces = udx.watchNetworkInterfaces()
|
|
23
25
|
|
|
24
26
|
this.onrequest = onrequest
|
|
25
27
|
this.onresponse = onresponse
|
|
@@ -32,13 +34,13 @@ module.exports = class IO {
|
|
|
32
34
|
this._drainInterval = null
|
|
33
35
|
this._destroying = null
|
|
34
36
|
this._binding = null
|
|
35
|
-
this.
|
|
37
|
+
this._port = port
|
|
36
38
|
}
|
|
37
39
|
|
|
38
|
-
onmessage (socket, buffer,
|
|
39
|
-
if (buffer.byteLength < 2 || !(
|
|
40
|
+
onmessage (socket, buffer, { host, port }) {
|
|
41
|
+
if (buffer.byteLength < 2 || !(port > 0 && port < 65536)) return
|
|
40
42
|
|
|
41
|
-
const from = { id: null, host
|
|
43
|
+
const from = { id: null, host, port }
|
|
42
44
|
const state = { start: 1, end: buffer.byteLength, buffer }
|
|
43
45
|
const expectedSocket = this.firewalled ? this.clientSocket : this.serverSocket
|
|
44
46
|
const external = socket !== expectedSocket
|
|
@@ -46,8 +48,8 @@ module.exports = class IO {
|
|
|
46
48
|
if (buffer[0] === REQUEST_ID) {
|
|
47
49
|
const req = Request.decode(this, socket, from, state)
|
|
48
50
|
if (req === null) return
|
|
49
|
-
if (req.token !== null && !req.token
|
|
50
|
-
req.error(INVALID_TOKEN, { token: true })
|
|
51
|
+
if (req.token !== null && !b4a.equals(req.token, this.token(req.from, 1)) && !b4a.equals(req.token, this.token(req.from, 0))) {
|
|
52
|
+
req.error(errors.INVALID_TOKEN, { token: true })
|
|
51
53
|
return
|
|
52
54
|
}
|
|
53
55
|
this.onrequest(req, external)
|
|
@@ -65,7 +67,7 @@ module.exports = class IO {
|
|
|
65
67
|
if (i === this.inflight.length - 1) this.inflight.pop()
|
|
66
68
|
else this.inflight[i] = this.inflight.pop()
|
|
67
69
|
|
|
68
|
-
// TODO: Auto retry here if INVALID_TOKEN is returned?
|
|
70
|
+
// TODO: Auto retry here if errors.INVALID_TOKEN is returned?
|
|
69
71
|
|
|
70
72
|
if (req._timeout) {
|
|
71
73
|
clearTimeout(req._timeout)
|
|
@@ -82,14 +84,14 @@ module.exports = class IO {
|
|
|
82
84
|
|
|
83
85
|
token (addr, i) {
|
|
84
86
|
if (this._secrets === null) {
|
|
85
|
-
const buf =
|
|
87
|
+
const buf = b4a.alloc(64)
|
|
86
88
|
this._secrets = [buf.subarray(0, 32), buf.subarray(32, 64)]
|
|
87
89
|
sodium.randombytes_buf(this._secrets[0])
|
|
88
90
|
sodium.randombytes_buf(this._secrets[1])
|
|
89
91
|
}
|
|
90
92
|
|
|
91
|
-
const token =
|
|
92
|
-
sodium.crypto_generichash(token,
|
|
93
|
+
const token = b4a.allocUnsafe(32)
|
|
94
|
+
sodium.crypto_generichash(token, b4a.from(addr.host), this._secrets[i])
|
|
93
95
|
return token
|
|
94
96
|
}
|
|
95
97
|
|
|
@@ -109,19 +111,15 @@ module.exports = class IO {
|
|
|
109
111
|
if (req._timeout) clearTimeout(req._timeout)
|
|
110
112
|
req._timeout = null
|
|
111
113
|
req.destroyed = true
|
|
112
|
-
req.onerror(DESTROY, req)
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
this._destroying = new Promise((resolve) => {
|
|
116
|
-
let missing = 2
|
|
117
114
|
|
|
118
|
-
|
|
119
|
-
|
|
115
|
+
req.onerror(errors.createDestroyedError(), req)
|
|
116
|
+
}
|
|
120
117
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
118
|
+
this._destroying = Promise.allSettled([
|
|
119
|
+
this.serverSocket.close(),
|
|
120
|
+
this.clientSocket.close(),
|
|
121
|
+
this.networkInterfaces.destroy()
|
|
122
|
+
])
|
|
125
123
|
|
|
126
124
|
return this._destroying
|
|
127
125
|
}
|
|
@@ -133,19 +131,32 @@ module.exports = class IO {
|
|
|
133
131
|
}
|
|
134
132
|
|
|
135
133
|
async _bindSockets () {
|
|
136
|
-
const serverSocket =
|
|
134
|
+
const serverSocket = this.udx.createSocket()
|
|
137
135
|
|
|
138
136
|
try {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
137
|
+
serverSocket.bind(this._port)
|
|
138
|
+
} catch {
|
|
139
|
+
try {
|
|
140
|
+
serverSocket.bind()
|
|
141
|
+
} catch (err) {
|
|
142
|
+
await serverSocket.close()
|
|
143
|
+
throw err
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const clientSocket = this.udx.createSocket()
|
|
148
|
+
|
|
149
|
+
try {
|
|
150
|
+
clientSocket.bind()
|
|
144
151
|
} catch (err) {
|
|
145
|
-
await
|
|
152
|
+
await serverSocket.close()
|
|
153
|
+
await clientSocket.close()
|
|
146
154
|
throw err
|
|
147
155
|
}
|
|
148
156
|
|
|
157
|
+
this.clientSocket = clientSocket
|
|
158
|
+
this.serverSocket = serverSocket
|
|
159
|
+
|
|
149
160
|
this.serverSocket.on('message', this.onmessage.bind(this, this.serverSocket))
|
|
150
161
|
this.clientSocket.on('message', this.onmessage.bind(this, this.clientSocket))
|
|
151
162
|
|
|
@@ -240,8 +251,7 @@ class Request {
|
|
|
240
251
|
reply (value, opts = {}) {
|
|
241
252
|
const socket = opts.socket || this.socket
|
|
242
253
|
const to = opts.to || this.from
|
|
243
|
-
|
|
244
|
-
this._sendReply(0, value || null, opts.token !== false, opts.closerNodes !== false, to, socket, onflush)
|
|
254
|
+
this._sendReply(0, value || null, opts.token !== false, opts.closerNodes !== false, to, socket)
|
|
245
255
|
}
|
|
246
256
|
|
|
247
257
|
error (code, opts = {}) {
|
|
@@ -253,7 +263,7 @@ class Request {
|
|
|
253
263
|
relay (value, to, opts) {
|
|
254
264
|
const socket = (opts && opts.socket) || this.socket
|
|
255
265
|
const buffer = this._encodeRequest(null, value, to, socket)
|
|
256
|
-
socket.
|
|
266
|
+
socket.trySend(buffer, to.port, to.host)
|
|
257
267
|
}
|
|
258
268
|
|
|
259
269
|
send (force = false) {
|
|
@@ -278,7 +288,7 @@ class Request {
|
|
|
278
288
|
if (this.destroyed) return
|
|
279
289
|
this.sent++
|
|
280
290
|
this._io.congestion.send()
|
|
281
|
-
this.socket.
|
|
291
|
+
this.socket.trySend(this._buffer, this.to.port, this.to.host)
|
|
282
292
|
if (this._timeout) clearTimeout(this._timeout)
|
|
283
293
|
this._timeout = setTimeout(oncycle, 1000, this)
|
|
284
294
|
}
|
|
@@ -293,10 +303,10 @@ class Request {
|
|
|
293
303
|
if (i === this._io.inflight.length - 1) this._io.inflight.pop()
|
|
294
304
|
else this._io.inflight[i] = this._io.inflight.pop()
|
|
295
305
|
|
|
296
|
-
this.onerror(err ||
|
|
306
|
+
this.onerror(err || errors.createDestroyedError(), this)
|
|
297
307
|
}
|
|
298
308
|
|
|
299
|
-
_sendReply (error, value, token, hasCloserNodes, from, socket
|
|
309
|
+
_sendReply (error, value, token, hasCloserNodes, from, socket) {
|
|
300
310
|
if (socket === null || this.destroyed) return
|
|
301
311
|
|
|
302
312
|
const id = this._io.ephemeral === false && socket === this._io.serverSocket
|
|
@@ -309,7 +319,7 @@ class Request {
|
|
|
309
319
|
if (error > 0) c.uint.preencode(state, error)
|
|
310
320
|
if (value) c.buffer.preencode(state, value)
|
|
311
321
|
|
|
312
|
-
state.buffer =
|
|
322
|
+
state.buffer = b4a.allocUnsafe(state.end)
|
|
313
323
|
state.buffer[state.start++] = RESPONSE_ID
|
|
314
324
|
state.buffer[state.start++] = (id ? 1 : 0) | (token ? 2 : 0) | (closerNodes.length > 0 ? 4 : 0) | (error > 0 ? 8 : 0) | (value ? 16 : 0)
|
|
315
325
|
|
|
@@ -322,7 +332,7 @@ class Request {
|
|
|
322
332
|
if (error > 0) c.uint.encode(state, error)
|
|
323
333
|
if (value) c.buffer.encode(state, value)
|
|
324
334
|
|
|
325
|
-
socket.
|
|
335
|
+
socket.trySend(state.buffer, from.port, from.host)
|
|
326
336
|
}
|
|
327
337
|
|
|
328
338
|
_encodeRequest (token, value, to, socket) {
|
|
@@ -337,7 +347,7 @@ class Request {
|
|
|
337
347
|
if (this.target) state.end += 32
|
|
338
348
|
if (value) c.buffer.preencode(state, value)
|
|
339
349
|
|
|
340
|
-
state.buffer =
|
|
350
|
+
state.buffer = b4a.allocUnsafe(state.end)
|
|
341
351
|
state.buffer[state.start++] = REQUEST_ID
|
|
342
352
|
state.buffer[state.start++] = (id ? 1 : 0) | (token ? 2 : 0) | (this.internal ? 4 : 0) | (this.target ? 8 : 0) | (value ? 16 : 0)
|
|
343
353
|
|
|
@@ -393,7 +403,7 @@ function oncycle (req) {
|
|
|
393
403
|
req._timeout = null
|
|
394
404
|
req.oncycle(req)
|
|
395
405
|
if (req.sent >= req.retries) {
|
|
396
|
-
req.destroy(
|
|
406
|
+
req.destroy(errors.createTimeoutError())
|
|
397
407
|
req._io.ontimeout(req)
|
|
398
408
|
} else {
|
|
399
409
|
req.send()
|
|
@@ -420,5 +430,5 @@ function decodeReply (from, state) {
|
|
|
420
430
|
}
|
|
421
431
|
|
|
422
432
|
function validateId (id, from) {
|
|
423
|
-
return peer.id(from.host, from.port, TMP)
|
|
433
|
+
return b4a.equals(peer.id(from.host, from.port, TMP), id) ? id : null
|
|
424
434
|
}
|
package/lib/peer.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const sodium = require('sodium-universal')
|
|
2
2
|
const c = require('compact-encoding')
|
|
3
3
|
const net = require('compact-encoding-net')
|
|
4
|
+
const b4a = require('b4a')
|
|
4
5
|
|
|
5
6
|
const ipv4 = {
|
|
6
7
|
...net.ipv4Address,
|
|
@@ -16,7 +17,7 @@ const ipv4 = {
|
|
|
16
17
|
|
|
17
18
|
module.exports = { id, ipv4, ipv4Array: c.array(ipv4) }
|
|
18
19
|
|
|
19
|
-
function id (host, port, out =
|
|
20
|
+
function id (host, port, out = b4a.allocUnsafe(32)) {
|
|
20
21
|
const addr = out.subarray(0, 6)
|
|
21
22
|
ipv4.encode(
|
|
22
23
|
{ start: 0, end: 6, buffer: addr },
|
package/lib/query.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const { Readable } = require('streamx')
|
|
2
|
+
const b4a = require('b4a')
|
|
2
3
|
const peer = require('./peer')
|
|
3
4
|
const { DOWN_HINT } = require('./commands')
|
|
4
5
|
|
|
@@ -240,7 +241,7 @@ module.exports = class Query extends Readable {
|
|
|
240
241
|
if (m.closerNodes !== null) {
|
|
241
242
|
for (const node of m.closerNodes) {
|
|
242
243
|
node.id = peer.id(node.host, node.port)
|
|
243
|
-
if (node.id
|
|
244
|
+
if (b4a.equals(node.id, this.dht.table.id)) continue
|
|
244
245
|
// TODO: we could continue here instead of breaking to ensure that one of the nodes in the closer list
|
|
245
246
|
// is later marked as DOWN that we gossip that back
|
|
246
247
|
if (!this._addPending(node, m.from)) break
|
|
@@ -281,7 +282,7 @@ module.exports = class Query extends Readable {
|
|
|
281
282
|
}
|
|
282
283
|
|
|
283
284
|
_downHint (node, down) {
|
|
284
|
-
const state = { start: 0, end: 6, buffer:
|
|
285
|
+
const state = { start: 0, end: 6, buffer: b4a.allocUnsafe(6) }
|
|
285
286
|
peer.ipv4.encode(state, down)
|
|
286
287
|
this.dht._request(node, true, DOWN_HINT, null, state.buffer, noop, noop)
|
|
287
288
|
}
|
package/package.json
CHANGED
|
@@ -1,21 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dht-rpc",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "6.0.0",
|
|
4
4
|
"description": "Make RPC calls over a Kademlia based DHT",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"dependencies": {
|
|
7
|
-
"
|
|
7
|
+
"b4a": "^1.3.1",
|
|
8
8
|
"compact-encoding": "^2.1.0",
|
|
9
9
|
"compact-encoding-net": "^1.0.1",
|
|
10
|
+
"events": "^3.3.0",
|
|
10
11
|
"fast-fifo": "^1.0.0",
|
|
11
12
|
"kademlia-routing-table": "^1.0.0",
|
|
12
13
|
"nat-sampler": "^1.0.1",
|
|
13
14
|
"sodium-universal": "^3.0.4",
|
|
14
15
|
"streamx": "^2.10.3",
|
|
15
|
-
"time-ordered-set": "^1.0.2"
|
|
16
|
+
"time-ordered-set": "^1.0.2",
|
|
17
|
+
"udx-native": "^1.1.0"
|
|
16
18
|
},
|
|
17
19
|
"devDependencies": {
|
|
18
|
-
"brittle": "^
|
|
20
|
+
"brittle": "^2.3.1",
|
|
19
21
|
"standard": "^16.0.3"
|
|
20
22
|
},
|
|
21
23
|
"scripts": {
|
package/test.js
CHANGED
|
@@ -2,14 +2,12 @@ const test = require('brittle')
|
|
|
2
2
|
const dgram = require('dgram')
|
|
3
3
|
const DHT = require('./')
|
|
4
4
|
|
|
5
|
-
test.configure({ serial: true })
|
|
6
|
-
|
|
7
5
|
test('make tiny swarm', async function (t) {
|
|
8
6
|
await makeSwarm(2, t)
|
|
9
7
|
t.pass('could make swarm')
|
|
10
8
|
})
|
|
11
9
|
|
|
12
|
-
test('make bigger swarm', async function (t) {
|
|
10
|
+
test('make bigger swarm', { timeout: 60000 }, async function (t) {
|
|
13
11
|
const swarm = await makeSwarm(500, t)
|
|
14
12
|
|
|
15
13
|
const targetNode = swarm[25]
|
|
@@ -165,23 +163,6 @@ test('request with/without retries', async function (t) {
|
|
|
165
163
|
t.is(tries, 4)
|
|
166
164
|
})
|
|
167
165
|
|
|
168
|
-
test('reply onflush', async function (t) {
|
|
169
|
-
const [, a, b] = await makeSwarm(3, t)
|
|
170
|
-
|
|
171
|
-
let flushed = false
|
|
172
|
-
|
|
173
|
-
b.on('request', function (req) {
|
|
174
|
-
req.reply(null, {
|
|
175
|
-
onflush () {
|
|
176
|
-
flushed = true
|
|
177
|
-
}
|
|
178
|
-
})
|
|
179
|
-
})
|
|
180
|
-
|
|
181
|
-
await a.request({ command: 42 }, { host: '127.0.0.1', port: b.address().port })
|
|
182
|
-
t.ok(flushed)
|
|
183
|
-
})
|
|
184
|
-
|
|
185
166
|
test('shorthand commit', async function (t) {
|
|
186
167
|
const swarm = await makeSwarm(40, t)
|
|
187
168
|
|
|
@@ -290,12 +271,12 @@ test('addNode / nodes option', async function (t) {
|
|
|
290
271
|
test('set bind', async function (t) {
|
|
291
272
|
const port = await freePort()
|
|
292
273
|
|
|
293
|
-
const a = new DHT({
|
|
274
|
+
const a = new DHT({ port, firewalled: false })
|
|
294
275
|
await a.ready()
|
|
295
276
|
|
|
296
277
|
t.alike(a.address().port, port, 'bound to explicit port')
|
|
297
278
|
|
|
298
|
-
const b = new DHT({
|
|
279
|
+
const b = new DHT({ port })
|
|
299
280
|
await b.ready()
|
|
300
281
|
|
|
301
282
|
t.not(b.address().port, port, 'bound to different port as explicit one is taken')
|