diodejs 0.0.3 → 0.0.5

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
@@ -1,20 +1,19 @@
1
- # diode_js
1
+ # DiodeJs
2
2
 
3
3
  ## Overview
4
- `diode_js` is a JavaScript client for interacting with the Diode network. It provides functionalities to bind and publish ports, send RPC commands, and handle responses.
4
+ `diodejs` is 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
 
6
6
  ## Installation
7
7
  ```bash
8
8
  npm install diodejs
9
9
  ```
10
- ## Quick Start
11
10
 
12
- To get started, you need to generate a device certificate using OpenSSL. You can use this command:
11
+ ### Quick Start
13
12
 
14
- ```bash
15
- openssl ecparam -name secp256k1 -out secp256k1_params.pem
16
- openssl req -newkey ec:./secp256k1_params.pem -nodes -keyout device_certificate.pem -x509 -days 365 -out device_certificate.pem -subj "/CN=device"
17
- ```
13
+ If you want to enable logs, set environment variable LOG to true.
14
+ If you want to enable debug logs, set environment variable DEBUG to true.
15
+
16
+ Can also use .env files
18
17
 
19
18
  ### Test RPC
20
19
 
@@ -96,3 +95,65 @@ async function main() {
96
95
  main();
97
96
 
98
97
  ```
98
+
99
+ ## Reference
100
+
101
+ ### Classes and Methods
102
+
103
+ #### `DiodeConnection`
104
+
105
+ - **Constructor**: `new DiodeConnection(host, port, certPath)`
106
+ - `host` (string): The host address of the Diode server.
107
+ - `port` (number): The port number of the Diode server.
108
+ - `certPath` (string)(default: ./cert/device_certificate.pem): The path to the device certificate. If doesn't exist, generates automaticly.
109
+
110
+ - **Methods**:
111
+ - `connect()`: Connects to the Diode server. Returns a promise.
112
+ - `sendCommand(commandArray)`: Sends a command to the Diode server. Returns a promise.
113
+ - `sendCommandWithSessionId(commandArray, sessionId)`: Sends a command with a session ID. Returns a promise.
114
+ - `getEthereumAddress()`: Returns the Ethereum address derived from the device certificate.
115
+ - `getServerEthereumAddress()`: Returns the Ethereum address of the server.
116
+ - `createTicketCommand()`: Creates a ticket command for authentication. Returns a promise.
117
+ - `close()`: Closes the connection to the Diode server.
118
+
119
+ #### `DiodeRPC`
120
+
121
+ - **Constructor**: `new DiodeRPC(connection)`
122
+ - `connection` (DiodeConnection): An instance of `DiodeConnection`.
123
+
124
+ - **Methods**:
125
+ - `getBlockPeak()`: Retrieves the current block peak. Returns a promise.
126
+ - `getBlockHeader(index)`: Retrieves the block header for a given index. Returns a promise.
127
+ - `getBlock(index)`: Retrieves the block for a given index. Returns a promise.
128
+ - `ping()`: Sends a ping command. Returns a promise.
129
+ - `portOpen(deviceId, port, flags)`: Opens a port on the device. Returns a promise.
130
+ - `portSend(ref, data)`: Sends data to the device. Returns a promise.
131
+ - `portClose(ref)`: Closes a port on the device. Returns a promise.
132
+ - `sendError(sessionId, ref, error)`: Sends an error response. Returns a promise.
133
+ - `sendResponse(sessionId, ref, response)`: Sends a response. Returns a promise.
134
+ - `getEpoch()`: Retrieves the current epoch. Returns a promise.
135
+ - `parseTimestamp(blockHeader)`: Parses the timestamp from a block header. Returns a number.
136
+
137
+ #### `BindPort`
138
+
139
+ - **Constructor**: `new BindPort(connection, localPort, targetPort, deviceIdHex)`
140
+ - `connection` (DiodeConnection): An instance of `DiodeConnection`.
141
+ - `localPort` (number): The local port to bind.
142
+ - `targetPort` (number): The target port on the device.
143
+ - `deviceIdHex` (string): The device ID in hexadecimal format.
144
+
145
+ - **Methods**:
146
+ - `bind()`: Binds the local port to the target port on the device.
147
+
148
+ #### `PublishPort`
149
+
150
+ - **Constructor**: `new PublishPort(connection, publishedPorts, certPath)`
151
+ - `connection` (DiodeConnection): An instance of `DiodeConnection`.
152
+ - `publishedPorts` (array): An array of ports to publish.
153
+ - `certPath` (string): The path to the device certificate.
154
+
155
+ - **Methods**:
156
+ - `startListening()`: Starts listening for unsolicited messages.
157
+ - `handlePortOpen(sessionIdRaw, messageContent)`: Handles port open requests.
158
+ - `handlePortSend(sessionIdRaw, messageContent)`: Handles port send requests.
159
+ - `handlePortClose(sessionIdRaw, messageContent)`: Handles port close requests.
package/bindPort.js CHANGED
@@ -1,6 +1,7 @@
1
1
  const net = require('net');
2
2
  const { Buffer } = require('buffer');
3
3
  const DiodeRPC = require('./rpc');
4
+ const logger = require('./logger');
4
5
 
5
6
  class BindPort {
6
7
  constructor(connection, localPort, targetPort,deviceIdHex) {
@@ -35,7 +36,7 @@ class BindPort {
35
36
  if (clientSocket) {
36
37
  clientSocket.write(data);
37
38
  } else {
38
- console.warn(`No client socket found for ref: ${dataRef.toString('hex')}`);
39
+ logger.warn(`No client socket found for ref: ${dataRef.toString('hex')}`);
39
40
  }
40
41
  } else if (messageType === 'portclose') {
41
42
  const refRaw = messageContent[1];
@@ -47,24 +48,30 @@ class BindPort {
47
48
  if (clientSocket) {
48
49
  clientSocket.end();
49
50
  clientSockets.delete(dataRef.toString('hex'));
50
- console.log(`Port closed for ref: ${dataRef.toString('hex')}`);
51
+ logger.info(`Port closed for ref: ${dataRef.toString('hex')}`);
51
52
  }
52
53
  } else {
53
- console.warn(`Unknown unsolicited message type: ${messageType}`);
54
+ logger.warn(`Unknown unsolicited message type: ${messageType}`);
54
55
  }
55
56
  });
56
57
 
57
58
  // Set up local server
58
59
  const server = net.createServer(async (clientSocket) => {
59
- console.log('Client connected to local server');
60
+ logger.info('Client connected to local server');
60
61
 
61
62
  // Open a new port on the device for this client
62
63
  let ref;
63
64
  try {
64
65
  ref = await rpc.portOpen(deviceId, this.targetPort, 'rw');
65
- console.log(`Port opened on device with ref: ${ref.toString('hex')} for client`);
66
+ if (!ref) {
67
+ logger.error('Error opening port on device');
68
+ clientSocket.destroy();
69
+ return
70
+ } else {
71
+ logger.info(`Port opened on device with ref: ${ref.toString('hex')} for client`);
72
+ }
66
73
  } catch (error) {
67
- console.error('Error opening port on device:', error);
74
+ logger.error(`Error opening port on device: ${error}`);
68
75
  clientSocket.destroy();
69
76
  return;
70
77
  }
@@ -77,41 +84,46 @@ class BindPort {
77
84
  try {
78
85
  await rpc.portSend(ref, data);
79
86
  } catch (error) {
80
- console.error('Error sending data to device:', error);
87
+ logger.error(`Error sending data to device: ${error}`);
81
88
  clientSocket.destroy();
82
89
  }
83
90
  });
84
91
 
85
92
  // Handle client socket closure
86
93
  clientSocket.on('end', async () => {
87
- console.log('Client disconnected');
88
- clientSockets.delete(ref.toString('hex'));
89
- try {
90
- console.log(`Port closed on device for ref: ${ref.toString('hex')}`);
91
- } catch (error) {
92
- console.error('Error closing port on device:', error);
94
+ logger.info('Client disconnected');
95
+ if (ref && clientSockets.has(ref.toString('hex'))) {
96
+ try {
97
+ await rpc.portClose(ref);
98
+ logger.info(`Port closed on device for ref: ${ref.toString('hex')}`);
99
+ clientSockets.delete(ref.toString('hex'));
100
+ } catch (error) {
101
+ logger.error(`Error closing port on device: ${error}`);
102
+ }
103
+ } else {
104
+ logger.warn('Ref is invalid or no longer in clientSockets.');
93
105
  }
94
106
  });
95
107
 
96
108
  // Handle client socket errors
97
109
  clientSocket.on('error', (err) => {
98
- console.error('Client socket error:', err);
110
+ logger.error('Client socket error:', err);
99
111
  });
100
112
  });
101
113
 
102
114
  server.listen(this.localPort, () => {
103
- console.log(`Local server listening on port ${this.localPort}`);
115
+ logger.info(`Local server listening on port ${this.localPort}`);
104
116
  });
105
117
 
106
118
  // Handle device disconnect
107
119
  this.connection.on('end', () => {
108
- console.log('Disconnected from Diode.io server');
120
+ logger.info('Disconnected from Diode.io server');
109
121
  server.close();
110
122
  });
111
123
 
112
124
  // Handle connection errors
113
125
  this.connection.on('error', (err) => {
114
- console.error('Connection error:', err);
126
+ logger.error(`Connection error: ${err}`);
115
127
  server.close();
116
128
  });
117
129
  }
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 } = require('./utils');
6
+ const { makeReadable, parseRequestId, parseResponseType, parseReason, generateCert } = require('./utils');
7
7
  const { Buffer } = require('buffer'); // Import Buffer
8
8
  const asn1 = require('asn1.js');
9
9
  const secp256k1 = require('secp256k1');
@@ -11,8 +11,9 @@ const ethUtil = require('ethereumjs-util');
11
11
  const crypto = require('crypto');
12
12
  const DiodeRPC = require('./rpc');
13
13
  const abi = require('ethereumjs-abi');
14
+ const logger = require('./logger');
14
15
  class DiodeConnection extends EventEmitter {
15
- constructor(host, port, certPath) {
16
+ constructor(host, port, certPath = './cert/device_certificate.pem') {
16
17
  super();
17
18
  this.host = host;
18
19
  this.port = port;
@@ -25,6 +26,13 @@ class DiodeConnection extends EventEmitter {
25
26
  // Add buffer to handle partial data
26
27
  this.receiveBuffer = Buffer.alloc(0);
27
28
  this.RPC = new DiodeRPC(this);
29
+ this.isReconnecting = false;
30
+ this.connectPromise = null;
31
+
32
+ // Check if certPath exists, if not generate the certificate
33
+ if (!fs.existsSync(this.certPath)) {
34
+ generateCert(this.certPath);
35
+ }
28
36
  }
29
37
 
30
38
  connect() {
@@ -40,41 +48,63 @@ class DiodeConnection extends EventEmitter {
40
48
  };
41
49
 
42
50
  this.socket = tls.connect(this.port, this.host, options, async () => {
43
- console.log('Connected to Diode.io server');
51
+ logger.info('Connected to Diode.io server');
44
52
  // Set keep-alive to prevent connection timeout forever
45
53
  this.socket.setKeepAlive(true, 0);
46
54
 
47
55
  // Send the ticketv2 command
48
56
  try {
49
57
  const ticketCommand = await this.createTicketCommand();
50
- const response = await this.sendCommand(ticketCommand);
51
- console.log('Ticket accepted:', response);
58
+ const response = await this.sendCommand(ticketCommand).catch(reject);
59
+ logger.info(`Ticket accepted: ${makeReadable(response)}`);
52
60
  resolve();
53
61
  } catch (error) {
54
- console.error('Error sending ticket:', error);
62
+ logger.error(`Error sending ticket: ${error}`);
55
63
  reject(error);
56
64
  }
57
65
  });
58
66
 
59
67
  this.socket.on('data', (data) => {
68
+ logger.debug(`Received data: ${data.toString('hex')}`);
60
69
  try {
61
70
  this._handleData(data);
62
71
  } catch (error) {
63
- console.error('Error handling data:', error);
72
+ logger.error(`Error handling data: ${error}`);
64
73
  }
65
74
  });
66
75
  this.socket.on('error', (err) => {
67
- console.error('Connection error:', err);
76
+ logger.error(`Connection error: ${err}`);
68
77
  reject(err);
69
78
  });
70
- this.socket.on('end', () => console.log('Disconnected from server'));
79
+ this.socket.on('end', () => logger.info('Disconnected from server'));
71
80
  });
72
81
  }
73
82
 
83
+ _ensureConnected() {
84
+ if (this.socket && !this.socket.destroyed) {
85
+ return Promise.resolve();
86
+ }
87
+ if (this.connectPromise) {
88
+ return this.connectPromise;
89
+ }
90
+ this.isReconnecting = true;
91
+ this.connectPromise = this.connect()
92
+ .then(() => {
93
+ this.isReconnecting = false;
94
+ this.connectPromise = null;
95
+ })
96
+ .catch((err) => {
97
+ this.isReconnecting = false;
98
+ this.connectPromise = null;
99
+ throw err;
100
+ });
101
+ return this.connectPromise;
102
+ }
103
+
74
104
  _handleData(data) {
75
105
  // Append new data to the receive buffer
76
106
  this.receiveBuffer = Buffer.concat([this.receiveBuffer, data]);
77
- console.log('Received data:', data.toString('hex'));
107
+ logger.debug(`Received data: ${data.toString('hex')}`);
78
108
 
79
109
  let offset = 0;
80
110
  while (offset + 2 <= this.receiveBuffer.length) {
@@ -92,7 +122,7 @@ class DiodeConnection extends EventEmitter {
92
122
 
93
123
  try {
94
124
  const decodedMessage = RLP.decode(Uint8Array.from(messageBuffer));
95
- console.log('Decoded message:', makeReadable(decodedMessage));
125
+ logger.debug(`Decoded message: ${makeReadable(decodedMessage)}`);
96
126
 
97
127
  if (Array.isArray(decodedMessage) && decodedMessage.length > 1) {
98
128
  const requestIdRaw = decodedMessage[0];
@@ -102,8 +132,8 @@ class DiodeConnection extends EventEmitter {
102
132
  const requestId = parseRequestId(requestIdRaw);
103
133
 
104
134
  // Debug statements
105
- console.log('requestIdRaw:', requestIdRaw);
106
- console.log('Parsed requestId:', requestId);
135
+ logger.debug(`requestIdRaw: ${requestIdRaw}`);
136
+ logger.debug(`Parsed requestId: ${requestId}`);
107
137
 
108
138
  if (requestId !== null && this.pendingRequests.has(requestId)) {
109
139
  // This is a response to a pending request
@@ -111,16 +141,14 @@ class DiodeConnection extends EventEmitter {
111
141
  const responseRaw = responseData[0];
112
142
 
113
143
  // Debug statements
114
- console.log('responseTypeRaw:', responseTypeRaw);
115
- console.log('Type of responseTypeRaw:', typeof responseTypeRaw);
116
- console.log('Instance of responseTypeRaw:', responseTypeRaw instanceof Uint8Array);
117
- console.log('Is Array:', Array.isArray(responseTypeRaw));
144
+ logger.debug(`responseTypeRaw: ${responseTypeRaw}`);
145
+ logger.debug(`Type of responseTypeRaw: ${typeof responseTypeRaw}`);
118
146
 
119
147
  // Parse responseType
120
148
  const responseType = parseResponseType(responseTypeRaw);
121
149
 
122
- console.log(`Received response for requestId: ${requestId}`);
123
- console.log(`Response Type: '${responseType}'`);
150
+ logger.debug(`Received response for requestId: ${requestId}`);
151
+ logger.debug(`Response Type: '${responseType}'`);
124
152
 
125
153
  const { resolve, reject } = this.pendingRequests.get(requestId);
126
154
  try{
@@ -137,29 +165,29 @@ class DiodeConnection extends EventEmitter {
137
165
  } else if (responseType === 'error') {
138
166
  if (responseData.length > 1) {
139
167
  const reason = parseReason(responseData[1]);
140
- reject(new Error(reason));
168
+ reject(reason);
141
169
  } else {
142
170
  const reason = parseReason(responseData[0]);
143
- reject(new Error(reason));
171
+ reject(reason);
144
172
  }
145
173
  } else {
146
174
  resolve(responseData);
147
175
  }
148
176
  } catch (error) {
149
- console.error('Error handling response:', error);
177
+ logger.error(`Error handling response: ${error}`);
150
178
  }
151
179
  this.pendingRequests.delete(requestId);
152
180
  } else {
153
181
  // This is an unsolicited message
154
- console.log('Received unsolicited message:', decodedMessage);
182
+ logger.debug(`Received unsolicited message: ${makeReadable(decodedMessage)}`);
155
183
  this.emit('unsolicited', decodedMessage);
156
184
  }
157
185
  } else {
158
186
  // Invalid message format
159
- console.error('Invalid message format:', decodedMessage);
187
+ logger.error(`Invalid message format: ${makeReadable(decodedMessage)}`);
160
188
  }
161
189
  } catch (error) {
162
- console.error('Error decoding message:', error);
190
+ logger.error(`Error decoding message: ${error}`);
163
191
  }
164
192
  }
165
193
 
@@ -190,67 +218,55 @@ class DiodeConnection extends EventEmitter {
190
218
 
191
219
  sendCommand(commandArray) {
192
220
  return new Promise((resolve, reject) => {
193
- //check if connection is alive
194
- if (!this.socket || this.socket.destroyed) {
195
- //reconnect
196
- this.connect().then(() => {
197
- this.sendCommand(commandArray).then(resolve).catch(reject);
198
- }).catch(reject);
199
- return;
200
- }
201
- const requestId = this._getNextRequestId();
202
- // Build the message as [requestId, [commandArray]]
203
- const commandWithId = [requestId, commandArray];
204
-
205
- // Store the promise callbacks to resolve/reject later
206
- this.pendingRequests.set(requestId, { resolve, reject });
207
-
208
- const commandBuffer = RLP.encode(commandWithId);
209
- const byteLength = Buffer.byteLength(commandBuffer);
210
-
211
- // Create a 2-byte length buffer
212
- const lengthBuffer = Buffer.alloc(2);
213
- lengthBuffer.writeUInt16BE(byteLength, 0);
214
-
215
- const message = Buffer.concat([lengthBuffer, commandBuffer]);
216
-
217
- console.log(`Sending command with requestId ${requestId}:`, commandArray);
218
- console.log('Command buffer:', message.toString('hex'));
219
-
220
- this.socket.write(message);
221
+ this._ensureConnected().then(() => {
222
+ const requestId = this._getNextRequestId();
223
+ // Build the message as [requestId, [commandArray]]
224
+ const commandWithId = [requestId, commandArray];
225
+
226
+ // Store the promise callbacks to resolve/reject later
227
+ this.pendingRequests.set(requestId, { resolve, reject });
228
+
229
+ const commandBuffer = RLP.encode(commandWithId);
230
+ const byteLength = Buffer.byteLength(commandBuffer);
231
+
232
+ // Create a 2-byte length buffer
233
+ const lengthBuffer = Buffer.alloc(2);
234
+ lengthBuffer.writeUInt16BE(byteLength, 0);
235
+
236
+ const message = Buffer.concat([lengthBuffer, commandBuffer]);
237
+
238
+ logger.debug(`Sending command with requestId ${requestId}: ${commandArray}`);
239
+ logger.debug(`Command buffer: ${message.toString('hex')}`);
240
+
241
+ this.socket.write(message);
242
+ }).catch(reject);
221
243
  });
222
244
  }
223
245
 
224
246
  sendCommandWithSessionId(commandArray, sessionId) {
225
247
  return new Promise((resolve, reject) => {
226
- //check if connection is alive
227
- if (!this.socket || this.socket.destroyed) {
228
- //reconnect
229
- this.connect().then(() => {
230
- this.sendCommand(commandArray).then(resolve).catch(reject);
231
- }).catch(reject);
232
- return;
233
- }
234
- const requestId = sessionId;
235
- // Build the message as [requestId, [commandArray]]
236
- const commandWithId = [requestId, commandArray];
237
-
238
- // Store the promise callbacks to resolve/reject later
239
- this.pendingRequests.set(requestId, { resolve, reject });
240
-
241
- const commandBuffer = RLP.encode(commandWithId);
242
- const byteLength = Buffer.byteLength(commandBuffer);
243
-
244
- // Create a 2-byte length buffer
245
- const lengthBuffer = Buffer.alloc(2);
246
- lengthBuffer.writeUInt16BE(byteLength, 0);
247
-
248
- const message = Buffer.concat([lengthBuffer, commandBuffer]);
249
-
250
- console.log(`Sending command with requestId ${requestId}:`, commandArray);
251
- console.log('Command buffer:', message.toString('hex'));
252
-
253
- this.socket.write(message);
248
+ this._ensureConnected().then(() => {
249
+ const requestId = sessionId;
250
+ // Build the message as [requestId, [commandArray]]
251
+ const commandWithId = [requestId, commandArray];
252
+
253
+ // Store the promise callbacks to resolve/reject later
254
+ this.pendingRequests.set(requestId, { resolve, reject });
255
+
256
+ const commandBuffer = RLP.encode(commandWithId);
257
+ const byteLength = Buffer.byteLength(commandBuffer);
258
+
259
+ // Create a 2-byte length buffer
260
+ const lengthBuffer = Buffer.alloc(2);
261
+ lengthBuffer.writeUInt16BE(byteLength, 0);
262
+
263
+ const message = Buffer.concat([lengthBuffer, commandBuffer]);
264
+
265
+ logger.debug(`Sending command with requestId ${requestId}: ${commandArray}`);
266
+ logger.debug(`Command buffer: ${message.toString('hex')}`);
267
+
268
+ this.socket.write(message);
269
+ }).catch(reject);
254
270
  });
255
271
  }
256
272
 
@@ -300,7 +316,7 @@ class DiodeConnection extends EventEmitter {
300
316
 
301
317
  const ecPrivateKey = ECPrivateKeyASN.decode(privateKeyOctetString, 'der');
302
318
  privateKeyBytes = ecPrivateKey.privateKey;
303
- console.log('Private key bytes:', privateKeyBytes.toString('hex'));
319
+ logger.debug(`Private key bytes: ${privateKeyBytes.toString('hex')}`);
304
320
  } else {
305
321
  throw new Error('Unsupported key format. Expected EC PRIVATE KEY or PRIVATE KEY in PEM format.');
306
322
  }
@@ -317,10 +333,10 @@ class DiodeConnection extends EventEmitter {
317
333
  const addressBuffer = ethUtil.pubToAddress(publicKeyBuffer, true);
318
334
  const address = '0x' + addressBuffer.toString('hex');
319
335
 
320
- console.log('Ethereum address:', address);
336
+ logger.info(`Ethereum address: ${address}`);
321
337
  return address;
322
338
  } catch (error) {
323
- console.error('Error extracting Ethereum address:', error);
339
+ logger.error(`Error extracting Ethereum address: ${error}`);
324
340
  throw error;
325
341
  }
326
342
  }
@@ -336,15 +352,15 @@ class DiodeConnection extends EventEmitter {
336
352
  ? serverCert.pubkey
337
353
  : Buffer.from(serverCert.pubkey);
338
354
 
339
- console.log('Public key Server:', publicKeyBuffer.toString('hex'));
355
+ logger.debug(`Public key Server: ${publicKeyBuffer.toString('hex')}`);
340
356
 
341
357
  const addressBuffer = ethUtil.pubToAddress(publicKeyBuffer, true);
342
358
  const address = '0x' + addressBuffer.toString('hex');
343
359
 
344
- console.log('Server Ethereum address:', address);
360
+ logger.info(`Server Ethereum address: ${address}`);
345
361
  return address;
346
362
  } catch (error) {
347
- console.error('Error extracting server Ethereum address:', error);
363
+ logger.error(`Error extracting server Ethereum address: ${error}`);
348
364
  throw error;
349
365
  }
350
366
  }
@@ -448,7 +464,7 @@ class DiodeConnection extends EventEmitter {
448
464
 
449
465
  return privateKeyBytes;
450
466
  } catch (error) {
451
- console.error('Error extracting Ethereum address:', error);
467
+ logger.error(`Error extracting Ethereum address: ${error}`);
452
468
  throw error;
453
469
  }
454
470
  }
@@ -475,17 +491,17 @@ class DiodeConnection extends EventEmitter {
475
491
  // Convert each element in dataToSign to bytes32 and concatenate them
476
492
  const encodedData = Buffer.concat(dataToSign.map(item => abi.rawEncode(['bytes32'], [item])));
477
493
 
478
- console.log('Encoded data:', encodedData.toString('hex'));
494
+ logger.debug(`Encoded data: ${encodedData.toString('hex')}`);
479
495
 
480
- console.log('Data to sign:', makeReadable(dataToSign));
496
+ logger.debug(`Data to sign: ${makeReadable(dataToSign)}`);
481
497
 
482
498
 
483
499
  // Sign the data
484
500
  const privateKey = this.getPrivateKey();
485
501
  const msgHash = ethUtil.keccak256(encodedData);
486
- console.log('Message hash:', msgHash.toString('hex'));
502
+ logger.debug(`Message hash: ${msgHash.toString('hex')}`);
487
503
  const signature = secp256k1.ecdsaSign(msgHash, privateKey);
488
- console.log('Signature:', signature);
504
+ logger.debug(`Signature: ${signature.signature.toString('hex')}`);
489
505
 
490
506
  const signatureBuffer = Buffer.concat([
491
507
  ethUtil.toBuffer([signature.recid]),
@@ -519,7 +535,7 @@ class DiodeConnection extends EventEmitter {
519
535
  localAddress,
520
536
  epoch
521
537
  );
522
- console.log('Signature hex:', signature.toString('hex'));
538
+ logger.debug(`Signature hex: ${signature.toString('hex')}`);
523
539
 
524
540
 
525
541
  // Construct the ticket command
@@ -8,7 +8,7 @@ async function main() {
8
8
  const connection = new DiodeConnection(host, port, certPath);
9
9
  await connection.connect();
10
10
 
11
- const portForward = new BindPort(connection, 3002, 80, "5365baf29cb7ab58de588dfc448913cb609283e2");
11
+ const portForward = new BindPort(connection, 3002, 8080, "5365baf29cb7ab58de588dfc448913cb609283e2");
12
12
  portForward.bind();
13
13
 
14
14
  }
package/index.js CHANGED
@@ -4,4 +4,5 @@ const DiodeRPC = require('./rpc');
4
4
  const BindPort = require('./bindPort');
5
5
  const PublishPort = require('./publishPort');
6
6
  const makeReadable = require('./utils').makeReadable;
7
- module.exports = { DiodeConnection, DiodeRPC, BindPort , PublishPort, makeReadable };
7
+ const logger = require('./logger');
8
+ module.exports = { DiodeConnection, DiodeRPC, BindPort , PublishPort, makeReadable, logger };
package/logger.js ADDED
@@ -0,0 +1,28 @@
1
+ const setupLogger = require('dera-logger');
2
+ require('dotenv').config();
3
+ const isDebug = (process.env.DEBUG === 'true'); // Simple debug flag
4
+ const isLogEnabled = (process.env.LOG === 'true'); // Simple log flag
5
+
6
+ const options = {
7
+ logDirectory: 'logs',
8
+ timestampFormat: 'HH:mm:ss',
9
+ fileDatePattern: 'YYYY-MM-DD',
10
+ zippedArchive: false,
11
+ maxLogFileSize: null,
12
+ maxFiles: '14d',
13
+ addConsoleInNonProduction: true,
14
+ transports: [
15
+ { filename: 'combined', level: 'silly', source: 'app' },
16
+ { filename: 'error', level: 'warn', source: 'app' }
17
+ ]
18
+ };
19
+
20
+ const logger = setupLogger(options);
21
+
22
+ // Wrap logger calls to respect debug mode
23
+ module.exports = {
24
+ debug: (...args) => { if (isDebug && isLogEnabled) logger.debug(...args, 'app'); },
25
+ info: (...args) => { if (isLogEnabled) logger.info(...args, 'app'); },
26
+ warn: (...args) => { if (isLogEnabled) logger.warn(...args, 'app'); },
27
+ error: (...args) => { if (isLogEnabled) logger.error(...args, 'app'); },
28
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "diodejs",
3
- "version": "0.0.3",
3
+ "version": "0.0.5",
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": {
@@ -11,18 +11,20 @@
11
11
  "dependencies": {
12
12
  "@ethereumjs/rlp": "^5.0.2",
13
13
  "asn1.js": "^5.4.1",
14
- "axios": "^1.6.8",
15
14
  "buffer": "^6.0.3",
16
15
  "crypto": "^1.0.1",
16
+ "dera-logger": "^2.0.0",
17
17
  "dgram": "^1.0.1",
18
+ "dotenv": "^16.4.7",
18
19
  "ethereumjs-abi": "^0.6.8",
19
20
  "ethereumjs-util": "^7.1.5",
20
21
  "ethers": "^6.13.2",
21
22
  "fs": "^0.0.1-security",
23
+ "jsrsasign": "^11.1.0",
22
24
  "net": "^1.0.2",
23
25
  "node-fetch": "^2.7.0",
24
26
  "rlp": "^3.0.0",
25
- "secp256k1": "^5.0.0",
27
+ "secp256k1": "^5.0.1",
26
28
  "tls": "^0.0.1"
27
29
  }
28
30
  }
package/publishPort.js CHANGED
@@ -8,6 +8,8 @@ const { Buffer } = require('buffer');
8
8
  const EventEmitter = require('events');
9
9
  const { Duplex } = require('stream');
10
10
  const DiodeRPC = require('./rpc');
11
+ const { makeReadable } = require('./utils');
12
+ const logger = require('./logger');
11
13
 
12
14
  class DiodeSocket extends Duplex {
13
15
  constructor(ref, rpc) {
@@ -37,7 +39,7 @@ class PublishPort extends EventEmitter {
37
39
  constructor(connection, publishedPorts, certPath) {
38
40
  super();
39
41
  this.connection = connection;
40
- this.publishedPorts = publishedPorts; // Array of ports to publish
42
+ this.publishedPorts = new Set(publishedPorts); // Convert array to a Set
41
43
  this.connections = new Map(); // Map to store active connections
42
44
  this.startListening();
43
45
  this.rpc = new DiodeRPC(connection);
@@ -58,7 +60,7 @@ class PublishPort extends EventEmitter {
58
60
  } else if (messageType === 'portclose') {
59
61
  this.handlePortClose(sessionIdRaw, messageContent);
60
62
  } else {
61
- console.warn(`Unknown unsolicited message type: ${messageType}`);
63
+ logger.warn(`Unknown unsolicited message type: ${messageType}`);
62
64
  }
63
65
  });
64
66
  }
@@ -70,19 +72,30 @@ class PublishPort extends EventEmitter {
70
72
  const deviceIdRaw = messageContent[3];
71
73
 
72
74
  const sessionId = Buffer.from(sessionIdRaw);
73
- const portString = Buffer.from(portStringRaw).toString('utf8');
75
+ const portString = makeReadable(portStringRaw);
74
76
  const ref = Buffer.from(refRaw);
75
77
  const deviceId = Buffer.from(deviceIdRaw).toString('hex');
76
78
 
77
- console.log(`Received portopen request for portString ${portString} with ref ${ref.toString('hex')} from device ${deviceId}`);
79
+ logger.info(`Received portopen request for portString ${portString} with ref ${ref.toString('hex')} from device ${deviceId}`);
78
80
 
79
81
  // Extract protocol and port number from portString
80
- const [protocol, portStr] = portString.split(':');
81
- const port = parseInt(portStr, 10);
82
+ var protocol = 'tcp';
83
+ var port = 0;
84
+ if (typeof portString == 'number') {
85
+ port = portString;
86
+ } else {
87
+ var [protocol, portStr] = portString.split(':');
88
+ console.log(`Protocol: ${protocol}, Port: ${portStr}`);
89
+ if (!portStr) {
90
+ portStr = protocol;
91
+ protocol = 'tcp';
92
+ }
93
+ port = parseInt(portStr, 10);
94
+ }
82
95
 
83
96
  // Check if the port is published
84
- if (!this.publishedPorts.includes(port)) {
85
- console.warn(`Port ${port} is not published. Rejecting request.`);
97
+ if (!this.publishedPorts.has(port)) { // Use .has() instead of .includes()
98
+ logger.warn(`Port ${port} is not published. Rejecting request.`);
86
99
  // Send error response
87
100
  this.rpc.sendError(sessionId, ref, 'Port is not published');
88
101
  return;
@@ -96,7 +109,7 @@ class PublishPort extends EventEmitter {
96
109
  } else if (protocol === 'udp') {
97
110
  this.handleUDPConnection(sessionId, ref, port);
98
111
  } else {
99
- console.warn(`Unsupported protocol: ${protocol}`);
112
+ logger.warn(`Unsupported protocol: ${protocol}`);
100
113
  this.rpc.sendError(sessionId, ref, `Unsupported protocol: ${protocol}`);
101
114
  }
102
115
  }
@@ -111,14 +124,14 @@ class PublishPort extends EventEmitter {
111
124
  });
112
125
 
113
126
  localSocket.on('end', () => {
114
- console.log(`Local service disconnected`);
127
+ logger.info(`Local service disconnected`);
115
128
  // Send portclose message to Diode
116
129
  this.rpc.portClose(ref);
117
130
  this.connections.delete(ref.toString('hex'));
118
131
  });
119
132
 
120
133
  localSocket.on('error', (err) => {
121
- console.error(`Error with local service:`, err);
134
+ logger.error(`Error with local service: ${err}`);
122
135
  // Send portclose message to Diode
123
136
  this.rpc.portClose(ref);
124
137
  this.connections.delete(ref.toString('hex'));
@@ -129,7 +142,7 @@ class PublishPort extends EventEmitter {
129
142
  handleTCPConnection(sessionId, ref, port) {
130
143
  // Create a TCP connection to the local service on the specified port
131
144
  const localSocket = net.connect({ port: port }, () => {
132
- console.log(`Connected to local TCP service on port ${port}`);
145
+ logger.info(`Connected to local TCP service on port ${port}`);
133
146
  // Send success response
134
147
  this.rpc.sendResponse(sessionId, ref, 'ok');
135
148
  });
@@ -164,7 +177,7 @@ class PublishPort extends EventEmitter {
164
177
 
165
178
  // Connect to the local service (TCP or TLS as needed)
166
179
  const localSocket = net.connect({ port: port }, () => {
167
- console.log(`Connected to local TCP service on port ${port}`);
180
+ logger.info(`Connected to local TCP service on port ${port}`);
168
181
  // Send success response
169
182
  this.rpc.sendResponse(sessionId, ref, 'ok');
170
183
  });
@@ -174,7 +187,7 @@ class PublishPort extends EventEmitter {
174
187
 
175
188
  // Handle errors and cleanup
176
189
  tlsSocket.on('error', (err) => {
177
- console.error('TLS Socket error:', err);
190
+ logger.error(`TLS Socket error: ${err}`);
178
191
  this.rpc.portClose(ref);
179
192
  this.connections.delete(ref.toString('hex'));
180
193
  });
@@ -210,6 +223,8 @@ class PublishPort extends EventEmitter {
210
223
  remoteInfo,
211
224
  });
212
225
 
226
+ logger.info(`UDP connection set up on port ${port}`);
227
+
213
228
  // Handle messages from the local UDP service
214
229
  localSocket.on('message', (msg, rinfo) => {
215
230
  //need to add 4 bytes of data length to the beginning of the message but it's Big Endian
@@ -221,7 +236,7 @@ class PublishPort extends EventEmitter {
221
236
  });
222
237
 
223
238
  localSocket.on('error', (err) => {
224
- console.error(`UDP Socket error:`, err);
239
+ logger.error(`UDP Socket error: ${err}`);
225
240
  this.rpc.portClose(ref);
226
241
  this.connections.delete(ref.toString('hex'));
227
242
  });
@@ -233,7 +248,7 @@ class PublishPort extends EventEmitter {
233
248
 
234
249
  const sessionId = Buffer.from(sessionIdRaw);
235
250
  const ref = Buffer.from(refRaw);
236
- const data = Buffer.from(dataRaw).slice(4);
251
+ const data = Buffer.from(dataRaw)//.slice(4);
237
252
 
238
253
  const connectionInfo = this.connections.get(ref.toString('hex'));
239
254
  if (connectionInfo) {
@@ -262,7 +277,7 @@ class PublishPort extends EventEmitter {
262
277
  diodeSocket.pushData(data);
263
278
  }
264
279
  } else {
265
- console.warn(`No local connection found for ref ${ref.toString('hex')}. Sending portclose.`);
280
+ logger.warn(`No local connection found for ref ${ref.toString('hex')}. Sending portclose.`);
266
281
  this.rpc.sendError(sessionId, ref, 'No local connection found');
267
282
  }
268
283
  }
@@ -272,7 +287,7 @@ class PublishPort extends EventEmitter {
272
287
  const sessionId = Buffer.from(sessionIdRaw);
273
288
  const ref = Buffer.from(refRaw);
274
289
 
275
- console.log(`Received portclose for ref ${ref.toString('hex')}`);
290
+ logger.info(`Received portclose for ref ${ref.toString('hex')}`);
276
291
 
277
292
  const connectionInfo = this.connections.get(ref.toString('hex'));
278
293
  if (connectionInfo) {
package/rpc.js CHANGED
@@ -1,5 +1,6 @@
1
1
  //rpc.js
2
2
  const { makeReadable, parseRequestId, parseResponseType, parseReason } = require('./utils');
3
+ const logger = require('./logger');
3
4
 
4
5
  class DiodeRPC {
5
6
  constructor(connection) {
@@ -24,36 +25,49 @@ class DiodeRPC {
24
25
  } else {
25
26
  throw new Error('Invalid block number format. response:', makeReadable(responseData));
26
27
  }
28
+ logger.debug(`Block number is: ${blockNumber}`);
27
29
  return blockNumber;
30
+ }).catch((error) => {
31
+ logger.error(`Error during get block peak: ${error}`);
32
+ return;
28
33
  });
29
34
  }
30
35
  getBlockHeader(index) {
31
36
  return this.connection.sendCommand(['getblockheader', index]).then((responseData) => {
32
37
  return responseData[0]; // block_header
38
+ }).catch((error) => {
39
+ logger.error(`Error during get block header: ${error}`);
40
+ return;
33
41
  });
34
42
  }
35
43
 
36
44
  getBlock(index) {
37
45
  return this.connection.sendCommand(['getblock', index]).then((responseData) => {
38
46
  return responseData[0]; // block
47
+ }).catch((error) => {
48
+ logger.error(`Error during get block: ${error}`);
49
+ return;
39
50
  });
40
51
  }
41
52
 
42
53
  ping() {
43
- return this.connection.sendCommand(['ping']).then((responseData) => {
44
- // responseData is an array containing [status]
45
- const statusRaw = responseData[0];
46
- const status = parseResponseType(statusRaw);
47
-
48
- if (status === 'pong') {
49
- return true;
50
- } else if (status === 'error') {
51
- throw new Error('Ping failed');
52
- } else {
53
- throw new Error(`Unknown status in response: '${status}'`);
54
- }
55
- });
54
+ return this.connection.sendCommand(['ping']).then((responseData) => {
55
+ // responseData is an array containing [status]
56
+ const statusRaw = responseData[0];
57
+ const status = parseResponseType(statusRaw);
58
+
59
+ if (status === 'pong') {
60
+ return true;
61
+ } else if (status === 'error') {
62
+ throw new Error('Ping failed');
63
+ } else {
64
+ throw new Error(`Unknown status in response: '${status}'`);
56
65
  }
66
+ }).catch((error) => {
67
+ logger.error(`Error during ping: ${error}`);
68
+ return false;
69
+ })
70
+ }
57
71
 
58
72
 
59
73
 
@@ -77,6 +91,9 @@ class DiodeRPC {
77
91
  } else {
78
92
  throw new Error(`Unknown status in response: '${status}'`);
79
93
  }
94
+ }).catch((error) => {
95
+ logger.error(`Error during port open: ${error}`);
96
+ return;
80
97
  });
81
98
  }
82
99
 
@@ -94,10 +111,13 @@ class DiodeRPC {
94
111
  if (status === 'ok') {
95
112
  try {
96
113
  const ticketCommand = await this.connection.createTicketCommand();
97
- const ticketResponse = await this.connection.sendCommand(ticketCommand);
98
- console.log('Ticket updated:', ticketResponse);
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)}`);
99
119
  } catch (error) {
100
- console.error('Error updating ticket:', error);
120
+ logger.error(`Error updating ticket: ${error}`);
101
121
  throw error;
102
122
  }
103
123
  return;
@@ -106,6 +126,9 @@ class DiodeRPC {
106
126
  } else {
107
127
  throw new Error(`Unknown status in response: '${status}'`);
108
128
  }
129
+ }).catch((error) => {
130
+ logger.error(`Error during port send: ${error}`);
131
+ return;
109
132
  });
110
133
  }
111
134
 
@@ -124,24 +147,33 @@ class DiodeRPC {
124
147
  } else {
125
148
  throw new Error(`Unknown status in response: '${status}'`);
126
149
  }
150
+ }).catch((error) => {
151
+ logger.error(`Error during port close: ${error}`);
152
+ return;
127
153
  });
128
154
  }
129
155
 
130
156
  sendError(sessionId, ref, error) {
131
- return this.connection.sendCommandWithSessionId(['response', ref, 'error', error], sessionId);
157
+ return this.connection.sendCommandWithSessionId(['response', ref, 'error', error], sessionId).catch((error) => {
158
+ logger.error(`Error during send error: ${error}`);
159
+ return;
160
+ });
132
161
  }
133
162
 
134
163
  sendResponse(sessionId, ref, response) {
135
- return this.connection.sendCommandWithSessionId(['response', ref, response], sessionId);
164
+ return this.connection.sendCommandWithSessionId(['response', ref, response], sessionId).catch((error) => {
165
+ logger.error(`Error during send response: ${error}`);
166
+ return;
167
+ });
136
168
  }
137
169
 
138
170
  async getEpoch() {
139
171
  const currentTime = Math.floor(Date.now() / 1000); // Current time in seconds
140
172
  if (this.epochCache.expiry && this.epochCache.expiry > currentTime) {
141
- console.log('Using cached epoch:', this.epochCache.epoch);
173
+ logger.debug(`Using cached epoch: ${this.epochCache.epoch}`);
142
174
  return this.epochCache.epoch;
143
175
  }
144
- console.log('Fetching new epoch. Expiry:', this.epochCache.expiry);
176
+ logger.debug(`Fetching new epoch. Expiry: ${this.epochCache.expiry}, Current time: ${currentTime}`);
145
177
  const blockPeak = await this.getBlockPeak();
146
178
  const blockHeader = await this.getBlockHeader(blockPeak);
147
179
 
@@ -175,4 +207,3 @@ class DiodeRPC {
175
207
  }
176
208
 
177
209
  module.exports = DiodeRPC;
178
-
package/utils.js CHANGED
@@ -1,5 +1,11 @@
1
1
  // utils.js
2
2
  const { Buffer } = require('buffer');
3
+ const logger = require('./logger');
4
+ const { KJUR } = require("jsrsasign");
5
+ const { KEYUTIL } = require("jsrsasign");
6
+ const fs = require('fs');
7
+ var path = require('path');
8
+
3
9
  function makeReadable(decodedMessage) {
4
10
  if (Array.isArray(decodedMessage)) {
5
11
  return decodedMessage.map((item) => makeReadable(item));
@@ -46,10 +52,10 @@ function parseRequestId(requestIdRaw) {
46
52
  }
47
53
 
48
54
  function parseResponseType(responseTypeRaw) {
49
- console.log('responseTypeRaw:', responseTypeRaw);
50
- console.log('Type of responseTypeRaw:', typeof responseTypeRaw);
51
- console.log('Instance of responseTypeRaw:', responseTypeRaw instanceof Uint8Array);
52
- console.log('Is Array:', Array.isArray(responseTypeRaw));
55
+ logger.debug(`responseTypeRaw: ${responseTypeRaw}`);
56
+ logger.debug(`Type of responseTypeRaw: ${typeof responseTypeRaw}`);
57
+ logger.debug(`Instance of responseTypeRaw: ${responseTypeRaw instanceof Uint8Array}`);
58
+ logger.debug(`Is Array: ${Array.isArray(responseTypeRaw)}`);
53
59
  if (responseTypeRaw instanceof Uint8Array || Buffer.isBuffer(responseTypeRaw)) {
54
60
  return Buffer.from(responseTypeRaw).toString('utf8');
55
61
  } else if (Array.isArray(responseTypeRaw)) {
@@ -74,4 +80,45 @@ function parseReason(reasonRaw) {
74
80
  }
75
81
  }
76
82
 
77
- module.exports = { makeReadable, parseRequestId, parseResponseType, parseReason };
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
+
90
+ var x = new KJUR.asn1.x509.Certificate({
91
+ version: 3,
92
+ serial: { int: 4 },
93
+ issuer: { str: "/CN=device" },
94
+ subject: { str: "/CN=device" },
95
+ sbjpubkey: kp.pubKeyObj,
96
+ ext: [
97
+ { extname: "basicConstraints", cA: false },
98
+ { extname: "keyUsage", critical: true, names: ["digitalSignature"] },
99
+ {
100
+ extname: "cRLDistributionPoints",
101
+ array: [{ fulluri: 'https://diode.io/' }]
102
+ }
103
+ ],
104
+ sigalg: "SHA256withECDSA",
105
+ cakey: kp.prvKeyObj
106
+ });
107
+
108
+
109
+ const pemFile = priv + x.getPEM();
110
+ ensureDirectoryExistence(path);
111
+
112
+ fs.writeFileSync(path, pemFile , 'utf8');
113
+ }
114
+
115
+ function ensureDirectoryExistence(filePath) {
116
+ var dirname = path.dirname(filePath);
117
+ if (fs.existsSync(dirname)) {
118
+ return true;
119
+ }
120
+ ensureDirectoryExistence(dirname);
121
+ fs.mkdirSync(dirname);
122
+ }
123
+
124
+ module.exports = { makeReadable, parseRequestId, parseResponseType, parseReason, generateCert };