diodejs 0.1.1 → 0.1.4

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
@@ -25,9 +25,9 @@ const { DiodeConnection, DiodeRPC, makeReadable } = require('diodejs');
25
25
  async function main() {
26
26
  const host = 'eu2.prenet.diode.io';
27
27
  const port = 41046;
28
- const certPath = 'device_certificate.pem';
28
+ const keyLocation = './db/keys.json'; // Optional, defaults to './db/keys.json'
29
29
 
30
- const connection = new DiodeConnection(host, port, certPath);
30
+ const connection = new DiodeConnection(host, port, keyLocation);
31
31
  await connection.connect();
32
32
 
33
33
  const rpc = new DiodeRPC(connection);
@@ -54,24 +54,55 @@ main();
54
54
  ### Bind Port
55
55
  Here's a quick example to get you started with port forwarding using the `BindPort` class.
56
56
 
57
+ #### Port Binding
57
58
  ```javascript
58
59
  const { DiodeConnection, BindPort } = require('diodejs');
59
60
 
60
61
  async function main() {
61
62
  const host = 'eu2.prenet.diode.io';
62
63
  const port = 41046;
63
- const certPath = 'device_certificate.pem';
64
+ const keyLocation = './db/keys.json';
64
65
 
65
- const connection = new DiodeConnection(host, port, certPath);
66
+ const connection = new DiodeConnection(host, port, keyLocation);
66
67
  await connection.connect();
67
68
 
68
- const portForward = new BindPort(connection, 3002, 80, "5365baf29cb7ab58de588dfc448913cb609283e2");
69
+ // Multiple or single port binding with configuration object
70
+ const portsConfig = {
71
+ 3002: { targetPort: 80, deviceIdHex: "5365baf29cb7ab58de588dfc448913cb609283e2" },
72
+ 3003: { targetPort: 443, deviceIdHex: "5365baf29cb7ab58de588dfc448913cb609283e2" }
73
+ };
74
+
75
+ const portForward = new BindPort(connection, portsConfig);
69
76
  portForward.bind();
70
77
 
78
+ // You can also dynamically add and remove ports
79
+ portForward.addPort(3004, 8080, "5365baf29cb7ab58de588dfc448913cb609283e2");
80
+ portForward.removePort(3003);
81
+ }
82
+
83
+ main();
84
+ ```
85
+
86
+ #### Single Port Binding (Legacy)
87
+ ```javascript
88
+ const { DiodeConnection, BindPort } = require('diodejs');
89
+
90
+ async function main() {
91
+ const host = 'eu2.prenet.diode.io';
92
+ const port = 41046;
93
+ const keyLocation = './db/keys.json';
94
+
95
+ const connection = new DiodeConnection(host, port, keyLocation);
96
+ await connection.connect();
97
+
98
+ // Legacy method - single port binding
99
+ const portForward = new BindPort(connection, 3002, 80, "5365baf29cb7ab58de588dfc448913cb609283e2");
100
+ portForward.bind();
71
101
  }
72
102
 
73
103
  main();
74
104
  ```
105
+
75
106
  ### Publish Port
76
107
 
77
108
  Here's a quick example to get you started with publishing ports using the `PublishPort` class:
@@ -82,9 +113,9 @@ const { DiodeConnection, PublishPort } = require('diodejs');
82
113
  async function main() {
83
114
  const host = 'us2.prenet.diode.io';
84
115
  const port = 41046;
85
- const certPath = 'device_certificate.pem';
116
+ const keyLocation = './db/keys.json';
86
117
 
87
- const connection = new DiodeConnection(host, port, certPath);
118
+ const connection = new DiodeConnection(host, port, keyLocation);
88
119
  await connection.connect();
89
120
 
90
121
  // Option 1: Simple array of ports (all public)
@@ -99,7 +130,8 @@ async function main() {
99
130
  }
100
131
  };
101
132
 
102
- const publishPort = new PublishPort(connection, publishedPortsWithConfig, certPath);
133
+ // certPath parameter is maintained for backward compatibility but not required
134
+ const publishPort = new PublishPort(connection, publishedPortsWithConfig);
103
135
  }
104
136
 
105
137
  main();
@@ -111,19 +143,20 @@ main();
111
143
 
112
144
  #### `DiodeConnection`
113
145
 
114
- - **Constructor**: `new DiodeConnection(host, port, certPath)`
146
+ - **Constructor**: `new DiodeConnection(host, port, keyLocation)`
115
147
  - `host` (string): The host address of the Diode server.
116
148
  - `port` (number): The port number of the Diode server.
117
- - `certPath` (string)(default: ./cert/device_certificate.pem): The path to the device certificate. If doesn't exist, generates automaticly.
149
+ - `keyLocation` (string)(default: './db/keys.json'): The path to the key storage file. If the file doesn't exist, keys are generated automatically.
118
150
 
119
151
  - **Methods**:
120
152
  - `connect()`: Connects to the Diode server. Returns a promise.
121
153
  - `sendCommand(commandArray)`: Sends a command to the Diode server. Returns a promise.
122
154
  - `sendCommandWithSessionId(commandArray, sessionId)`: Sends a command with a session ID. Returns a promise.
123
- - `getEthereumAddress()`: Returns the Ethereum address derived from the device certificate.
155
+ - `getEthereumAddress()`: Returns the Ethereum address derived from the device keys.
124
156
  - `getServerEthereumAddress()`: Returns the Ethereum address of the server.
125
157
  - `createTicketCommand()`: Creates a ticket command for authentication. Returns a promise.
126
158
  - `close()`: Closes the connection to the Diode server.
159
+ - `getDeviceCertificate()`: Returns the generated certificate PEM.
127
160
 
128
161
  #### `DiodeRPC`
129
162
 
@@ -145,25 +178,47 @@ main();
145
178
 
146
179
  #### `BindPort`
147
180
 
148
- - **Constructor**: `new BindPort(connection, localPort, targetPort, deviceIdHex)`
149
- - `connection` (DiodeConnection): An instance of `DiodeConnection`.
150
- - `localPort` (number): The local port to bind.
151
- - `targetPort` (number): The target port on the device.
152
- - `deviceIdHex` (string): The device ID in hexadecimal format.
181
+ - **Constructors**:
182
+
183
+ Legacy Constructor:
184
+ - `new BindPort(connection, localPort, targetPort, deviceIdHex)`
185
+ - `connection` (DiodeConnection): An instance of `DiodeConnection`.
186
+ - `localPort` (number): The local port to bind.
187
+ - `targetPort` (number): The target port on the device.
188
+ - `deviceIdHex` (string): The device ID in hexadecimal format.
189
+
190
+ New Constructor:
191
+ - `new BindPort(connection, portsConfig)`
192
+ - `connection` (DiodeConnection): An instance of `DiodeConnection`.
193
+ - `portsConfig` (object): A configuration object where keys are local ports and values are objects with `targetPort` and `deviceIdHex`.
194
+ Example: `{ 3002: { targetPort: 80, deviceIdHex: "5365baf29cb7ab58de588dfc448913cb609283e2" } }`
153
195
 
154
196
  - **Methods**:
155
- - `bind()`: Binds the local port to the target port on the device.
197
+ - `bind()`: Binds all configured local ports to their target ports on the devices.
198
+ - `addPort(localPort, targetPort, deviceIdHex)`: Adds a new port binding configuration.
199
+ - `removePort(localPort)`: Removes a port binding configuration.
200
+ - `bindSinglePort(localPort)`: Binds a single local port to its target.
201
+ - `closeAllServers()`: Closes all active server instances.
156
202
 
157
203
  #### `PublishPort`
158
204
 
159
- - **Constructor**: `new PublishPort(connection, publishedPorts, certPath)`
205
+ - **Constructor**: `new PublishPort(connection, publishedPorts, _certPath)`
160
206
  - `connection` (DiodeConnection): An instance of `DiodeConnection`.
161
207
  - `publishedPorts` (array|object): Either:
162
208
  - An array of ports to publish (all public mode)
163
209
  - An object mapping ports to their configuration: `{ port: { mode: 'public'|'private', whitelist: ['0x123...'] } }`
164
- - `certPath` (string): The path to the device certificate.
210
+ - `_certPath` (string): Has no functionality and maintained for backward compatibility.
165
211
 
166
212
  - **Methods**:
213
+ - `addPort(port, config)`: Adds a new port to publish. Config is optional and defaults to public mode.
214
+ - `port` (number): The port number to publish.
215
+ - `config` (object): Optional configuration with `mode` ('public'|'private') and `whitelist` array.
216
+ - `removePort(port)`: Removes a published port.
217
+ - `port` (number): The port number to remove.
218
+ - `addPorts(ports)`: Adds multiple ports at once (equivalent to the constructor's publishedPorts parameter).
219
+ - `ports` (array|object): Either an array of port numbers or an object mapping ports to their configurations.
220
+ - `getPublishedPorts()`: Returns a plain object with all published ports and their configurations.
221
+ - `clearPorts()`: Removes all published ports. Returns the number of ports that were cleared.
167
222
  - `startListening()`: Starts listening for unsolicited messages.
168
223
  - `handlePortOpen(sessionIdRaw, messageContent)`: Handles port open requests.
169
224
  - `handlePortSend(sessionIdRaw, messageContent)`: Handles port send requests.
package/bindPort.js CHANGED
@@ -4,18 +4,27 @@ const DiodeRPC = require('./rpc');
4
4
  const logger = require('./logger');
5
5
 
6
6
  class BindPort {
7
- constructor(connection, localPort, targetPort,deviceIdHex) {
7
+ constructor(connection, localPortOrPortsConfig, targetPort, deviceIdHex) {
8
8
  this.connection = connection;
9
- this.localPort = localPort;
10
- this.targetPort = targetPort;
11
- this.deviceIdHex = deviceIdHex;
9
+
10
+ // Handle legacy constructor (connection, localPort, targetPort, deviceIdHex)
11
+ if (typeof localPortOrPortsConfig === 'number' && targetPort !== undefined && deviceIdHex !== undefined) {
12
+ this.portsConfig = {
13
+ [localPortOrPortsConfig]: { targetPort, deviceIdHex }
14
+ };
15
+ } else {
16
+ // New constructor (connection, portsConfig)
17
+ this.portsConfig = localPortOrPortsConfig || {};
18
+ }
19
+
20
+ this.servers = new Map(); // Track server instances by localPort
21
+ this.rpc = new DiodeRPC(this.connection);
22
+
23
+ // Set up listener for unsolicited messages once
24
+ this._setupMessageListener();
12
25
  }
13
-
14
- bind () {
15
- const deviceId = Buffer.from(this.deviceIdHex, 'hex');
16
- // Remove local clientSockets map and use the one from connection
17
- const rpc = new DiodeRPC(this.connection);
18
-
26
+
27
+ _setupMessageListener() {
19
28
  // Listen for data events from the device
20
29
  this.connection.on('unsolicited', (message) => {
21
30
  // message is [messageId, [messageType, ...]]
@@ -44,7 +53,6 @@ class BindPort {
44
53
  }
45
54
  } else if (messageType === 'portclose') {
46
55
  const refRaw = messageContent[1];
47
-
48
56
  const dataRef = Buffer.from(refRaw);
49
57
 
50
58
  // Close the associated client socket
@@ -60,24 +68,88 @@ class BindPort {
60
68
  }
61
69
  }
62
70
  });
71
+
72
+ // Handle device disconnect
73
+ this.connection.on('end', () => {
74
+ logger.info('Disconnected from Diode.io server');
75
+ this.closeAllServers();
76
+ });
63
77
 
78
+ // Handle connection errors
79
+ this.connection.on('error', (err) => {
80
+ logger.error(`Connection error: ${err}`);
81
+ this.closeAllServers();
82
+ });
83
+ }
84
+
85
+ addPort(localPort, targetPort, deviceIdHex) {
86
+ if (this.servers.has(localPort)) {
87
+ logger.warn(`Port ${localPort} is already bound`);
88
+ return false;
89
+ }
90
+
91
+ this.portsConfig[localPort] = { targetPort, deviceIdHex };
92
+
93
+ this.bindSinglePort(localPort);
94
+
95
+ return true;
96
+ }
97
+
98
+ removePort(localPort) {
99
+ if (!this.portsConfig[localPort]) {
100
+ logger.warn(`Port ${localPort} is not configured`);
101
+ return false;
102
+ }
103
+
104
+ // Close the server if it's running
105
+ if (this.servers.has(localPort)) {
106
+ const server = this.servers.get(localPort);
107
+ server.close(() => {
108
+ logger.info(`Server on port ${localPort} closed`);
109
+ });
110
+ this.servers.delete(localPort);
111
+ }
112
+
113
+ // Remove from config
114
+ delete this.portsConfig[localPort];
115
+ return true;
116
+ }
117
+
118
+ closeAllServers() {
119
+ for (const [localPort, server] of this.servers.entries()) {
120
+ server.close();
121
+ logger.info(`Server on port ${localPort} closed`);
122
+ }
123
+ this.servers.clear();
124
+ }
125
+
126
+ bindSinglePort(localPort) {
127
+ const config = this.portsConfig[localPort];
128
+ if (!config) {
129
+ logger.error(`No configuration found for port ${localPort}`);
130
+ return false;
131
+ }
132
+
133
+ const { targetPort, deviceIdHex } = config;
134
+ const deviceId = Buffer.from(deviceIdHex, 'hex');
135
+
64
136
  // Set up local server
65
137
  const server = net.createServer(async (clientSocket) => {
66
- logger.info('Client connected to local server');
138
+ logger.info(`Client connected to local server on port ${localPort}`);
67
139
 
68
140
  // Open a new port on the device for this client
69
141
  let ref;
70
142
  try {
71
- ref = await rpc.portOpen(deviceId, this.targetPort, 'rw');
143
+ ref = await this.rpc.portOpen(deviceId, targetPort, 'rw');
72
144
  if (!ref) {
73
- logger.error('Error opening port on device');
145
+ logger.error(`Error opening port ${targetPort} on deviceId: ${deviceIdHex}`);
74
146
  clientSocket.destroy();
75
- return
147
+ return;
76
148
  } else {
77
- logger.info(`Port opened on device with ref: ${ref.toString('hex')} for client`);
149
+ logger.info(`Port ${targetPort} opened on device with ref: ${ref.toString('hex')} for client`);
78
150
  }
79
151
  } catch (error) {
80
- logger.error(`Error opening port on device: ${error}`);
152
+ logger.error(`Error opening port ${targetPort} on device: ${error}`);
81
153
  clientSocket.destroy();
82
154
  return;
83
155
  }
@@ -88,7 +160,7 @@ class BindPort {
88
160
  // When data is received from the client, send it to the device
89
161
  clientSocket.on('data', async (data) => {
90
162
  try {
91
- await rpc.portSend(ref, data);
163
+ await this.rpc.portSend(ref, data);
92
164
  } catch (error) {
93
165
  logger.error(`Error sending data to device: ${error}`);
94
166
  clientSocket.destroy();
@@ -100,7 +172,7 @@ class BindPort {
100
172
  logger.info('Client disconnected');
101
173
  if (ref && this.connection.hasClientSocket(ref)) {
102
174
  try {
103
- await rpc.portClose(ref);
175
+ await this.rpc.portClose(ref);
104
176
  logger.info(`Port closed on device for ref: ${ref.toString('hex')}`);
105
177
  this.connection.deleteClientSocket(ref);
106
178
  } catch (error) {
@@ -117,21 +189,22 @@ class BindPort {
117
189
  });
118
190
  });
119
191
 
120
- server.listen(this.localPort, () => {
121
- logger.info(`Local server listening on port ${this.localPort}`);
122
- });
123
-
124
- // Handle device disconnect
125
- this.connection.on('end', () => {
126
- logger.info('Disconnected from Diode.io server');
127
- server.close();
192
+ server.listen(localPort, () => {
193
+ logger.info(`Local server listening on port ${localPort} forwarding to device port ${targetPort}`);
128
194
  });
195
+
196
+ this.servers.set(parseInt(localPort), server);
197
+ return true;
198
+ }
129
199
 
130
- // Handle connection errors
131
- this.connection.on('error', (err) => {
132
- logger.error(`Connection error: ${err}`);
133
- server.close();
134
- });
200
+ bind() {
201
+ // Close any existing servers first
202
+ this.closeAllServers();
203
+
204
+ // Create servers for each port in the config
205
+ for (const localPort in this.portsConfig) {
206
+ this.bindSinglePort(parseInt(localPort));
207
+ }
135
208
  }
136
209
  }
137
210
 
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 } = require('./utils');
6
+ const { makeReadable, parseRequestId, parseResponseType, parseReason, generateCert, ensureDirectoryExistence, loadOrGenerateKeyPair } = require('./utils');
7
7
  const { Buffer } = require('buffer'); // Import Buffer
8
8
  const asn1 = require('asn1.js');
9
9
  const secp256k1 = require('secp256k1');
@@ -12,12 +12,14 @@ const crypto = require('crypto');
12
12
  const DiodeRPC = require('./rpc');
13
13
  const abi = require('ethereumjs-abi');
14
14
  const logger = require('./logger');
15
+ const path = require('path');
16
+
15
17
  class DiodeConnection extends EventEmitter {
16
- constructor(host, port, certPath = './cert/device_certificate.pem') {
18
+ constructor(host, port, keyLocation = './db/keys.json') {
17
19
  super();
18
20
  this.host = host;
19
21
  this.port = port;
20
- this.certPath = certPath;
22
+ this.keyLocation = keyLocation;
21
23
  this.socket = null;
22
24
  this.requestId = 0; // Initialize request ID counter
23
25
  this.pendingRequests = new Map(); // Map to store pending requests
@@ -32,18 +34,19 @@ class DiodeConnection extends EventEmitter {
32
34
  // Add maps for storing client sockets and connections
33
35
  this.clientSockets = new Map(); // For BindPort
34
36
  this.connections = new Map(); // For PublishPort
35
-
36
- // Check if certPath exists, if not generate the certificate
37
- if (!fs.existsSync(this.certPath)) {
38
- generateCert(this.certPath);
39
- }
37
+ this.certPem = null;
38
+ // Load or generate keypair
39
+ this.keyPair = loadOrGenerateKeyPair(this.keyLocation);
40
40
  }
41
41
 
42
42
  connect() {
43
43
  return new Promise((resolve, reject) => {
44
+ // Generate a temporary certificate valid for 1 month
45
+ this.certPem = generateCert(this.keyPair.prvKeyObj, this.keyPair.pubKeyObj);
46
+
44
47
  const options = {
45
- cert: fs.readFileSync(this.certPath),
46
- key: fs.readFileSync(this.certPath),
48
+ cert: this.certPem,
49
+ key: this.certPem,
47
50
  rejectUnauthorized: false,
48
51
  ciphers: 'ECDHE-ECDSA-AES256-GCM-SHA384',
49
52
  ecdhCurve: 'secp256k1',
@@ -276,63 +279,10 @@ class DiodeConnection extends EventEmitter {
276
279
 
277
280
  getEthereumAddress() {
278
281
  try {
279
- const pem = fs.readFileSync(this.certPath, 'utf8');
280
- let privateKeyPem;
281
- let privateKeyDer;
282
- let privateKeyBytes;
283
-
284
- if (pem.includes('-----BEGIN PRIVATE KEY-----')) {
285
- // Handle PKCS#8 format
286
- privateKeyPem = pem
287
- .replace('-----BEGIN PRIVATE KEY-----', '')
288
- .replace('-----END PRIVATE KEY-----', '')
289
- .replace(/\r?\n|\r/g, '');
290
-
291
- privateKeyDer = Buffer.from(privateKeyPem, 'base64');
292
-
293
- // Define ASN.1 structure for PKCS#8 private key
294
- const PrivateKeyInfoASN = asn1.define('PrivateKeyInfo', function () {
295
- this.seq().obj(
296
- this.key('version').int(),
297
- this.key('privateKeyAlgorithm').seq().obj(
298
- this.key('algorithm').objid(),
299
- this.key('parameters').optional()
300
- ),
301
- this.key('privateKey').octstr(),
302
- this.key('attributes').implicit(0).any().optional(),
303
- this.key('publicKey').implicit(1).bitstr().optional()
304
- );
305
- });
306
-
307
- // Decode the DER-encoded private key
308
- const privateKeyInfo = PrivateKeyInfoASN.decode(privateKeyDer, 'der');
309
- const privateKeyOctetString = privateKeyInfo.privateKey;
310
-
311
- // Now parse the ECPrivateKey structure inside the octet string
312
- const ECPrivateKeyASN = asn1.define('ECPrivateKey', function () {
313
- this.seq().obj(
314
- this.key('version').int(),
315
- this.key('privateKey').octstr(),
316
- this.key('parameters').explicit(0).objid().optional(),
317
- this.key('publicKey').explicit(1).bitstr().optional()
318
- );
319
- });
320
-
321
- const ecPrivateKey = ECPrivateKeyASN.decode(privateKeyOctetString, 'der');
322
- privateKeyBytes = ecPrivateKey.privateKey;
323
- logger.debug(`Private key bytes: ${privateKeyBytes.toString('hex')}`);
324
- } else {
325
- throw new Error('Unsupported key format. Expected EC PRIVATE KEY or PRIVATE KEY in PEM format.');
326
- }
327
-
328
- // Compute the public key
329
- const publicKeyUint8Array = secp256k1.publicKeyCreate(privateKeyBytes, false); // uncompressed
330
-
331
- // Convert publicKey to Buffer if necessary
332
- const publicKeyBuffer = Buffer.isBuffer(publicKeyUint8Array)
333
- ? publicKeyUint8Array
334
- : Buffer.from(publicKeyUint8Array);
335
-
282
+ // Use the stored keyPair.pubKeyObj to derive Ethereum address
283
+ const publicKeyDer = this.keyPair.prvKeyObj.generatePublicKeyHex();
284
+ const publicKeyBuffer = Buffer.from(publicKeyDer, 'hex');
285
+
336
286
  // Derive the Ethereum address
337
287
  const addressBuffer = ethUtil.pubToAddress(publicKeyBuffer, true);
338
288
  const address = '0x' + addressBuffer.toString('hex');
@@ -369,106 +319,15 @@ class DiodeConnection extends EventEmitter {
369
319
  }
370
320
  }
371
321
 
372
- // getServerEthereumAddress() {
373
- // try {
374
- // const serverCert = this.socket.getPeerCertificate(true);
375
- // if (!serverCert.raw) {
376
- // throw new Error('Failed to get server certificate.');
377
- // }
378
-
379
- // // Extract public key from the certificate
380
- // const publicKey = serverCert.pubkey; // May need to parse ASN.1 structure to get the public key
381
- // // Assume you have a method to extract the public key buffer from the certificate
382
-
383
- // // Compute Ethereum address from public key
384
- // const publicKeyBuffer = Buffer.from(publicKey); // Ensure it's a Buffer
385
- // const addressBuffer = ethUtil.pubToAddress(publicKeyBuffer, true);
386
-
387
- // return addressBuffer; // Return as Buffer
388
- // } catch (error) {
389
- // console.error('Error extracting server Ethereum address:', error);
390
- // throw error;
391
- // }
392
- // }
393
-
394
- // Method to extract private key bytes from certPath
322
+ // Method to extract private key bytes from keyPair
395
323
  getPrivateKey() {
396
- // Similar to getEthereumAddress(), but return privateKeyBytes
397
- // Ensure to handle different key formats (EC PRIVATE KEY and PRIVATE KEY)
398
324
  try {
399
- const pem = fs.readFileSync(this.certPath, 'utf8');
400
- let privateKeyPem;
401
- let privateKeyDer;
402
- let privateKeyBytes;
403
-
404
- if (pem.includes('-----BEGIN PRIVATE KEY-----')) {
405
- // Handle PKCS#8 format
406
- privateKeyPem = pem
407
- .replace('-----BEGIN PRIVATE KEY-----', '')
408
- .replace('-----END PRIVATE KEY-----', '')
409
- .replace(/\r?\n|\r/g, '');
410
-
411
- privateKeyDer = Buffer.from(privateKeyPem, 'base64');
412
-
413
- // Define ASN.1 structure for PKCS#8 private key
414
- const PrivateKeyInfoASN = asn1.define('PrivateKeyInfo', function () {
415
- this.seq().obj(
416
- this.key('version').int(),
417
- this.key('privateKeyAlgorithm').seq().obj(
418
- this.key('algorithm').objid(),
419
- this.key('parameters').optional()
420
- ),
421
- this.key('privateKey').octstr(),
422
- this.key('attributes').implicit(0).any().optional(),
423
- this.key('publicKey').implicit(1).bitstr().optional()
424
- );
425
- });
426
-
427
- // Decode the DER-encoded private key
428
- const privateKeyInfo = PrivateKeyInfoASN.decode(privateKeyDer, 'der');
429
- const privateKeyOctetString = privateKeyInfo.privateKey;
430
-
431
- // Now parse the ECPrivateKey structure inside the octet string
432
- const ECPrivateKeyASN = asn1.define('ECPrivateKey', function () {
433
- this.seq().obj(
434
- this.key('version').int(),
435
- this.key('privateKey').octstr(),
436
- this.key('parameters').explicit(0).objid().optional(),
437
- this.key('publicKey').explicit(1).bitstr().optional()
438
- );
439
- });
440
-
441
- const ecPrivateKey = ECPrivateKeyASN.decode(privateKeyOctetString, 'der');
442
- privateKeyBytes = ecPrivateKey.privateKey;
443
- } else if (pem.includes('-----BEGIN EC PRIVATE KEY-----')) {
444
- // Handle EC PRIVATE KEY format
445
- privateKeyPem = pem
446
- .replace('-----BEGIN EC PRIVATE KEY-----', '')
447
- .replace('-----END EC PRIVATE KEY-----', '')
448
- .replace(/\r?\n|\r/g, '');
449
-
450
- privateKeyDer = Buffer.from(privateKeyPem, 'base64');
451
-
452
- // Define ASN.1 structure for EC private key
453
- const ECPrivateKeyASN = asn1.define('ECPrivateKey', function () {
454
- this.seq().obj(
455
- this.key('version').int(),
456
- this.key('privateKey').octstr(),
457
- this.key('parameters').explicit(0).objid().optional(),
458
- this.key('publicKey').explicit(1).bitstr().optional()
459
- );
460
- });
461
-
462
- // Decode the DER-encoded private key
463
- const ecPrivateKey = ECPrivateKeyASN.decode(privateKeyDer, 'der');
464
- privateKeyBytes = ecPrivateKey.privateKey;
465
- } else {
466
- throw new Error('Unsupported key format. Expected EC PRIVATE KEY or PRIVATE KEY in PEM format.');
467
- }
468
-
325
+ // Extract private key bytes from the keyPair.prvKeyObj
326
+ const privateKeyHex = this.keyPair.prvKeyObj.prvKeyHex;
327
+ const privateKeyBytes = Buffer.from(privateKeyHex, 'hex');
469
328
  return privateKeyBytes;
470
329
  } catch (error) {
471
- logger.error(`Error extracting Ethereum address: ${error}`);
330
+ logger.error(`Error extracting private key: ${error}`);
472
331
  throw error;
473
332
  }
474
333
  }
@@ -557,6 +416,12 @@ class DiodeConnection extends EventEmitter {
557
416
  return ticketCommand;
558
417
  }
559
418
 
419
+ getDeviceCertificate() {
420
+ return this.certPem;
421
+ }
422
+
423
+
424
+
560
425
  _getNextRequestId() {
561
426
  // Increment the request ID counter, wrap around if necessary
562
427
  this.requestId = (this.requestId + 1) % Number.MAX_SAFE_INTEGER;
@@ -4,9 +4,9 @@ const { makeReadable } = require('../utils');
4
4
  async function main() {
5
5
  const host = 'us2.prenet.diode.io';
6
6
  const port = 41046;
7
- const certPath = 'device_certificate.pem';
7
+ const keyLocation = './db/keys.json';
8
8
 
9
- const connection = new DiodeConnection(host, port, certPath);
9
+ const connection = new DiodeConnection(host, port, keyLocation);
10
10
  await connection.connect();
11
11
  const rpc = connection.RPC;
12
12
 
@@ -3,14 +3,30 @@ const { DiodeConnection, BindPort } = require('../index');
3
3
  async function main() {
4
4
  const host = 'us2.prenet.diode.io';
5
5
  const port = 41046;
6
- const certPath = 'device_certificate.pem';
6
+ const keyLocation = './db/keys.json';
7
7
 
8
- const connection = new DiodeConnection(host, port, certPath);
8
+ const connection = new DiodeConnection(host, port, keyLocation);
9
9
  await connection.connect();
10
10
 
11
- const portForward = new BindPort(connection, 3002, 8080, "5365baf29cb7ab58de588dfc448913cb609283e2");
11
+ const portForward = new BindPort(connection, {
12
+ 3003: { targetPort: 8080, deviceIdHex: "ca1e71d8105a598810578fb6042fa8cbc1e7f039" },
13
+ 3004: { targetPort: 443, deviceIdHex: "5365baf29cb7ab58de588dfc448913cb609283e2" }
14
+ });
12
15
  portForward.bind();
16
+
17
+ // after 5 seconds, remove port 3003
18
+ setTimeout(() => {
19
+ console.log("Removing port 3003");
20
+ portForward.removePort(3003);
21
+ }, 5000);
22
+
23
+ // after 10 seconds, add port 3003 back
24
+ setTimeout(() => {
25
+ console.log("Adding port 3003 back");
26
+ portForward.addPort(3003, 8080, "ca1e71d8105a598810578fb6042fa8cbc1e7f039");
27
+ }, 10000);
28
+
13
29
 
14
30
  }
15
31
 
16
- main();
32
+ main().catch(console.error);
@@ -4,14 +4,14 @@ const BindPort = require('../bindPort')
4
4
 
5
5
  const host = 'us2.prenet.diode.io';
6
6
  const port = 41046;
7
- const certPath = 'device_certificate.pem';
7
+ const keyLocation = './db/keys.json';
8
8
 
9
- const connection = new DiodeConnection(host, port, certPath);
9
+ const connection = new DiodeConnection(host, port, keyLocation);
10
10
 
11
11
  async function main() {
12
12
  await connection.connect();
13
13
  const publishedPorts = [8080]; // Ports you want to publish
14
- const publishPort = new PublishPort(connection, publishedPorts, certPath);
14
+ const publishPort = new PublishPort(connection, publishedPorts);
15
15
 
16
16
  const portForward = new BindPort(connection, 3002, 8080, "5365baf29cb7ab58de588dfc448913cb609283e2");
17
17
  portForward.bind();
@@ -1,19 +1,40 @@
1
1
  // example.js
2
2
 
3
- const DiodeConnection = require('../connection')
4
- const PublishPort = require('../publishPort')
3
+ const { DiodeConnection, PublishPort } = require('../index');
5
4
 
6
5
  async function startPublishing() {
7
6
  const host = 'us2.prenet.diode.io';
8
7
  const port = 41046;
9
- const certPath = 'device_certificate.pem';
8
+ const keyLocation = './db/keys.json';
10
9
 
11
- const connection = new DiodeConnection(host, port, certPath);
10
+ const connection = new DiodeConnection(host, port, keyLocation);
12
11
  await connection.connect();
13
12
 
14
- const publishedPorts = {8080: {mode: 'private', whitelist: ['0xca1e71d8105a598810578fb6042fa8cbc1e7f039']}}
15
- const publishPort = new PublishPort(connection, publishedPorts, certPath);
16
-
13
+ // Create a PublishPort instance with initial ports
14
+ const publishPort = new PublishPort(connection, {
15
+ 3000: { mode: 'public' },
16
+ 8080: {
17
+ mode: 'private',
18
+ whitelist: ['0xca1e71d8105a598810578fb6042fa8cbc1e7f039'] // Replace with actual addresses
19
+ }
20
+ }, keyLocation);
21
+
22
+ console.log('Initial published ports:', publishPort.getPublishedPorts());
23
+
24
+ // After 10 seconds, remove a port
25
+ setTimeout(() => {
26
+ console.log("Removing port 8080");
27
+ publishPort.removePort(3000);
28
+ console.log('Updated published ports:', publishPort.getPublishedPorts());
29
+ }, 10000);
30
+
31
+ // After 15 seconds, add multiple ports
32
+ setTimeout(() => {
33
+ console.log("Adding multiple ports");
34
+ publishPort.addPort(3000,{ mode: 'public' });
35
+ console.log('Updated published ports:', publishPort.getPublishedPorts());
36
+ }, 15000);
37
+
17
38
  }
18
39
 
19
- startPublishing();
40
+ startPublishing().catch(console.error);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "diodejs",
3
- "version": "0.1.1",
3
+ "version": "0.1.4",
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
@@ -36,39 +36,98 @@ class DiodeSocket extends Duplex {
36
36
  }
37
37
 
38
38
  class PublishPort extends EventEmitter {
39
- constructor(connection, publishedPorts, certPath) {
39
+ constructor(connection, publishedPorts, _certPath = null) {
40
40
  super();
41
41
  this.connection = connection;
42
+ this.rpc = new DiodeRPC(connection);
42
43
 
43
44
  // Convert publishedPorts to a Map with configurations
44
45
  this.publishedPorts = new Map();
45
46
 
46
- // Handle both array format and object format
47
- if (Array.isArray(publishedPorts)) {
47
+ // Initialize with the provided ports
48
+ if (publishedPorts) {
49
+ this.addPorts(publishedPorts);
50
+ }
51
+
52
+ this.startListening();
53
+ if (this.publishedPorts.size > 0) {
54
+ logger.info(`Publishing ports: ${Array.from(this.publishedPorts.keys())}`);
55
+ } else {
56
+ logger.info("No ports published initially");
57
+ }
58
+ }
59
+
60
+ // Add a single port with configuration
61
+ addPort(port, config = { mode: 'public', whitelist: [] }) {
62
+ const portNum = parseInt(port, 10);
63
+
64
+ // Normalize the configuration
65
+ const portConfig = {
66
+ mode: config.mode || 'public',
67
+ whitelist: Array.isArray(config.whitelist) ? config.whitelist : []
68
+ };
69
+
70
+ // Add to map
71
+ this.publishedPorts.set(portNum, portConfig);
72
+ logger.info(`Added published port ${portNum} with mode: ${portConfig.mode}`);
73
+
74
+ return true;
75
+ }
76
+
77
+ // Remove a published port
78
+ removePort(port) {
79
+ const portNum = parseInt(port, 10);
80
+
81
+ if (!this.publishedPorts.has(portNum)) {
82
+ logger.warn(`Port ${portNum} is not published`);
83
+ return false;
84
+ }
85
+
86
+ // Close any active connections for this port
87
+ // This could require tracking active connections by port
88
+ // For now, let's log about active connections
89
+ const activeConnections = Array.from(this.connection.connections.values())
90
+ .filter(conn => conn.port === portNum);
91
+
92
+ if (activeConnections.length > 0) {
93
+ logger.warn(`Removing port ${portNum} with ${activeConnections.length} active connections`);
94
+ // We could close these connections, but they'll be rejected naturally on next data transfer
95
+ }
96
+
97
+ this.publishedPorts.delete(portNum);
98
+ logger.info(`Removed published port ${portNum}`);
99
+
100
+ return true;
101
+ }
102
+
103
+ // Add multiple ports at once (from array or object)
104
+ addPorts(ports) {
105
+ if (Array.isArray(ports)) {
48
106
  // Legacy array format - treat all ports as public
49
- publishedPorts.forEach(port => {
50
- this.publishedPorts.set(port, { mode: 'public', whitelist: [] });
107
+ ports.forEach(port => {
108
+ this.addPort(port);
51
109
  });
52
- } else if (typeof publishedPorts === 'object' && publishedPorts !== null) {
110
+ } else if (typeof ports === 'object' && ports !== null) {
53
111
  // New object format with configurations
54
- Object.entries(publishedPorts).forEach(([port, config]) => {
55
- const portNum = parseInt(port, 10);
56
- // Ensure config is properly structured
57
- const portConfig = typeof config === 'object' && config !== null
58
- ? {
59
- mode: config.mode || 'public',
60
- whitelist: Array.isArray(config.whitelist) ? config.whitelist : []
61
- }
62
- : { mode: 'public', whitelist: [] };
63
-
64
- this.publishedPorts.set(portNum, portConfig);
112
+ Object.entries(ports).forEach(([port, config]) => {
113
+ this.addPort(port, config);
65
114
  });
66
115
  }
67
116
 
68
- this.startListening();
69
- logger.info(`Publishing ports: ${Array.from(this.publishedPorts.keys())}`);
70
- this.rpc = new DiodeRPC(connection);
71
- this.certPath = certPath;
117
+ return this;
118
+ }
119
+
120
+ // Get all published ports with their configurations
121
+ getPublishedPorts() {
122
+ return Object.fromEntries(this.publishedPorts.entries());
123
+ }
124
+
125
+ // Clear all published ports
126
+ clearPorts() {
127
+ const portCount = this.publishedPorts.size;
128
+ this.publishedPorts.clear();
129
+ logger.info(`Cleared ${portCount} published ports`);
130
+ return portCount;
72
131
  }
73
132
 
74
133
  startListening() {
@@ -112,7 +171,6 @@ class PublishPort extends EventEmitter {
112
171
  port = portString;
113
172
  } else {
114
173
  var [protocol, portStr] = portString.split(':');
115
- console.log(`Protocol: ${protocol}, Port: ${portStr}`);
116
174
  if (!portStr) {
117
175
  portStr = protocol;
118
176
  protocol = 'tcp';
@@ -141,11 +199,11 @@ class PublishPort extends EventEmitter {
141
199
 
142
200
  // Handle based on protocol
143
201
  if (protocol === 'tcp') {
144
- this.handleTCPConnection(sessionId, ref, port);
202
+ this.handleTCPConnection(sessionId, ref, port, deviceId);
145
203
  } else if (protocol === 'tls') {
146
- this.handleTLSConnection(sessionId, ref, port);
204
+ this.handleTLSConnection(sessionId, ref, port, deviceId);
147
205
  } else if (protocol === 'udp') {
148
- this.handleUDPConnection(sessionId, ref, port);
206
+ this.handleUDPConnection(sessionId, ref, port, deviceId);
149
207
  } else {
150
208
  logger.warn(`Unsupported protocol: ${protocol}`);
151
209
  this.rpc.sendError(sessionId, ref, `Unsupported protocol: ${protocol}`);
@@ -177,7 +235,7 @@ class PublishPort extends EventEmitter {
177
235
  }
178
236
  }
179
237
 
180
- handleTCPConnection(sessionId, ref, port) {
238
+ handleTCPConnection(sessionId, ref, port, deviceId) {
181
239
  // Create a TCP connection to the local service on the specified port
182
240
  const localSocket = net.connect({ port: port }, () => {
183
241
  logger.info(`Connected to local TCP service on port ${port}`);
@@ -189,17 +247,19 @@ class PublishPort extends EventEmitter {
189
247
  this.setupLocalSocketHandlers(localSocket, ref, 'tcp');
190
248
 
191
249
  // Store the local socket with the ref using connection's method
192
- this.connection.addConnection(ref, { socket: localSocket, protocol: 'tcp' });
250
+ this.connection.addConnection(ref, { socket: localSocket, protocol: 'tcp', port, deviceId });
193
251
  }
194
252
 
195
- handleTLSConnection(sessionId, ref, port) {
253
+ handleTLSConnection(sessionId, ref, port, deviceId) {
196
254
  // Create a DiodeSocket instance
197
255
  const diodeSocket = new DiodeSocket(ref, this.rpc);
198
256
 
257
+ certPem = this.connection.getDeviceCertificate();
258
+
199
259
  // TLS options with your server's certificate and key
200
260
  const tlsOptions = {
201
- cert: fs.readFileSync(this.certPath),
202
- key: fs.readFileSync(this.certPath),
261
+ cert: certPem,
262
+ key: certPem,
203
263
  rejectUnauthorized: false,
204
264
  ciphers: 'ECDHE-ECDSA-AES256-GCM-SHA384',
205
265
  ecdhCurve: 'secp256k1',
@@ -231,7 +291,6 @@ class PublishPort extends EventEmitter {
231
291
  });
232
292
 
233
293
  tlsSocket.on('close', () => {
234
- console.log('TLS Socket closed');
235
294
  this.connection.deleteConnection(ref);
236
295
  });
237
296
 
@@ -241,10 +300,12 @@ class PublishPort extends EventEmitter {
241
300
  tlsSocket,
242
301
  localSocket,
243
302
  protocol: 'tls',
303
+ port,
304
+ deviceId,
244
305
  });
245
306
  }
246
307
 
247
- handleUDPConnection(sessionId, ref, port) {
308
+ handleUDPConnection(sessionId, ref, port, deviceId) {
248
309
  // Create a UDP socket
249
310
  const localSocket = dgram.createSocket('udp4');
250
311
 
@@ -259,6 +320,8 @@ class PublishPort extends EventEmitter {
259
320
  socket: localSocket,
260
321
  protocol: 'udp',
261
322
  remoteInfo,
323
+ port,
324
+ deviceId
262
325
  });
263
326
 
264
327
  logger.info(`UDP connection set up on port ${port}`);
@@ -289,8 +352,26 @@ class PublishPort extends EventEmitter {
289
352
  const data = Buffer.from(dataRaw)//.slice(4);
290
353
 
291
354
  const connectionInfo = this.connection.getConnection(ref);
355
+ // Check if the port is still open and address is still in whitelist
292
356
  if (connectionInfo) {
293
- const { socket: localSocket, protocol, remoteInfo } = connectionInfo;
357
+ const { socket: localSocket, protocol, remoteInfo, port, deviceId } = connectionInfo;
358
+
359
+ if (!this.publishedPorts.has(port)) {
360
+ logger.warn(`Port ${port} is not published. Sending portclose.`);
361
+ this.rpc.portClose(ref);
362
+ this.connection.deleteConnection(ref);
363
+ return;
364
+ }
365
+
366
+ const portConfig = this.publishedPorts.get(port);
367
+ if (portConfig.mode === 'private' && Array.isArray(portConfig.whitelist)) {
368
+ if (!portConfig.whitelist.includes(deviceId)) {
369
+ logger.warn(`Device ${deviceId} is not whitelisted for port ${port}. Sending portclose.`);
370
+ this.rpc.portClose(ref);
371
+ this.connection.deleteConnection(ref);
372
+ return;
373
+ }
374
+ }
294
375
 
295
376
  if (protocol === 'udp') {
296
377
  // Send data to the local UDP service
package/utils.js CHANGED
@@ -80,19 +80,32 @@ function parseReason(reasonRaw) {
80
80
  }
81
81
  }
82
82
 
83
- function generateCert(path) {
84
- var kp = KEYUTIL.generateKeypair("EC", "secp256k1");
85
-
86
- var priv = KEYUTIL.getPEM(kp.prvKeyObj, "PKCS8PRV");
87
-
88
- pub = KEYUTIL.getPEM(kp.pubKeyObj, "PKCS8PUB");
89
-
83
+ function generateCert(privateKeyObj, publicKeyObj) {
84
+ // Generate a certificate valid for 1 month
85
+ function formatDate(date) {
86
+ const pad = n => n < 10 ? '0' + n : n;
87
+ return String(date.getUTCFullYear()).slice(2) +
88
+ pad(date.getUTCMonth() + 1) +
89
+ pad(date.getUTCDate()) +
90
+ pad(date.getUTCHours()) +
91
+ pad(date.getUTCMinutes()) +
92
+ pad(date.getUTCSeconds()) +
93
+ 'Z';
94
+ }
95
+
96
+ const now = new Date();
97
+ const notBefore = formatDate(new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000)); // 30 days before now
98
+ const notAfter = formatDate(new Date(now.getTime() + 30 * 24 * 60 * 60 * 1000)); // 30 days after now
99
+
100
+
90
101
  var x = new KJUR.asn1.x509.Certificate({
91
102
  version: 3,
92
- serial: { int: 4 },
103
+ serial: { int: Math.floor(Math.random() * 1000000) },
93
104
  issuer: { str: "/CN=device" },
105
+ notbefore: notBefore,
106
+ notafter: notAfter,
94
107
  subject: { str: "/CN=device" },
95
- sbjpubkey: kp.pubKeyObj,
108
+ sbjpubkey: publicKeyObj,
96
109
  ext: [
97
110
  { extname: "basicConstraints", cA: false },
98
111
  { extname: "keyUsage", critical: true, names: ["digitalSignature"] },
@@ -102,14 +115,52 @@ function generateCert(path) {
102
115
  }
103
116
  ],
104
117
  sigalg: "SHA256withECDSA",
105
- cakey: kp.prvKeyObj
118
+ cakey: privateKeyObj
106
119
  });
107
120
 
121
+ // Get PEM representations
122
+ const priv = KEYUTIL.getPEM(privateKeyObj, "PKCS8PRV");
123
+
124
+
125
+ // Return the certificate with private key
126
+ return priv + x.getPEM();
127
+ }
108
128
 
109
- const pemFile = priv + x.getPEM();
110
- ensureDirectoryExistence(path);
111
-
112
- fs.writeFileSync(path, pemFile , 'utf8');
129
+ function loadOrGenerateKeyPair(keyLocation) {
130
+ try {
131
+ ensureDirectoryExistence(keyLocation);
132
+
133
+ // Try to load existing keys
134
+ if (fs.existsSync(keyLocation)) {
135
+ logger.info(`Loading keys from ${keyLocation}`);
136
+ const keyData = JSON.parse(fs.readFileSync(keyLocation, 'utf8'));
137
+
138
+ // Convert the stored JSON back to keypair objects
139
+ const prvKeyObj = KEYUTIL.getKeyFromPlainPrivatePKCS8PEM(keyData.privateKey);
140
+ const pubKeyObj = KEYUTIL.getKey(keyData.publicKey);
141
+
142
+ return { prvKeyObj, pubKeyObj };
143
+ } else {
144
+ // Generate new keypair
145
+ logger.info(`Generating new key pair at ${keyLocation}`);
146
+ const kp = KEYUTIL.generateKeypair("EC", "secp256k1");
147
+
148
+ // Store the keys in a serializable format
149
+ const keyData = {
150
+ privateKey: KEYUTIL.getPEM(kp.prvKeyObj, "PKCS8PRV"),
151
+ publicKey: KEYUTIL.getPEM(kp.pubKeyObj, "PKCS8PUB"),
152
+ check: kp.prvKeyObj.prvKeyHex
153
+ };
154
+
155
+ // Save to file
156
+ fs.writeFileSync(keyLocation, JSON.stringify(keyData, null, 2), 'utf8');
157
+
158
+ return kp;
159
+ }
160
+ } catch (error) {
161
+ logger.error(`Error loading or generating key pair: ${error}`);
162
+ throw error;
163
+ }
113
164
  }
114
165
 
115
166
  function ensureDirectoryExistence(filePath) {
@@ -121,4 +172,12 @@ function ensureDirectoryExistence(filePath) {
121
172
  fs.mkdirSync(dirname);
122
173
  }
123
174
 
124
- module.exports = { makeReadable, parseRequestId, parseResponseType, parseReason, generateCert };
175
+ module.exports = {
176
+ makeReadable,
177
+ parseRequestId,
178
+ parseResponseType,
179
+ parseReason,
180
+ generateCert,
181
+ loadOrGenerateKeyPair,
182
+ ensureDirectoryExistence
183
+ };