diodejs 0.1.6 → 0.2.1
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 +31 -11
- package/bindPort.js +225 -50
- package/connection.js +93 -3
- package/examples/portForwardTest.js +3 -3
- package/package.json +1 -1
- package/publishPort.js +1 -1
- package/rpc.js +36 -23
package/README.md
CHANGED
|
@@ -25,6 +25,8 @@ Connection retry behavior can be configured via environment variables:
|
|
|
25
25
|
| DIODE_RETRY_DELAY | Initial delay between retries (ms) | 1000 |
|
|
26
26
|
| DIODE_MAX_RETRY_DELAY | Maximum delay between retries (ms) | 30000 |
|
|
27
27
|
| DIODE_AUTO_RECONNECT | Whether to automatically reconnect | true |
|
|
28
|
+
| DIODE_TICKET_BYTES_THRESHOLD | Bytes threshold for ticket updates | 512000 (512KB) |
|
|
29
|
+
| DIODE_TICKET_UPDATE_INTERVAL | Time interval for ticket updates (ms) | 30000 (30s) |
|
|
28
30
|
|
|
29
31
|
Example `.env` file:
|
|
30
32
|
```
|
|
@@ -32,6 +34,8 @@ DIODE_MAX_RETRIES=10
|
|
|
32
34
|
DIODE_RETRY_DELAY=2000
|
|
33
35
|
DIODE_MAX_RETRY_DELAY=20000
|
|
34
36
|
DIODE_AUTO_RECONNECT=true
|
|
37
|
+
DIODE_TICKET_BYTES_THRESHOLD=512000
|
|
38
|
+
DIODE_TICKET_UPDATE_INTERVAL=30000
|
|
35
39
|
```
|
|
36
40
|
|
|
37
41
|
These settings can also be configured programmatically:
|
|
@@ -40,7 +44,9 @@ connection.setReconnectOptions({
|
|
|
40
44
|
maxRetries: 10,
|
|
41
45
|
retryDelay: 2000,
|
|
42
46
|
maxRetryDelay: 20000,
|
|
43
|
-
autoReconnect: true
|
|
47
|
+
autoReconnect: true,
|
|
48
|
+
ticketBytesThreshold: 512000,
|
|
49
|
+
ticketUpdateInterval: 30000
|
|
44
50
|
});
|
|
45
51
|
```
|
|
46
52
|
|
|
@@ -63,7 +69,9 @@ async function main() {
|
|
|
63
69
|
maxRetries: Infinity, // Unlimited reconnection attempts
|
|
64
70
|
retryDelay: 1000, // Initial delay of 1 second
|
|
65
71
|
maxRetryDelay: 30000, // Maximum delay of 30 seconds
|
|
66
|
-
autoReconnect: true
|
|
72
|
+
autoReconnect: true, // Automatically reconnect on disconnection
|
|
73
|
+
ticketBytesThreshold: 512000, // Bytes threshold for ticket updates
|
|
74
|
+
ticketUpdateInterval: 30000 // Time interval for ticket updates
|
|
67
75
|
});
|
|
68
76
|
|
|
69
77
|
// Listen for reconnection events (optional)
|
|
@@ -117,15 +125,23 @@ async function main() {
|
|
|
117
125
|
|
|
118
126
|
// Multiple or single port binding with configuration object
|
|
119
127
|
const portsConfig = {
|
|
120
|
-
3002: {
|
|
121
|
-
|
|
128
|
+
3002: {
|
|
129
|
+
targetPort: 80,
|
|
130
|
+
deviceIdHex: "5365baf29cb7ab58de588dfc448913cb609283e2",
|
|
131
|
+
protocol: "tls" // Optional - defaults to TLS if not specified
|
|
132
|
+
},
|
|
133
|
+
3003: {
|
|
134
|
+
targetPort: 443,
|
|
135
|
+
deviceIdHex: "0x5365baf29cb7ab58de588dfc448913cb609283e2",
|
|
136
|
+
protocol: "tcp" // Can be "tls", "tcp", or "udp"
|
|
137
|
+
}
|
|
122
138
|
};
|
|
123
139
|
|
|
124
140
|
const portForward = new BindPort(connection, portsConfig);
|
|
125
141
|
portForward.bind();
|
|
126
142
|
|
|
127
|
-
// You can also dynamically add
|
|
128
|
-
portForward.addPort(3004, 8080, "5365baf29cb7ab58de588dfc448913cb609283e2");
|
|
143
|
+
// You can also dynamically add ports with protocol specification
|
|
144
|
+
portForward.addPort(3004, 8080, "5365baf29cb7ab58de588dfc448913cb609283e2", "udp");
|
|
129
145
|
portForward.removePort(3003);
|
|
130
146
|
}
|
|
131
147
|
|
|
@@ -144,7 +160,7 @@ async function main() {
|
|
|
144
160
|
const connection = new DiodeConnection(host, port, keyLocation);
|
|
145
161
|
await connection.connect();
|
|
146
162
|
|
|
147
|
-
// Legacy method - single port binding
|
|
163
|
+
// Legacy method - single port binding (defaults to TLS protocol)
|
|
148
164
|
const portForward = new BindPort(connection, 3002, 80, "5365baf29cb7ab58de588dfc448913cb609283e2");
|
|
149
165
|
portForward.bind();
|
|
150
166
|
}
|
|
@@ -211,6 +227,8 @@ main();
|
|
|
211
227
|
- `retryDelay` (number): Initial delay between retries in ms (default: 1000)
|
|
212
228
|
- `maxRetryDelay` (number): Maximum delay between retries in ms (default: 30000)
|
|
213
229
|
- `autoReconnect` (boolean): Whether to automatically reconnect on disconnection (default: true)
|
|
230
|
+
- `ticketBytesThreshold` (number): Bytes threshold for ticket updates (default: 512000)
|
|
231
|
+
- `ticketUpdateInterval` (number): Time interval for ticket updates in ms (default: 30000)
|
|
214
232
|
|
|
215
233
|
- **Events**:
|
|
216
234
|
- `reconnecting`: Emitted when a reconnection attempt is about to start, with `attempt` and `delay` information
|
|
@@ -249,13 +267,15 @@ main();
|
|
|
249
267
|
New Constructor:
|
|
250
268
|
- `new BindPort(connection, portsConfig)`
|
|
251
269
|
- `connection` (DiodeConnection): An instance of `DiodeConnection`.
|
|
252
|
-
- `portsConfig` (object): A configuration object where keys are local ports and values are objects with
|
|
253
|
-
|
|
254
|
-
|
|
270
|
+
- `portsConfig` (object): A configuration object where keys are local ports and values are objects with:
|
|
271
|
+
- `targetPort` (number): The target port on the device.
|
|
272
|
+
- `deviceIdHex` (string): The device ID in hexadecimal format (with or without '0x' prefix).
|
|
273
|
+
- `protocol` (string, optional): The protocol to use ("tls", "tcp", or "udp"). Defaults to "tls".
|
|
255
274
|
|
|
256
275
|
- **Methods**:
|
|
257
276
|
- `bind()`: Binds all configured local ports to their target ports on the devices.
|
|
258
|
-
- `addPort(localPort, targetPort, deviceIdHex)`: Adds a new port binding configuration.
|
|
277
|
+
- `addPort(localPort, targetPort, deviceIdHex, protocol)`: Adds a new port binding configuration.
|
|
278
|
+
- `protocol` (string, optional): The protocol to use. Can be "tls", "tcp", or "udp". Defaults to "tls".
|
|
259
279
|
- `removePort(localPort)`: Removes a port binding configuration.
|
|
260
280
|
- `bindSinglePort(localPort)`: Binds a single local port to its target.
|
|
261
281
|
- `closeAllServers()`: Closes all active server instances.
|
package/bindPort.js
CHANGED
|
@@ -1,8 +1,45 @@
|
|
|
1
1
|
const net = require('net');
|
|
2
|
+
const tls = require('tls');
|
|
3
|
+
const dgram = require('dgram');
|
|
2
4
|
const { Buffer } = require('buffer');
|
|
5
|
+
const { Duplex } = require('stream');
|
|
3
6
|
const DiodeRPC = require('./rpc');
|
|
4
7
|
const logger = require('./logger');
|
|
5
8
|
|
|
9
|
+
// Custom Duplex stream to handle the Diode connection
|
|
10
|
+
class DiodeSocket extends Duplex {
|
|
11
|
+
constructor(ref, rpc) {
|
|
12
|
+
super();
|
|
13
|
+
this.ref = ref;
|
|
14
|
+
this.rpc = rpc;
|
|
15
|
+
this.destroyed = false;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
_write(chunk, encoding, callback) {
|
|
19
|
+
// Send data to the remote device via portSend
|
|
20
|
+
this.rpc.portSend(this.ref, chunk)
|
|
21
|
+
.then(() => callback())
|
|
22
|
+
.catch((err) => callback(err));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
_read(size) {
|
|
26
|
+
// No need to implement this method for our use case
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Method to push data received from the remote device
|
|
30
|
+
pushData(data) {
|
|
31
|
+
if (!this.destroyed) {
|
|
32
|
+
this.push(data);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
_destroy(err, callback) {
|
|
37
|
+
this.destroyed = true;
|
|
38
|
+
this.push(null);
|
|
39
|
+
callback(err);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
6
43
|
class BindPort {
|
|
7
44
|
constructor(connection, localPortOrPortsConfig, targetPort, deviceIdHex) {
|
|
8
45
|
this.connection = connection;
|
|
@@ -12,7 +49,8 @@ class BindPort {
|
|
|
12
49
|
this.portsConfig = {
|
|
13
50
|
[localPortOrPortsConfig]: {
|
|
14
51
|
targetPort,
|
|
15
|
-
deviceIdHex: this._stripHexPrefix(deviceIdHex)
|
|
52
|
+
deviceIdHex: this._stripHexPrefix(deviceIdHex),
|
|
53
|
+
protocol: 'tls' // Default protocol is tls
|
|
16
54
|
}
|
|
17
55
|
};
|
|
18
56
|
} else {
|
|
@@ -20,10 +58,17 @@ class BindPort {
|
|
|
20
58
|
this.portsConfig = localPortOrPortsConfig || {};
|
|
21
59
|
|
|
22
60
|
// Strip 0x prefix from all deviceIdHex values in portsConfig
|
|
61
|
+
// And ensure protocol is specified (default to tls)
|
|
23
62
|
for (const port in this.portsConfig) {
|
|
24
63
|
if (this.portsConfig[port].deviceIdHex) {
|
|
25
64
|
this.portsConfig[port].deviceIdHex = this._stripHexPrefix(this.portsConfig[port].deviceIdHex);
|
|
26
65
|
}
|
|
66
|
+
// Set default protocol if not provided
|
|
67
|
+
if (!this.portsConfig[port].protocol) {
|
|
68
|
+
this.portsConfig[port].protocol = 'tls';
|
|
69
|
+
}
|
|
70
|
+
// Ensure protocol is uppercase
|
|
71
|
+
this.portsConfig[port].protocol = this.portsConfig[port].protocol.toLowerCase();
|
|
27
72
|
}
|
|
28
73
|
}
|
|
29
74
|
|
|
@@ -45,7 +90,6 @@ class BindPort {
|
|
|
45
90
|
_setupMessageListener() {
|
|
46
91
|
// Listen for data events from the device
|
|
47
92
|
this.connection.on('unsolicited', (message) => {
|
|
48
|
-
// message is [messageId, [messageType, ...]]
|
|
49
93
|
const [messageIdRaw, messageContent] = message;
|
|
50
94
|
const messageTypeRaw = messageContent[0];
|
|
51
95
|
const messageType = Buffer.from(messageTypeRaw).toString('utf8');
|
|
@@ -60,7 +104,13 @@ class BindPort {
|
|
|
60
104
|
// Find the associated client socket from connection
|
|
61
105
|
const clientSocket = this.connection.getClientSocket(dataRef);
|
|
62
106
|
if (clientSocket) {
|
|
63
|
-
clientSocket.
|
|
107
|
+
if (clientSocket.diodeSocket) {
|
|
108
|
+
// If it's a DiodeSocket, push data to it so tls can process
|
|
109
|
+
clientSocket.diodeSocket.pushData(data);
|
|
110
|
+
} else {
|
|
111
|
+
// Otherwise write directly to the socket
|
|
112
|
+
clientSocket.write(data);
|
|
113
|
+
}
|
|
64
114
|
} else {
|
|
65
115
|
const connectionInfo = this.connection.getConnection(dataRef);
|
|
66
116
|
if (connectionInfo) {
|
|
@@ -76,6 +126,9 @@ class BindPort {
|
|
|
76
126
|
// Close the associated client socket
|
|
77
127
|
const clientSocket = this.connection.getClientSocket(dataRef);
|
|
78
128
|
if (clientSocket) {
|
|
129
|
+
if (clientSocket.diodeSocket) {
|
|
130
|
+
clientSocket.diodeSocket._destroy(null, () => {});
|
|
131
|
+
}
|
|
79
132
|
clientSocket.end();
|
|
80
133
|
this.connection.deleteClientSocket(dataRef);
|
|
81
134
|
logger.info(`Port closed for ref: ${dataRef.toString('hex')}`);
|
|
@@ -100,7 +153,7 @@ class BindPort {
|
|
|
100
153
|
});
|
|
101
154
|
}
|
|
102
155
|
|
|
103
|
-
addPort(localPort, targetPort, deviceIdHex) {
|
|
156
|
+
addPort(localPort, targetPort, deviceIdHex, protocol = 'tls') {
|
|
104
157
|
if (this.servers.has(localPort)) {
|
|
105
158
|
logger.warn(`Port ${localPort} is already bound`);
|
|
106
159
|
return false;
|
|
@@ -108,7 +161,8 @@ class BindPort {
|
|
|
108
161
|
|
|
109
162
|
this.portsConfig[localPort] = {
|
|
110
163
|
targetPort,
|
|
111
|
-
deviceIdHex: this._stripHexPrefix(deviceIdHex)
|
|
164
|
+
deviceIdHex: this._stripHexPrefix(deviceIdHex),
|
|
165
|
+
protocol: protocol.toLowerCase()
|
|
112
166
|
};
|
|
113
167
|
|
|
114
168
|
this.bindSinglePort(localPort);
|
|
@@ -151,70 +205,191 @@ class BindPort {
|
|
|
151
205
|
return false;
|
|
152
206
|
}
|
|
153
207
|
|
|
154
|
-
const { targetPort, deviceIdHex } = config;
|
|
208
|
+
const { targetPort, deviceIdHex, protocol = 'tls' } = config;
|
|
155
209
|
const deviceId = Buffer.from(deviceIdHex, 'hex');
|
|
156
210
|
|
|
157
|
-
//
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
211
|
+
// Format the target port with protocol prefix for the remote connection
|
|
212
|
+
const formattedTargetPort = `${protocol}:${targetPort}`;
|
|
213
|
+
logger.info(`Binding local port ${localPort} to remote ${formattedTargetPort}`);
|
|
214
|
+
|
|
215
|
+
// For udp protocol, use udp server
|
|
216
|
+
if (protocol === 'udp') {
|
|
217
|
+
const server = dgram.createSocket('udp4');
|
|
218
|
+
|
|
219
|
+
server.on('listening', () => {
|
|
220
|
+
logger.info(`udp server listening on port ${localPort} forwarding to device port ${targetPort}`);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
server.on('message', async (data, rinfo) => {
|
|
224
|
+
// Open a new port on the device if this is a new client
|
|
225
|
+
const clientKey = `${rinfo.address}:${rinfo.port}`;
|
|
226
|
+
let ref = server.clientRefs && server.clientRefs[clientKey];
|
|
227
|
+
|
|
165
228
|
if (!ref) {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
229
|
+
try {
|
|
230
|
+
ref = await this.rpc.portOpen(deviceId, formattedTargetPort, 'rw');
|
|
231
|
+
if (!ref) {
|
|
232
|
+
logger.error(`Error opening port ${formattedTargetPort} on deviceId: ${deviceIdHex}`);
|
|
233
|
+
return;
|
|
234
|
+
} else {
|
|
235
|
+
logger.info(`Port ${formattedTargetPort} opened on device with ref: ${ref.toString('hex')} for udp client ${clientKey}`);
|
|
236
|
+
if (!server.clientRefs) server.clientRefs = {};
|
|
237
|
+
server.clientRefs[clientKey] = ref;
|
|
238
|
+
|
|
239
|
+
// Store the client info
|
|
240
|
+
this.connection.addClientSocket(ref, {
|
|
241
|
+
address: rinfo.address,
|
|
242
|
+
port: rinfo.port,
|
|
243
|
+
protocol: 'udp',
|
|
244
|
+
write: (data) => {
|
|
245
|
+
server.send(data, rinfo.port, rinfo.address);
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
} catch (error) {
|
|
250
|
+
logger.error(`Error opening port ${formattedTargetPort} on device: ${error}`);
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
171
253
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
clientSocket.destroy();
|
|
175
|
-
return;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// Store the client socket with the ref using connection's method
|
|
179
|
-
this.connection.addClientSocket(ref, clientSocket);
|
|
180
|
-
|
|
181
|
-
// When data is received from the client, send it to the device
|
|
182
|
-
clientSocket.on('data', async (data) => {
|
|
254
|
+
|
|
255
|
+
// Send data to the device
|
|
183
256
|
try {
|
|
184
257
|
await this.rpc.portSend(ref, data);
|
|
185
258
|
} catch (error) {
|
|
186
|
-
logger.error(`Error sending data to device: ${error}`);
|
|
187
|
-
clientSocket.destroy();
|
|
259
|
+
logger.error(`Error sending udp data to device: ${error}`);
|
|
188
260
|
}
|
|
189
261
|
});
|
|
262
|
+
|
|
263
|
+
server.on('error', (err) => {
|
|
264
|
+
logger.error(`udp Server error: ${err}`);
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
server.bind(localPort);
|
|
268
|
+
this.servers.set(parseInt(localPort), server);
|
|
269
|
+
} else {
|
|
270
|
+
// For TCP and tls protocols, use TCP server locally
|
|
271
|
+
const server = net.createServer(async (clientSocket) => {
|
|
272
|
+
logger.info(`Client connected to local server on port ${localPort}`);
|
|
190
273
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
274
|
+
// Open a new port on the device for this client
|
|
275
|
+
let ref;
|
|
276
|
+
try {
|
|
277
|
+
ref = await this.rpc.portOpen(deviceId, formattedTargetPort, 'rw');
|
|
278
|
+
if (!ref) {
|
|
279
|
+
logger.error(`Error opening port ${formattedTargetPort} on deviceId: ${deviceIdHex}`);
|
|
280
|
+
clientSocket.destroy();
|
|
281
|
+
return;
|
|
282
|
+
} else {
|
|
283
|
+
logger.info(`Port ${formattedTargetPort} opened on device with ref: ${ref.toString('hex')} for client`);
|
|
284
|
+
}
|
|
285
|
+
} catch (error) {
|
|
286
|
+
logger.error(`Error opening port ${formattedTargetPort} on device: ${error}`);
|
|
287
|
+
clientSocket.destroy();
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (protocol === 'tls') {
|
|
292
|
+
// For tls protocol, create a proper tls connection
|
|
195
293
|
try {
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
294
|
+
// Create a DiodeSocket to handle communication with the device
|
|
295
|
+
const diodeSocket = new DiodeSocket(ref, this.rpc);
|
|
296
|
+
|
|
297
|
+
// Get the device certificate for tls
|
|
298
|
+
const certPem = this.connection.getDeviceCertificate();
|
|
299
|
+
if (!certPem) {
|
|
300
|
+
throw new Error('No device certificate available');
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Setup tls options for client mode
|
|
304
|
+
const tlsOptions = {
|
|
305
|
+
cert: certPem,
|
|
306
|
+
key: certPem,
|
|
307
|
+
rejectUnauthorized: false,
|
|
308
|
+
ciphers: 'ECDHE-ECDSA-AES256-GCM-SHA384',
|
|
309
|
+
ecdhCurve: 'secp256k1',
|
|
310
|
+
minVersion: 'TLSv1.2',
|
|
311
|
+
maxVersion: 'TLSv1.2',
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
// Create tls socket as client (not server)
|
|
315
|
+
const tlsSocket = tls.connect({
|
|
316
|
+
socket: diodeSocket,
|
|
317
|
+
...tlsOptions
|
|
318
|
+
}, () => {
|
|
319
|
+
logger.info(`tls connection established to device ${deviceIdHex}`);
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
// Pipe data between the client socket and the tls socket
|
|
323
|
+
tlsSocket.pipe(clientSocket).pipe(tlsSocket);
|
|
324
|
+
|
|
325
|
+
// Handle tls socket errors
|
|
326
|
+
tlsSocket.on('error', (err) => {
|
|
327
|
+
logger.error(`tls Socket error: ${err}`);
|
|
328
|
+
clientSocket.destroy();
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
// Store reference to the diodeSocket so we can push data to it
|
|
332
|
+
const socketWrapper = {
|
|
333
|
+
diodeSocket,
|
|
334
|
+
tlsSocket,
|
|
335
|
+
end: () => {
|
|
336
|
+
tlsSocket.end();
|
|
337
|
+
diodeSocket._destroy(null, () => {});
|
|
338
|
+
}
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
// Store the socket wrapper
|
|
342
|
+
this.connection.addClientSocket(ref, socketWrapper);
|
|
343
|
+
|
|
199
344
|
} catch (error) {
|
|
200
|
-
logger.error(`Error
|
|
345
|
+
logger.error(`Error setting up tls connection: ${error}`);
|
|
346
|
+
clientSocket.destroy();
|
|
347
|
+
return;
|
|
201
348
|
}
|
|
202
349
|
} else {
|
|
203
|
-
|
|
350
|
+
// For TCP protocol, just use the raw socket
|
|
351
|
+
this.connection.addClientSocket(ref, clientSocket);
|
|
352
|
+
|
|
353
|
+
// Handle data from client to device
|
|
354
|
+
clientSocket.on('data', async (data) => {
|
|
355
|
+
try {
|
|
356
|
+
await this.rpc.portSend(ref, data);
|
|
357
|
+
} catch (error) {
|
|
358
|
+
logger.error(`Error sending data to device: ${error}`);
|
|
359
|
+
clientSocket.destroy();
|
|
360
|
+
}
|
|
361
|
+
});
|
|
204
362
|
}
|
|
205
|
-
});
|
|
206
363
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
364
|
+
// Handle client socket closure (common for all protocols)
|
|
365
|
+
clientSocket.on('end', async () => {
|
|
366
|
+
logger.info('Client disconnected');
|
|
367
|
+
if (ref && this.connection.hasClientSocket(ref)) {
|
|
368
|
+
try {
|
|
369
|
+
await this.rpc.portClose(ref);
|
|
370
|
+
logger.info(`Port closed on device for ref: ${ref.toString('hex')}`);
|
|
371
|
+
this.connection.deleteClientSocket(ref);
|
|
372
|
+
} catch (error) {
|
|
373
|
+
logger.error(`Error closing port on device: ${error}`);
|
|
374
|
+
}
|
|
375
|
+
} else {
|
|
376
|
+
logger.warn('Ref is invalid or no longer in clientSockets.');
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
// Handle client socket errors
|
|
381
|
+
clientSocket.on('error', (err) => {
|
|
382
|
+
logger.error(`Client socket error: ${err}`);
|
|
383
|
+
});
|
|
210
384
|
});
|
|
211
|
-
});
|
|
212
385
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
386
|
+
server.listen(localPort, () => {
|
|
387
|
+
logger.info(`Local server listening on port ${localPort} forwarding to device ${protocol} port ${targetPort}`);
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
this.servers.set(parseInt(localPort), server);
|
|
391
|
+
}
|
|
216
392
|
|
|
217
|
-
this.servers.set(parseInt(localPort), server);
|
|
218
393
|
return true;
|
|
219
394
|
}
|
|
220
395
|
|
package/connection.js
CHANGED
|
@@ -62,6 +62,16 @@ class DiodeConnection extends EventEmitter {
|
|
|
62
62
|
logger.info(`Connection settings - Auto Reconnect: ${this.autoReconnect}, Max Retries: ${
|
|
63
63
|
this.maxRetries === Infinity ? 'Infinity' : this.maxRetries
|
|
64
64
|
}, Retry Delay: ${this.retryDelay}ms, Max Retry Delay: ${this.maxRetryDelay}ms`);
|
|
65
|
+
|
|
66
|
+
// Add ticket batching configuration
|
|
67
|
+
this.lastTicketUpdate = Date.now();
|
|
68
|
+
this.accumulatedBytes = 0;
|
|
69
|
+
this.ticketUpdateThreshold = parseInt(process.env.DIODE_TICKET_BYTES_THRESHOLD, 10) || 512000; // 512KB default
|
|
70
|
+
this.ticketUpdateInterval = parseInt(process.env.DIODE_TICKET_UPDATE_INTERVAL, 10) || 30000; // 30 seconds default
|
|
71
|
+
this.ticketUpdateTimer = null;
|
|
72
|
+
|
|
73
|
+
// Log the ticket batching settings
|
|
74
|
+
logger.info(`Ticket batching settings - Bytes Threshold: ${this.ticketUpdateThreshold} bytes, Update Interval: ${this.ticketUpdateInterval}ms`);
|
|
65
75
|
}
|
|
66
76
|
|
|
67
77
|
connect() {
|
|
@@ -96,7 +106,6 @@ class DiodeConnection extends EventEmitter {
|
|
|
96
106
|
try {
|
|
97
107
|
const ticketCommand = await this.createTicketCommand();
|
|
98
108
|
const response = await this.sendCommand(ticketCommand).catch(reject);
|
|
99
|
-
logger.info(`Ticket accepted: ${makeReadable(response)}`);
|
|
100
109
|
resolve();
|
|
101
110
|
} catch (error) {
|
|
102
111
|
logger.error(`Error sending ticket: ${error}`);
|
|
@@ -113,6 +122,11 @@ class DiodeConnection extends EventEmitter {
|
|
|
113
122
|
}
|
|
114
123
|
});
|
|
115
124
|
|
|
125
|
+
// Start the periodic ticket update timer after successful connection
|
|
126
|
+
this.socket.on('connect', () => {
|
|
127
|
+
this._startTicketUpdateTimer();
|
|
128
|
+
});
|
|
129
|
+
|
|
116
130
|
this.socket.on('error', (err) => {
|
|
117
131
|
logger.error(`Connection error: ${err}`);
|
|
118
132
|
reject(err);
|
|
@@ -242,6 +256,11 @@ class DiodeConnection extends EventEmitter {
|
|
|
242
256
|
|
|
243
257
|
// Update close method to prevent reconnection when intentionally closing
|
|
244
258
|
close() {
|
|
259
|
+
if (this.ticketUpdateTimer) {
|
|
260
|
+
clearTimeout(this.ticketUpdateTimer);
|
|
261
|
+
this.ticketUpdateTimer = null;
|
|
262
|
+
}
|
|
263
|
+
|
|
245
264
|
this.autoReconnect = false;
|
|
246
265
|
if (this.retryTimeoutId) {
|
|
247
266
|
clearTimeout(this.retryTimeoutId);
|
|
@@ -431,7 +450,6 @@ class DiodeConnection extends EventEmitter {
|
|
|
431
450
|
const addressBuffer = ethUtil.pubToAddress(publicKeyBuffer, true);
|
|
432
451
|
const address = '0x' + addressBuffer.toString('hex');
|
|
433
452
|
|
|
434
|
-
logger.info(`Ethereum address: ${address}`);
|
|
435
453
|
return address;
|
|
436
454
|
} catch (error) {
|
|
437
455
|
logger.error(`Error extracting Ethereum address: ${error}`);
|
|
@@ -455,7 +473,6 @@ class DiodeConnection extends EventEmitter {
|
|
|
455
473
|
const addressBuffer = ethUtil.pubToAddress(publicKeyBuffer, true);
|
|
456
474
|
const address = '0x' + addressBuffer.toString('hex');
|
|
457
475
|
|
|
458
|
-
logger.info(`Server Ethereum address: ${address}`);
|
|
459
476
|
return address;
|
|
460
477
|
} catch (error) {
|
|
461
478
|
logger.error(`Error extracting server Ethereum address: ${error}`);
|
|
@@ -605,6 +622,79 @@ class DiodeConnection extends EventEmitter {
|
|
|
605
622
|
hasConnection(ref) {
|
|
606
623
|
return this.connections.has(ref.toString('hex'));
|
|
607
624
|
}
|
|
625
|
+
|
|
626
|
+
// Start timer for periodic ticket updates
|
|
627
|
+
_startTicketUpdateTimer() {
|
|
628
|
+
if (this.ticketUpdateTimer) {
|
|
629
|
+
clearTimeout(this.ticketUpdateTimer);
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
this.ticketUpdateTimer = setTimeout(() => {
|
|
633
|
+
this._updateTicketIfNeeded(true);
|
|
634
|
+
}, this.ticketUpdateInterval);
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
// Method to check if ticket update is needed and perform it
|
|
638
|
+
async _updateTicketIfNeeded(force = false) {
|
|
639
|
+
// If socket is not connected, don't try to update
|
|
640
|
+
if (!this.socket || this.socket.destroyed) {
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
const timeSinceLastUpdate = Date.now() - this.lastTicketUpdate;
|
|
645
|
+
|
|
646
|
+
if (force ||
|
|
647
|
+
this.accumulatedBytes >= this.ticketUpdateThreshold ||
|
|
648
|
+
timeSinceLastUpdate >= this.ticketUpdateInterval) {
|
|
649
|
+
|
|
650
|
+
try {
|
|
651
|
+
if (this.accumulatedBytes > 0 || force) {
|
|
652
|
+
logger.debug(`Updating ticket: accumulated ${this.accumulatedBytes} bytes, ${timeSinceLastUpdate}ms since last update`);
|
|
653
|
+
const ticketCommand = await this.createTicketCommand();
|
|
654
|
+
await this.sendCommand(ticketCommand);
|
|
655
|
+
|
|
656
|
+
// Reset counters
|
|
657
|
+
this.accumulatedBytes = 0;
|
|
658
|
+
this.lastTicketUpdate = Date.now();
|
|
659
|
+
}
|
|
660
|
+
} catch (error) {
|
|
661
|
+
logger.error(`Error updating ticket: ${error}`);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
// Restart the timer
|
|
666
|
+
this._startTicketUpdateTimer();
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
// Add method to track bytes without immediate ticket update
|
|
670
|
+
addBytes(bytesCount) {
|
|
671
|
+
this.totalBytes += bytesCount;
|
|
672
|
+
this.accumulatedBytes += bytesCount;
|
|
673
|
+
|
|
674
|
+
// Optionally check if we should update ticket now
|
|
675
|
+
if (this.accumulatedBytes >= this.ticketUpdateThreshold) {
|
|
676
|
+
this._updateTicketIfNeeded();
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
// Method to set ticket batching options
|
|
681
|
+
setTicketBatchingOptions(options = {}) {
|
|
682
|
+
if (typeof options.threshold === 'number') {
|
|
683
|
+
this.ticketUpdateThreshold = options.threshold;
|
|
684
|
+
}
|
|
685
|
+
if (typeof options.interval === 'number') {
|
|
686
|
+
this.ticketUpdateInterval = options.interval;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
logger.info(`Updated ticket batching settings - Bytes Threshold: ${this.ticketUpdateThreshold} bytes, Update Interval: ${this.ticketUpdateInterval}ms`);
|
|
690
|
+
|
|
691
|
+
// Reset the timer with new interval
|
|
692
|
+
if (this.socket && !this.socket.destroyed) {
|
|
693
|
+
this._startTicketUpdateTimer();
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
return this;
|
|
697
|
+
}
|
|
608
698
|
}
|
|
609
699
|
|
|
610
700
|
|
|
@@ -9,8 +9,8 @@ async function main() {
|
|
|
9
9
|
await connection.connect();
|
|
10
10
|
|
|
11
11
|
const portForward = new BindPort(connection, {
|
|
12
|
-
3003: { targetPort: 8080, deviceIdHex: "0xca1e71d8105a598810578fb6042fa8cbc1e7f039" },
|
|
13
|
-
3004: { targetPort:
|
|
12
|
+
3003: { targetPort: 8080, deviceIdHex: "0xca1e71d8105a598810578fb6042fa8cbc1e7f039", protocol: "tcp" },
|
|
13
|
+
3004: { targetPort: 8081, deviceIdHex: "5365baf29cb7ab58de588dfc448913cb609283e2", protocol: "tls" }
|
|
14
14
|
});
|
|
15
15
|
portForward.bind();
|
|
16
16
|
|
|
@@ -23,7 +23,7 @@ async function main() {
|
|
|
23
23
|
// after 10 seconds, add port 3003 back
|
|
24
24
|
setTimeout(() => {
|
|
25
25
|
console.log("Adding port 3003 back");
|
|
26
|
-
portForward.addPort(3003, 8080, "0xca1e71d8105a598810578fb6042fa8cbc1e7f039");
|
|
26
|
+
portForward.addPort(3003, 8080, "0xca1e71d8105a598810578fb6042fa8cbc1e7f039", "tcp");
|
|
27
27
|
}, 10000);
|
|
28
28
|
|
|
29
29
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "diodejs",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "A JavaScript client for interacting with the Diode network. It provides functionalities to bind and publish ports, send RPC commands, and handle responses.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
package/publishPort.js
CHANGED
|
@@ -254,7 +254,7 @@ class PublishPort extends EventEmitter {
|
|
|
254
254
|
// Create a DiodeSocket instance
|
|
255
255
|
const diodeSocket = new DiodeSocket(ref, this.rpc);
|
|
256
256
|
|
|
257
|
-
certPem = this.connection.getDeviceCertificate();
|
|
257
|
+
const certPem = this.connection.getDeviceCertificate();
|
|
258
258
|
|
|
259
259
|
// TLS options with your server's certificate and key
|
|
260
260
|
const tlsOptions = {
|
package/rpc.js
CHANGED
|
@@ -98,9 +98,9 @@ class DiodeRPC {
|
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
async portSend(ref, data) {
|
|
101
|
-
// Update
|
|
101
|
+
// Update bytes count but don't update ticket yet
|
|
102
102
|
const bytesToSend = data.length;
|
|
103
|
-
this.connection.
|
|
103
|
+
this.connection.addBytes(bytesToSend);
|
|
104
104
|
|
|
105
105
|
// Now send the data
|
|
106
106
|
return this.connection.sendCommand(['portsend', ref, data]).then(async (responseData) => {
|
|
@@ -109,17 +109,7 @@ class DiodeRPC {
|
|
|
109
109
|
const status = parseResponseType(statusRaw);
|
|
110
110
|
|
|
111
111
|
if (status === 'ok') {
|
|
112
|
-
|
|
113
|
-
const ticketCommand = await this.connection.createTicketCommand();
|
|
114
|
-
const ticketResponse = await this.connection.sendCommand(ticketCommand).catch((error) => {
|
|
115
|
-
logger.error(`Error during ticket command: ${error}`);
|
|
116
|
-
throw error;
|
|
117
|
-
});
|
|
118
|
-
logger.debug(`Ticket updated: ${makeReadable(ticketResponse)}`);
|
|
119
|
-
} catch (error) {
|
|
120
|
-
logger.error(`Error updating ticket: ${error}`);
|
|
121
|
-
throw error;
|
|
122
|
-
}
|
|
112
|
+
// No ticket update here anymore - handled by batching mechanism
|
|
123
113
|
return;
|
|
124
114
|
} else if (status === 'error') {
|
|
125
115
|
throw new Error('Error during port send');
|
|
@@ -192,17 +182,40 @@ class DiodeRPC {
|
|
|
192
182
|
return epoch;
|
|
193
183
|
}
|
|
194
184
|
|
|
195
|
-
parseTimestamp(blockHeader) {
|
|
196
|
-
//
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
185
|
+
parseTimestamp(blockHeader) {
|
|
186
|
+
// Search for the timestamp field by name
|
|
187
|
+
if (Array.isArray(blockHeader)) {
|
|
188
|
+
for (const field of blockHeader) {
|
|
189
|
+
if (Array.isArray(field) && field.length >= 2 && field[0] === 'timestamp') {
|
|
190
|
+
const timestampValue = field[1];
|
|
191
|
+
|
|
192
|
+
// Handle different timestamp value types
|
|
193
|
+
if (typeof timestampValue === 'number') {
|
|
194
|
+
return timestampValue;
|
|
195
|
+
} else if (typeof timestampValue === 'string' && timestampValue.startsWith('0x')) {
|
|
196
|
+
// Handle hex string
|
|
197
|
+
return parseInt(timestampValue.slice(2), 16);
|
|
198
|
+
} else if (typeof timestampValue === 'string') {
|
|
199
|
+
// Handle decimal string
|
|
200
|
+
return parseInt(timestampValue, 10);
|
|
201
|
+
} else if (timestampValue instanceof Uint8Array || Buffer.isBuffer(timestampValue)) {
|
|
202
|
+
// Handle buffer - carefully determine the byte length
|
|
203
|
+
const buf = Buffer.from(timestampValue);
|
|
204
|
+
// Use a safe approach to read the value based on buffer length
|
|
205
|
+
if (buf.length <= 6) {
|
|
206
|
+
return buf.readUIntBE(0, buf.length);
|
|
207
|
+
} else {
|
|
208
|
+
// For larger buffers, convert to hex string first
|
|
209
|
+
return parseInt(buf.toString('hex'), 16);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
205
214
|
}
|
|
215
|
+
|
|
216
|
+
// Fallback: if we couldn't find the timestamp or parse it correctly
|
|
217
|
+
logger.warn('Could not find or parse timestamp in block header, using current time');
|
|
218
|
+
return Math.floor(Date.now() / 1000);
|
|
206
219
|
}
|
|
207
220
|
}
|
|
208
221
|
|