diodejs 0.1.6 → 0.2.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 CHANGED
@@ -117,15 +117,23 @@ async function main() {
117
117
 
118
118
  // Multiple or single port binding with configuration object
119
119
  const portsConfig = {
120
- 3002: { targetPort: 80, deviceIdHex: "5365baf29cb7ab58de588dfc448913cb609283e2" },
121
- 3003: { targetPort: 443, deviceIdHex: "0x5365baf29cb7ab58de588dfc448913cb609283e2" } // Works with or without 0x prefix
120
+ 3002: {
121
+ targetPort: 80,
122
+ deviceIdHex: "5365baf29cb7ab58de588dfc448913cb609283e2",
123
+ protocol: "tls" // Optional - defaults to TLS if not specified
124
+ },
125
+ 3003: {
126
+ targetPort: 443,
127
+ deviceIdHex: "0x5365baf29cb7ab58de588dfc448913cb609283e2",
128
+ protocol: "tcp" // Can be "tls", "tcp", or "udp"
129
+ }
122
130
  };
123
131
 
124
132
  const portForward = new BindPort(connection, portsConfig);
125
133
  portForward.bind();
126
134
 
127
- // You can also dynamically add and remove ports
128
- portForward.addPort(3004, 8080, "5365baf29cb7ab58de588dfc448913cb609283e2");
135
+ // You can also dynamically add ports with protocol specification
136
+ portForward.addPort(3004, 8080, "5365baf29cb7ab58de588dfc448913cb609283e2", "udp");
129
137
  portForward.removePort(3003);
130
138
  }
131
139
 
@@ -144,7 +152,7 @@ async function main() {
144
152
  const connection = new DiodeConnection(host, port, keyLocation);
145
153
  await connection.connect();
146
154
 
147
- // Legacy method - single port binding
155
+ // Legacy method - single port binding (defaults to TLS protocol)
148
156
  const portForward = new BindPort(connection, 3002, 80, "5365baf29cb7ab58de588dfc448913cb609283e2");
149
157
  portForward.bind();
150
158
  }
@@ -249,13 +257,15 @@ main();
249
257
  New Constructor:
250
258
  - `new BindPort(connection, portsConfig)`
251
259
  - `connection` (DiodeConnection): An instance of `DiodeConnection`.
252
- - `portsConfig` (object): A configuration object where keys are local ports and values are objects with `targetPort` and `deviceIdHex`.
253
- Example: `{ 3002: { targetPort: 80, deviceIdHex: "5365baf29cb7ab58de588dfc448913cb609283e2" } }`
254
- Note: deviceIdHex can be provided with or without the '0x' prefix.
260
+ - `portsConfig` (object): A configuration object where keys are local ports and values are objects with:
261
+ - `targetPort` (number): The target port on the device.
262
+ - `deviceIdHex` (string): The device ID in hexadecimal format (with or without '0x' prefix).
263
+ - `protocol` (string, optional): The protocol to use ("tls", "tcp", or "udp"). Defaults to "tls".
255
264
 
256
265
  - **Methods**:
257
266
  - `bind()`: Binds all configured local ports to their target ports on the devices.
258
- - `addPort(localPort, targetPort, deviceIdHex)`: Adds a new port binding configuration. deviceIdHex can include '0x' prefix.
267
+ - `addPort(localPort, targetPort, deviceIdHex, protocol)`: Adds a new port binding configuration.
268
+ - `protocol` (string, optional): The protocol to use. Can be "tls", "tcp", or "udp". Defaults to "tls".
259
269
  - `removePort(localPort)`: Removes a port binding configuration.
260
270
  - `bindSinglePort(localPort)`: Binds a single local port to its target.
261
271
  - `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.write(data);
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
- // Set up local server
158
- const server = net.createServer(async (clientSocket) => {
159
- logger.info(`Client connected to local server on port ${localPort}`);
160
-
161
- // Open a new port on the device for this client
162
- let ref;
163
- try {
164
- ref = await this.rpc.portOpen(deviceId, targetPort, 'rw');
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
- logger.error(`Error opening port ${targetPort} on deviceId: ${deviceIdHex}`);
167
- clientSocket.destroy();
168
- return;
169
- } else {
170
- logger.info(`Port ${targetPort} opened on device with ref: ${ref.toString('hex')} for client`);
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
- } catch (error) {
173
- logger.error(`Error opening port ${targetPort} on device: ${error}`);
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
- // Handle client socket closure
192
- clientSocket.on('end', async () => {
193
- logger.info('Client disconnected');
194
- if (ref && this.connection.hasClientSocket(ref)) {
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
- await this.rpc.portClose(ref);
197
- logger.info(`Port closed on device for ref: ${ref.toString('hex')}`);
198
- this.connection.deleteClientSocket(ref);
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 closing port on device: ${error}`);
345
+ logger.error(`Error setting up tls connection: ${error}`);
346
+ clientSocket.destroy();
347
+ return;
201
348
  }
202
349
  } else {
203
- logger.warn('Ref is invalid or no longer in clientSockets.');
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
- // Handle client socket errors
208
- clientSocket.on('error', (err) => {
209
- logger.error('Client socket error:', err);
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
- server.listen(localPort, () => {
214
- logger.info(`Local server listening on port ${localPort} forwarding to device port ${targetPort}`);
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
 
@@ -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: 443, deviceIdHex: "5365baf29cb7ab58de588dfc448913cb609283e2" }
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.6",
3
+ "version": "0.2.0",
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 = {