diodejs 0.0.3

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/publishPort.js ADDED
@@ -0,0 +1,295 @@
1
+ // publishPort.js
2
+
3
+ const net = require('net');
4
+ const tls = require('tls');
5
+ const dgram = require('dgram');
6
+ const fs = require('fs');
7
+ const { Buffer } = require('buffer');
8
+ const EventEmitter = require('events');
9
+ const { Duplex } = require('stream');
10
+ const DiodeRPC = require('./rpc');
11
+
12
+ class DiodeSocket extends Duplex {
13
+ constructor(ref, rpc) {
14
+ super();
15
+ this.ref = ref;
16
+ this.rpc = rpc;
17
+ }
18
+
19
+ _write(chunk, encoding, callback) {
20
+ // Send data to the Diode client via portSend
21
+ this.rpc.portSend(this.ref, chunk)
22
+ .then(() => callback())
23
+ .catch((err) => callback(err));
24
+ }
25
+
26
+ _read(size) {
27
+ // No need to implement this method
28
+ }
29
+
30
+ // Method to push data received from Diode client
31
+ pushData(data) {
32
+ this.push(data);
33
+ }
34
+ }
35
+
36
+ class PublishPort extends EventEmitter {
37
+ constructor(connection, publishedPorts, certPath) {
38
+ super();
39
+ this.connection = connection;
40
+ this.publishedPorts = publishedPorts; // Array of ports to publish
41
+ this.connections = new Map(); // Map to store active connections
42
+ this.startListening();
43
+ this.rpc = new DiodeRPC(connection);
44
+ this.certPath = certPath;
45
+ }
46
+
47
+ startListening() {
48
+ // Listen for unsolicited messages from the connection
49
+ this.connection.on('unsolicited', (message) => {
50
+ const [sessionIdRaw, messageContent] = message;
51
+ const messageTypeRaw = messageContent[0];
52
+ const messageType = Buffer.from(messageTypeRaw).toString('utf8');
53
+
54
+ if (messageType === 'portopen') {
55
+ this.handlePortOpen(sessionIdRaw, messageContent);
56
+ } else if (messageType === 'portsend') {
57
+ this.handlePortSend(sessionIdRaw, messageContent);
58
+ } else if (messageType === 'portclose') {
59
+ this.handlePortClose(sessionIdRaw, messageContent);
60
+ } else {
61
+ console.warn(`Unknown unsolicited message type: ${messageType}`);
62
+ }
63
+ });
64
+ }
65
+
66
+ handlePortOpen(sessionIdRaw, messageContent) {
67
+ // messageContent: ['portopen', portString, ref, deviceId]
68
+ const portStringRaw = messageContent[1];
69
+ const refRaw = messageContent[2];
70
+ const deviceIdRaw = messageContent[3];
71
+
72
+ const sessionId = Buffer.from(sessionIdRaw);
73
+ const portString = Buffer.from(portStringRaw).toString('utf8');
74
+ const ref = Buffer.from(refRaw);
75
+ const deviceId = Buffer.from(deviceIdRaw).toString('hex');
76
+
77
+ console.log(`Received portopen request for portString ${portString} with ref ${ref.toString('hex')} from device ${deviceId}`);
78
+
79
+ // Extract protocol and port number from portString
80
+ const [protocol, portStr] = portString.split(':');
81
+ const port = parseInt(portStr, 10);
82
+
83
+ // Check if the port is published
84
+ if (!this.publishedPorts.includes(port)) {
85
+ console.warn(`Port ${port} is not published. Rejecting request.`);
86
+ // Send error response
87
+ this.rpc.sendError(sessionId, ref, 'Port is not published');
88
+ return;
89
+ }
90
+
91
+ // Handle based on protocol
92
+ if (protocol === 'tcp') {
93
+ this.handleTCPConnection(sessionId, ref, port);
94
+ } else if (protocol === 'tls') {
95
+ this.handleTLSConnection(sessionId, ref, port);
96
+ } else if (protocol === 'udp') {
97
+ this.handleUDPConnection(sessionId, ref, port);
98
+ } else {
99
+ console.warn(`Unsupported protocol: ${protocol}`);
100
+ this.rpc.sendError(sessionId, ref, `Unsupported protocol: ${protocol}`);
101
+ }
102
+ }
103
+
104
+ setupLocalSocketHandlers(localSocket, ref, protocol) {
105
+ if (protocol === 'udp') {
106
+
107
+ } else {
108
+ localSocket.on('data', (data) => {
109
+ // When data is received from the local service, send it back via Diode
110
+ this.rpc.portSend(ref, data);
111
+ });
112
+
113
+ localSocket.on('end', () => {
114
+ console.log(`Local service disconnected`);
115
+ // Send portclose message to Diode
116
+ this.rpc.portClose(ref);
117
+ this.connections.delete(ref.toString('hex'));
118
+ });
119
+
120
+ localSocket.on('error', (err) => {
121
+ console.error(`Error with local service:`, err);
122
+ // Send portclose message to Diode
123
+ this.rpc.portClose(ref);
124
+ this.connections.delete(ref.toString('hex'));
125
+ });
126
+ }
127
+ }
128
+
129
+ handleTCPConnection(sessionId, ref, port) {
130
+ // Create a TCP connection to the local service on the specified port
131
+ const localSocket = net.connect({ port: port }, () => {
132
+ console.log(`Connected to local TCP service on port ${port}`);
133
+ // Send success response
134
+ this.rpc.sendResponse(sessionId, ref, 'ok');
135
+ });
136
+
137
+ // Handle data, end, and error events
138
+ this.setupLocalSocketHandlers(localSocket, ref, 'tcp');
139
+
140
+ // Store the local socket with the ref
141
+ this.connections.set(ref.toString('hex'), { socket: localSocket, protocol: 'tcp' });
142
+ }
143
+
144
+ handleTLSConnection(sessionId, ref, port) {
145
+ // Create a DiodeSocket instance
146
+ const diodeSocket = new DiodeSocket(ref, this.rpc);
147
+
148
+ // TLS options with your server's certificate and key
149
+ const tlsOptions = {
150
+ cert: fs.readFileSync(this.certPath),
151
+ key: fs.readFileSync(this.certPath),
152
+ rejectUnauthorized: false,
153
+ ciphers: 'ECDHE-ECDSA-AES256-GCM-SHA384',
154
+ ecdhCurve: 'secp256k1',
155
+ minVersion: 'TLSv1.2',
156
+ maxVersion: 'TLSv1.2',
157
+ };
158
+
159
+ // Create a TLS socket in server mode using the DiodeSocket
160
+ const tlsSocket = new tls.TLSSocket(diodeSocket, {
161
+ isServer: true,
162
+ ...tlsOptions,
163
+ });
164
+
165
+ // Connect to the local service (TCP or TLS as needed)
166
+ const localSocket = net.connect({ port: port }, () => {
167
+ console.log(`Connected to local TCP service on port ${port}`);
168
+ // Send success response
169
+ this.rpc.sendResponse(sessionId, ref, 'ok');
170
+ });
171
+
172
+ // Pipe data between the TLS socket and the local service
173
+ tlsSocket.pipe(localSocket).pipe(tlsSocket);
174
+
175
+ // Handle errors and cleanup
176
+ tlsSocket.on('error', (err) => {
177
+ console.error('TLS Socket error:', err);
178
+ this.rpc.portClose(ref);
179
+ this.connections.delete(ref.toString('hex'));
180
+ });
181
+
182
+ tlsSocket.on('close', () => {
183
+ console.log('TLS Socket closed');
184
+ this.connections.delete(ref.toString('hex'));
185
+ });
186
+
187
+ // Store the connection info
188
+ this.connections.set(ref.toString('hex'), {
189
+ diodeSocket,
190
+ tlsSocket,
191
+ localSocket,
192
+ protocol: 'tls',
193
+ });
194
+ }
195
+
196
+ handleUDPConnection(sessionId, ref, port) {
197
+ // Create a UDP socket
198
+ const localSocket = dgram.createSocket('udp4');
199
+
200
+ // Store the remote address and port from the Diode client
201
+ const remoteInfo = {port, address: '127.0.0.1'};
202
+
203
+ // Send success response
204
+ this.rpc.sendResponse(sessionId, ref, 'ok');
205
+
206
+ // Store the connection info
207
+ this.connections.set(ref.toString('hex'), {
208
+ socket: localSocket,
209
+ protocol: 'udp',
210
+ remoteInfo,
211
+ });
212
+
213
+ // Handle messages from the local UDP service
214
+ localSocket.on('message', (msg, rinfo) => {
215
+ //need to add 4 bytes of data length to the beginning of the message but it's Big Endian
216
+ const dataLength = Buffer.alloc(4);
217
+ dataLength.writeUInt32LE(msg.length, 0);
218
+ const data = Buffer.concat([dataLength, msg]);
219
+ // Send the data back to the Diode client via portSend
220
+ this.rpc.portSend(ref, data);
221
+ });
222
+
223
+ localSocket.on('error', (err) => {
224
+ console.error(`UDP Socket error:`, err);
225
+ this.rpc.portClose(ref);
226
+ this.connections.delete(ref.toString('hex'));
227
+ });
228
+ }
229
+
230
+ handlePortSend(sessionIdRaw, messageContent) {
231
+ const refRaw = messageContent[1];
232
+ const dataRaw = messageContent[2];
233
+
234
+ const sessionId = Buffer.from(sessionIdRaw);
235
+ const ref = Buffer.from(refRaw);
236
+ const data = Buffer.from(dataRaw).slice(4);
237
+
238
+ const connectionInfo = this.connections.get(ref.toString('hex'));
239
+ if (connectionInfo) {
240
+ const { socket: localSocket, protocol, remoteInfo } = connectionInfo;
241
+
242
+ if (protocol === 'udp') {
243
+ // Send data to the local UDP service
244
+ // Since UDP is connectionless, we need to specify the address and port
245
+ localSocket.send(data, remoteInfo.port, remoteInfo.address, (err) => {
246
+ if (err) {
247
+ console.error(`Error sending UDP data:`, err);
248
+ }
249
+ });
250
+
251
+ // Update remoteInfo if not set
252
+ if (!localSocket.remoteAddress) {
253
+ localSocket.remoteAddress = '127.0.0.1'; // Assuming local service is on localhost
254
+ localSocket.remotePort = port;
255
+ }
256
+ } else if (protocol === 'tcp') {
257
+ // Write data to the local service
258
+ localSocket.write(data);
259
+ } else if (protocol === 'tls') {
260
+ const { diodeSocket } = connectionInfo;
261
+ // Push data into the DiodeSocket
262
+ diodeSocket.pushData(data);
263
+ }
264
+ } else {
265
+ console.warn(`No local connection found for ref ${ref.toString('hex')}. Sending portclose.`);
266
+ this.rpc.sendError(sessionId, ref, 'No local connection found');
267
+ }
268
+ }
269
+
270
+ handlePortClose(sessionIdRaw, messageContent) {
271
+ const refRaw = messageContent[1];
272
+ const sessionId = Buffer.from(sessionIdRaw);
273
+ const ref = Buffer.from(refRaw);
274
+
275
+ console.log(`Received portclose for ref ${ref.toString('hex')}`);
276
+
277
+ const connectionInfo = this.connections.get(ref.toString('hex'));
278
+ if (connectionInfo) {
279
+ const { diodeSocket, tlsSocket, socket: localSocket } = connectionInfo;
280
+ // End all sockets
281
+ if (diodeSocket) diodeSocket.end();
282
+ if (tlsSocket) tlsSocket.end();
283
+ if (localSocket) {
284
+ if (localSocket.type === 'udp4' || localSocket.type === 'udp6') {
285
+ localSocket.close();
286
+ } else {
287
+ localSocket.end();
288
+ }
289
+ }
290
+ this.connections.delete(ref.toString('hex'));
291
+ }
292
+ }
293
+ }
294
+
295
+ module.exports = PublishPort;
package/rpc.js ADDED
@@ -0,0 +1,178 @@
1
+ //rpc.js
2
+ const { makeReadable, parseRequestId, parseResponseType, parseReason } = require('./utils');
3
+
4
+ class DiodeRPC {
5
+ constructor(connection) {
6
+ this.connection = connection;
7
+ this.epochCache = {
8
+ epoch: null,
9
+ expiry: null,
10
+ };
11
+ }
12
+
13
+ getBlockPeak() {
14
+ return this.connection.sendCommand(['getblockpeak']).then((responseData) => {
15
+ // responseData is an array containing [blockNumber]
16
+ const blockNumberRaw = responseData[0];
17
+ let blockNumber;
18
+ if (blockNumberRaw instanceof Uint8Array) {
19
+ blockNumber = Buffer.from(blockNumberRaw).readUIntBE(0, blockNumberRaw.length);
20
+ } else if (Buffer.isBuffer(blockNumberRaw)) {
21
+ blockNumber = blockNumberRaw.readUIntBE(0, blockNumberRaw.length);
22
+ } else if (typeof blockNumberRaw === 'number') {
23
+ blockNumber = blockNumberRaw;
24
+ } else {
25
+ throw new Error('Invalid block number format. response:', makeReadable(responseData));
26
+ }
27
+ return blockNumber;
28
+ });
29
+ }
30
+ getBlockHeader(index) {
31
+ return this.connection.sendCommand(['getblockheader', index]).then((responseData) => {
32
+ return responseData[0]; // block_header
33
+ });
34
+ }
35
+
36
+ getBlock(index) {
37
+ return this.connection.sendCommand(['getblock', index]).then((responseData) => {
38
+ return responseData[0]; // block
39
+ });
40
+ }
41
+
42
+ 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
+ });
56
+ }
57
+
58
+
59
+
60
+ portOpen(deviceId, port, flags = 'rw') {
61
+ return this.connection.sendCommand(['portopen', deviceId, port, flags]).then((responseData) => {
62
+ // responseData is [status, refOrReason]
63
+ const [statusRaw, refOrReasonRaw] = responseData;
64
+
65
+ // Convert status to string
66
+ const status = parseResponseType(statusRaw);
67
+
68
+ if (status === 'ok') {
69
+ let ref = refOrReasonRaw;
70
+ if (Buffer.isBuffer(ref) || ref instanceof Uint8Array) {
71
+ ref = Buffer.from(ref);
72
+ }
73
+ return ref;
74
+ } else if (status === 'error') {
75
+ let reason = parseReason(refOrReasonRaw);
76
+ throw new Error(reason);
77
+ } else {
78
+ throw new Error(`Unknown status in response: '${status}'`);
79
+ }
80
+ });
81
+ }
82
+
83
+ async portSend(ref, data) {
84
+ // Update totalBytes
85
+ const bytesToSend = data.length;
86
+ this.connection.totalBytes += bytesToSend;
87
+
88
+ // Now send the data
89
+ return this.connection.sendCommand(['portsend', ref, data]).then(async (responseData) => {
90
+ // responseData is [status]
91
+ const [statusRaw] = responseData;
92
+ const status = parseResponseType(statusRaw);
93
+
94
+ if (status === 'ok') {
95
+ try {
96
+ const ticketCommand = await this.connection.createTicketCommand();
97
+ const ticketResponse = await this.connection.sendCommand(ticketCommand);
98
+ console.log('Ticket updated:', ticketResponse);
99
+ } catch (error) {
100
+ console.error('Error updating ticket:', error);
101
+ throw error;
102
+ }
103
+ return;
104
+ } else if (status === 'error') {
105
+ throw new Error('Error during port send');
106
+ } else {
107
+ throw new Error(`Unknown status in response: '${status}'`);
108
+ }
109
+ });
110
+ }
111
+
112
+ portClose(ref) {
113
+ return this.connection.sendCommand(['portclose', ref]).then((responseData) => {
114
+ const [statusRaw] = responseData;
115
+
116
+ const status = Buffer.isBuffer(statusRaw) || statusRaw instanceof Uint8Array
117
+ ? Buffer.from(statusRaw).toString('utf8')
118
+ : statusRaw;
119
+
120
+ if (status === 'ok') {
121
+ return;
122
+ } else if (status === 'error') {
123
+ throw new Error('Error during port close');
124
+ } else {
125
+ throw new Error(`Unknown status in response: '${status}'`);
126
+ }
127
+ });
128
+ }
129
+
130
+ sendError(sessionId, ref, error) {
131
+ return this.connection.sendCommandWithSessionId(['response', ref, 'error', error], sessionId);
132
+ }
133
+
134
+ sendResponse(sessionId, ref, response) {
135
+ return this.connection.sendCommandWithSessionId(['response', ref, response], sessionId);
136
+ }
137
+
138
+ async getEpoch() {
139
+ const currentTime = Math.floor(Date.now() / 1000); // Current time in seconds
140
+ if (this.epochCache.expiry && this.epochCache.expiry > currentTime) {
141
+ console.log('Using cached epoch:', this.epochCache.epoch);
142
+ return this.epochCache.epoch;
143
+ }
144
+ console.log('Fetching new epoch. Expiry:', this.epochCache.expiry);
145
+ const blockPeak = await this.getBlockPeak();
146
+ const blockHeader = await this.getBlockHeader(blockPeak);
147
+
148
+ // Assuming blockHeader is an object with a timestamp property
149
+ const timestamp = this.parseTimestamp(blockHeader);
150
+ const epochDuration = 2592000; // 30 days in seconds
151
+ const epoch = Math.floor(timestamp / epochDuration);
152
+
153
+ // Calculate the time left for the next epoch
154
+ const timeLeft = epochDuration - (timestamp % epochDuration);
155
+
156
+ // Cache the epoch and the expiry time
157
+ this.epochCache.epoch = epoch;
158
+ this.epochCache.expiry = currentTime + timeLeft;
159
+
160
+ return epoch;
161
+ }
162
+
163
+ parseTimestamp(blockHeader) {
164
+ // Implement parsing of timestamp from blockHeader
165
+ const timestampRaw = blockHeader[0][1]; // Adjust index based on actual structure
166
+ //Timestamp Raw: [ 'timestamp', 1726689425 ]
167
+ if (timestampRaw instanceof Uint8Array || Buffer.isBuffer(timestampRaw)) {
168
+ return Buffer.from(timestampRaw).readUIntBE(0, timestampRaw.length);
169
+ } else if (typeof timestampRaw === 'number') {
170
+ return timestampRaw;
171
+ } else {
172
+ throw new Error('Invalid timestamp format in block header');
173
+ }
174
+ }
175
+ }
176
+
177
+ module.exports = DiodeRPC;
178
+
@@ -0,0 +1,45 @@
1
+ const dgram = require('dgram');
2
+ const server = dgram.createSocket('udp4');
3
+
4
+ // emits when any error occurs
5
+ server.on('error',function(error){
6
+ console.log('Error: ' + error);
7
+ server.close();
8
+ });
9
+
10
+ // emits on new datagram msg
11
+ server.on('message',function(msg,info){
12
+ console.log('Data received from client : ' + msg.toString());
13
+ console.log('Received %d bytes from %s:%d\n',msg.length, info.address, info.port);
14
+
15
+ //sending msg
16
+ server.send(msg,info.port,'localhost',function(error){
17
+ if(error){
18
+ client.close();
19
+ }else{
20
+ console.log('Data sent !!!');
21
+ }
22
+
23
+ });
24
+
25
+ });
26
+
27
+ //emits when socket is ready and listening for datagram msgs
28
+ server.on('listening',function(){
29
+ var address = server.address();
30
+ var port = address.port;
31
+ var family = address.family;
32
+ var ipaddr = address.address;
33
+ console.log('Server is listening at port' + port);
34
+ console.log('Server ip :' + ipaddr);
35
+ console.log('Server is IP4/IP6 : ' + family);
36
+ });
37
+
38
+ //emits after the socket is closed using socket.close();
39
+ server.on('close',function(){
40
+ console.log('Socket is closed !');
41
+ });
42
+
43
+ server.bind(8080, () => {
44
+ console.log('UDP server listening on port 8080');
45
+ });
package/utils.js ADDED
@@ -0,0 +1,77 @@
1
+ // utils.js
2
+ const { Buffer } = require('buffer');
3
+ function makeReadable(decodedMessage) {
4
+ if (Array.isArray(decodedMessage)) {
5
+ return decodedMessage.map((item) => makeReadable(item));
6
+ } else if (decodedMessage instanceof Uint8Array) {
7
+ const buffer = Buffer.from(decodedMessage);
8
+ // Try to interpret the Buffer as a UTF-8 string
9
+ const str = buffer.toString('utf8');
10
+ if (/^[\x20-\x7E]+$/.test(str)) {
11
+ // If it's printable ASCII, return the string
12
+ return str;
13
+ } else if (buffer.length <= 6) {
14
+ // If it's a small Buffer, interpret it as an integer
15
+ return buffer.length > 0 && buffer.length <= 6 ? buffer.readUIntBE(0, buffer.length) : '0x' + buffer.toString('hex');
16
+ } else {
17
+ // Otherwise, return the hex representation
18
+ return '0x' + buffer.toString('hex');
19
+ }
20
+ } else if (Buffer.isBuffer(decodedMessage)) {
21
+ // Similar handling for Buffer
22
+ const str = decodedMessage.toString('utf8');
23
+ if (/^[\x20-\x7E]+$/.test(str)) {
24
+ return str;
25
+ } else if (decodedMessage.length <= 6) {
26
+ return decodedMessage.readUIntBE(0, decodedMessage.length);
27
+ } else {
28
+ return '0x' + decodedMessage.toString('hex');
29
+ }
30
+ } else if (typeof decodedMessage === 'number') {
31
+ return decodedMessage;
32
+ }
33
+ return decodedMessage;
34
+ }
35
+
36
+ // Helper functions
37
+ function parseRequestId(requestIdRaw) {
38
+ if (requestIdRaw instanceof Uint8Array || Buffer.isBuffer(requestIdRaw)) {
39
+ const buffer = Buffer.from(requestIdRaw);
40
+ return buffer.readUIntBE(0, buffer.length);
41
+ } else if (typeof requestIdRaw === 'number') {
42
+ return requestIdRaw;
43
+ } else {
44
+ return null;
45
+ }
46
+ }
47
+
48
+ 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));
53
+ if (responseTypeRaw instanceof Uint8Array || Buffer.isBuffer(responseTypeRaw)) {
54
+ return Buffer.from(responseTypeRaw).toString('utf8');
55
+ } else if (Array.isArray(responseTypeRaw)) {
56
+ // Convert each element to Buffer and concatenate
57
+ const buffers = responseTypeRaw.map((item) => Buffer.from(item));
58
+ const concatenated = Buffer.concat(buffers);
59
+ return concatenated.toString('utf8');
60
+ } else if (typeof responseTypeRaw === 'string') {
61
+ return responseTypeRaw;
62
+ } else {
63
+ throw new Error('Invalid responseType type');
64
+ }
65
+ }
66
+
67
+ function parseReason(reasonRaw) {
68
+ if (Buffer.isBuffer(reasonRaw) || reasonRaw instanceof Uint8Array) {
69
+ return Buffer.from(reasonRaw).toString('utf8');
70
+ } else if (typeof reasonRaw === 'string') {
71
+ return reasonRaw;
72
+ } else {
73
+ return '';
74
+ }
75
+ }
76
+
77
+ module.exports = { makeReadable, parseRequestId, parseResponseType, parseReason };