aes70 1.3.6 → 1.3.8

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.
@@ -1,280 +1,19 @@
1
1
  /* eslint-env node */
2
2
 
3
- import { createSocket } from 'dgram';
4
- import { Buffer } from 'buffer';
3
+ import { NodeUDP } from './node_udp.js';
5
4
  import { performance } from 'perf_hooks';
6
- import { encodeMessage } from '../OCP1/encode_message.js';
7
- import { decodeMessage } from '../OCP1/decode_message.js';
8
- import { KeepAlive } from '../OCP1/keepalive.js';
9
-
10
- import { lookup } from 'dns';
11
-
12
- import { ClientConnection } from './client_connection.js';
13
-
14
- function delay(n) {
15
- return new Promise((resolve) => setTimeout(resolve, n));
16
- }
17
-
18
- async function waitForKeepalive(socket, options) {
19
- const waiter = new Promise((resolve, reject) => {
20
- let onMessage;
21
-
22
- onMessage = (data, rinfo) => {
23
- const pdus = [];
24
- let pos = 0;
25
-
26
- pos = decodeMessage(new DataView(data.buffer), 0, pdus);
27
-
28
- if (pdus.length !== 1) throw new Error('Expected keepalive response.');
29
-
30
- socket.off('message', onMessage);
31
- socket.off('error', reject);
32
- resolve(true);
33
- };
34
-
35
- socket.on('message', onMessage);
36
- socket.on('error', reject);
37
- });
38
-
39
- const msg = Buffer.from(encodeMessage(new KeepAlive(1000)));
40
- const t = 5 * (options.retry_interval || 250);
41
-
42
- for (let i = 0; i < 3; i++) {
43
- socket.send(msg);
44
-
45
- if (await Promise.race([waiter, delay(t)])) return;
46
- }
47
-
48
- throw new Error('Failed to connect.');
49
- }
50
-
51
- function is_ip(host) {
52
- let tmp = host.split('.');
53
-
54
- if (tmp.length !== 4) return false;
55
-
56
- tmp = tmp.map(parseInt);
57
-
58
- return !tmp.some(function (v) {
59
- return v < 0 || v > 255;
60
- });
61
- }
62
-
63
- // TODO: handle IPv6
64
- function lookup_address(options) {
65
- const host = options.host;
66
- const port = parseInt(options.port);
67
-
68
- const o = { port: port };
69
-
70
- if (is_ip(host))
71
- return Promise.resolve(Object.assign({}, options, o, { address: host }));
72
-
73
- return new Promise(function (resolve, reject) {
74
- lookup(host, { family: 4 }, function (err, address) {
75
- if (err) reject(err);
76
- else resolve(Object.assign({}, options, o, { address: address }));
77
- });
78
- });
79
- }
5
+ import { AbstractUDPConnection } from './abstract_udp_connection.js';
80
6
 
81
7
  /**
82
8
  * :class:`ClientConnection` subclass which implements OCP.1 with UDP
83
9
  * transport.
84
10
  */
85
- export class UDPConnection extends ClientConnection {
86
- constructor(socket, options) {
87
- // allow us to batch 128 bytes max
88
- // Set this to a higher value, e.g. close to MTU
89
- // if you are sure that the device can handle it.
90
- if (!(options.batch >= 0)) options.batch = 128;
91
- super(options);
92
- this.socket = socket;
93
- this.delay = options.delay >= 0 ? options.delay : 5;
94
- this.retry_interval =
95
- options.retry_interval >= 0 ? options.retry_interval : 250;
96
- this.retry_count = options.retry_count >= 0 ? options.retry_count : 3;
97
- this._write_out_id = -1;
98
- this._write_out_callback = () => {
99
- this._write_out_id = -1;
100
- this._write_out();
101
- };
102
- this._retry_id =
103
- this.retry_interval > 0
104
- ? setInterval(() => this._retryCommands(), this.retry_interval)
105
- : -1;
106
- this.q = [];
107
- socket.on('message', (data, rinfo) => {
108
- try {
109
- this.read(data.buffer);
110
- } catch (err) {
111
- console.warn('Failed to parse incoming AES70 packet: %o', err);
112
- }
113
- if (this.inbuf !== null) this.close();
114
- });
115
- socket.on('error', (err) => this.error(err));
116
- this.set_keepalive_interval(1);
117
- }
118
-
119
- get is_reliable() {
120
- return false;
121
- }
122
-
123
- /**
124
- * Connect to the given endpoint.
125
- *
126
- * @param {String} options.host - hostname or ip
127
- * @param {number} options.port - port number
128
- * @param {number} [options.delay=10] - Delay in ms between individual packets.
129
- * This can be a useful strategy when communicating with devices which
130
- * cannot handle high packet rates.
131
- * @param {number} [options.retry_interval=250] - Delay in ms after which a
132
- * command should be automatically re-sent if no response has been
133
- * received, yet.
134
- * @param {number} [options.retry_count=3] - Number of times to retry sending
135
- * commands. If no response has been received after all retries, the
136
- * command will fail with an error.
137
- * @param {number} [options.batch=128] - Maximum number of bytes to send
138
- * in an individual UDP packet. Note that AES70 messages which are larger
139
- * than this limit are sent anyway. This only limits how many seperate
140
- * messages are batched into a single packet.
141
- * @returns {Promise<UDPConnection>}
142
- * The connection.
143
- */
144
- static connect(options) {
145
- return lookup_address(options).then((options) => {
146
- return new Promise((resolve, reject) => {
147
- const socket = createSocket(options.type || 'udp4');
148
- const onerror = function (ev) {
149
- reject(ev);
150
- };
151
- socket.on('error', onerror);
152
- socket.bind(
153
- {
154
- exclusive: true,
155
- },
156
- () => {
157
- socket.on('connect', async () => {
158
- try {
159
- await waitForKeepalive(socket, options);
160
-
161
- socket.removeListener('error', onerror);
162
-
163
- const connection = new this(socket, options);
164
- resolve(connection);
165
- } catch (err) {
166
- reject(err);
167
- }
168
- });
169
- socket.connect(options.port, options.host);
170
- }
171
- );
172
- });
173
- });
174
- }
175
-
176
- write(buf) {
177
- this.q.push(buf);
178
-
179
- if (this.tx_idle_time() >= this.delay) this._write_out();
180
- else this._schedule_write_out();
181
- }
182
-
183
- flush() {
184
- super.flush();
185
- if (this.tx_idle_time() > this.delay) this._write_out();
186
- }
187
-
188
- cleanup() {
189
- super.cleanup();
190
- if (this.socket) {
191
- this.socket.close();
192
- this.socket = null;
193
- }
194
- if (this._write_out_id !== -1) {
195
- clearTimeout(this._write_out_id);
196
- this._write_out_id = -1;
197
- }
198
- if (this._retry_id !== -1) {
199
- clearInterval(this._retry_id);
200
- this._retry_id = -1;
201
- }
202
- }
203
-
11
+ export class UDPConnection extends AbstractUDPConnection {
204
12
  _now() {
205
13
  return performance.now();
206
14
  }
207
15
 
208
- _write_out() {
209
- if (!this.socket) return;
210
- const q = this.q;
211
-
212
- if (!q.length) return;
213
-
214
- const buf = q.shift();
215
-
216
- this.socket.send(Buffer.from(buf));
217
- super.write(buf);
218
-
219
- if (q.length) this._schedule_write_out();
220
- }
221
-
222
- _schedule_write_out() {
223
- const tx_idle_time = this.tx_idle_time();
224
- const delay = this.delay;
225
-
226
- if (tx_idle_time >= delay) {
227
- this._write_out();
228
- return;
229
- }
230
-
231
- // Already scheduled.
232
- if (this._write_out_id !== -1) return;
233
-
234
- this._write_out_id = setTimeout(
235
- this._write_out_callback,
236
- delay - tx_idle_time
237
- );
238
- }
239
-
240
- _retryCommands() {
241
- const now = this._now();
242
- const retryTime = now - this.retry_interval;
243
- // This is an estimate for how many commands we would manage to send
244
- // off.
245
- const max = 5 * (this.retry_interval / this.delay) - this.q.length;
246
- const pendingCommands = this._pendingCommands;
247
-
248
- const retries = [];
249
- const failed = [];
250
-
251
- for (const entry of pendingCommands) {
252
- const [handle, pendingCommand] = entry;
253
-
254
- // All later commands are newer than the cutoff.
255
- if (pendingCommand.lastSent > retryTime) break;
256
- if (pendingCommand.retries >= this.retry_count) {
257
- failed.push(entry);
258
- } else if (retries.length < max) {
259
- retries.push(entry);
260
- }
261
- }
262
-
263
- if (failed.length) {
264
- const timeoutError = new Error('Timeout.');
265
-
266
- failed.forEach(([handle, pendingCommand]) => {
267
- pendingCommands.delete(handle);
268
- pendingCommand.reject(timeoutError);
269
- });
270
- }
271
-
272
- retries.forEach(([handle, pendingCommand]) => {
273
- pendingCommands.delete(handle);
274
- pendingCommands.set(handle, pendingCommand);
275
- this.send(pendingCommand.command);
276
- pendingCommand.lastSent = now;
277
- pendingCommand.retries++;
278
- });
16
+ static connect(options) {
17
+ return AbstractUDPConnection.connect.call(this, NodeUDP, options);
279
18
  }
280
19
  }
@@ -16,6 +16,7 @@ import { define_custom_class } from './controller/define_custom_class.js';
16
16
  import * as RemoteControlClasses from './controller/ControlClasses.js';
17
17
  import * as Types from './types.js';
18
18
  import { WebSocketConnection } from './controller/websocket_connection.js';
19
+ import { AbstractUDPConnection } from './controller/abstract_udp_connection.js';
19
20
 
20
21
  const controller = {
21
22
  Connection: ClientConnection,
@@ -43,4 +44,5 @@ export {
43
44
  warn,
44
45
  WebSocketConnection,
45
46
  RemoteControlClasses,
47
+ AbstractUDPConnection,
46
48
  };
package/src/index.js CHANGED
@@ -18,6 +18,7 @@ import * as Types from './types.js';
18
18
  import { WebSocketConnection } from './controller/websocket_connection_node.js';
19
19
  import { TCPConnection } from './controller/tcp_connection.js';
20
20
  import { UDPConnection } from './controller/udp_connection.js';
21
+ import { AbstractUDPConnection } from './controller/abstract_udp_connection.js';
21
22
 
22
23
  export const controller = {
23
24
  Connection: ClientConnection,
@@ -47,4 +48,5 @@ export {
47
48
  WebSocketConnection,
48
49
  TCPConnection,
49
50
  UDPConnection,
51
+ AbstractUDPConnection,
50
52
  };
@@ -44,7 +44,8 @@ class Locking extends ObjectTest
44
44
  cleanup()
45
45
  {
46
46
  super.cleanup();
47
- this.other_device.close();
47
+ this.close_device(this.other_device);
48
+ this.other_device = null;
48
49
  }
49
50
  }
50
51
 
@@ -0,0 +1,24 @@
1
+ import { Test } from './test.js';
2
+
3
+ class CheckCallbackMethod extends Test {
4
+ async run() {
5
+ const root = this.device.Root;
6
+
7
+ const result = await root.GetMembers();
8
+
9
+ const result2 = await new Promise((resolve, reject) => {
10
+ const tmp = root.GetMembers((ok, result) => {
11
+ (ok ? resolve : reject)(result);
12
+ });
13
+ if (tmp) {
14
+ console.error('got %o', tmp);
15
+ throw new Error('Unexpected return value.');
16
+ }
17
+ });
18
+
19
+ if (result.length !== result2.length)
20
+ throw new Error('Result mismatch.');
21
+ }
22
+ }
23
+
24
+ export default [ CheckCallbackMethod ];
@@ -44,16 +44,22 @@ export class Test {
44
44
  {
45
45
  }
46
46
 
47
- cleanup()
47
+ close_device(device)
48
48
  {
49
- if (this.device !== null)
49
+ if (device !== null)
50
50
  {
51
- this.device.connection.emit('test_done');
52
- this.device.close();
53
- this.device = null;
51
+ device.connection.emit('test_done');
52
+ device.close();
53
+ device = null;
54
54
  }
55
55
  }
56
56
 
57
+ cleanup()
58
+ {
59
+ this.close_device(this.device);
60
+ this.device = null;
61
+ }
62
+
57
63
  static focus()
58
64
  {
59
65
  return false;
package/tests/device.js CHANGED
@@ -13,6 +13,7 @@ import CheckTreeTests from './device/check_tree.js';
13
13
  import KeepaliveTests from './device/keepalive.js';
14
14
  import LockingTests from './device/locking.js';
15
15
  import PropertyChangesTests from './device/property_changes.js';
16
+ import MethodCallbackTests from './device/method_callback.js';
16
17
 
17
18
  if (argv.length < 3)
18
19
  {
@@ -169,7 +170,8 @@ function get_runner(get_device)
169
170
  CheckTreeTests,
170
171
  //KeepaliveTests,
171
172
  LockingTests,
172
- PropertyChangesTests
173
+ PropertyChangesTests,
174
+ MethodCallbackTests
173
175
  ].flat();
174
176
 
175
177
  // require('./device/property_changes'),
@@ -1,64 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { stdout, argv, exit } from 'process';
4
- import { RemoteDevice } from '../src/controller/remote_device.js';
5
- import { TCPConnection } from '../src/controller/tcp_connection.js';
6
- import { format } from 'util';
7
-
8
- if (argv.length < 4)
9
- {
10
- console.log('Usage: node print_tree.js <ip> <port>');
11
- exit(1);
12
- }
13
-
14
- function delay(n) {
15
- if (!n) return Promise.resolve();
16
- return new Promise((resolve) => setTimeout(resolve, n));
17
- }
18
-
19
- const host = argv[2];
20
- const port = parseInt(argv[3]);
21
- const N = 1000;
22
-
23
- const destinations = new Array(N).fill(0).map((_, index) => {
24
- return { host: host, port: port + index };
25
- });
26
-
27
- async function connectAndDiscover(destination) {
28
- const con = await TCPConnection.connect(destination);
29
-
30
- const device = new RemoteDevice(con);
31
-
32
- const objectTree = await device.get_device_tree();
33
-
34
- device.close();
35
-
36
- return objectTree;
37
- }
38
-
39
- async function run() {
40
- for (let i = 0; i < destinations.length;) {
41
- let tasks = [];
42
-
43
- while (tasks.length < 5) {
44
- tasks.push(connectAndDiscover(destinations[i++]));
45
- }
46
-
47
- await Promise.all(tasks);
48
-
49
- stdout.write(format('\r %d %% done.',
50
- Math.floor(100 * i / destinations.length)));
51
-
52
- await delay(50);
53
- }
54
- }
55
-
56
- run().then(
57
- () => {
58
- console.log('\nDone');
59
- },
60
- (err) => {
61
- console.error(err);
62
- exit(1);
63
- }
64
- );