forked from tintfoundation/bitboot
-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.js
180 lines (151 loc) · 5.55 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
module.exports = Bitboot
var EventEmitter = require('events').EventEmitter
var inherits = require('util').inherits
var crypto = require('crypto')
var DHT = require('bittorrent-dht')
var KBucket = require('k-bucket')
var debug = require('debug')
var bitwise = require('bitwise')
inherits(Bitboot, EventEmitter)
function Bitboot (rallyName, opts) {
if (!opts) opts = {}
var self = this
this.rallyName = rallyName
this.rallyId = crypto.createHash('sha1').update(this.rallyName).digest()
this.destroyed = false
this.dht = null
this._interval = null
this.debug = debug('bitboot')
EventEmitter.call(this)
process.nextTick(bootstrap)
this.debug('Using rally point ' + this.rallyName + ' (' + this.rallyId.toString('hex') + ')')
function bootstrap () {
if (self.destroyed) return
var id = crypto.randomBytes(20)
id[0] = self.rallyId[0]
id[1] = self.rallyId[1]
id[18] = self.rallyId[18]
id[19] = self.rallyId[19]
self.dht = self._createDHT(id, opts.bootstrap !== false)
}
}
Bitboot.prototype.destroy = function (cb) {
this.debug('destroying all connections')
if (this.destroyed && cb) {
process.nextTick(cb)
} else if (!this.destroyed) {
if (this._interval) clearInterval(this._interval)
this.destroyed = true
this.dht.destroy(cb)
}
}
Bitboot.prototype._createDHT = function (id, bootstrap) {
var dht = new DHT({id: id, bootstrap: bootstrap})
var self = this
var dmsg = 'Joining network with id ' + id.toString('hex') +
' (distance to rally ' + KBucket.distance(id, this.rallyId) + ')'
this.debug(dmsg)
dht.on('error', onerror)
dht.on('ready', onready)
function onerror (err) {
self.emit('error', err)
}
function onready () {
self.emit('rejoin', dht.nodeId)
if (!self._interval && bootstrap) {
// first run, so search and set interval for future searches
self.debug('Searching, and creating interval for future searches')
search()
self._interval = setInterval(search, 1000 * 60)
}
}
function search () {
self._search()
}
return dht
}
Bitboot.prototype._search = function () {
// get all nodes close to the rally point
this.debug('Searching for nodes near rally point')
var self = this
var query = {q: 'find_node', a: {id: this.dht.nodeId, target: this.rallyId}}
this.dht._rpc.closest(this.rallyId, query, null, finished)
function finished (err) {
if (err) self.emit('error', err)
else self._attemptCommunication()
}
}
Bitboot.prototype._attemptCommunication = function () {
var closest = this.dht.nodes.closest(this.rallyId, 40)
var peers = this._extractPeers(closest)
var mydist = KBucket.distance(this.dht.nodeId, this.rallyId)
var closestdist = KBucket.distance(closest[0].id, this.rallyId)
// emit all peers found
this.emit('peers', peers)
if (mydist <= closestdist) {
this.debug('We are already at the rally point. Keep hanging out.')
} else if (peers.length === 0) {
this.debug('No peers found, so it\'s up to us to hang out at the rally point')
this.debug('The closest other node is ' + closest[0].id.toString('hex'))
this._waddleCloser(closest)
} else if (!peers[0].id.equals(closest[0].id) && mydist > closestdist) {
this.debug('Peers exist but far away - it\'s up to us to go to the rally point')
this._waddleCloser(closest)
} else if (peers[0].id.equals(closest[0].id)) {
this.debug('Another peer is already at the rally point - so we won\'t move.')
}
}
Bitboot.prototype._waddleCloser = function (closest) {
var self = this
this.dht.destroy(restart)
this.debug('Waddling closer to the rally point destroying old DHT connection')
function restart () {
self.debug('Old DHT connection destroyed')
var id = self._twiddleMarch(closest[0].id)
self.dht = self._createDHT(id, true)
self.dht.on('ready', sayhi)
}
// make sure other close nodes know about us
function sayhi () {
self.debug('Saying hi to ' + closest.length + ' nodes so they know about us')
for (var i = 0; i < closest.length; i++) {
self.dht.addNode(closest[i])
}
}
}
Bitboot.prototype._extractPeers = function (nodes) {
var peers = []
for (var i = 0; i < nodes.length; i++) {
var validid = nodes[i].id[18] === this.rallyId[18] && nodes[i].id[19] === this.rallyId[19]
if (validid && !nodes[i].id.equals(this.dht.nodeId)) {
peers.push(nodes[i])
}
}
return peers
}
// This function moves an initial node id closer to the target node id
// until it beats the closest id. It returns the new id as a buffer.
Bitboot.prototype._twiddleMarch = function (closest) {
var xdistance = KBucket.distance(closest, this.rallyId)
var targetBits = bitwise.readBuffer(this.rallyId)
var resultBits = bitwise.readBuffer(this.dht.nodeId)
var result = null
var rdistance = null
for (var leftIndex = 0; leftIndex < resultBits.length; leftIndex++) {
// next line - start at length-17 because -1 is last position, then move two bytes (16 bits)
// to the left since the last two bytes are already going to match the target
for (var rightIndex = resultBits.length - 17; rightIndex >= leftIndex; rightIndex--) {
if (resultBits[rightIndex] !== targetBits[rightIndex]) {
resultBits[rightIndex] = targetBits[rightIndex]
result = bitwise.createBuffer(resultBits)
rdistance = KBucket.distance(result, this.rallyId)
if (rdistance < xdistance) return result
if (rightIndex !== leftIndex) {
resultBits[rightIndex] = bitwise.not([resultBits[rightIndex]])[0]
result = bitwise.createBuffer(resultBits)
}
}
}
}
return result
}