diodejs 0.2.2 → 0.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/README.md +56 -5
- package/bindPort.js +446 -66
- package/clientManager.js +435 -0
- package/connection.js +186 -72
- package/examples/RPCTest.js +9 -7
- package/examples/nativeBindTest.js +55 -0
- package/examples/nativeForwardTest.js +80 -0
- package/examples/nativeTcpClientTest.js +56 -0
- package/examples/nativeUdpClientTest.js +40 -0
- package/examples/portForwardTest.js +5 -7
- package/examples/publishAndBind.js +6 -10
- package/examples/publishPortTest.js +4 -6
- package/index.js +8 -1
- package/logger.js +10 -5
- package/nativeCrypto.js +321 -0
- package/package.json +6 -8
- package/publishPort.js +575 -94
- package/rpc.js +161 -41
- package/testServers/udpTest.js +1 -2
- package/utils.js +42 -9
package/connection.js
CHANGED
|
@@ -3,7 +3,7 @@ const tls = require('tls');
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const { RLP } = require('@ethereumjs/rlp');
|
|
5
5
|
const EventEmitter = require('events');
|
|
6
|
-
const { makeReadable, parseRequestId, parseResponseType, parseReason, generateCert, ensureDirectoryExistence, loadOrGenerateKeyPair } = require('./utils');
|
|
6
|
+
const { makeReadable, parseRequestId, parseResponseType, parseReason, parseUInt, generateCert, ensureDirectoryExistence, loadOrGenerateKeyPair, toBufferView } = require('./utils');
|
|
7
7
|
const { Buffer } = require('buffer'); // Import Buffer
|
|
8
8
|
const asn1 = require('asn1.js');
|
|
9
9
|
const secp256k1 = require('secp256k1');
|
|
@@ -16,6 +16,15 @@ const path = require('path');
|
|
|
16
16
|
// Add dotenv for environment variables
|
|
17
17
|
require('dotenv').config();
|
|
18
18
|
|
|
19
|
+
// Try to use native keccak if available (optional perf boost)
|
|
20
|
+
let nativeKeccak = null;
|
|
21
|
+
try {
|
|
22
|
+
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
23
|
+
nativeKeccak = require('keccak');
|
|
24
|
+
} catch (_) {
|
|
25
|
+
// optional dependency; fallback to ethereumjs-util.keccak256
|
|
26
|
+
}
|
|
27
|
+
|
|
19
28
|
class DiodeConnection extends EventEmitter {
|
|
20
29
|
constructor(host, port, keyLocation = './db/keys.json') {
|
|
21
30
|
super();
|
|
@@ -37,6 +46,8 @@ class DiodeConnection extends EventEmitter {
|
|
|
37
46
|
this.clientSockets = new Map(); // For BindPort
|
|
38
47
|
this.connections = new Map(); // For PublishPort
|
|
39
48
|
this.certPem = null;
|
|
49
|
+
this._serverEthAddress = null; // cache after first read
|
|
50
|
+
this.localAddressProvider = null;
|
|
40
51
|
// Load or generate keypair
|
|
41
52
|
this.keyPair = loadOrGenerateKeyPair(this.keyLocation);
|
|
42
53
|
|
|
@@ -59,7 +70,7 @@ class DiodeConnection extends EventEmitter {
|
|
|
59
70
|
this.retryTimeoutId = null;
|
|
60
71
|
|
|
61
72
|
// Log the reconnection settings
|
|
62
|
-
logger.info(`Connection settings - Auto Reconnect: ${this.autoReconnect}, Max Retries: ${
|
|
73
|
+
logger.info(() => `Connection settings - Auto Reconnect: ${this.autoReconnect}, Max Retries: ${
|
|
63
74
|
this.maxRetries === Infinity ? 'Infinity' : this.maxRetries
|
|
64
75
|
}, Retry Delay: ${this.retryDelay}ms, Max Retry Delay: ${this.maxRetryDelay}ms`);
|
|
65
76
|
|
|
@@ -71,7 +82,13 @@ class DiodeConnection extends EventEmitter {
|
|
|
71
82
|
this.ticketUpdateTimer = null;
|
|
72
83
|
|
|
73
84
|
// Log the ticket batching settings
|
|
74
|
-
logger.info(`Ticket batching settings - Bytes Threshold: ${this.ticketUpdateThreshold} bytes, Update Interval: ${this.ticketUpdateInterval}ms`);
|
|
85
|
+
logger.info(() => `Ticket batching settings - Bytes Threshold: ${this.ticketUpdateThreshold} bytes, Update Interval: ${this.ticketUpdateInterval}ms`);
|
|
86
|
+
|
|
87
|
+
// Handle server ticket requests on the API socket
|
|
88
|
+
this._onUnsolicited = (message) => {
|
|
89
|
+
this._handleUnsolicitedMessage(message);
|
|
90
|
+
};
|
|
91
|
+
this.on('unsolicited', this._onUnsolicited);
|
|
75
92
|
}
|
|
76
93
|
|
|
77
94
|
connect() {
|
|
@@ -96,54 +113,60 @@ class DiodeConnection extends EventEmitter {
|
|
|
96
113
|
};
|
|
97
114
|
|
|
98
115
|
this.socket = tls.connect(this.port, this.host, options, async () => {
|
|
99
|
-
|
|
116
|
+
const relayHost = this.getServerRelayHost();
|
|
117
|
+
const relayPort = (this.socket && this.socket.remotePort) ? this.socket.remotePort : this.port;
|
|
118
|
+
logger.info(() => `Connected to Diode relay ${relayHost}:${relayPort}`);
|
|
100
119
|
// Reset retry counter on successful connection
|
|
101
120
|
this.retryCount = 0;
|
|
102
121
|
// Set keep-alive to prevent connection timeout forever
|
|
103
122
|
this.socket.setKeepAlive(true, 1500);
|
|
104
|
-
|
|
123
|
+
this.socket.setNoDelay(true);
|
|
124
|
+
// Cache server address after handshake
|
|
125
|
+
const cachedServerAddress = await this._waitForServerEthereumAddress();
|
|
126
|
+
if (cachedServerAddress) {
|
|
127
|
+
this._serverEthAddress = cachedServerAddress;
|
|
128
|
+
}
|
|
129
|
+
// Start periodic ticket updates now that we are fully connected
|
|
130
|
+
this._startTicketUpdateTimer();
|
|
105
131
|
// Send the ticketv2 command
|
|
106
132
|
try {
|
|
107
133
|
const ticketCommand = await this.createTicketCommand();
|
|
108
134
|
const response = await this.sendCommand(ticketCommand).catch(reject);
|
|
109
135
|
resolve();
|
|
110
136
|
} catch (error) {
|
|
111
|
-
logger.error(`Error sending ticket: ${error}`);
|
|
137
|
+
logger.error(() => `Error sending ticket: ${error}`);
|
|
112
138
|
reject(error);
|
|
113
139
|
}
|
|
114
140
|
});
|
|
115
141
|
|
|
116
142
|
this.socket.on('data', (data) => {
|
|
117
|
-
// logger.debug(`Received data: ${data.toString('hex')}`);
|
|
143
|
+
// logger.debug(() => `Received data: ${data.toString('hex')}`);
|
|
118
144
|
try {
|
|
119
145
|
this._handleData(data);
|
|
120
146
|
} catch (error) {
|
|
121
|
-
logger.error(`Error handling data: ${error}`);
|
|
147
|
+
logger.error(() => `Error handling data: ${error}`);
|
|
122
148
|
}
|
|
123
149
|
});
|
|
124
150
|
|
|
125
|
-
//
|
|
126
|
-
this.socket.on('connect', () => {
|
|
127
|
-
this._startTicketUpdateTimer();
|
|
128
|
-
});
|
|
151
|
+
// No-op: rely on secure handshake callback above for timers/caching
|
|
129
152
|
|
|
130
153
|
this.socket.on('error', (err) => {
|
|
131
|
-
logger.error(`Connection error: ${err}`);
|
|
154
|
+
logger.error(() => `Connection error: ${err}`);
|
|
132
155
|
reject(err);
|
|
133
156
|
});
|
|
134
157
|
|
|
135
158
|
this.socket.on('end', () => {
|
|
136
|
-
logger.info('Disconnected from server');
|
|
159
|
+
logger.info(() => 'Disconnected from server');
|
|
137
160
|
this._handleDisconnect();
|
|
138
161
|
});
|
|
139
162
|
|
|
140
163
|
this.socket.on('close', (hadError) => {
|
|
141
|
-
logger.info(`Connection closed${hadError ? ' due to error' : ''}`);
|
|
164
|
+
logger.info(() => `Connection closed${hadError ? ' due to error' : ''}`);
|
|
142
165
|
this._handleDisconnect();
|
|
143
166
|
});
|
|
144
167
|
|
|
145
168
|
this.socket.on('timeout', () => {
|
|
146
|
-
logger.warn('Connection timeout');
|
|
169
|
+
logger.warn(() => 'Connection timeout');
|
|
147
170
|
this._handleDisconnect();
|
|
148
171
|
});
|
|
149
172
|
});
|
|
@@ -156,7 +179,7 @@ class DiodeConnection extends EventEmitter {
|
|
|
156
179
|
this.retryCount++;
|
|
157
180
|
|
|
158
181
|
if (this.maxRetries !== Infinity && this.retryCount > this.maxRetries) {
|
|
159
|
-
logger.error(`Maximum reconnection attempts (${this.maxRetries}) reached. Giving up.`);
|
|
182
|
+
logger.error(() => `Maximum reconnection attempts (${this.maxRetries}) reached. Giving up.`);
|
|
160
183
|
this.emit('reconnect_failed');
|
|
161
184
|
return;
|
|
162
185
|
}
|
|
@@ -164,7 +187,7 @@ class DiodeConnection extends EventEmitter {
|
|
|
164
187
|
// Calculate delay with exponential backoff
|
|
165
188
|
const delay = Math.min(this.retryDelay * Math.pow(1.5, this.retryCount - 1), this.maxRetryDelay);
|
|
166
189
|
|
|
167
|
-
logger.info(`Reconnecting in ${delay}ms... (Attempt ${this.retryCount})`);
|
|
190
|
+
logger.info(() => `Reconnecting in ${delay}ms... (Attempt ${this.retryCount})`);
|
|
168
191
|
this.emit('reconnecting', { attempt: this.retryCount, delay });
|
|
169
192
|
|
|
170
193
|
this.retryTimeoutId = setTimeout(() => {
|
|
@@ -184,11 +207,11 @@ class DiodeConnection extends EventEmitter {
|
|
|
184
207
|
.then(() => {
|
|
185
208
|
this.isReconnecting = false;
|
|
186
209
|
this.emit('reconnected');
|
|
187
|
-
logger.info('Successfully reconnected to Diode.io server');
|
|
210
|
+
logger.info(() => 'Successfully reconnected to Diode.io server');
|
|
188
211
|
})
|
|
189
212
|
.catch((err) => {
|
|
190
213
|
this.isReconnecting = false;
|
|
191
|
-
logger.error(`Reconnection attempt failed: ${err}`);
|
|
214
|
+
logger.error(() => `Reconnection attempt failed: ${err}`);
|
|
192
215
|
});
|
|
193
216
|
}, delay);
|
|
194
217
|
}
|
|
@@ -254,6 +277,12 @@ class DiodeConnection extends EventEmitter {
|
|
|
254
277
|
return this;
|
|
255
278
|
}
|
|
256
279
|
|
|
280
|
+
// Optional provider for LocalAddr ticket hint (Buffer or string)
|
|
281
|
+
setLocalAddressProvider(provider) {
|
|
282
|
+
this.localAddressProvider = typeof provider === 'function' ? provider : null;
|
|
283
|
+
return this;
|
|
284
|
+
}
|
|
285
|
+
|
|
257
286
|
// Update close method to prevent reconnection when intentionally closing
|
|
258
287
|
close() {
|
|
259
288
|
if (this.ticketUpdateTimer) {
|
|
@@ -274,7 +303,7 @@ class DiodeConnection extends EventEmitter {
|
|
|
274
303
|
_handleData(data) {
|
|
275
304
|
// Append new data to the receive buffer
|
|
276
305
|
this.receiveBuffer = Buffer.concat([this.receiveBuffer, data]);
|
|
277
|
-
// logger.debug(`Received data: ${data.toString('hex')}`);
|
|
306
|
+
// logger.debug(() => `Received data: ${data.toString('hex')}`);
|
|
278
307
|
|
|
279
308
|
let offset = 0;
|
|
280
309
|
while (offset + 2 <= this.receiveBuffer.length) {
|
|
@@ -291,8 +320,9 @@ class DiodeConnection extends EventEmitter {
|
|
|
291
320
|
offset += 2 + length;
|
|
292
321
|
|
|
293
322
|
try {
|
|
294
|
-
|
|
295
|
-
|
|
323
|
+
// Avoid copying: pass Buffer directly to RLP.decode
|
|
324
|
+
const decodedMessage = RLP.decode(messageBuffer);
|
|
325
|
+
// logger.debug(() => `Decoded message: ${makeReadable(decodedMessage)}`);
|
|
296
326
|
|
|
297
327
|
if (Array.isArray(decodedMessage) && decodedMessage.length > 1) {
|
|
298
328
|
const requestIdRaw = decodedMessage[0];
|
|
@@ -302,8 +332,8 @@ class DiodeConnection extends EventEmitter {
|
|
|
302
332
|
const requestId = parseRequestId(requestIdRaw);
|
|
303
333
|
|
|
304
334
|
// Debug statements
|
|
305
|
-
logger.debug(`requestIdRaw: ${requestIdRaw}`);
|
|
306
|
-
logger.debug(`Parsed requestId: ${requestId}`);
|
|
335
|
+
logger.debug(() => `requestIdRaw: ${requestIdRaw}`);
|
|
336
|
+
logger.debug(() => `Parsed requestId: ${requestId}`);
|
|
307
337
|
|
|
308
338
|
if (requestId !== null && this.pendingRequests.has(requestId)) {
|
|
309
339
|
// This is a response to a pending request
|
|
@@ -311,14 +341,14 @@ class DiodeConnection extends EventEmitter {
|
|
|
311
341
|
const responseRaw = responseData[0];
|
|
312
342
|
|
|
313
343
|
// Debug statements
|
|
314
|
-
logger.debug(`responseTypeRaw: ${responseTypeRaw}`);
|
|
315
|
-
logger.debug(`Type of responseTypeRaw: ${typeof responseTypeRaw}`);
|
|
344
|
+
logger.debug(() => `responseTypeRaw: ${responseTypeRaw}`);
|
|
345
|
+
logger.debug(() => `Type of responseTypeRaw: ${typeof responseTypeRaw}`);
|
|
316
346
|
|
|
317
347
|
// Parse responseType
|
|
318
348
|
const responseType = parseResponseType(responseTypeRaw);
|
|
319
349
|
|
|
320
|
-
logger.debug(`Received response for requestId: ${requestId}`);
|
|
321
|
-
logger.debug(`Response Type: '${responseType}'`);
|
|
350
|
+
logger.debug(() => `Received response for requestId: ${requestId}`);
|
|
351
|
+
logger.debug(() => `Response Type: '${responseType}'`);
|
|
322
352
|
|
|
323
353
|
const { resolve, reject } = this.pendingRequests.get(requestId);
|
|
324
354
|
try{
|
|
@@ -344,20 +374,20 @@ class DiodeConnection extends EventEmitter {
|
|
|
344
374
|
resolve(responseData);
|
|
345
375
|
}
|
|
346
376
|
} catch (error) {
|
|
347
|
-
logger.error(`Error handling response: ${error}`);
|
|
377
|
+
logger.error(() => `Error handling response: ${error}`);
|
|
348
378
|
}
|
|
349
379
|
this.pendingRequests.delete(requestId);
|
|
350
380
|
} else {
|
|
351
381
|
// This is an unsolicited message
|
|
352
|
-
logger.debug(`Received unsolicited message`);
|
|
382
|
+
logger.debug(() => `Received unsolicited message`);
|
|
353
383
|
this.emit('unsolicited', decodedMessage);
|
|
354
384
|
}
|
|
355
385
|
} else {
|
|
356
386
|
// Invalid message format
|
|
357
|
-
logger.error(`Invalid message format: ${makeReadable(decodedMessage)}`);
|
|
387
|
+
logger.error(() => `Invalid message format: ${makeReadable(decodedMessage)}`);
|
|
358
388
|
}
|
|
359
389
|
} catch (error) {
|
|
360
|
-
logger.error(`Error decoding message: ${error}`);
|
|
390
|
+
logger.error(() => `Error decoding message: ${error}`);
|
|
361
391
|
}
|
|
362
392
|
}
|
|
363
393
|
|
|
@@ -366,6 +396,34 @@ class DiodeConnection extends EventEmitter {
|
|
|
366
396
|
this.receiveBuffer = this.receiveBuffer.slice(offset);
|
|
367
397
|
}
|
|
368
398
|
|
|
399
|
+
_handleUnsolicitedMessage(message) {
|
|
400
|
+
if (!Array.isArray(message) || message.length < 2) return;
|
|
401
|
+
const messageContent = message[1];
|
|
402
|
+
if (!Array.isArray(messageContent) || messageContent.length < 1) return;
|
|
403
|
+
|
|
404
|
+
const messageTypeRaw = messageContent[0];
|
|
405
|
+
const messageType = toBufferView(messageTypeRaw).toString('utf8');
|
|
406
|
+
|
|
407
|
+
if (messageType === 'ticket_request') {
|
|
408
|
+
const deviceUsageRaw = messageContent[1];
|
|
409
|
+
const deviceUsage = parseUInt(deviceUsageRaw);
|
|
410
|
+
if (typeof deviceUsage === 'number' && deviceUsage > this.totalBytes) {
|
|
411
|
+
this.totalBytes = deviceUsage;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// Send a fresh ticket promptly to avoid disconnect
|
|
415
|
+
this.createTicketCommand()
|
|
416
|
+
.then((ticketCommand) => this.sendCommand(ticketCommand))
|
|
417
|
+
.then(() => {
|
|
418
|
+
this.accumulatedBytes = 0;
|
|
419
|
+
this.lastTicketUpdate = Date.now();
|
|
420
|
+
})
|
|
421
|
+
.catch((error) => {
|
|
422
|
+
logger.error(() => `Error handling ticket_request: ${error}`);
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
369
427
|
fixResponse(response) {
|
|
370
428
|
/* response is :
|
|
371
429
|
[
|
|
@@ -392,21 +450,21 @@ class DiodeConnection extends EventEmitter {
|
|
|
392
450
|
const requestId = this._getNextRequestId();
|
|
393
451
|
// Build the message as [requestId, [commandArray]]
|
|
394
452
|
const commandWithId = [requestId, commandArray];
|
|
395
|
-
|
|
453
|
+
|
|
396
454
|
// Store the promise callbacks to resolve/reject later
|
|
397
455
|
this.pendingRequests.set(requestId, { resolve, reject });
|
|
398
|
-
|
|
456
|
+
|
|
399
457
|
const commandBuffer = RLP.encode(commandWithId);
|
|
400
|
-
const byteLength =
|
|
401
|
-
|
|
458
|
+
const byteLength = commandBuffer.length; // Buffer/Uint8Array length is bytes
|
|
459
|
+
|
|
402
460
|
// Create a 2-byte length buffer
|
|
403
461
|
const lengthBuffer = Buffer.alloc(2);
|
|
404
462
|
lengthBuffer.writeUInt16BE(byteLength, 0);
|
|
405
|
-
|
|
463
|
+
|
|
406
464
|
const message = Buffer.concat([lengthBuffer, commandBuffer]);
|
|
407
465
|
|
|
408
|
-
logger.debug(`Sending command with requestId ${requestId}: ${commandArray}`);
|
|
409
|
-
// logger.debug(`Command buffer: ${message.toString('hex')}`);
|
|
466
|
+
logger.debug(() => `Sending command with requestId ${requestId}: ${commandArray}`);
|
|
467
|
+
// logger.debug(() => `Command buffer: ${message.toString('hex')}`);
|
|
410
468
|
|
|
411
469
|
this.socket.write(message);
|
|
412
470
|
}).catch(reject);
|
|
@@ -419,23 +477,24 @@ class DiodeConnection extends EventEmitter {
|
|
|
419
477
|
const requestId = sessionId;
|
|
420
478
|
// Build the message as [requestId, [commandArray]]
|
|
421
479
|
const commandWithId = [requestId, commandArray];
|
|
422
|
-
|
|
480
|
+
|
|
423
481
|
// Store the promise callbacks to resolve/reject later
|
|
424
482
|
this.pendingRequests.set(requestId, { resolve, reject });
|
|
425
|
-
|
|
483
|
+
|
|
426
484
|
const commandBuffer = RLP.encode(commandWithId);
|
|
427
|
-
const byteLength =
|
|
428
|
-
|
|
485
|
+
const byteLength = commandBuffer.length; // Buffer/Uint8Array length is bytes
|
|
486
|
+
|
|
429
487
|
// Create a 2-byte length buffer
|
|
430
488
|
const lengthBuffer = Buffer.alloc(2);
|
|
431
489
|
lengthBuffer.writeUInt16BE(byteLength, 0);
|
|
432
|
-
|
|
490
|
+
|
|
433
491
|
const message = Buffer.concat([lengthBuffer, commandBuffer]);
|
|
434
492
|
|
|
435
|
-
logger.debug(`Sending command with requestId ${requestId}: ${commandArray}`);
|
|
436
|
-
// logger.debug(`Command buffer: ${message.toString('hex')}`);
|
|
493
|
+
logger.debug(() => `Sending command with requestId ${requestId}: ${commandArray}`);
|
|
494
|
+
// logger.debug(() => `Command buffer: ${message.toString('hex')}`);
|
|
437
495
|
|
|
438
496
|
this.socket.write(message);
|
|
497
|
+
resolve();
|
|
439
498
|
}).catch(reject);
|
|
440
499
|
});
|
|
441
500
|
}
|
|
@@ -452,32 +511,70 @@ class DiodeConnection extends EventEmitter {
|
|
|
452
511
|
|
|
453
512
|
return address;
|
|
454
513
|
} catch (error) {
|
|
455
|
-
logger.error(`Error extracting Ethereum address: ${error}`);
|
|
514
|
+
logger.error(() => `Error extracting Ethereum address: ${error}`);
|
|
456
515
|
throw error;
|
|
457
516
|
}
|
|
458
517
|
}
|
|
459
518
|
|
|
460
|
-
getServerEthereumAddress() {
|
|
519
|
+
getServerEthereumAddress(quiet = false) {
|
|
461
520
|
try {
|
|
521
|
+
if (this._serverEthAddress) {
|
|
522
|
+
return this._serverEthAddress;
|
|
523
|
+
}
|
|
462
524
|
const serverCert = this.socket.getPeerCertificate(true);
|
|
463
525
|
if (!serverCert.raw) {
|
|
464
|
-
|
|
526
|
+
const err = new Error('Failed to get server certificate.');
|
|
527
|
+
if (!quiet) {
|
|
528
|
+
throw err;
|
|
529
|
+
}
|
|
530
|
+
return null;
|
|
465
531
|
}
|
|
466
532
|
|
|
467
533
|
const publicKeyBuffer = Buffer.isBuffer(serverCert.pubkey)
|
|
468
534
|
? serverCert.pubkey
|
|
469
535
|
: Buffer.from(serverCert.pubkey);
|
|
470
536
|
|
|
471
|
-
logger.debug(`Public key Server: ${publicKeyBuffer.toString('hex')}`);
|
|
537
|
+
logger.debug(() => `Public key Server: ${publicKeyBuffer.toString('hex')}`);
|
|
472
538
|
|
|
473
539
|
const addressBuffer = ethUtil.pubToAddress(publicKeyBuffer, true);
|
|
474
540
|
const address = '0x' + addressBuffer.toString('hex');
|
|
541
|
+
this._serverEthAddress = address;
|
|
542
|
+
return this._serverEthAddress;
|
|
543
|
+
} catch (error) {
|
|
544
|
+
if (!quiet) {
|
|
545
|
+
logger.error(() => `Error extracting server Ethereum address: ${error}`);
|
|
546
|
+
throw error;
|
|
547
|
+
}
|
|
548
|
+
return null;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
475
551
|
|
|
552
|
+
getServerRelayHost() {
|
|
553
|
+
if (this.socket && this.socket.remoteAddress) {
|
|
554
|
+
const address = this.socket.remoteAddress;
|
|
555
|
+
if (address.startsWith('::ffff:')) {
|
|
556
|
+
return address.slice(7);
|
|
557
|
+
}
|
|
558
|
+
if (address.includes(':')) {
|
|
559
|
+
return this.host;
|
|
560
|
+
}
|
|
476
561
|
return address;
|
|
477
|
-
} catch (error) {
|
|
478
|
-
logger.error(`Error extracting server Ethereum address: ${error}`);
|
|
479
|
-
throw error;
|
|
480
562
|
}
|
|
563
|
+
return this.host;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
async _waitForServerEthereumAddress(options = {}) {
|
|
567
|
+
const timeoutMs = Number.isFinite(options.timeoutMs) ? options.timeoutMs : 2000;
|
|
568
|
+
const intervalMs = Number.isFinite(options.intervalMs) ? options.intervalMs : 50;
|
|
569
|
+
const start = Date.now();
|
|
570
|
+
let address = this.getServerEthereumAddress(true);
|
|
571
|
+
if (address) return address;
|
|
572
|
+
while (Date.now() - start < timeoutMs) {
|
|
573
|
+
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
574
|
+
address = this.getServerEthereumAddress(true);
|
|
575
|
+
if (address) return address;
|
|
576
|
+
}
|
|
577
|
+
return null;
|
|
481
578
|
}
|
|
482
579
|
|
|
483
580
|
// Method to extract private key bytes from keyPair
|
|
@@ -488,18 +585,19 @@ class DiodeConnection extends EventEmitter {
|
|
|
488
585
|
const privateKeyBytes = Buffer.from(privateKeyHex, 'hex');
|
|
489
586
|
return privateKeyBytes;
|
|
490
587
|
} catch (error) {
|
|
491
|
-
logger.error(`Error extracting private key: ${error}`);
|
|
588
|
+
logger.error(() => `Error extracting private key: ${error}`);
|
|
492
589
|
throw error;
|
|
493
590
|
}
|
|
494
591
|
}
|
|
495
592
|
|
|
496
593
|
async createTicketSignature(serverIdBuffer, totalConnections, totalBytes, localAddress, epoch) {
|
|
497
|
-
this.getEthereumAddress()
|
|
498
594
|
const chainId = 1284;
|
|
499
595
|
const fleetContractBuffer = ethUtil.toBuffer('0x6000000000000000000000000000000000000000'); // 20-byte Buffer
|
|
500
596
|
|
|
501
|
-
|
|
502
|
-
|
|
597
|
+
const localAddressBytes = Buffer.isBuffer(localAddress) || localAddress instanceof Uint8Array
|
|
598
|
+
? toBufferView(localAddress)
|
|
599
|
+
: Buffer.from(localAddress || '', 'utf8');
|
|
600
|
+
const localAddressHash = crypto.createHash('sha256').update(localAddressBytes).digest();
|
|
503
601
|
|
|
504
602
|
// Data to sign
|
|
505
603
|
const dataToSign = [
|
|
@@ -512,23 +610,25 @@ class DiodeConnection extends EventEmitter {
|
|
|
512
610
|
ethUtil.setLengthLeft(localAddressHash, 32),
|
|
513
611
|
];
|
|
514
612
|
|
|
515
|
-
//
|
|
516
|
-
const encodedData = Buffer.concat(dataToSign
|
|
613
|
+
// Elements are already bytes32; concatenate directly to avoid ABI overhead
|
|
614
|
+
const encodedData = Buffer.concat(dataToSign);
|
|
517
615
|
|
|
518
|
-
logger.debug(`Encoded data: ${encodedData.toString('hex')}`);
|
|
616
|
+
logger.debug(() => `Encoded data: ${encodedData.toString('hex')}`);
|
|
519
617
|
|
|
520
|
-
logger.debug(`Data to sign: ${makeReadable(dataToSign)}`);
|
|
618
|
+
logger.debug(() => `Data to sign: ${makeReadable(dataToSign)}`);
|
|
521
619
|
|
|
522
620
|
|
|
523
621
|
// Sign the data
|
|
524
622
|
const privateKey = this.getPrivateKey();
|
|
525
|
-
const msgHash =
|
|
526
|
-
|
|
623
|
+
const msgHash = nativeKeccak
|
|
624
|
+
? nativeKeccak('keccak256').update(encodedData).digest()
|
|
625
|
+
: ethUtil.keccak256(encodedData);
|
|
626
|
+
logger.debug(() => `Message hash: ${msgHash.toString('hex')}`);
|
|
527
627
|
const signature = secp256k1.ecdsaSign(msgHash, privateKey);
|
|
528
|
-
logger.debug(`Signature: ${signature.signature.toString('hex')}`);
|
|
628
|
+
logger.debug(() => `Signature: ${signature.signature.toString('hex')}`);
|
|
529
629
|
|
|
530
630
|
const signatureBuffer = Buffer.concat([
|
|
531
|
-
|
|
631
|
+
Buffer.from([signature.recid]),
|
|
532
632
|
signature.signature
|
|
533
633
|
]);
|
|
534
634
|
|
|
@@ -538,7 +638,18 @@ class DiodeConnection extends EventEmitter {
|
|
|
538
638
|
async createTicketCommand() {
|
|
539
639
|
const chainId = 1284;
|
|
540
640
|
const fleetContract = ethUtil.toBuffer('0x6000000000000000000000000000000000000000')
|
|
541
|
-
|
|
641
|
+
let localAddress = '';
|
|
642
|
+
if (typeof this.localAddressProvider === 'function') {
|
|
643
|
+
try {
|
|
644
|
+
localAddress = this.localAddressProvider();
|
|
645
|
+
} catch (error) {
|
|
646
|
+
logger.warn(() => `Failed to get local address hint: ${error}`);
|
|
647
|
+
localAddress = '';
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
if (localAddress === null || localAddress === undefined) {
|
|
651
|
+
localAddress = '';
|
|
652
|
+
}
|
|
542
653
|
|
|
543
654
|
// Increment totalConnections
|
|
544
655
|
this.totalConnections += 1;
|
|
@@ -548,7 +659,10 @@ class DiodeConnection extends EventEmitter {
|
|
|
548
659
|
const totalBytes = this.totalBytes;
|
|
549
660
|
|
|
550
661
|
// Get server Ethereum address as Buffer
|
|
551
|
-
const serverIdBuffer = this.
|
|
662
|
+
const serverIdBuffer = await this._waitForServerEthereumAddress();
|
|
663
|
+
if (!serverIdBuffer) {
|
|
664
|
+
throw new Error('Failed to get server certificate.');
|
|
665
|
+
}
|
|
552
666
|
|
|
553
667
|
// Get epoch
|
|
554
668
|
const epoch = await this.RPC.getEpoch();
|
|
@@ -559,7 +673,7 @@ class DiodeConnection extends EventEmitter {
|
|
|
559
673
|
localAddress,
|
|
560
674
|
epoch
|
|
561
675
|
);
|
|
562
|
-
logger.debug(`Signature hex: ${signature.toString('hex')}`);
|
|
676
|
+
logger.debug(() => `Signature hex: ${signature.toString('hex')}`);
|
|
563
677
|
|
|
564
678
|
|
|
565
679
|
// Construct the ticket command
|
|
@@ -650,7 +764,7 @@ class DiodeConnection extends EventEmitter {
|
|
|
650
764
|
|
|
651
765
|
try {
|
|
652
766
|
if (this.accumulatedBytes > 0 || force) {
|
|
653
|
-
logger.debug(`Updating ticket: accumulated ${this.accumulatedBytes} bytes, ${timeSinceLastUpdate}ms since last update`);
|
|
767
|
+
logger.debug(() => `Updating ticket: accumulated ${this.accumulatedBytes} bytes, ${timeSinceLastUpdate}ms since last update`);
|
|
654
768
|
const ticketCommand = await this.createTicketCommand();
|
|
655
769
|
await this.sendCommand(ticketCommand);
|
|
656
770
|
|
|
@@ -659,7 +773,7 @@ class DiodeConnection extends EventEmitter {
|
|
|
659
773
|
this.lastTicketUpdate = Date.now();
|
|
660
774
|
}
|
|
661
775
|
} catch (error) {
|
|
662
|
-
logger.error(`Error updating ticket: ${error}`);
|
|
776
|
+
logger.error(() => `Error updating ticket: ${error}`);
|
|
663
777
|
}
|
|
664
778
|
}
|
|
665
779
|
|
|
@@ -687,7 +801,7 @@ class DiodeConnection extends EventEmitter {
|
|
|
687
801
|
this.ticketUpdateInterval = options.interval;
|
|
688
802
|
}
|
|
689
803
|
|
|
690
|
-
logger.info(`Updated ticket batching settings - Bytes Threshold: ${this.ticketUpdateThreshold} bytes, Update Interval: ${this.ticketUpdateInterval}ms`);
|
|
804
|
+
logger.info(() => `Updated ticket batching settings - Bytes Threshold: ${this.ticketUpdateThreshold} bytes, Update Interval: ${this.ticketUpdateInterval}ms`);
|
|
691
805
|
|
|
692
806
|
// Reset the timer with new interval
|
|
693
807
|
if (this.socket && !this.socket.destroyed) {
|
package/examples/RPCTest.js
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
const {
|
|
1
|
+
const { DiodeClientManager, DiodeRPC } = require('../index');
|
|
2
2
|
const { makeReadable } = require('../utils');
|
|
3
3
|
|
|
4
4
|
async function main() {
|
|
5
|
-
const host = 'us2.prenet.diode.io';
|
|
6
|
-
const port = 41046;
|
|
7
5
|
const keyLocation = './db/keys.json';
|
|
8
6
|
|
|
9
|
-
const
|
|
10
|
-
await
|
|
11
|
-
const
|
|
7
|
+
const client = new DiodeClientManager({ keyLocation });
|
|
8
|
+
await client.connect();
|
|
9
|
+
const [connection] = client.getConnections();
|
|
10
|
+
if (!connection) {
|
|
11
|
+
throw new Error('No relay connection available');
|
|
12
|
+
}
|
|
13
|
+
const rpc = connection.RPC || new DiodeRPC(connection);
|
|
12
14
|
|
|
13
15
|
try {
|
|
14
16
|
const address = connection.getEthereumAddress();
|
|
@@ -22,7 +24,7 @@ async function main() {
|
|
|
22
24
|
} catch (error) {
|
|
23
25
|
console.error('RPC Error:', error);
|
|
24
26
|
} finally {
|
|
25
|
-
|
|
27
|
+
client.close();
|
|
26
28
|
}
|
|
27
29
|
}
|
|
28
30
|
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
const { KEYUTIL } = require('jsrsasign');
|
|
2
|
+
const { DiodeClientManager, BindPort } = require('../index');
|
|
3
|
+
|
|
4
|
+
function printPublicKey(connection) {
|
|
5
|
+
try {
|
|
6
|
+
const pem = KEYUTIL.getPEM(connection.keyPair.pubKeyObj, 'PKCS8PUB');
|
|
7
|
+
console.log('Public key (PEM):');
|
|
8
|
+
console.log(pem);
|
|
9
|
+
} catch (error) {
|
|
10
|
+
console.error('Failed to print public key:', error);
|
|
11
|
+
}
|
|
12
|
+
try {
|
|
13
|
+
console.log('Ethereum address:', connection.getEthereumAddress());
|
|
14
|
+
} catch (error) {
|
|
15
|
+
console.error('Failed to print address:', error);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async function main() {
|
|
20
|
+
const keyLocation = './db/keys2.json';
|
|
21
|
+
|
|
22
|
+
const deviceIdHex = process.env.DIODE_TARGET_DEVICE || process.argv[2] || '0x4632c04cf8c44a586554951e7de03ca2bd3e8f1c'; // Replace with actual device
|
|
23
|
+
|
|
24
|
+
const client = new DiodeClientManager({keyLocation });
|
|
25
|
+
await client.connect();
|
|
26
|
+
const [connection] = client.getConnections();
|
|
27
|
+
if (!connection) {
|
|
28
|
+
throw new Error('No relay connection available');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
printPublicKey(connection);
|
|
32
|
+
|
|
33
|
+
const portsConfig = {
|
|
34
|
+
3005: {
|
|
35
|
+
targetPort: 8089,
|
|
36
|
+
deviceIdHex,
|
|
37
|
+
protocol: 'tcp',
|
|
38
|
+
transport: 'native'
|
|
39
|
+
},
|
|
40
|
+
3006: {
|
|
41
|
+
targetPort: 8090,
|
|
42
|
+
deviceIdHex,
|
|
43
|
+
protocol: 'udp',
|
|
44
|
+
transport: 'native',
|
|
45
|
+
flags: 'rwu'
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const portForward = new BindPort(client, portsConfig);
|
|
50
|
+
portForward.bind();
|
|
51
|
+
|
|
52
|
+
console.log('Native bind ports active:', Object.keys(portsConfig).join(', '));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
main().catch(console.error);
|