-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathindex.js
More file actions
257 lines (243 loc) · 8.01 KB
/
Copy pathindex.js
File metadata and controls
257 lines (243 loc) · 8.01 KB
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
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
// Importing required modules
const HyperDHT = require('hyperdht') // HyperDHT module for DHT functionality
const libNet = require('@holesail/hyper-cmd-lib-net') // Custom network library
const libKeys = require('hyper-cmd-lib-keys') // To generate a random preSeed for server seed.
const b4a = require('b4a')
const z32 = require('z32')
class HolesailServer {
constructor(opts = {}) {
this.logger = opts.logger || { log: () => {} }
this.dht = new HyperDHT()
this.stats = {}
this.server = null
this.keyPair = null
this.seed = null
this.state = null
this.connection = null
this.refreshInterval = null
this.activeConnections = new Map()
}
generateKeyPair(seed) {
// Allows us to keep the same keyPair everytime.
if (!seed) {
seed = libKeys.randomBytes(32).toString('hex')
}
// generate a seed from the buffer key
this.seed = Buffer.from(seed, 'hex')
// generate a keypair from the seed
this.keyPair = HyperDHT.keyPair(this.seed)
this.logger.log({ type: 0, msg: `Generated key pair from seed: ${seed}` })
return this.keyPair
}
// start the client on port and the address specified
async start(args, callback) {
this.logger.log({ type: 1, msg: 'Starting server' })
this.args = args
this.secure = args.secure === true
// generate the keypair
this.generateKeyPair(args.seed)
// this is needed for the secure mode to work and is implemented by HyperDHT
let privateFirewall = false
if (this.secure) {
privateFirewall = (remotePublicKey) => {
return !b4a.equals(remotePublicKey, this.keyPair.publicKey)
}
this.logger.log({ type: 1, msg: 'Using Private Mode' })
} else {
this.logger.log({ type: 1, msg: 'Using Public Mode' })
}
this.server = this.dht.createServer(
{
firewall: privateFirewall,
reusableSocket: true
},
(c) => {
const encodedKey = z32.encode(c.remotePublicKey)
this.logger.log({
type: 0,
msg: `Incoming connection received from ${encodedKey}`
})
const count = this.activeConnections.get(encodedKey) || 0
this.activeConnections.set(encodedKey, count + 1)
if (!args.udp) {
this.handleTCP(c, args)
} else {
this.handleUDP(c, args)
}
}
)
this.logger.log({ type: 0, msg: 'Server created, awaiting listen' })
// start listening on the keyPair
this.server.listen(this.keyPair).then(() => {
this.state = 'listening'
this.logger.log({ type: 1, msg: `Server listening on key: ${this.key}` })
if (typeof callback === 'function') {
callback() // Invoke the callback after the server has started
}
})
const interval = 50 * 60 * 1000
// put host information on the dht
const data = JSON.stringify({
host: this.args.host,
udp: this.args.udp,
port: this.args.port
})
this.logger.log({
type: 0,
msg: `Initializing DHT with host info: ${data}`
})
await this.put(data)
this.refreshInterval = setInterval(async () => {
this.logger.log({ type: 0, msg: `Refreshing DHT record: ${data}` })
await this.put(data)
}, interval)
}
// Handle TCP connections
handleTCP(c, args) {
this.logger.log({ type: 0, msg: 'Handling TCP connection' })
const encodedKey = z32.encode(c.remotePublicKey)
c.on('close', () => {
let count = this.activeConnections.get(encodedKey) || 1
count--
if (count <= 0) {
this.logger.log({ type: 0, msg: `Disconnected from ${encodedKey}` })
this.activeConnections.delete(encodedKey)
} else {
this.activeConnections.set(encodedKey, count)
}
})
// Connection handling using custom connection piper function
this.connection = libNet.pipeTcpServer(
c,
{ port: args.port, host: args.host },
{ isServer: true, compress: false, logger: this.logger },
this.stats
)
this.logger.log({ type: 0, msg: 'TCP connection piped' })
}
// Handle UDP connections (updated to use framed reliable tunneling)
handleUDP(c, args) {
this.logger.log({ type: 0, msg: 'Handling UDP connection' })
const encodedKey = z32.encode(c.remotePublicKey)
c.on('close', () => {
let count = this.activeConnections.get(encodedKey) || 1
count--
if (count <= 0) {
this.logger.log({ type: 0, msg: `Disconnected from ${encodedKey}` })
this.activeConnections.delete(encodedKey)
} else {
this.activeConnections.set(encodedKey, count)
}
})
this.connection = libNet.pipeUdpFramedServer(
c,
{ port: args.port, host: args.host },
this.logger,
this.stats
)
this.logger.log({ type: 0, msg: 'UDP connection framed and piped' })
}
// Return the public/connection key
get key() {
if (this.secure) {
return z32.encode(this.seed)
} else {
return z32.encode(this.keyPair.publicKey)
}
}
// resume functionality
async resume() {
this.logger.log({ type: 1, msg: 'Resuming server' })
await this.dht.resume()
this.state = 'listening'
this.logger.log({ type: 1, msg: 'Server resumed' })
}
async pause() {
this.logger.log({ type: 1, msg: 'Pausing server' })
await this.dht.suspend()
this.state = 'paused'
this.logger.log({ type: 1, msg: 'Server paused' })
}
// destroy the dht instance and free up resources
async destroy() {
this.logger.log({ type: 1, msg: 'Destroying server' })
if (this.refreshInterval) {
clearInterval(this.refreshInterval)
this.refreshInterval = null
this.logger.log({ type: 1, msg: 'Cleared DHT refresh interval' })
}
if (this.dht) await this.dht.destroy()
this.dht = null
if (this.server) this.server = null
if (this.connection) this.connection = null
this.state = 'destroyed'
this.logger.log({ type: 1, msg: 'Server destroyed' })
}
// put a mutable record on the dht, can be retrieved by any client using the keypair, max limit is 1KB
async put(data, opts = {}) {
if (data == null) {
throw new Error('data cannot be undefined')
}
this.logger.log({ type: 0, msg: `Putting DHT record: ${data}` })
this.logger.log({
type: 0,
msg: `Incoming data type: ${typeof data}, value: ${data}`
})
data = b4a.isBuffer(data) ? data : Buffer.from(data)
this.logger.log({ type: 0, msg: 'Checking for existing DHT record' })
const oldRecord = await this.get({ latest: true })
const putOpts = { ...opts }
if (oldRecord) {
if (oldRecord.value == null) {
this.logger.log({
type: 0,
msg: 'oldRecord.value is null or undefined'
})
putOpts.seq = oldRecord.seq + 1
} else {
const same = b4a.equals(b4a.from(oldRecord.value), data)
putOpts.seq = same ? oldRecord.seq : oldRecord.seq + 1
this.logger.log({
type: 0,
msg: `Existing record found, putting with seq: ${putOpts.seq} (same: ${same})`
})
}
} else {
this.logger.log({
type: 0,
msg: 'No existing DHT record found, creating new'
})
}
const { seq } = await this.dht.mutablePut(this.keyPair, data, putOpts)
this.logger.log({ type: 0, msg: `DHT put completed with seq: ${seq}` })
return seq
}
// get mutable record from dht
async get(opts = {}) {
const record = await this.dht.mutableGet(this.keyPair.publicKey, opts)
if (record) {
const value = b4a.toString(record.value)
this.logger.log({
type: 0,
msg: `Existing DHT record found: seq=${record.seq}, value=${value}`
})
return { seq: record.seq, value: value }
}
return null
}
// return information about the server
get info() {
return {
type: 'server',
state: this.state,
secure: this.secure,
port: this.args.port,
host: this.args.host,
protocol: this.args.udp ? 'udp' : 'tcp',
seed: this.args.seed,
key: this.key,
publicKey: z32.encode(this.keyPair.publicKey)
}
}
}
module.exports = HolesailServer