njs-modbus 2.1.0 → 3.0.2

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.
Files changed (56) hide show
  1. package/README.md +264 -154
  2. package/README.zh-CN.md +314 -0
  3. package/dist/index.cjs +1887 -1066
  4. package/dist/index.d.ts +368 -215
  5. package/dist/index.mjs +1884 -1065
  6. package/dist/src/error-code.d.ts +2 -24
  7. package/dist/src/layers/application/abstract-application-layer.d.ts +12 -7
  8. package/dist/src/layers/application/ascii-application-layer.d.ts +8 -9
  9. package/dist/src/layers/application/rtu-application-layer.d.ts +21 -44
  10. package/dist/src/layers/application/tcp-application-layer.d.ts +8 -6
  11. package/dist/src/layers/physical/abstract-physical-layer.d.ts +40 -12
  12. package/dist/src/layers/physical/index.d.ts +9 -3
  13. package/dist/src/layers/physical/serial-physical-layer.d.ts +43 -13
  14. package/dist/src/layers/physical/tcp-client-physical-layer.d.ts +12 -10
  15. package/dist/src/layers/physical/tcp-physical-connection.d.ts +17 -0
  16. package/dist/src/layers/physical/tcp-server-physical-layer.d.ts +23 -12
  17. package/dist/src/layers/physical/udp-client-physical-layer.d.ts +35 -0
  18. package/dist/src/layers/physical/udp-server-physical-layer.d.ts +54 -0
  19. package/dist/src/layers/physical/utils.d.ts +39 -0
  20. package/dist/src/layers/physical/vars.d.ts +11 -0
  21. package/dist/src/master/master.d.ts +45 -17
  22. package/dist/src/slave/slave.d.ts +57 -33
  23. package/dist/src/types.d.ts +2 -2
  24. package/dist/src/utils/index.d.ts +4 -2
  25. package/dist/src/utils/rtu-timing.d.ts +49 -0
  26. package/dist/src/utils/whitelist.d.ts +11 -0
  27. package/package.json +8 -8
  28. package/dist/src/layers/physical/udp-physical-layer.d.ts +0 -42
  29. package/dist/src/utils/genConnectionId.d.ts +0 -2
  30. package/dist/test/adu-buffer.test.d.ts +0 -1
  31. package/dist/test/ascii-hex-sentry.test.d.ts +0 -1
  32. package/dist/test/ascii-hex-validation.test.d.ts +0 -1
  33. package/dist/test/ascii-tcp-fragmentation.test.d.ts +0 -1
  34. package/dist/test/check-range.test.d.ts +0 -1
  35. package/dist/test/fallback-atomic.test.d.ts +0 -1
  36. package/dist/test/fallback-serial.test.d.ts +0 -1
  37. package/dist/test/fc17-serverid-validation.test.d.ts +0 -1
  38. package/dist/test/fc43-conformity.test.d.ts +0 -1
  39. package/dist/test/fc43-utf8-objects.test.d.ts +0 -1
  40. package/dist/test/gen-connection-id.test.d.ts +0 -1
  41. package/dist/test/helpers/raw-tcp.d.ts +0 -38
  42. package/dist/test/master-concurrent.test.d.ts +0 -1
  43. package/dist/test/modbus-error.test.d.ts +0 -1
  44. package/dist/test/physical-lifecycle.test.d.ts +0 -1
  45. package/dist/test/predict-rtu.test.d.ts +0 -1
  46. package/dist/test/rtu-custom-fc.test.d.ts +0 -1
  47. package/dist/test/rtu-pool-overflow.test.d.ts +0 -1
  48. package/dist/test/rtu-t15-timing.test.d.ts +0 -1
  49. package/dist/test/rtu-t35-default.test.d.ts +0 -1
  50. package/dist/test/rtu-t35-strict.test.d.ts +0 -1
  51. package/dist/test/rtu-tcp-fragmentation.test.d.ts +0 -1
  52. package/dist/test/serial-e2e.test.d.ts +0 -1
  53. package/dist/test/slave-multi-connection.test.d.ts +0 -1
  54. package/dist/test/slave.test.d.ts +0 -1
  55. package/dist/test/tcp-fragmentation.test.d.ts +0 -1
  56. package/dist/test/udp-multi-client.test.d.ts +0 -1
package/dist/index.cjs CHANGED
@@ -2,7 +2,6 @@
2
2
 
3
3
  var EventEmitter = require('node:events');
4
4
  var serialport = require('serialport');
5
- var node_crypto = require('node:crypto');
6
5
  var node_net = require('node:net');
7
6
  var node_dgram = require('node:dgram');
8
7
 
@@ -18,32 +17,20 @@ exports.ErrorCode = void 0;
18
17
  ErrorCode[ErrorCode["GATEWAY_PATH_UNAVAILABLE"] = 10] = "GATEWAY_PATH_UNAVAILABLE";
19
18
  ErrorCode[ErrorCode["GATEWAY_TARGET_DEVICE_FAILED_TO_RESPOND"] = 11] = "GATEWAY_TARGET_DEVICE_FAILED_TO_RESPOND";
20
19
  })(exports.ErrorCode || (exports.ErrorCode = {}));
21
- /** Internal error codes for programmatic error handling. */
22
- const ModbusErrorCode = {
23
- TIMEOUT: 'ETIMEOUT',
24
- INVALID_RESPONSE: 'EINVALID_RESPONSE',
25
- INSUFFICIENT_DATA: 'EINSUFFICIENT_DATA',
26
- MASTER_CLOSED: 'EMASTER_CLOSED',
27
- MASTER_DESTROYED: 'EMASTER_DESTROYED',
28
- CONCURRENT_NOT_TCP: 'ECONCURRENT_NOT_TCP',
29
- PORT_DESTROYED: 'EPORT_DESTROYED',
30
- PORT_ALREADY_OPEN: 'EPORT_ALREADY_OPEN',
31
- PORT_NOT_OPEN: 'EPORT_NOT_OPEN',
32
- NOT_SUPPORTED: 'ENOT_SUPPORTED',
33
- INVALID_DATA: 'EINVALID_DATA',
34
- INVALID_HEX: 'EINVALID_HEX',
35
- CRC_MISMATCH: 'ECRC_MISMATCH',
36
- LRC_MISMATCH: 'ELRC_MISMATCH',
37
- INCOMPLETE_FRAME: 'EINCOMPLETE_FRAME',
38
- T1_5_EXCEEDED: 'ET1_5_EXCEEDED',
39
- UNKNOWN_FC: 'EUNKNOWN_FC',
40
- INVALID_ROLE: 'EINVALID_ROLE',
41
- RANGE: 'ERANGE',
20
+ const ERROR_MESSAGES = {
21
+ [exports.ErrorCode.ILLEGAL_FUNCTION]: 'Illegal function',
22
+ [exports.ErrorCode.ILLEGAL_DATA_ADDRESS]: 'Illegal data address',
23
+ [exports.ErrorCode.ILLEGAL_DATA_VALUE]: 'Illegal data value',
24
+ [exports.ErrorCode.SERVER_DEVICE_FAILURE]: 'Server device failure',
25
+ [exports.ErrorCode.ACKNOWLEDGE]: 'Acknowledge',
26
+ [exports.ErrorCode.SERVER_DEVICE_BUSY]: 'Server device busy',
27
+ [exports.ErrorCode.MEMORY_PARITY_ERROR]: 'Memory parity error',
28
+ [exports.ErrorCode.GATEWAY_PATH_UNAVAILABLE]: 'Gateway path unavailable',
29
+ [exports.ErrorCode.GATEWAY_TARGET_DEVICE_FAILED_TO_RESPOND]: 'Gateway target device failed to respond',
42
30
  };
43
- const MODBUS_PREFIX = 'MODBUS_ERROR_CODE_';
44
31
  class ModbusError extends Error {
45
- constructor(code, message) {
46
- super(message !== null && message !== void 0 ? message : code);
32
+ constructor(code, message = ERROR_MESSAGES[code]) {
33
+ super(message);
47
34
  Object.defineProperty(this, "code", {
48
35
  enumerable: true,
49
36
  configurable: true,
@@ -54,18 +41,15 @@ class ModbusError extends Error {
54
41
  }
55
42
  }
56
43
  function getErrorByCode(code) {
57
- return new ModbusError(String(code), `${MODBUS_PREFIX}${code}`);
44
+ return new ModbusError(code);
58
45
  }
59
46
  function getCodeByError(err) {
60
- if (err instanceof ModbusError) {
61
- const num = Number(err.code);
62
- if (!Number.isNaN(num) && num in exports.ErrorCode) {
63
- return num;
47
+ if (err.name === 'ModbusError' && 'code' in err) {
48
+ const code = err.code;
49
+ if (code in exports.ErrorCode) {
50
+ return code;
64
51
  }
65
52
  }
66
- if (err.message.startsWith(MODBUS_PREFIX)) {
67
- return Number(err.message.slice(MODBUS_PREFIX.length));
68
- }
69
53
  return exports.ErrorCode.SERVER_DEVICE_FAILURE;
70
54
  }
71
55
 
@@ -120,23 +104,584 @@ const LIMITS = {
120
104
  RW_REGISTERS_WRITE_MAX: 0x0079,
121
105
  };
122
106
 
107
+ /**
108
+ * A one-way data transmission channel.
109
+ *
110
+ * State transitions are unidirectional: CONNECTED → DESTROYING → DESTROYED.
111
+ * Once closed, the instance cannot be reused; create a new connection instead.
112
+ */
113
+ class AbstractPhysicalConnection extends EventEmitter {
114
+ }
115
+ /**
116
+ * An abstraction over local hardware or network resources.
117
+ *
118
+ * `open()` acquires the resource (serial port, TCP socket, UDP socket, etc.).
119
+ * Once ready, it emits `connect` with an {@link AbstractPhysicalConnection},
120
+ * unifying serial, TCP client, TCP server, and UDP under a single
121
+ * "connection-oriented" model similar to a TCP server accepting sockets.
122
+ */
123
123
  class AbstractPhysicalLayer extends EventEmitter {
124
+ is(type) {
125
+ return this.TYPE === type;
126
+ }
127
+ }
128
+
129
+ exports.PhysicalState = void 0;
130
+ (function (PhysicalState) {
131
+ PhysicalState["OPENING"] = "opening";
132
+ PhysicalState["OPEN"] = "open";
133
+ PhysicalState["CLOSING"] = "closing";
134
+ PhysicalState["CLOSED"] = "closed";
135
+ })(exports.PhysicalState || (exports.PhysicalState = {}));
136
+ exports.PhysicalConnectionState = void 0;
137
+ (function (PhysicalConnectionState) {
138
+ PhysicalConnectionState["CONNECTED"] = "connected";
139
+ PhysicalConnectionState["DESTROYING"] = "destroying";
140
+ PhysicalConnectionState["DESTROYED"] = "destroyed";
141
+ })(exports.PhysicalConnectionState || (exports.PhysicalConnectionState = {}));
142
+
143
+ class SerialPhysicalConnection extends AbstractPhysicalConnection {
144
+ get state() {
145
+ return this._state;
146
+ }
147
+ get physicalLayer() {
148
+ return this._physicalLayer;
149
+ }
150
+ constructor(physicalLayer, serialport) {
151
+ super();
152
+ Object.defineProperty(this, "_state", {
153
+ enumerable: true,
154
+ configurable: true,
155
+ writable: true,
156
+ value: exports.PhysicalConnectionState.CONNECTED
157
+ });
158
+ Object.defineProperty(this, "_physicalLayer", {
159
+ enumerable: true,
160
+ configurable: true,
161
+ writable: true,
162
+ value: void 0
163
+ });
164
+ Object.defineProperty(this, "_serialport", {
165
+ enumerable: true,
166
+ configurable: true,
167
+ writable: true,
168
+ value: void 0
169
+ });
170
+ Object.defineProperty(this, "_destroyPromise", {
171
+ enumerable: true,
172
+ configurable: true,
173
+ writable: true,
174
+ value: null
175
+ });
176
+ Object.defineProperty(this, "_resolveDestroy", {
177
+ enumerable: true,
178
+ configurable: true,
179
+ writable: true,
180
+ value: null
181
+ });
182
+ Object.defineProperty(this, "_cleanupFns", {
183
+ enumerable: true,
184
+ configurable: true,
185
+ writable: true,
186
+ value: new Set()
187
+ });
188
+ this._physicalLayer = physicalLayer;
189
+ this._serialport = serialport;
190
+ const onData = (chunk) => {
191
+ if (this.state !== exports.PhysicalConnectionState.CONNECTED) {
192
+ return;
193
+ }
194
+ this.emit('data', chunk);
195
+ };
196
+ serialport.on('data', onData);
197
+ this._cleanupFns.add(() => serialport.off('data', onData));
198
+ const onSerialError = (err) => {
199
+ this.physicalLayer.emit('error', err);
200
+ };
201
+ serialport.on('error', onSerialError);
202
+ this._cleanupFns.add(() => serialport.off('error', onSerialError));
203
+ const cleanupClose = () => serialport.off('close', onClose);
204
+ const onClose = () => {
205
+ var _a;
206
+ this._cleanupFns.delete(cleanupClose);
207
+ this._state = exports.PhysicalConnectionState.DESTROYED;
208
+ this._destroyPromise = null;
209
+ for (const fn of this._cleanupFns) {
210
+ fn();
211
+ }
212
+ this._cleanupFns.clear();
213
+ this.emit('close');
214
+ (_a = this._resolveDestroy) === null || _a === void 0 ? void 0 : _a.call(this);
215
+ this._resolveDestroy = null;
216
+ };
217
+ serialport.once('close', onClose);
218
+ this._cleanupFns.add(cleanupClose);
219
+ }
220
+ write(data) {
221
+ return new Promise((resolve, reject) => {
222
+ if (this.state === exports.PhysicalConnectionState.CONNECTED) {
223
+ this._serialport.write(data, (error) => {
224
+ if (error) {
225
+ reject(error);
226
+ }
227
+ else {
228
+ resolve();
229
+ }
230
+ });
231
+ }
232
+ else {
233
+ reject(new Error('Connection is not connected'));
234
+ }
235
+ });
236
+ }
237
+ destroy() {
238
+ if (this.state === exports.PhysicalConnectionState.DESTROYED) {
239
+ return Promise.resolve();
240
+ }
241
+ if (this.state === exports.PhysicalConnectionState.DESTROYING) {
242
+ return this._destroyPromise;
243
+ }
244
+ this._state = exports.PhysicalConnectionState.DESTROYING;
245
+ this._destroyPromise = new Promise((resolve, reject) => {
246
+ this._resolveDestroy = resolve;
247
+ this._serialport.close((err) => {
248
+ if (err) {
249
+ // Design choice: serialport.close() only rejects in extreme hardware
250
+ // deadlock scenarios (e.g., stuck USB-to-serial adapter). At that
251
+ // point the device is unrecoverable from software; propagating the
252
+ // error is the best we can do.
253
+ this._destroyPromise = null;
254
+ this._resolveDestroy = null;
255
+ reject(err);
256
+ }
257
+ });
258
+ });
259
+ return this._destroyPromise;
260
+ }
261
+ }
262
+ class SerialPhysicalLayer extends AbstractPhysicalLayer {
263
+ get state() {
264
+ return this._state;
265
+ }
266
+ get serialport() {
267
+ return this._serialport;
268
+ }
269
+ get path() {
270
+ return this._path;
271
+ }
272
+ get baudRate() {
273
+ return this._baudRate;
274
+ }
275
+ constructor(options) {
276
+ super();
277
+ Object.defineProperty(this, "TYPE", {
278
+ enumerable: true,
279
+ configurable: true,
280
+ writable: true,
281
+ value: 'SERIAL'
282
+ });
283
+ Object.defineProperty(this, "_state", {
284
+ enumerable: true,
285
+ configurable: true,
286
+ writable: true,
287
+ value: exports.PhysicalState.CLOSED
288
+ });
289
+ Object.defineProperty(this, "_connections", {
290
+ enumerable: true,
291
+ configurable: true,
292
+ writable: true,
293
+ value: new Set()
294
+ });
295
+ Object.defineProperty(this, "_serialport", {
296
+ enumerable: true,
297
+ configurable: true,
298
+ writable: true,
299
+ value: null
300
+ });
301
+ Object.defineProperty(this, "_serialportOpts", {
302
+ enumerable: true,
303
+ configurable: true,
304
+ writable: true,
305
+ value: void 0
306
+ });
307
+ Object.defineProperty(this, "_path", {
308
+ enumerable: true,
309
+ configurable: true,
310
+ writable: true,
311
+ value: void 0
312
+ });
313
+ Object.defineProperty(this, "_baudRate", {
314
+ enumerable: true,
315
+ configurable: true,
316
+ writable: true,
317
+ value: void 0
318
+ });
319
+ Object.defineProperty(this, "_openPromise", {
320
+ enumerable: true,
321
+ configurable: true,
322
+ writable: true,
323
+ value: null
324
+ });
325
+ Object.defineProperty(this, "_closePromise", {
326
+ enumerable: true,
327
+ configurable: true,
328
+ writable: true,
329
+ value: null
330
+ });
331
+ Object.defineProperty(this, "_resolveClose", {
332
+ enumerable: true,
333
+ configurable: true,
334
+ writable: true,
335
+ value: null
336
+ });
337
+ Object.defineProperty(this, "_cleanupFns", {
338
+ enumerable: true,
339
+ configurable: true,
340
+ writable: true,
341
+ value: new Set()
342
+ });
343
+ this._serialportOpts = options;
344
+ this._path = options.path;
345
+ this._baudRate = options.baudRate;
346
+ }
347
+ open() {
348
+ if (this.state === exports.PhysicalState.OPEN) {
349
+ return Promise.resolve();
350
+ }
351
+ if (this.state === exports.PhysicalState.OPENING) {
352
+ return this._openPromise;
353
+ }
354
+ if (this.state === exports.PhysicalState.CLOSING) {
355
+ return Promise.reject(new Error('Port is closing'));
356
+ }
357
+ this._state = exports.PhysicalState.OPENING;
358
+ this._openPromise = new Promise((resolve, reject) => {
359
+ const serialport$1 = new serialport.SerialPort(Object.assign(Object.assign({}, this._serialportOpts), { autoOpen: false }));
360
+ this._serialport = serialport$1;
361
+ serialport$1.open((err) => {
362
+ if (err) {
363
+ this._state = exports.PhysicalState.CLOSED;
364
+ this._openPromise = null;
365
+ this._serialport = null;
366
+ reject(err);
367
+ }
368
+ else {
369
+ this._state = exports.PhysicalState.OPEN;
370
+ this._openPromise = null;
371
+ this.emit('open');
372
+ resolve();
373
+ const connection = new SerialPhysicalConnection(this, serialport$1);
374
+ this._connections.add(connection);
375
+ const cleanupConnectionClose = () => connection.off('close', onConnectionClose);
376
+ const onConnectionClose = () => {
377
+ var _a;
378
+ this._cleanupFns.delete(cleanupConnectionClose);
379
+ this._connections.delete(connection);
380
+ this._state = exports.PhysicalState.CLOSED;
381
+ this._closePromise = null;
382
+ for (const fn of this._cleanupFns) {
383
+ fn();
384
+ }
385
+ this._cleanupFns.clear();
386
+ this._serialport = null;
387
+ this.emit('close');
388
+ (_a = this._resolveClose) === null || _a === void 0 ? void 0 : _a.call(this);
389
+ this._resolveClose = null;
390
+ };
391
+ connection.once('close', onConnectionClose);
392
+ this._cleanupFns.add(cleanupConnectionClose);
393
+ this.emit('connect', connection);
394
+ }
395
+ });
396
+ });
397
+ return this._openPromise;
398
+ }
399
+ close() {
400
+ if (this.state === exports.PhysicalState.CLOSED) {
401
+ return Promise.resolve();
402
+ }
403
+ if (this.state === exports.PhysicalState.CLOSING) {
404
+ return this._closePromise;
405
+ }
406
+ if (this.state === exports.PhysicalState.OPENING) {
407
+ return Promise.reject(new Error('Port is opening'));
408
+ }
409
+ this._state = exports.PhysicalState.CLOSING;
410
+ this._closePromise = new Promise((resolve, reject) => {
411
+ this._resolveClose = resolve;
412
+ Promise.all(Array.from(this._connections).map((connection) => connection.destroy())).catch((err) => {
413
+ this._resolveClose = null;
414
+ reject(err);
415
+ });
416
+ });
417
+ return this._closePromise;
418
+ }
419
+ }
420
+
421
+ class TcpPhysicalConnection extends AbstractPhysicalConnection {
422
+ get state() {
423
+ return this._state;
424
+ }
425
+ get physicalLayer() {
426
+ return this._physicalLayer;
427
+ }
428
+ constructor(physicalLayer, socket) {
429
+ super();
430
+ Object.defineProperty(this, "_state", {
431
+ enumerable: true,
432
+ configurable: true,
433
+ writable: true,
434
+ value: exports.PhysicalConnectionState.CONNECTED
435
+ });
436
+ Object.defineProperty(this, "_physicalLayer", {
437
+ enumerable: true,
438
+ configurable: true,
439
+ writable: true,
440
+ value: void 0
441
+ });
442
+ Object.defineProperty(this, "_socket", {
443
+ enumerable: true,
444
+ configurable: true,
445
+ writable: true,
446
+ value: void 0
447
+ });
448
+ Object.defineProperty(this, "_destroyPromise", {
449
+ enumerable: true,
450
+ configurable: true,
451
+ writable: true,
452
+ value: null
453
+ });
454
+ Object.defineProperty(this, "_resolveDestroy", {
455
+ enumerable: true,
456
+ configurable: true,
457
+ writable: true,
458
+ value: null
459
+ });
460
+ Object.defineProperty(this, "_cleanupFns", {
461
+ enumerable: true,
462
+ configurable: true,
463
+ writable: true,
464
+ value: new Set()
465
+ });
466
+ this._physicalLayer = physicalLayer;
467
+ this._socket = socket;
468
+ const onData = (chunk) => {
469
+ if (this.state !== exports.PhysicalConnectionState.CONNECTED) {
470
+ return;
471
+ }
472
+ this.emit('data', chunk);
473
+ };
474
+ socket.on('data', onData);
475
+ this._cleanupFns.add(() => socket.off('data', onData));
476
+ const onSocketError = (err) => {
477
+ this.physicalLayer.emit('error', err);
478
+ };
479
+ socket.on('error', onSocketError);
480
+ this._cleanupFns.add(() => socket.off('error', onSocketError));
481
+ const cleanupClose = () => socket.off('close', onClose);
482
+ const onClose = () => {
483
+ var _a;
484
+ this._cleanupFns.delete(cleanupClose);
485
+ this._state = exports.PhysicalConnectionState.DESTROYED;
486
+ this._destroyPromise = null;
487
+ for (const fn of this._cleanupFns) {
488
+ fn();
489
+ }
490
+ this._cleanupFns.clear();
491
+ this.emit('close');
492
+ (_a = this._resolveDestroy) === null || _a === void 0 ? void 0 : _a.call(this);
493
+ this._resolveDestroy = null;
494
+ };
495
+ socket.once('close', onClose);
496
+ this._cleanupFns.add(cleanupClose);
497
+ }
498
+ write(data) {
499
+ return new Promise((resolve, reject) => {
500
+ if (this.state === exports.PhysicalConnectionState.CONNECTED) {
501
+ this._socket.write(data, (error) => {
502
+ if (error) {
503
+ reject(error);
504
+ }
505
+ else {
506
+ resolve();
507
+ }
508
+ });
509
+ }
510
+ else {
511
+ reject(new Error('Connection is not connected'));
512
+ }
513
+ });
514
+ }
515
+ destroy() {
516
+ if (this.state === exports.PhysicalConnectionState.DESTROYED) {
517
+ return Promise.resolve();
518
+ }
519
+ if (this.state === exports.PhysicalConnectionState.DESTROYING) {
520
+ return this._destroyPromise;
521
+ }
522
+ this._state = exports.PhysicalConnectionState.DESTROYING;
523
+ this._destroyPromise = new Promise((resolve) => {
524
+ this._resolveDestroy = resolve;
525
+ this._socket.destroy();
526
+ });
527
+ return this._destroyPromise;
528
+ }
529
+ }
530
+
531
+ class TcpClientPhysicalLayer extends AbstractPhysicalLayer {
532
+ get state() {
533
+ return this._state;
534
+ }
535
+ get socket() {
536
+ return this._socket;
537
+ }
538
+ constructor(options) {
539
+ super();
540
+ Object.defineProperty(this, "TYPE", {
541
+ enumerable: true,
542
+ configurable: true,
543
+ writable: true,
544
+ value: 'TCP_CLIENT'
545
+ });
546
+ Object.defineProperty(this, "_state", {
547
+ enumerable: true,
548
+ configurable: true,
549
+ writable: true,
550
+ value: exports.PhysicalState.CLOSED
551
+ });
552
+ Object.defineProperty(this, "_connections", {
553
+ enumerable: true,
554
+ configurable: true,
555
+ writable: true,
556
+ value: new Set()
557
+ });
558
+ Object.defineProperty(this, "_socket", {
559
+ enumerable: true,
560
+ configurable: true,
561
+ writable: true,
562
+ value: null
563
+ });
564
+ Object.defineProperty(this, "_socketOpts", {
565
+ enumerable: true,
566
+ configurable: true,
567
+ writable: true,
568
+ value: void 0
569
+ });
570
+ Object.defineProperty(this, "_openPromise", {
571
+ enumerable: true,
572
+ configurable: true,
573
+ writable: true,
574
+ value: null
575
+ });
576
+ Object.defineProperty(this, "_closePromise", {
577
+ enumerable: true,
578
+ configurable: true,
579
+ writable: true,
580
+ value: null
581
+ });
582
+ Object.defineProperty(this, "_resolveClose", {
583
+ enumerable: true,
584
+ configurable: true,
585
+ writable: true,
586
+ value: null
587
+ });
588
+ Object.defineProperty(this, "_cleanupFns", {
589
+ enumerable: true,
590
+ configurable: true,
591
+ writable: true,
592
+ value: new Set()
593
+ });
594
+ this._socketOpts = options;
595
+ }
596
+ open(options) {
597
+ if (this.state === exports.PhysicalState.OPEN) {
598
+ return Promise.resolve();
599
+ }
600
+ if (this.state === exports.PhysicalState.OPENING) {
601
+ return this._openPromise;
602
+ }
603
+ if (this.state === exports.PhysicalState.CLOSING) {
604
+ return Promise.reject(new Error('Port is closing'));
605
+ }
606
+ this._state = exports.PhysicalState.OPENING;
607
+ this._openPromise = new Promise((resolve, reject) => {
608
+ const socket = new node_net.Socket(this._socketOpts);
609
+ this._socket = socket;
610
+ const onConnect = () => {
611
+ socket.off('error', onError);
612
+ this._state = exports.PhysicalState.OPEN;
613
+ this._openPromise = null;
614
+ this.emit('open');
615
+ resolve();
616
+ const connection = new TcpPhysicalConnection(this, socket);
617
+ this._connections.add(connection);
618
+ const cleanupConnectionClose = () => connection.off('close', onConnectionClose);
619
+ const onConnectionClose = () => {
620
+ var _a;
621
+ this._cleanupFns.delete(cleanupConnectionClose);
622
+ this._connections.delete(connection);
623
+ this._state = exports.PhysicalState.CLOSED;
624
+ this._closePromise = null;
625
+ for (const fn of this._cleanupFns) {
626
+ fn();
627
+ }
628
+ this._cleanupFns.clear();
629
+ this._socket = null;
630
+ this.emit('close');
631
+ (_a = this._resolveClose) === null || _a === void 0 ? void 0 : _a.call(this);
632
+ this._resolveClose = null;
633
+ };
634
+ connection.once('close', onConnectionClose);
635
+ this._cleanupFns.add(cleanupConnectionClose);
636
+ this.emit('connect', connection);
637
+ };
638
+ const onError = (err) => {
639
+ socket.off('connect', onConnect);
640
+ this._state = exports.PhysicalState.CLOSED;
641
+ this._openPromise = null;
642
+ this._socket = null;
643
+ reject(err);
644
+ };
645
+ socket.once('connect', onConnect);
646
+ socket.once('error', onError);
647
+ socket.connect(options !== null && options !== void 0 ? options : { port: 502 });
648
+ });
649
+ return this._openPromise;
650
+ }
651
+ close() {
652
+ if (this.state === exports.PhysicalState.CLOSED) {
653
+ return Promise.resolve();
654
+ }
655
+ if (this.state === exports.PhysicalState.CLOSING) {
656
+ return this._closePromise;
657
+ }
658
+ if (this.state === exports.PhysicalState.OPENING) {
659
+ return Promise.reject(new Error('Port is opening'));
660
+ }
661
+ this._state = exports.PhysicalState.CLOSING;
662
+ this._closePromise = new Promise((resolve, reject) => {
663
+ this._resolveClose = resolve;
664
+ Promise.all(Array.from(this._connections).map((connection) => connection.destroy())).catch((err) => {
665
+ this._resolveClose = null;
666
+ reject(err);
667
+ });
668
+ });
669
+ return this._closePromise;
670
+ }
124
671
  }
125
672
 
126
673
  function inRange(n, [min, max]) {
127
674
  return n >= min && n <= max;
128
675
  }
676
+ function isRangeArray(range) {
677
+ return Array.isArray(range[0]);
678
+ }
129
679
  function checkRange(value, range) {
130
- if (!range) {
680
+ if (!range || range.length === 0) {
131
681
  return true;
132
682
  }
133
683
  const values = Array.isArray(value) ? value : [value];
134
- if (typeof range[0] === 'number' && typeof range[1] === 'number') {
135
- const [min, max] = range;
136
- const [lo, hi] = min <= max ? [min, max] : [max, min];
137
- return values.every((n) => inRange(n, [lo, hi]));
138
- }
139
- else if (range.length > 0) {
684
+ if (isRangeArray(range)) {
140
685
  for (const r of range) {
141
686
  const [min, max] = r;
142
687
  const [lo, hi] = min <= max ? [min, max] : [max, min];
@@ -146,7 +691,9 @@ function checkRange(value, range) {
146
691
  }
147
692
  return false;
148
693
  }
149
- return true;
694
+ const [min, max] = range;
695
+ const [lo, hi] = min <= max ? [min, max] : [max, min];
696
+ return values.every((n) => inRange(n, [lo, hi]));
150
697
  }
151
698
 
152
699
  const TABLE = [
@@ -175,11 +722,6 @@ function crc(data, seed = 0xffff) {
175
722
  return crc;
176
723
  }
177
724
 
178
- /** Cross-process unique id: `${prefix}-${uuid-v4}`. */
179
- function genConnectionId(prefix) {
180
- return `${prefix}-${node_crypto.randomUUID()}`;
181
- }
182
-
183
725
  /**
184
726
  * Convert a number of bits to milliseconds at a given baud rate.
185
727
  *
@@ -305,15 +847,64 @@ function predictFc43_14Response(buffer) {
305
847
  return { kind: 'length', length: offset + 2 };
306
848
  }
307
849
 
308
- class SerialPhysicalLayer extends AbstractPhysicalLayer {
309
- get isOpen() {
310
- return this._serialport.isOpen;
850
+ /**
851
+ * Resolve Modbus RTU timing parameters from user options into milliseconds.
852
+ *
853
+ * - `intervalBetweenFrames` (t3.5): if omitted and `baudRate` is present,
854
+ * defaults to 38.5 bits per Modbus V1.02 §2.5.1.1.
855
+ * - `interCharTimeout` (t1.5): opt-in; only resolved when explicitly provided.
856
+ *
857
+ * Per the spec, at baud rates > 19200 fixed values are used
858
+ * (1.75 ms for t3.5, 0.75 ms for t1.5) regardless of the bit value.
859
+ */
860
+ function resolveRtuTiming(opts = {}, baudRate) {
861
+ var _a, _b, _c;
862
+ let intervalBetweenFrames = 0;
863
+ let interCharTimeout = 0;
864
+ if (((_a = opts.intervalBetweenFrames) === null || _a === void 0 ? void 0 : _a.unit) === 'ms') {
865
+ intervalBetweenFrames = opts.intervalBetweenFrames.value;
311
866
  }
312
- get destroyed() {
313
- return this._destroyed;
867
+ else if (baudRate !== undefined) {
868
+ const bitValue = (_c = (_b = opts.intervalBetweenFrames) === null || _b === void 0 ? void 0 : _b.value) !== null && _c !== void 0 ? _c : 38.5;
869
+ intervalBetweenFrames = baudRate > 19200 ? 1.75 : Math.ceil(bitsToMs(baudRate, bitValue));
314
870
  }
315
- get baudRate() {
316
- return this._baudRate;
871
+ if (opts.interCharTimeout) {
872
+ if (opts.interCharTimeout.unit === 'ms') {
873
+ interCharTimeout = opts.interCharTimeout.value;
874
+ }
875
+ else if (baudRate !== undefined) {
876
+ interCharTimeout = baudRate > 19200 ? 0.75 : Math.ceil(bitsToMs(baudRate, opts.interCharTimeout.value));
877
+ }
878
+ }
879
+ return { intervalBetweenFrames, interCharTimeout };
880
+ }
881
+
882
+ /**
883
+ * Normalize an IP address by stripping the IPv4-mapped IPv6 prefix.
884
+ * This ensures consistent comparison of addresses like `::ffff:192.168.1.1`.
885
+ */
886
+ function normalizeAddress(address = '') {
887
+ return address.replace(/^::ffff:/, '');
888
+ }
889
+ /**
890
+ * Check whether a remote address is allowed by the given whitelist.
891
+ * IPv4-mapped IPv6 addresses are normalized before comparison.
892
+ * Returns `true` when whitelist is absent or empty.
893
+ */
894
+ function isWhitelisted(address, whitelist) {
895
+ if (!whitelist || whitelist.length === 0) {
896
+ return true;
897
+ }
898
+ const normalized = normalizeAddress(address);
899
+ return whitelist.includes(normalized);
900
+ }
901
+
902
+ class TcpServerPhysicalLayer extends AbstractPhysicalLayer {
903
+ get state() {
904
+ return this._state;
905
+ }
906
+ get server() {
907
+ return this._server;
317
908
  }
318
909
  constructor(options) {
319
910
  super();
@@ -321,327 +912,502 @@ class SerialPhysicalLayer extends AbstractPhysicalLayer {
321
912
  enumerable: true,
322
913
  configurable: true,
323
914
  writable: true,
324
- value: 'SERIAL'
915
+ value: 'TCP_SERVER'
325
916
  });
326
- Object.defineProperty(this, "_serialport", {
917
+ Object.defineProperty(this, "_state", {
918
+ enumerable: true,
919
+ configurable: true,
920
+ writable: true,
921
+ value: exports.PhysicalState.CLOSED
922
+ });
923
+ Object.defineProperty(this, "_connections", {
924
+ enumerable: true,
925
+ configurable: true,
926
+ writable: true,
927
+ value: new Set()
928
+ });
929
+ Object.defineProperty(this, "_server", {
930
+ enumerable: true,
931
+ configurable: true,
932
+ writable: true,
933
+ value: null
934
+ });
935
+ Object.defineProperty(this, "_opts", {
327
936
  enumerable: true,
328
937
  configurable: true,
329
938
  writable: true,
330
939
  value: void 0
331
940
  });
332
- Object.defineProperty(this, "_connection", {
941
+ Object.defineProperty(this, "_openPromise", {
333
942
  enumerable: true,
334
943
  configurable: true,
335
944
  writable: true,
336
945
  value: null
337
946
  });
338
- Object.defineProperty(this, "_isOpening", {
947
+ Object.defineProperty(this, "_closePromise", {
339
948
  enumerable: true,
340
949
  configurable: true,
341
950
  writable: true,
342
- value: false
951
+ value: null
343
952
  });
344
- Object.defineProperty(this, "_destroyed", {
953
+ Object.defineProperty(this, "_resolveClose", {
345
954
  enumerable: true,
346
955
  configurable: true,
347
956
  writable: true,
348
- value: false
957
+ value: null
349
958
  });
350
- Object.defineProperty(this, "_baudRate", {
959
+ Object.defineProperty(this, "_cleanupFns", {
351
960
  enumerable: true,
352
961
  configurable: true,
353
962
  writable: true,
354
- value: void 0
963
+ value: new Set()
355
964
  });
356
- this._serialport = new serialport.SerialPort(Object.assign(Object.assign({}, options), { autoOpen: false }));
357
- this._baudRate = options.baudRate;
965
+ this._opts = options !== null && options !== void 0 ? options : {};
358
966
  }
359
- open() {
360
- if (this._destroyed) {
361
- return Promise.reject(new ModbusError(ModbusErrorCode.PORT_DESTROYED, 'Port is destroyed'));
967
+ open(options) {
968
+ if (this.state === exports.PhysicalState.OPEN) {
969
+ return Promise.resolve();
362
970
  }
363
- if (this.isOpen || this._isOpening) {
364
- return Promise.reject(new ModbusError(ModbusErrorCode.PORT_ALREADY_OPEN, 'Port is already open'));
971
+ if (this.state === exports.PhysicalState.OPENING) {
972
+ return this._openPromise;
365
973
  }
366
- this._isOpening = true;
367
- const connection = { id: genConnectionId('serial') };
368
- this._connection = connection;
369
- return new Promise((resolve, reject) => {
370
- this._serialport.open((error) => {
371
- if (error) {
372
- this._isOpening = false;
373
- if (this._connection === connection) {
374
- this._connection = null;
375
- }
376
- reject(error);
974
+ if (this.state === exports.PhysicalState.CLOSING) {
975
+ return Promise.reject(new Error('Port is closing'));
976
+ }
977
+ this._state = exports.PhysicalState.OPENING;
978
+ this._openPromise = new Promise((resolve, reject) => {
979
+ var _a;
980
+ const server = node_net.createServer(this._opts.serverOpts, (socket) => {
981
+ if (!isWhitelisted(socket.remoteAddress, this._opts.whitelist)) {
982
+ socket.destroy();
377
983
  return;
378
984
  }
379
- this._isOpening = false;
380
- this._serialport.on('data', (data) => {
381
- this.emit('data', data, (d) => this.write(d), connection);
382
- });
383
- this._serialport.on('error', (err) => {
384
- this.emit('error', err);
385
- });
386
- this._serialport.once('close', () => {
387
- if (this._connection === connection) {
388
- this._serialport.removeAllListeners();
389
- this._connection = null;
390
- this.emit('connection-close', connection);
391
- this.emit('close');
985
+ let cleanupTimeout = null;
986
+ if (this._opts.idleTimeout) {
987
+ socket.setTimeout(this._opts.idleTimeout);
988
+ cleanupTimeout = () => socket.off('timeout', onTimeout);
989
+ const onTimeout = () => {
990
+ this._cleanupFns.delete(cleanupTimeout);
991
+ socket.destroy();
992
+ };
993
+ socket.once('timeout', onTimeout);
994
+ this._cleanupFns.add(cleanupTimeout);
995
+ }
996
+ const connection = new TcpPhysicalConnection(this, socket);
997
+ this._connections.add(connection);
998
+ const cleanupConnectionClose = () => connection.off('close', onConnectionClose);
999
+ const onConnectionClose = () => {
1000
+ if (cleanupTimeout) {
1001
+ cleanupTimeout();
1002
+ this._cleanupFns.delete(cleanupTimeout);
392
1003
  }
393
- });
1004
+ this._cleanupFns.delete(cleanupConnectionClose);
1005
+ this._connections.delete(connection);
1006
+ };
1007
+ connection.once('close', onConnectionClose);
1008
+ this._cleanupFns.add(cleanupConnectionClose);
1009
+ this.emit('connect', connection);
1010
+ });
1011
+ this._server = server;
1012
+ if (this._opts.maxConnections !== undefined) {
1013
+ server.maxConnections = this._opts.maxConnections;
1014
+ }
1015
+ const onConnect = () => {
1016
+ server.off('error', onError);
1017
+ this._state = exports.PhysicalState.OPEN;
1018
+ this._openPromise = null;
1019
+ this.emit('open');
394
1020
  resolve();
1021
+ {
1022
+ const onError = (err) => {
1023
+ this.emit('error', err);
1024
+ };
1025
+ server.on('error', onError);
1026
+ this._cleanupFns.add(() => server.off('error', onError));
1027
+ const cleanupClose = () => server.off('close', onClose);
1028
+ const onClose = () => {
1029
+ var _a;
1030
+ this._cleanupFns.delete(cleanupClose);
1031
+ this._state = exports.PhysicalState.CLOSED;
1032
+ this._closePromise = null;
1033
+ for (const fn of this._cleanupFns) {
1034
+ fn();
1035
+ }
1036
+ this._cleanupFns.clear();
1037
+ this._server = null;
1038
+ this.emit('close');
1039
+ (_a = this._resolveClose) === null || _a === void 0 ? void 0 : _a.call(this);
1040
+ this._resolveClose = null;
1041
+ };
1042
+ server.once('close', onClose);
1043
+ this._cleanupFns.add(cleanupClose);
1044
+ }
1045
+ };
1046
+ const onError = (err) => {
1047
+ server.off('listening', onConnect);
1048
+ this._state = exports.PhysicalState.CLOSED;
1049
+ this._openPromise = null;
1050
+ this._server = null;
1051
+ reject(err);
1052
+ };
1053
+ server.once('listening', onConnect);
1054
+ server.once('error', onError);
1055
+ server.listen(Object.assign(Object.assign({}, options), { port: (_a = options === null || options === void 0 ? void 0 : options.port) !== null && _a !== void 0 ? _a : 502 }));
1056
+ });
1057
+ return this._openPromise;
1058
+ }
1059
+ close() {
1060
+ if (this.state === exports.PhysicalState.CLOSED) {
1061
+ return Promise.resolve();
1062
+ }
1063
+ if (this.state === exports.PhysicalState.CLOSING) {
1064
+ return this._closePromise;
1065
+ }
1066
+ if (this.state === exports.PhysicalState.OPENING) {
1067
+ return Promise.reject(new Error('Port is opening'));
1068
+ }
1069
+ this._state = exports.PhysicalState.CLOSING;
1070
+ this._closePromise = new Promise((resolve, reject) => {
1071
+ this._resolveClose = resolve;
1072
+ this.server.close();
1073
+ Promise.all(Array.from(this._connections).map((connection) => connection.destroy())).catch((err) => {
1074
+ this._resolveClose = null;
1075
+ reject(err);
395
1076
  });
396
1077
  });
1078
+ return this._closePromise;
1079
+ }
1080
+ }
1081
+
1082
+ class UdpClientPhysicalConnection extends AbstractPhysicalConnection {
1083
+ get state() {
1084
+ return this._state;
1085
+ }
1086
+ get physicalLayer() {
1087
+ return this._physicalLayer;
1088
+ }
1089
+ constructor(physicalLayer, socket) {
1090
+ super();
1091
+ Object.defineProperty(this, "_state", {
1092
+ enumerable: true,
1093
+ configurable: true,
1094
+ writable: true,
1095
+ value: exports.PhysicalConnectionState.CONNECTED
1096
+ });
1097
+ Object.defineProperty(this, "_physicalLayer", {
1098
+ enumerable: true,
1099
+ configurable: true,
1100
+ writable: true,
1101
+ value: void 0
1102
+ });
1103
+ Object.defineProperty(this, "_socket", {
1104
+ enumerable: true,
1105
+ configurable: true,
1106
+ writable: true,
1107
+ value: void 0
1108
+ });
1109
+ Object.defineProperty(this, "_destroyPromise", {
1110
+ enumerable: true,
1111
+ configurable: true,
1112
+ writable: true,
1113
+ value: null
1114
+ });
1115
+ Object.defineProperty(this, "_resolveDestroy", {
1116
+ enumerable: true,
1117
+ configurable: true,
1118
+ writable: true,
1119
+ value: null
1120
+ });
1121
+ Object.defineProperty(this, "_cleanupFns", {
1122
+ enumerable: true,
1123
+ configurable: true,
1124
+ writable: true,
1125
+ value: new Set()
1126
+ });
1127
+ this._physicalLayer = physicalLayer;
1128
+ this._socket = socket;
1129
+ const onMessage = (msg) => {
1130
+ if (this.state !== exports.PhysicalConnectionState.CONNECTED) {
1131
+ return;
1132
+ }
1133
+ this.emit('data', msg);
1134
+ };
1135
+ socket.on('message', onMessage);
1136
+ this._cleanupFns.add(() => socket.off('message', onMessage));
1137
+ const onSocketError = (err) => {
1138
+ this.physicalLayer.emit('error', err);
1139
+ };
1140
+ socket.on('error', onSocketError);
1141
+ this._cleanupFns.add(() => socket.off('error', onSocketError));
1142
+ const cleanupClose = () => socket.off('close', onClose);
1143
+ const onClose = () => {
1144
+ var _a;
1145
+ this._cleanupFns.delete(cleanupClose);
1146
+ this._state = exports.PhysicalConnectionState.DESTROYED;
1147
+ this._destroyPromise = null;
1148
+ for (const fn of this._cleanupFns) {
1149
+ fn();
1150
+ }
1151
+ this._cleanupFns.clear();
1152
+ this.emit('close');
1153
+ (_a = this._resolveDestroy) === null || _a === void 0 ? void 0 : _a.call(this);
1154
+ this._resolveDestroy = null;
1155
+ };
1156
+ socket.once('close', onClose);
1157
+ this._cleanupFns.add(cleanupClose);
397
1158
  }
398
1159
  write(data) {
399
1160
  return new Promise((resolve, reject) => {
400
- if (this.isOpen) {
401
- this._serialport.write(data, (error) => {
1161
+ if (this.state === exports.PhysicalConnectionState.CONNECTED) {
1162
+ this._socket.send(data, (error) => {
402
1163
  if (error) {
403
1164
  reject(error);
404
1165
  }
405
1166
  else {
406
- this.emit('write', data);
407
1167
  resolve();
408
1168
  }
409
1169
  });
410
1170
  }
411
1171
  else {
412
- reject(new ModbusError(ModbusErrorCode.PORT_NOT_OPEN, 'Port is not open'));
1172
+ reject(new Error('Connection is not connected'));
413
1173
  }
414
1174
  });
415
1175
  }
416
- close() {
417
- if (!this._serialport.isOpen) {
418
- return Promise.resolve();
419
- }
420
- return new Promise((resolve) => {
421
- this._serialport.once('close', () => resolve());
422
- this._serialport.close();
423
- });
424
- }
425
1176
  destroy() {
426
- if (this._destroyed) {
1177
+ if (this.state === exports.PhysicalConnectionState.DESTROYED) {
427
1178
  return Promise.resolve();
428
1179
  }
429
- this._destroyed = true;
430
- return this.close().then(() => {
431
- this.removeAllListeners();
1180
+ if (this.state === exports.PhysicalConnectionState.DESTROYING) {
1181
+ return this._destroyPromise;
1182
+ }
1183
+ this._state = exports.PhysicalConnectionState.DESTROYING;
1184
+ this._destroyPromise = new Promise((resolve) => {
1185
+ this._resolveDestroy = resolve;
1186
+ this._socket.close();
432
1187
  });
1188
+ return this._destroyPromise;
433
1189
  }
434
1190
  }
435
-
436
- class TcpClientPhysicalLayer extends AbstractPhysicalLayer {
437
- get isOpen() {
438
- return this._isOpen;
1191
+ class UdpClientPhysicalLayer extends AbstractPhysicalLayer {
1192
+ get state() {
1193
+ return this._state;
439
1194
  }
440
- get destroyed() {
441
- return this._destroyed;
1195
+ get socket() {
1196
+ return this._socket;
442
1197
  }
443
1198
  constructor(options) {
1199
+ var _a;
444
1200
  super();
445
1201
  Object.defineProperty(this, "TYPE", {
446
1202
  enumerable: true,
447
1203
  configurable: true,
448
1204
  writable: true,
449
- value: 'NET'
1205
+ value: 'UDP_CLIENT'
450
1206
  });
451
- Object.defineProperty(this, "_socket", {
1207
+ Object.defineProperty(this, "_state", {
452
1208
  enumerable: true,
453
1209
  configurable: true,
454
1210
  writable: true,
455
- value: null
1211
+ value: exports.PhysicalState.CLOSED
456
1212
  });
457
- Object.defineProperty(this, "_connection", {
1213
+ Object.defineProperty(this, "_connections", {
1214
+ enumerable: true,
1215
+ configurable: true,
1216
+ writable: true,
1217
+ value: new Set()
1218
+ });
1219
+ Object.defineProperty(this, "_socket", {
458
1220
  enumerable: true,
459
1221
  configurable: true,
460
1222
  writable: true,
461
1223
  value: null
462
1224
  });
463
- Object.defineProperty(this, "_isOpen", {
1225
+ Object.defineProperty(this, "_socketOpts", {
464
1226
  enumerable: true,
465
1227
  configurable: true,
466
1228
  writable: true,
467
- value: false
1229
+ value: void 0
468
1230
  });
469
- Object.defineProperty(this, "_isOpening", {
1231
+ Object.defineProperty(this, "_openPromise", {
470
1232
  enumerable: true,
471
1233
  configurable: true,
472
1234
  writable: true,
473
- value: false
1235
+ value: null
474
1236
  });
475
- Object.defineProperty(this, "_destroyed", {
1237
+ Object.defineProperty(this, "_closePromise", {
476
1238
  enumerable: true,
477
1239
  configurable: true,
478
1240
  writable: true,
479
- value: false
1241
+ value: null
480
1242
  });
481
- Object.defineProperty(this, "_socketOptions", {
1243
+ Object.defineProperty(this, "_resolveClose", {
482
1244
  enumerable: true,
483
1245
  configurable: true,
484
1246
  writable: true,
485
- value: void 0
1247
+ value: null
486
1248
  });
487
- this._socketOptions = options;
488
- }
489
- open(options) {
490
- if (this._destroyed) {
491
- return Promise.reject(new ModbusError(ModbusErrorCode.PORT_DESTROYED, 'Port is destroyed'));
492
- }
493
- if (this._isOpen || this._isOpening) {
494
- return Promise.reject(new ModbusError(ModbusErrorCode.PORT_ALREADY_OPEN, 'Port is already open'));
495
- }
496
- this._isOpening = true;
497
- const socket = new node_net.Socket(this._socketOptions);
498
- const connection = { id: genConnectionId('tcp-client') };
499
- this._socket = socket;
500
- this._connection = connection;
501
- return new Promise((resolve, reject) => {
502
- let connected = false;
503
- socket.on('data', (data) => {
504
- this.emit('data', data, (d) => this.write(d), connection);
505
- });
506
- socket.on('error', (err) => {
507
- if (connected) {
508
- this.emit('error', err);
509
- }
510
- else {
511
- if (this._connection === connection) {
512
- this._isOpening = false;
513
- this._socket = null;
514
- this._connection = null;
515
- }
516
- socket.removeAllListeners();
517
- reject(err);
518
- }
519
- });
520
- socket.once('close', () => {
521
- if (this._connection === connection) {
522
- this._isOpen = false;
523
- this._isOpening = false;
524
- this._socket = null;
525
- this._connection = null;
526
- socket.removeAllListeners();
527
- this.emit('connection-close', connection);
528
- this.emit('close');
529
- }
530
- });
531
- socket.connect(options !== null && options !== void 0 ? options : { port: 502 }, () => {
532
- connected = true;
533
- this._isOpening = false;
534
- this._isOpen = true;
535
- resolve();
536
- });
1249
+ Object.defineProperty(this, "_cleanupFns", {
1250
+ enumerable: true,
1251
+ configurable: true,
1252
+ writable: true,
1253
+ value: new Set()
537
1254
  });
1255
+ this._socketOpts = Object.assign(Object.assign({}, options), { type: (_a = options === null || options === void 0 ? void 0 : options.type) !== null && _a !== void 0 ? _a : 'udp4' });
538
1256
  }
539
- write(data) {
540
- return new Promise((resolve, reject) => {
541
- if (this._isOpen && this._socket) {
542
- this._socket.write(data, (error) => {
543
- if (error) {
544
- reject(error);
545
- }
546
- else {
547
- this.emit('write', data);
548
- resolve();
1257
+ open(remote) {
1258
+ if (this.state === exports.PhysicalState.OPEN) {
1259
+ return Promise.resolve();
1260
+ }
1261
+ if (this.state === exports.PhysicalState.OPENING) {
1262
+ return this._openPromise;
1263
+ }
1264
+ if (this.state === exports.PhysicalState.CLOSING) {
1265
+ return Promise.reject(new Error('Port is closing'));
1266
+ }
1267
+ this._state = exports.PhysicalState.OPENING;
1268
+ this._openPromise = new Promise((resolve, reject) => {
1269
+ const socket = node_dgram.createSocket(this._socketOpts);
1270
+ this._socket = socket;
1271
+ const onListening = () => {
1272
+ var _a;
1273
+ socket.off('error', onError);
1274
+ socket.connect((_a = remote === null || remote === void 0 ? void 0 : remote.port) !== null && _a !== void 0 ? _a : 502, remote === null || remote === void 0 ? void 0 : remote.address);
1275
+ this._state = exports.PhysicalState.OPEN;
1276
+ this._openPromise = null;
1277
+ this.emit('open');
1278
+ resolve();
1279
+ const connection = new UdpClientPhysicalConnection(this, socket);
1280
+ this._connections.add(connection);
1281
+ const cleanupConnectionClose = () => connection.off('close', onConnectionClose);
1282
+ const onConnectionClose = () => {
1283
+ var _a;
1284
+ this._cleanupFns.delete(cleanupConnectionClose);
1285
+ this._connections.delete(connection);
1286
+ this._state = exports.PhysicalState.CLOSED;
1287
+ this._closePromise = null;
1288
+ for (const fn of this._cleanupFns) {
1289
+ fn();
549
1290
  }
550
- });
551
- }
552
- else {
553
- reject(new ModbusError(ModbusErrorCode.PORT_NOT_OPEN, 'Port is not open'));
554
- }
1291
+ this._cleanupFns.clear();
1292
+ this._socket = null;
1293
+ this.emit('close');
1294
+ (_a = this._resolveClose) === null || _a === void 0 ? void 0 : _a.call(this);
1295
+ this._resolveClose = null;
1296
+ };
1297
+ connection.once('close', onConnectionClose);
1298
+ this._cleanupFns.add(cleanupConnectionClose);
1299
+ this.emit('connect', connection);
1300
+ };
1301
+ const onError = (err) => {
1302
+ socket.off('listening', onListening);
1303
+ this._state = exports.PhysicalState.CLOSED;
1304
+ this._openPromise = null;
1305
+ this._socket = null;
1306
+ reject(err);
1307
+ };
1308
+ socket.once('listening', onListening);
1309
+ socket.once('error', onError);
1310
+ socket.bind();
555
1311
  });
1312
+ return this._openPromise;
556
1313
  }
557
1314
  close() {
558
- if (!this._socket) {
1315
+ if (this.state === exports.PhysicalState.CLOSED) {
559
1316
  return Promise.resolve();
560
1317
  }
561
- const socket = this._socket;
562
- return new Promise((resolve) => {
563
- socket.once('close', () => resolve());
564
- socket.destroy();
565
- });
566
- }
567
- destroy() {
568
- if (this._destroyed) {
569
- return Promise.resolve();
1318
+ if (this.state === exports.PhysicalState.CLOSING) {
1319
+ return this._closePromise;
570
1320
  }
571
- this._destroyed = true;
572
- return this.close().then(() => {
573
- this.removeAllListeners();
1321
+ if (this.state === exports.PhysicalState.OPENING) {
1322
+ return Promise.reject(new Error('Port is opening'));
1323
+ }
1324
+ this._state = exports.PhysicalState.CLOSING;
1325
+ this._closePromise = new Promise((resolve, reject) => {
1326
+ this._resolveClose = resolve;
1327
+ Promise.all(Array.from(this._connections).map((connection) => connection.destroy())).catch((err) => {
1328
+ this._resolveClose = null;
1329
+ reject(err);
1330
+ });
574
1331
  });
1332
+ return this._closePromise;
575
1333
  }
576
1334
  }
577
1335
 
578
- class TcpServerPhysicalLayer extends AbstractPhysicalLayer {
579
- get isOpen() {
580
- return this._isOpen;
1336
+ class UdpServerPhysicalConnection extends AbstractPhysicalConnection {
1337
+ get state() {
1338
+ return this._state;
581
1339
  }
582
- get destroyed() {
583
- return this._destroyed;
1340
+ get physicalLayer() {
1341
+ return this._physicalLayer;
584
1342
  }
585
- constructor(options) {
1343
+ constructor(physicalLayer, socket, remote, idleTimeout, messageEventDelegation) {
586
1344
  super();
587
- Object.defineProperty(this, "TYPE", {
588
- enumerable: true,
589
- configurable: true,
590
- writable: true,
591
- value: 'NET'
592
- });
593
- Object.defineProperty(this, "_server", {
1345
+ Object.defineProperty(this, "_state", {
594
1346
  enumerable: true,
595
1347
  configurable: true,
596
1348
  writable: true,
597
- value: null
1349
+ value: exports.PhysicalConnectionState.CONNECTED
598
1350
  });
599
- Object.defineProperty(this, "_isOpen", {
1351
+ Object.defineProperty(this, "_physicalLayer", {
600
1352
  enumerable: true,
601
1353
  configurable: true,
602
1354
  writable: true,
603
- value: false
1355
+ value: void 0
604
1356
  });
605
- Object.defineProperty(this, "_isOpening", {
1357
+ Object.defineProperty(this, "_socket", {
606
1358
  enumerable: true,
607
1359
  configurable: true,
608
1360
  writable: true,
609
- value: false
1361
+ value: void 0
610
1362
  });
611
- Object.defineProperty(this, "_destroyed", {
1363
+ Object.defineProperty(this, "_remote", {
612
1364
  enumerable: true,
613
1365
  configurable: true,
614
1366
  writable: true,
615
- value: false
1367
+ value: void 0
616
1368
  });
617
- Object.defineProperty(this, "_connections", {
1369
+ Object.defineProperty(this, "_idleTid", {
618
1370
  enumerable: true,
619
1371
  configurable: true,
620
1372
  writable: true,
621
- value: new Map()
1373
+ value: null
622
1374
  });
623
- Object.defineProperty(this, "_serverOptions", {
1375
+ Object.defineProperty(this, "_cleanupFns", {
624
1376
  enumerable: true,
625
1377
  configurable: true,
626
1378
  writable: true,
627
- value: void 0
1379
+ value: new Set()
628
1380
  });
629
- this._serverOptions = options;
1381
+ this._physicalLayer = physicalLayer;
1382
+ this._socket = socket;
1383
+ this._remote = remote;
1384
+ const onMessage = (msg) => {
1385
+ if (this.state !== exports.PhysicalConnectionState.CONNECTED) {
1386
+ return;
1387
+ }
1388
+ if (this._idleTid !== null) {
1389
+ clearTimeout(this._idleTid);
1390
+ this._idleTid = null;
1391
+ if (idleTimeout > 0) {
1392
+ this._idleTid = setTimeout(() => {
1393
+ this.destroy();
1394
+ }, idleTimeout);
1395
+ }
1396
+ }
1397
+ this.emit('data', msg);
1398
+ };
1399
+ messageEventDelegation.add(onMessage);
1400
+ this._cleanupFns.add(() => messageEventDelegation.remove(onMessage));
1401
+ if (idleTimeout > 0) {
1402
+ this._idleTid = setTimeout(() => {
1403
+ this.destroy();
1404
+ }, idleTimeout);
1405
+ }
630
1406
  }
631
- open(options) {
632
- if (this._destroyed) {
633
- return Promise.reject(new ModbusError(ModbusErrorCode.PORT_DESTROYED, 'Port is destroyed'));
634
- }
635
- if (this._isOpen || this._isOpening) {
636
- return Promise.reject(new ModbusError(ModbusErrorCode.PORT_ALREADY_OPEN, 'Port is already open'));
637
- }
638
- this._isOpening = true;
639
- this._connections.clear();
640
- const server = node_net.createServer(this._serverOptions, (socket) => {
641
- const conn = { id: genConnectionId('tcp-server') };
642
- this._connections.set(socket, conn);
643
- const response = (data) => new Promise((resolve, reject) => {
644
- socket.write(data, (error) => {
1407
+ write(data) {
1408
+ return new Promise((resolve, reject) => {
1409
+ if (this.state === exports.PhysicalConnectionState.CONNECTED) {
1410
+ this._socket.send(data, this._remote.port, this._remote.address, (error) => {
645
1411
  if (error) {
646
1412
  reject(error);
647
1413
  }
@@ -649,114 +1415,46 @@ class TcpServerPhysicalLayer extends AbstractPhysicalLayer {
649
1415
  resolve();
650
1416
  }
651
1417
  });
652
- });
653
- socket.on('data', (data) => {
654
- this.emit('data', data, response, conn);
655
- });
656
- socket.on('error', (err) => {
657
- this.emit('error', err);
658
- });
659
- socket.once('close', () => {
660
- if (this._connections.delete(socket)) {
661
- this.emit('connection-close', conn);
662
- }
663
- socket.removeAllListeners();
664
- });
665
- });
666
- this._server = server;
667
- return new Promise((resolve, reject) => {
668
- var _a;
669
- let listening = false;
670
- server.once('error', (err) => {
671
- if (listening) {
672
- this.emit('error', err);
673
- }
674
- else {
675
- if (this._server === server) {
676
- this._isOpening = false;
677
- this._server = null;
678
- }
679
- server.removeAllListeners();
680
- reject(err);
681
- }
682
- });
683
- server.once('close', () => {
684
- if (this._server === server) {
685
- this._isOpen = false;
686
- this._isOpening = false;
687
- this._server = null;
688
- const conns = Array.from(this._connections.values());
689
- this._connections.clear();
690
- for (const conn of conns) {
691
- this.emit('connection-close', conn);
692
- }
693
- this.emit('close');
694
- }
695
- });
696
- server.listen(Object.assign(Object.assign({}, options), { port: (_a = options === null || options === void 0 ? void 0 : options.port) !== null && _a !== void 0 ? _a : 502 }), () => {
697
- listening = true;
698
- this._isOpening = false;
699
- this._isOpen = true;
700
- resolve();
701
- });
702
- });
703
- }
704
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
705
- write(data) {
706
- return new Promise((resolve, reject) => {
707
- reject(new ModbusError(ModbusErrorCode.NOT_SUPPORTED, 'Not supported'));
708
- });
709
- }
710
- close() {
711
- if (!this._server) {
712
- return Promise.resolve();
713
- }
714
- const server = this._server;
715
- return new Promise((resolve) => {
716
- server.once('close', () => resolve());
717
- for (const [socket] of this._connections) {
718
- socket.destroy();
719
- }
720
- try {
721
- server.close();
722
1418
  }
723
- catch (_a) {
724
- resolve();
1419
+ else {
1420
+ reject(new Error('Connection is not connected'));
725
1421
  }
726
1422
  });
727
1423
  }
728
1424
  destroy() {
729
- if (this._destroyed) {
730
- return Promise.resolve();
1425
+ this._state = exports.PhysicalConnectionState.DESTROYED;
1426
+ for (const fn of this._cleanupFns) {
1427
+ fn();
731
1428
  }
732
- this._destroyed = true;
733
- return this.close().then(() => {
734
- this.removeAllListeners();
735
- });
1429
+ this._cleanupFns.clear();
1430
+ if (this._idleTid !== null) {
1431
+ clearTimeout(this._idleTid);
1432
+ this._idleTid = null;
1433
+ }
1434
+ this.emit('close');
1435
+ return Promise.resolve();
736
1436
  }
737
1437
  }
738
-
739
- class UdpPhysicalLayer extends AbstractPhysicalLayer {
740
- get isOpen() {
741
- return this._isOpen;
1438
+ class UdpServerPhysicalLayer extends AbstractPhysicalLayer {
1439
+ get state() {
1440
+ return this._state;
742
1441
  }
743
- get destroyed() {
744
- return this._destroyed;
1442
+ get socket() {
1443
+ return this._socket;
745
1444
  }
746
1445
  constructor(options) {
747
- var _a, _b, _c, _d;
748
1446
  super();
749
1447
  Object.defineProperty(this, "TYPE", {
750
1448
  enumerable: true,
751
1449
  configurable: true,
752
1450
  writable: true,
753
- value: 'NET'
1451
+ value: 'UDP_SERVER'
754
1452
  });
755
- Object.defineProperty(this, "_socket", {
1453
+ Object.defineProperty(this, "_state", {
756
1454
  enumerable: true,
757
1455
  configurable: true,
758
1456
  writable: true,
759
- value: null
1457
+ value: exports.PhysicalState.CLOSED
760
1458
  });
761
1459
  Object.defineProperty(this, "_connections", {
762
1460
  enumerable: true,
@@ -764,233 +1462,197 @@ class UdpPhysicalLayer extends AbstractPhysicalLayer {
764
1462
  writable: true,
765
1463
  value: new Map()
766
1464
  });
767
- Object.defineProperty(this, "_isOpen", {
768
- enumerable: true,
769
- configurable: true,
770
- writable: true,
771
- value: false
772
- });
773
- Object.defineProperty(this, "_isOpening", {
774
- enumerable: true,
775
- configurable: true,
776
- writable: true,
777
- value: false
778
- });
779
- Object.defineProperty(this, "_destroyed", {
1465
+ Object.defineProperty(this, "_socket", {
780
1466
  enumerable: true,
781
1467
  configurable: true,
782
1468
  writable: true,
783
- value: false
1469
+ value: null
784
1470
  });
785
- Object.defineProperty(this, "_socketOptions", {
1471
+ Object.defineProperty(this, "_opts", {
786
1472
  enumerable: true,
787
1473
  configurable: true,
788
1474
  writable: true,
789
1475
  value: void 0
790
1476
  });
791
- Object.defineProperty(this, "_port", {
1477
+ Object.defineProperty(this, "_openPromise", {
792
1478
  enumerable: true,
793
1479
  configurable: true,
794
1480
  writable: true,
795
- value: void 0
1481
+ value: null
796
1482
  });
797
- Object.defineProperty(this, "_address", {
1483
+ Object.defineProperty(this, "_closePromise", {
798
1484
  enumerable: true,
799
1485
  configurable: true,
800
1486
  writable: true,
801
- value: void 0
1487
+ value: null
802
1488
  });
803
- Object.defineProperty(this, "_idleTimeout", {
1489
+ Object.defineProperty(this, "_resolveClose", {
804
1490
  enumerable: true,
805
1491
  configurable: true,
806
1492
  writable: true,
807
- value: void 0
1493
+ value: null
808
1494
  });
809
- Object.defineProperty(this, "isServer", {
1495
+ Object.defineProperty(this, "_cleanupFns", {
810
1496
  enumerable: true,
811
1497
  configurable: true,
812
1498
  writable: true,
813
- value: void 0
1499
+ value: new Set()
814
1500
  });
815
- this._socketOptions = options === null || options === void 0 ? void 0 : options.socket;
816
- this.isServer = !(options === null || options === void 0 ? void 0 : options.remote);
817
- this._port = (_b = (_a = options === null || options === void 0 ? void 0 : options.remote) === null || _a === void 0 ? void 0 : _a.port) !== null && _b !== void 0 ? _b : 502;
818
- this._address = (_c = options === null || options === void 0 ? void 0 : options.remote) === null || _c === void 0 ? void 0 : _c.address;
819
- this._idleTimeout = (_d = options === null || options === void 0 ? void 0 : options.idleTimeout) !== null && _d !== void 0 ? _d : 30000;
1501
+ this._opts = options !== null && options !== void 0 ? options : {};
820
1502
  }
1503
+ /**
1504
+ * Bind the UDP socket and start accepting datagrams.
1505
+ *
1506
+ * @param options Bind options (port, address, etc.). Defaults to port 502.
1507
+ * @param [idleTimeout=30000] Maximum idle time in milliseconds before an
1508
+ * inactive client connection is evicted. Pass `0` to disable eviction
1509
+ * (connections never time out). Disabling eviction may cause unbounded
1510
+ * memory growth if the server sees many unique clients.
1511
+ */
821
1512
  open(options) {
822
- var _a, _b;
823
- if (this._destroyed) {
824
- return Promise.reject(new ModbusError(ModbusErrorCode.PORT_DESTROYED, 'Port is destroyed'));
1513
+ if (this.state === exports.PhysicalState.OPEN) {
1514
+ return Promise.resolve();
825
1515
  }
826
- if (this._isOpen || this._isOpening) {
827
- return Promise.reject(new ModbusError(ModbusErrorCode.PORT_ALREADY_OPEN, 'Port is already open'));
1516
+ if (this.state === exports.PhysicalState.OPENING) {
1517
+ return this._openPromise;
828
1518
  }
829
- this._isOpening = true;
830
- this._connections.clear();
831
- const socket = node_dgram.createSocket(Object.assign(Object.assign({}, this._socketOptions), { type: (_b = (_a = this._socketOptions) === null || _a === void 0 ? void 0 : _a.type) !== null && _b !== void 0 ? _b : 'udp4' }), (msg, rinfo) => this._handleMessage(socket, msg, rinfo));
832
- this._socket = socket;
833
- return new Promise((resolve, reject) => {
834
- var _a;
835
- let started = false;
836
- socket.on('error', (err) => {
837
- if (started) {
838
- this.emit('error', err);
839
- }
840
- else {
841
- if (this._socket === socket) {
842
- this._isOpening = false;
843
- this._socket = null;
844
- }
845
- socket.removeAllListeners();
846
- reject(err);
847
- }
848
- });
849
- socket.once('close', () => {
850
- if (this._socket !== socket) {
1519
+ if (this.state === exports.PhysicalState.CLOSING) {
1520
+ return Promise.reject(new Error('Port is closing'));
1521
+ }
1522
+ this._state = exports.PhysicalState.OPENING;
1523
+ this._openPromise = new Promise((resolve, reject) => {
1524
+ var _a, _b, _c;
1525
+ const socket = node_dgram.createSocket(Object.assign(Object.assign({}, this._opts.socketOpts), { type: (_b = (_a = this._opts.socketOpts) === null || _a === void 0 ? void 0 : _a.type) !== null && _b !== void 0 ? _b : 'udp4' }), (msg, rinfo) => {
1526
+ var _a;
1527
+ if (!isWhitelisted(rinfo.address, this._opts.whitelist)) {
851
1528
  return;
852
1529
  }
853
- this._isOpen = false;
854
- this._isOpening = false;
855
- this._socket = null;
856
- socket.removeAllListeners();
857
- const entries = Array.from(this._connections.values());
858
- this._connections.clear();
859
- for (const entry of entries) {
860
- if (entry.idleTimer) {
861
- clearTimeout(entry.idleTimer);
1530
+ const id = `${rinfo.address}:${rinfo.port}`;
1531
+ let connection = this._connections.get(id);
1532
+ if (!connection) {
1533
+ if (this._opts.maxConnections !== undefined && this._connections.size >= this._opts.maxConnections) {
1534
+ return;
862
1535
  }
863
- this.emit('connection-close', entry.conn);
1536
+ const listeners = new Set();
1537
+ const conn = new UdpServerPhysicalConnection(this, socket, rinfo, (_a = this._opts.idleTimeout) !== null && _a !== void 0 ? _a : 30000, {
1538
+ add: (listener) => {
1539
+ listeners.add(listener);
1540
+ },
1541
+ remove: (listener) => {
1542
+ listeners.delete(listener);
1543
+ },
1544
+ });
1545
+ connection = { connection: conn, listeners };
1546
+ this._connections.set(id, connection);
1547
+ const cleanupConnectionClose = () => conn.off('close', onConnectionClose);
1548
+ const onConnectionClose = () => {
1549
+ this._cleanupFns.delete(cleanupConnectionClose);
1550
+ this._connections.delete(id);
1551
+ };
1552
+ conn.once('close', onConnectionClose);
1553
+ this._cleanupFns.add(cleanupConnectionClose);
1554
+ this.emit('connect', conn);
1555
+ }
1556
+ for (const fn of connection.listeners) {
1557
+ fn(msg, rinfo);
864
1558
  }
865
- this.emit('close');
866
1559
  });
867
- if (this.isServer) {
868
- socket.bind(Object.assign(Object.assign({}, options), { port: (_a = options === null || options === void 0 ? void 0 : options.port) !== null && _a !== void 0 ? _a : 502 }), () => {
869
- started = true;
870
- this._isOpening = false;
871
- this._isOpen = true;
872
- resolve();
873
- });
874
- }
875
- else {
876
- started = true;
877
- this._isOpening = false;
878
- this._isOpen = true;
1560
+ this._socket = socket;
1561
+ const onListening = () => {
1562
+ socket.off('error', onError);
1563
+ this._state = exports.PhysicalState.OPEN;
1564
+ this._openPromise = null;
1565
+ this.emit('open');
879
1566
  resolve();
880
- }
881
- });
882
- }
883
- _handleMessage(socket, msg, rinfo) {
884
- if (this._socket !== socket) {
885
- return;
886
- }
887
- if (!this.isServer) {
888
- if (rinfo.port !== this._port) {
889
- return;
890
- }
891
- if (this._address !== undefined && rinfo.address !== this._address) {
892
- return;
893
- }
894
- }
895
- const key = `${rinfo.address}:${rinfo.port}`;
896
- let entry = this._connections.get(key);
897
- if (!entry) {
898
- entry = { conn: { id: genConnectionId('udp') } };
899
- this._connections.set(key, entry);
900
- }
901
- if (this.isServer && this._idleTimeout > 0) {
902
- if (entry.idleTimer) {
903
- clearTimeout(entry.idleTimer);
904
- }
905
- const conn = entry.conn;
906
- entry.idleTimer = setTimeout(() => {
907
- const e = this._connections.get(key);
908
- if (!e || e.conn !== conn) {
909
- return;
910
- }
911
- this._connections.delete(key);
912
- this.emit('connection-close', conn);
913
- }, this._idleTimeout);
914
- }
915
- const conn = entry.conn;
916
- const response = (data) => new Promise((resolve, reject) => {
917
- socket.send(data, rinfo.port, rinfo.address, (error) => {
918
- if (error) {
919
- reject(error);
920
- }
921
- else {
922
- resolve();
1567
+ {
1568
+ const onError = (err) => {
1569
+ this.emit('error', err);
1570
+ };
1571
+ socket.on('error', onError);
1572
+ this._cleanupFns.add(() => socket.off('error', onError));
1573
+ const cleanupClose = () => socket.off('close', onClose);
1574
+ const onClose = () => {
1575
+ var _a;
1576
+ this._socket = null;
1577
+ this._cleanupFns.delete(cleanupClose);
1578
+ this._state = exports.PhysicalState.CLOSED;
1579
+ this._closePromise = null;
1580
+ for (const fn of this._cleanupFns) {
1581
+ fn();
1582
+ }
1583
+ this._cleanupFns.clear();
1584
+ this._socket = null;
1585
+ this.emit('close');
1586
+ (_a = this._resolveClose) === null || _a === void 0 ? void 0 : _a.call(this);
1587
+ this._resolveClose = null;
1588
+ };
1589
+ socket.once('close', onClose);
1590
+ this._cleanupFns.add(cleanupClose);
923
1591
  }
924
- });
925
- });
926
- this.emit('data', msg, response, conn);
927
- }
928
- write(data) {
929
- return new Promise((resolve, reject) => {
930
- if (this._isOpen && this._socket) {
931
- this._socket.send(data, this._port, this._address, (error) => {
932
- if (error) {
933
- reject(error);
934
- }
935
- else {
936
- this.emit('write', data);
937
- resolve();
938
- }
939
- });
940
- }
941
- else {
942
- reject(new ModbusError(ModbusErrorCode.PORT_NOT_OPEN, 'Port is not open'));
943
- }
1592
+ };
1593
+ const onError = (err) => {
1594
+ socket.off('listening', onListening);
1595
+ this._state = exports.PhysicalState.CLOSED;
1596
+ this._openPromise = null;
1597
+ this._socket = null;
1598
+ reject(err);
1599
+ };
1600
+ socket.once('listening', onListening);
1601
+ socket.once('error', onError);
1602
+ socket.bind(Object.assign(Object.assign({}, options), { port: (_c = options === null || options === void 0 ? void 0 : options.port) !== null && _c !== void 0 ? _c : 502 }));
944
1603
  });
1604
+ return this._openPromise;
945
1605
  }
946
1606
  close() {
947
- if (!this._socket) {
1607
+ if (this.state === exports.PhysicalState.CLOSED) {
948
1608
  return Promise.resolve();
949
1609
  }
950
- const socket = this._socket;
951
- return new Promise((resolve) => {
952
- socket.once('close', () => resolve());
953
- try {
954
- socket.close();
955
- }
956
- catch (_a) {
957
- // Socket may already be closed; the registered 'close' listener still fires asynchronously.
1610
+ if (this.state === exports.PhysicalState.CLOSING) {
1611
+ return this._closePromise;
1612
+ }
1613
+ if (this.state === exports.PhysicalState.OPENING) {
1614
+ return Promise.reject(new Error('Port is opening'));
1615
+ }
1616
+ this._state = exports.PhysicalState.CLOSING;
1617
+ this._closePromise = new Promise((resolve) => {
1618
+ this._resolveClose = resolve;
1619
+ this.socket.close();
1620
+ // Snapshot connections so destruction (which removes from the Map)
1621
+ // does not interfere with iteration.
1622
+ const connections = Array.from(this._connections.values());
1623
+ for (const { connection } of connections) {
1624
+ connection.destroy();
958
1625
  }
959
1626
  });
1627
+ return this._closePromise;
960
1628
  }
961
- destroy() {
962
- if (this._destroyed) {
963
- return Promise.resolve();
964
- }
965
- this._destroyed = true;
966
- return this.close().then(() => {
967
- this.removeAllListeners();
968
- });
1629
+ }
1630
+
1631
+ function createPhysicalLayer(config) {
1632
+ switch (config.type) {
1633
+ case 'SERIAL':
1634
+ return new SerialPhysicalLayer(config.opts);
1635
+ case 'TCP_CLIENT':
1636
+ return new TcpClientPhysicalLayer(config.socketOpts);
1637
+ case 'TCP_SERVER':
1638
+ return new TcpServerPhysicalLayer(config.opts);
1639
+ case 'UDP_CLIENT':
1640
+ return new UdpClientPhysicalLayer(config.socketOpts);
1641
+ case 'UDP_SERVER':
1642
+ return new UdpServerPhysicalLayer(config.opts);
1643
+ case 'CUSTOM':
1644
+ return config.layer;
969
1645
  }
970
1646
  }
971
1647
 
1648
+ /**
1649
+ * Application-layer protocol handler bound to a single physical connection.
1650
+ *
1651
+ * Its lifetime follows the channel: created when the underlying connection is
1652
+ * established and discarded when the connection closes. Subclasses implement
1653
+ * ASCII, RTU, or TCP framing rules.
1654
+ */
972
1655
  class AbstractApplicationLayer extends EventEmitter {
973
- constructor() {
974
- super(...arguments);
975
- Object.defineProperty(this, "_role", {
976
- enumerable: true,
977
- configurable: true,
978
- writable: true,
979
- value: void 0
980
- });
981
- }
982
- get role() {
983
- if (!this._role) {
984
- throw new ModbusError(ModbusErrorCode.INVALID_ROLE, 'Application layer role not set');
985
- }
986
- return this._role;
987
- }
988
- set role(value) {
989
- if (this._role && this._role !== value) {
990
- throw new ModbusError(ModbusErrorCode.INVALID_ROLE, `Application layer role already set to ${this._role}`);
991
- }
992
- this._role = value;
993
- }
994
1656
  flush() {
995
1657
  // no-op — override in subclasses
996
1658
  }
@@ -1003,7 +1665,10 @@ class AbstractApplicationLayer extends EventEmitter {
1003
1665
  const MAX_FRAME_LENGTH = 256;
1004
1666
  const MIN_FRAME_LENGTH = 4;
1005
1667
  class RtuApplicationLayer extends AbstractApplicationLayer {
1006
- constructor(physicalLayer, options = {}) {
1668
+ get connection() {
1669
+ return this._connection;
1670
+ }
1671
+ constructor(role, connection, options = {}) {
1007
1672
  super();
1008
1673
  Object.defineProperty(this, "PROTOCOL", {
1009
1674
  enumerable: true,
@@ -1011,23 +1676,29 @@ class RtuApplicationLayer extends AbstractApplicationLayer {
1011
1676
  writable: true,
1012
1677
  value: 'RTU'
1013
1678
  });
1014
- Object.defineProperty(this, "_states", {
1679
+ Object.defineProperty(this, "ROLE", {
1015
1680
  enumerable: true,
1016
1681
  configurable: true,
1017
1682
  writable: true,
1018
- value: new Map()
1683
+ value: void 0
1019
1684
  });
1020
- Object.defineProperty(this, "_customFunctionCodes", {
1685
+ Object.defineProperty(this, "_connection", {
1021
1686
  enumerable: true,
1022
1687
  configurable: true,
1023
1688
  writable: true,
1024
- value: new Map()
1689
+ value: void 0
1025
1690
  });
1026
- Object.defineProperty(this, "_removeAllListeners", {
1691
+ Object.defineProperty(this, "_state", {
1027
1692
  enumerable: true,
1028
1693
  configurable: true,
1029
1694
  writable: true,
1030
- value: []
1695
+ value: void 0
1696
+ });
1697
+ Object.defineProperty(this, "_poolSize", {
1698
+ enumerable: true,
1699
+ configurable: true,
1700
+ writable: true,
1701
+ value: void 0
1031
1702
  });
1032
1703
  Object.defineProperty(this, "_threePointFiveT", {
1033
1704
  enumerable: true,
@@ -1041,39 +1712,29 @@ class RtuApplicationLayer extends AbstractApplicationLayer {
1041
1712
  writable: true,
1042
1713
  value: void 0
1043
1714
  });
1044
- Object.defineProperty(this, "_destroyed", {
1715
+ Object.defineProperty(this, "_customFunctionCodes", {
1045
1716
  enumerable: true,
1046
1717
  configurable: true,
1047
1718
  writable: true,
1048
- value: false
1719
+ value: new Map()
1049
1720
  });
1050
- const { intervalBetweenFrames, interCharTimeout } = options;
1051
- let threePointFiveT = 0;
1052
- let onePointFiveT = 0;
1053
- if (physicalLayer.TYPE === 'SERIAL') {
1054
- const baudRate = physicalLayer.baudRate;
1055
- if (intervalBetweenFrames && intervalBetweenFrames.unit === 'ms') {
1056
- threePointFiveT = intervalBetweenFrames.value;
1057
- }
1058
- else {
1059
- threePointFiveT =
1060
- baudRate > 19200 ? 1.75 : Math.ceil(bitsToMs(baudRate, intervalBetweenFrames ? intervalBetweenFrames.value : 38.5));
1061
- }
1062
- if (interCharTimeout) {
1063
- if (interCharTimeout.unit === 'ms') {
1064
- onePointFiveT = interCharTimeout.value;
1065
- }
1066
- else {
1067
- onePointFiveT = baudRate > 19200 ? 0.75 : Math.ceil(bitsToMs(baudRate, interCharTimeout.value));
1068
- }
1069
- }
1070
- }
1071
- this._threePointFiveT = threePointFiveT;
1072
- this._onePointFiveT = onePointFiveT;
1073
- const handleData = (data, response, connection) => {
1074
- let state = this.getState(connection.id);
1721
+ Object.defineProperty(this, "_cleanupFns", {
1722
+ enumerable: true,
1723
+ configurable: true,
1724
+ writable: true,
1725
+ value: new Set()
1726
+ });
1727
+ this.ROLE = role;
1728
+ this._connection = connection;
1729
+ const { intervalBetweenFrames, interCharTimeout, poolSize } = options;
1730
+ this._poolSize = poolSize !== null && poolSize !== void 0 ? poolSize : MAX_FRAME_LENGTH * 2;
1731
+ this._state = { pool: Buffer.alloc(this._poolSize), start: 0, end: 0 };
1732
+ this._threePointFiveT = intervalBetweenFrames !== null && intervalBetweenFrames !== void 0 ? intervalBetweenFrames : 0;
1733
+ this._onePointFiveT = interCharTimeout !== null && interCharTimeout !== void 0 ? interCharTimeout : 0;
1734
+ const onData = (data) => {
1735
+ let state = this._state;
1075
1736
  if (state.t15Expired && state.end > state.start) {
1076
- this.emit('framing-error', new ModbusError(ModbusErrorCode.T1_5_EXCEEDED, 'Inter-character timeout (t1.5) exceeded'));
1737
+ this.emit('framing-error', new Error('Inter-character timeout (t1.5) exceeded'));
1077
1738
  state.start = 0;
1078
1739
  state.end = 0;
1079
1740
  }
@@ -1082,11 +1743,11 @@ class RtuApplicationLayer extends AbstractApplicationLayer {
1082
1743
  while (dataOffset < data.length) {
1083
1744
  const space = state.pool.length - state.end;
1084
1745
  if (space === 0) {
1085
- this.clearStateTimers(state);
1086
- this.flushBuffer(connection.id, response, connection, this._threePointFiveT > 0);
1087
- state = this.getState(connection.id);
1746
+ this.clearStateTimers();
1747
+ this.flushBuffer(this._threePointFiveT > 0);
1748
+ state = this._state;
1088
1749
  if (state.pool.length - state.end === 0) {
1089
- this.emit('framing-error', new ModbusError(ModbusErrorCode.INVALID_DATA, 'Frame buffer exhausted before complete frame received'));
1750
+ this.emit('framing-error', new Error('Frame buffer exhausted before complete frame received'));
1090
1751
  state.start = 0;
1091
1752
  state.end = 0;
1092
1753
  state.t15Expired = false;
@@ -1098,10 +1759,10 @@ class RtuApplicationLayer extends AbstractApplicationLayer {
1098
1759
  state.end += toCopy;
1099
1760
  dataOffset += toCopy;
1100
1761
  }
1101
- this.clearStateTimers(state);
1762
+ this.clearStateTimers();
1102
1763
  const commitFrame = () => {
1103
- this.clearStateTimers(state);
1104
- this.flushBuffer(connection.id, response, connection, this._threePointFiveT > 0);
1764
+ this.clearStateTimers();
1765
+ this.flushBuffer(this._threePointFiveT > 0);
1105
1766
  };
1106
1767
  if (state.end - state.start >= MAX_FRAME_LENGTH) {
1107
1768
  commitFrame();
@@ -1119,30 +1780,20 @@ class RtuApplicationLayer extends AbstractApplicationLayer {
1119
1780
  commitFrame();
1120
1781
  }
1121
1782
  };
1122
- physicalLayer.on('data', handleData);
1123
- this._removeAllListeners.push(() => physicalLayer.removeListener('data', handleData));
1124
- const handleConnectionClose = (connection) => {
1125
- this.clearState(connection.id);
1126
- };
1127
- physicalLayer.on('connection-close', handleConnectionClose);
1128
- this._removeAllListeners.push(() => physicalLayer.removeListener('connection-close', handleConnectionClose));
1129
- const handleClose = () => {
1130
- for (const key of [...this._states.keys()]) {
1131
- this.clearState(key);
1132
- }
1783
+ connection.on('data', onData);
1784
+ this._cleanupFns.add(() => connection.off('data', onData));
1785
+ const onClose = () => {
1786
+ for (const fn of this._cleanupFns) {
1787
+ fn();
1788
+ }
1789
+ this._cleanupFns.clear();
1790
+ this.clearStateTimers();
1133
1791
  };
1134
- physicalLayer.on('close', handleClose);
1135
- this._removeAllListeners.push(() => physicalLayer.removeListener('close', handleClose));
1136
- }
1137
- getState(key) {
1138
- let state = this._states.get(key);
1139
- if (!state) {
1140
- state = { pool: Buffer.alloc(MAX_FRAME_LENGTH * 2), start: 0, end: 0 };
1141
- this._states.set(key, state);
1142
- }
1143
- return state;
1792
+ connection.on('close', onClose);
1793
+ this._cleanupFns.add(() => connection.off('close', onClose));
1144
1794
  }
1145
- clearStateTimers(state) {
1795
+ clearStateTimers() {
1796
+ const state = this._state;
1146
1797
  if (state.timer) {
1147
1798
  clearTimeout(state.timer);
1148
1799
  state.timer = undefined;
@@ -1152,45 +1803,18 @@ class RtuApplicationLayer extends AbstractApplicationLayer {
1152
1803
  state.interCharTimer = undefined;
1153
1804
  }
1154
1805
  }
1155
- clearState(key) {
1156
- const state = this._states.get(key);
1157
- if (state) {
1158
- this.clearStateTimers(state);
1159
- }
1160
- this._states.delete(key);
1161
- }
1162
- flush() {
1163
- for (const state of this._states.values()) {
1164
- this.clearStateTimers(state);
1165
- state.start = 0;
1166
- state.end = 0;
1167
- }
1168
- this._states.clear();
1169
- }
1170
- addCustomFunctionCode(cfc) {
1171
- if (!isUint8(cfc.fc)) {
1172
- throw new ModbusError(ModbusErrorCode.RANGE, `fc must be an integer in 0..255, got ${cfc.fc}`);
1173
- }
1174
- this._customFunctionCodes.set(cfc.fc, cfc);
1175
- }
1176
- removeCustomFunctionCode(fc) {
1177
- this._customFunctionCodes.delete(fc);
1178
- }
1179
- flushBuffer(key, response, connection, strict) {
1180
- const state = this._states.get(key);
1181
- if (!state) {
1182
- return;
1183
- }
1806
+ flushBuffer(strict) {
1807
+ const state = this._state;
1184
1808
  while (state.end - state.start > 0) {
1185
1809
  const buffer = state.pool.subarray(state.start, state.end);
1186
1810
  const result = this.tryExtract(buffer);
1187
1811
  if (result.kind === 'frame') {
1188
1812
  state.start += result.frame.length;
1189
- this.deliverFrame(result.frame, response, connection);
1813
+ this.deliverFrame(result.frame);
1190
1814
  }
1191
1815
  else if (result.kind === 'skip') {
1192
1816
  if (strict) {
1193
- this.emit('framing-error', new ModbusError(ModbusErrorCode.CRC_MISMATCH, 'CRC mismatch'));
1817
+ this.emit('framing-error', new Error('CRC mismatch'));
1194
1818
  state.start = 0;
1195
1819
  state.end = 0;
1196
1820
  state.t15Expired = false;
@@ -1204,14 +1828,14 @@ class RtuApplicationLayer extends AbstractApplicationLayer {
1204
1828
  continue;
1205
1829
  }
1206
1830
  if (strict) {
1207
- this.emit('framing-error', new ModbusError(state.t15Expired ? ModbusErrorCode.T1_5_EXCEEDED : ModbusErrorCode.INCOMPLETE_FRAME, state.t15Expired ? 'Inter-character timeout (t1.5) exceeded' : 'Incomplete frame at t3.5'));
1831
+ this.emit('framing-error', new Error(state.t15Expired ? 'Inter-character timeout (t1.5) exceeded' : 'Incomplete frame at t3.5'));
1208
1832
  state.start = 0;
1209
1833
  state.end = 0;
1210
1834
  state.t15Expired = false;
1211
1835
  return;
1212
1836
  }
1213
1837
  if (state.t15Expired) {
1214
- this.emit('framing-error', new ModbusError(ModbusErrorCode.T1_5_EXCEEDED, 'Inter-character timeout (t1.5) exceeded'));
1838
+ this.emit('framing-error', new Error('Inter-character timeout (t1.5) exceeded'));
1215
1839
  state.start = 0;
1216
1840
  state.end = 0;
1217
1841
  state.t15Expired = false;
@@ -1232,15 +1856,22 @@ class RtuApplicationLayer extends AbstractApplicationLayer {
1232
1856
  state.end -= state.start;
1233
1857
  state.start = 0;
1234
1858
  }
1235
- if (state.end === 0 && !state.timer) {
1236
- this._states.delete(key);
1237
- }
1859
+ }
1860
+ deliverFrame(buffer) {
1861
+ const frameBuf = Buffer.from(buffer);
1862
+ const frame = {
1863
+ unit: frameBuf[0],
1864
+ fc: frameBuf[1],
1865
+ data: frameBuf.subarray(2, frameBuf.length - 2),
1866
+ buffer: frameBuf,
1867
+ };
1868
+ this.emit('framing', frame);
1238
1869
  }
1239
1870
  tryExtract(buffer) {
1240
1871
  if (buffer.length < MIN_FRAME_LENGTH) {
1241
1872
  return { kind: 'insufficient' };
1242
1873
  }
1243
- const isResponse = this.role === 'MASTER';
1874
+ const isResponse = this.ROLE === 'MASTER';
1244
1875
  const fc = buffer[1];
1245
1876
  const cfc = this._customFunctionCodes.get(fc);
1246
1877
  if (cfc) {
@@ -1260,12 +1891,12 @@ class RtuApplicationLayer extends AbstractApplicationLayer {
1260
1891
  }
1261
1892
  return {
1262
1893
  kind: 'error',
1263
- error: new ModbusError(ModbusErrorCode.UNKNOWN_FC, `Unknown function code 0x${fc.toString(16).padStart(2, '0')} — register a CustomFunctionCode to frame this FC`),
1894
+ error: new Error(`Unknown function code 0x${fc.toString(16).padStart(2, '0')} — register a CustomFunctionCode to frame this FC`),
1264
1895
  };
1265
1896
  }
1266
1897
  checkExpected(buffer, expected) {
1267
1898
  if (expected > MAX_FRAME_LENGTH || expected < MIN_FRAME_LENGTH) {
1268
- return { kind: 'error', error: new ModbusError(ModbusErrorCode.INVALID_DATA, 'Invalid data') };
1899
+ return { kind: 'error', error: new Error('Invalid data') };
1269
1900
  }
1270
1901
  if (buffer.length < expected) {
1271
1902
  return { kind: 'insufficient' };
@@ -1278,40 +1909,28 @@ class RtuApplicationLayer extends AbstractApplicationLayer {
1278
1909
  crcMatches(buffer, length) {
1279
1910
  return buffer.readUInt16LE(length - 2) === crc(buffer.subarray(0, length - 2));
1280
1911
  }
1281
- deliverFrame(buffer, response, connection) {
1282
- const frameBuf = Buffer.from(buffer);
1283
- const frame = {
1284
- unit: frameBuf[0],
1285
- fc: frameBuf[1],
1286
- data: frameBuf.subarray(2, frameBuf.length - 2),
1287
- buffer: frameBuf,
1288
- };
1289
- this.emit('framing', frame, response, connection);
1912
+ flush() {
1913
+ this.clearStateTimers();
1914
+ this._state.start = 0;
1915
+ this._state.end = 0;
1916
+ }
1917
+ addCustomFunctionCode(cfc) {
1918
+ if (!isUint8(cfc.fc)) {
1919
+ throw new Error(`fc must be an integer in 0..255, got ${cfc.fc}`);
1920
+ }
1921
+ this._customFunctionCodes.set(cfc.fc, cfc);
1922
+ }
1923
+ removeCustomFunctionCode(fc) {
1924
+ this._customFunctionCodes.delete(fc);
1290
1925
  }
1291
1926
  encode(data) {
1292
- const buffer = Buffer.alloc(data.data.length + 4);
1927
+ const buffer = Buffer.allocUnsafe(data.data.length + 4);
1293
1928
  buffer.writeUInt8(data.unit, 0);
1294
1929
  buffer.writeUInt8(data.fc, 1);
1295
1930
  buffer.set(data.data, 2);
1296
1931
  buffer.writeUInt16LE(crc(buffer.subarray(0, -2)), buffer.length - 2);
1297
1932
  return buffer;
1298
1933
  }
1299
- destroy() {
1300
- if (this._destroyed) {
1301
- return;
1302
- }
1303
- this._destroyed = true;
1304
- this.removeAllListeners();
1305
- for (const removeListener of this._removeAllListeners) {
1306
- removeListener();
1307
- }
1308
- this._removeAllListeners = [];
1309
- for (const state of this._states.values()) {
1310
- this.clearStateTimers(state);
1311
- }
1312
- this._states.clear();
1313
- this._customFunctionCodes.clear();
1314
- }
1315
1934
  }
1316
1935
 
1317
1936
  const CHAR_CODE = {
@@ -1336,7 +1955,10 @@ for (let i = 0x61; i <= 0x66; i++) {
1336
1955
  }
1337
1956
  const HEX_ENCODE = new Uint8Array('0123456789ABCDEF'.split('').map((c) => c.charCodeAt(0)));
1338
1957
  class AsciiApplicationLayer extends AbstractApplicationLayer {
1339
- constructor(physicalLayer, options = {}) {
1958
+ get connection() {
1959
+ return this._connection;
1960
+ }
1961
+ constructor(role, connection, options = {}) {
1340
1962
  var _a;
1341
1963
  super();
1342
1964
  Object.defineProperty(this, "PROTOCOL", {
@@ -1345,30 +1967,38 @@ class AsciiApplicationLayer extends AbstractApplicationLayer {
1345
1967
  writable: true,
1346
1968
  value: 'ASCII'
1347
1969
  });
1970
+ Object.defineProperty(this, "ROLE", {
1971
+ enumerable: true,
1972
+ configurable: true,
1973
+ writable: true,
1974
+ value: void 0
1975
+ });
1348
1976
  Object.defineProperty(this, "lenientHex", {
1349
1977
  enumerable: true,
1350
1978
  configurable: true,
1351
1979
  writable: true,
1352
1980
  value: void 0
1353
1981
  });
1354
- Object.defineProperty(this, "_states", {
1982
+ Object.defineProperty(this, "_connection", {
1355
1983
  enumerable: true,
1356
1984
  configurable: true,
1357
1985
  writable: true,
1358
- value: new Map()
1986
+ value: void 0
1359
1987
  });
1360
- Object.defineProperty(this, "_removeAllListeners", {
1988
+ Object.defineProperty(this, "_state", {
1361
1989
  enumerable: true,
1362
1990
  configurable: true,
1363
1991
  writable: true,
1364
- value: []
1992
+ value: { status: 'idle', frame: [] }
1365
1993
  });
1366
- Object.defineProperty(this, "_destroyed", {
1994
+ Object.defineProperty(this, "_cleanupFns", {
1367
1995
  enumerable: true,
1368
1996
  configurable: true,
1369
1997
  writable: true,
1370
- value: false
1998
+ value: new Set()
1371
1999
  });
2000
+ this.ROLE = role;
2001
+ this._connection = connection;
1372
2002
  this.lenientHex = (_a = options.lenientHex) !== null && _a !== void 0 ? _a : false;
1373
2003
  const lenientHex = this.lenientHex;
1374
2004
  const isHexChar = (value) => {
@@ -1383,8 +2013,8 @@ class AsciiApplicationLayer extends AbstractApplicationLayer {
1383
2013
  }
1384
2014
  return false;
1385
2015
  };
1386
- const handleData = (data, response, connection) => {
1387
- const state = this.getState(connection.id);
2016
+ const onData = (data) => {
2017
+ const state = this._state;
1388
2018
  data.forEach((value) => {
1389
2019
  switch (state.status) {
1390
2020
  case 'idle': {
@@ -1404,12 +2034,12 @@ class AsciiApplicationLayer extends AbstractApplicationLayer {
1404
2034
  else if (state.frame.length >= MAX_ASCII_PAYLOAD) {
1405
2035
  state.status = 'idle';
1406
2036
  state.frame = [];
1407
- this.emit('framing-error', new ModbusError(ModbusErrorCode.INVALID_DATA, 'Invalid data'));
2037
+ this.emit('framing-error', new Error('Invalid data'));
1408
2038
  }
1409
2039
  else if (!isHexChar(value)) {
1410
2040
  state.status = 'idle';
1411
2041
  state.frame = [];
1412
- this.emit('framing-error', new ModbusError(ModbusErrorCode.INVALID_HEX, 'Invalid hex character'));
2042
+ this.emit('framing-error', new Error('Invalid hex character'));
1413
2043
  }
1414
2044
  else {
1415
2045
  state.frame.push(value);
@@ -1424,7 +2054,7 @@ class AsciiApplicationLayer extends AbstractApplicationLayer {
1424
2054
  else {
1425
2055
  state.status = 'idle';
1426
2056
  if (value === CHAR_CODE.LF) {
1427
- this.framing(Buffer.from(state.frame), response, connection);
2057
+ this.framing(Buffer.from(state.frame));
1428
2058
  }
1429
2059
  }
1430
2060
  break;
@@ -1432,64 +2062,56 @@ class AsciiApplicationLayer extends AbstractApplicationLayer {
1432
2062
  }
1433
2063
  });
1434
2064
  };
1435
- physicalLayer.on('data', handleData);
1436
- this._removeAllListeners.push(() => physicalLayer.removeListener('data', handleData));
1437
- const handleConnectionClose = (connection) => {
1438
- this._states.delete(connection.id);
1439
- };
1440
- physicalLayer.on('connection-close', handleConnectionClose);
1441
- this._removeAllListeners.push(() => physicalLayer.removeListener('connection-close', handleConnectionClose));
1442
- const handleClose = () => {
1443
- this._states.clear();
2065
+ connection.on('data', onData);
2066
+ this._cleanupFns.add(() => connection.off('data', onData));
2067
+ const onClose = () => {
2068
+ for (const fn of this._cleanupFns) {
2069
+ fn();
2070
+ }
2071
+ this._cleanupFns.clear();
1444
2072
  };
1445
- physicalLayer.on('close', handleClose);
1446
- this._removeAllListeners.push(() => physicalLayer.removeListener('close', handleClose));
1447
- }
1448
- getState(key) {
1449
- let state = this._states.get(key);
1450
- if (!state) {
1451
- state = { status: 'idle', frame: [] };
1452
- this._states.set(key, state);
1453
- }
1454
- return state;
2073
+ connection.on('close', onClose);
2074
+ this._cleanupFns.add(() => connection.off('close', onClose));
1455
2075
  }
1456
- flush() {
1457
- this._states.clear();
1458
- }
1459
- framing(_buffer, response, connection) {
1460
- if (_buffer.length < 6) {
1461
- this.emit('framing-error', new ModbusError(ModbusErrorCode.INSUFFICIENT_DATA, 'Insufficient data length'));
2076
+ framing(hexChars) {
2077
+ if (hexChars.length < 6) {
2078
+ this.emit('framing-error', new Error('Insufficient data length'));
1462
2079
  return;
1463
2080
  }
1464
- if (_buffer.length % 2 !== 0) {
1465
- this.emit('framing-error', new ModbusError(ModbusErrorCode.INVALID_DATA, 'Invalid data'));
2081
+ if (hexChars.length % 2 !== 0) {
2082
+ this.emit('framing-error', new Error('Invalid data'));
1466
2083
  return;
1467
2084
  }
1468
- const buffer = Buffer.allocUnsafe(_buffer.length / 2);
1469
- for (let i = 0; i < _buffer.length; i += 2) {
1470
- const hi = HEX_DECODE[_buffer[i]];
1471
- const lo = HEX_DECODE[_buffer[i + 1]];
2085
+ const decoded = Buffer.allocUnsafe(hexChars.length / 2);
2086
+ for (let i = 0; i < hexChars.length; i += 2) {
2087
+ const hi = HEX_DECODE[hexChars[i]];
2088
+ const lo = HEX_DECODE[hexChars[i + 1]];
2089
+ // Defensive: the FSM should already have filtered non-hex characters,
2090
+ // but guard here in case framing is ever called directly.
1472
2091
  if (hi === 0xff || lo === 0xff) {
1473
- this.emit('framing-error', new ModbusError(ModbusErrorCode.INVALID_HEX, 'Invalid hex character'));
2092
+ this.emit('framing-error', new Error('Invalid hex character'));
1474
2093
  return;
1475
2094
  }
1476
- buffer[i / 2] = (hi << 4) | lo;
2095
+ decoded[i / 2] = (hi << 4) | lo;
1477
2096
  }
1478
2097
  const frame = {
1479
- unit: buffer[0],
1480
- fc: buffer[1],
1481
- data: buffer.subarray(2, buffer.length - 1),
1482
- buffer: _buffer,
2098
+ unit: decoded[0],
2099
+ fc: decoded[1],
2100
+ data: decoded.subarray(2, decoded.length - 1),
2101
+ buffer: hexChars,
1483
2102
  };
1484
- const lrcPassed = buffer[buffer.length - 1] === lrc(buffer.subarray(0, buffer.length - 1));
2103
+ const lrcPassed = decoded[decoded.length - 1] === lrc(decoded.subarray(0, decoded.length - 1));
1485
2104
  if (!lrcPassed) {
1486
- this.emit('framing-error', new ModbusError(ModbusErrorCode.LRC_MISMATCH, 'LRC check failed'));
2105
+ this.emit('framing-error', new Error('LRC check failed'));
1487
2106
  return;
1488
2107
  }
1489
- this.emit('framing', frame, response, connection);
2108
+ this.emit('framing', frame);
2109
+ }
2110
+ flush() {
2111
+ this._state = { status: 'idle', frame: [] };
1490
2112
  }
1491
2113
  encode(data) {
1492
- const buffer = Buffer.alloc(data.data.length + 3);
2114
+ const buffer = Buffer.allocUnsafe(data.data.length + 3);
1493
2115
  buffer.writeUInt8(data.unit, 0);
1494
2116
  buffer.writeUInt8(data.fc, 1);
1495
2117
  buffer.set(data.data, 2);
@@ -1505,24 +2127,14 @@ class AsciiApplicationLayer extends AbstractApplicationLayer {
1505
2127
  out[out.length - 1] = CHAR_CODE.LF;
1506
2128
  return out;
1507
2129
  }
1508
- destroy() {
1509
- if (this._destroyed) {
1510
- return;
1511
- }
1512
- this._destroyed = true;
1513
- this.removeAllListeners();
1514
- for (const removeListener of this._removeAllListeners) {
1515
- removeListener();
1516
- }
1517
- this._removeAllListeners = [];
1518
- this._states.clear();
1519
- }
1520
2130
  }
1521
2131
 
1522
2132
  const MAX_TCP_FRAME = 260;
1523
- const EMPTY_BUFFER = Buffer.alloc(0);
1524
2133
  class TcpApplicationLayer extends AbstractApplicationLayer {
1525
- constructor(physicalLayer) {
2134
+ get connection() {
2135
+ return this._connection;
2136
+ }
2137
+ constructor(role, connection) {
1526
2138
  super();
1527
2139
  Object.defineProperty(this, "PROTOCOL", {
1528
2140
  enumerable: true,
@@ -1530,38 +2142,50 @@ class TcpApplicationLayer extends AbstractApplicationLayer {
1530
2142
  writable: true,
1531
2143
  value: 'TCP'
1532
2144
  });
1533
- Object.defineProperty(this, "_transactionId", {
2145
+ Object.defineProperty(this, "ROLE", {
1534
2146
  enumerable: true,
1535
2147
  configurable: true,
1536
2148
  writable: true,
1537
- value: 1
2149
+ value: void 0
1538
2150
  });
1539
- Object.defineProperty(this, "_buffers", {
2151
+ Object.defineProperty(this, "_connection", {
1540
2152
  enumerable: true,
1541
2153
  configurable: true,
1542
2154
  writable: true,
1543
- value: new Map()
2155
+ value: void 0
1544
2156
  });
1545
- Object.defineProperty(this, "_removeAllListeners", {
2157
+ Object.defineProperty(this, "_transactionId", {
1546
2158
  enumerable: true,
1547
2159
  configurable: true,
1548
2160
  writable: true,
1549
- value: []
2161
+ value: 1
1550
2162
  });
1551
- Object.defineProperty(this, "_destroyed", {
2163
+ Object.defineProperty(this, "_buffer", {
1552
2164
  enumerable: true,
1553
2165
  configurable: true,
1554
2166
  writable: true,
1555
- value: false
2167
+ value: Buffer.alloc(0)
1556
2168
  });
1557
- const handleData = (data, response, connection) => {
1558
- var _a;
1559
- let buffer = (_a = this._buffers.get(connection.id)) !== null && _a !== void 0 ? _a : EMPTY_BUFFER;
1560
- buffer = buffer.length === 0 ? data : Buffer.concat([buffer, data]);
2169
+ Object.defineProperty(this, "_cleanupFns", {
2170
+ enumerable: true,
2171
+ configurable: true,
2172
+ writable: true,
2173
+ value: new Set()
2174
+ });
2175
+ this.ROLE = role;
2176
+ this._connection = connection;
2177
+ const onData = (data) => {
2178
+ let buffer = this._buffer;
2179
+ if (buffer.length === 0) {
2180
+ buffer = data;
2181
+ }
2182
+ else if (data.length > 0) {
2183
+ buffer = Buffer.concat([buffer, data]);
2184
+ }
1561
2185
  while (buffer.length > 0) {
1562
2186
  const result = this.tryExtract(buffer);
1563
2187
  if (result.kind === 'frame') {
1564
- this.processFrame(result.frame, response, connection);
2188
+ this.processFrame(result.frame);
1565
2189
  buffer = result.rest;
1566
2190
  }
1567
2191
  else if (result.kind === 'insufficient') {
@@ -1569,48 +2193,53 @@ class TcpApplicationLayer extends AbstractApplicationLayer {
1569
2193
  }
1570
2194
  else {
1571
2195
  this.emit('framing-error', result.error);
1572
- buffer = EMPTY_BUFFER;
2196
+ buffer = Buffer.alloc(0);
1573
2197
  break;
1574
2198
  }
1575
2199
  }
1576
2200
  if (buffer.length === 0) {
1577
- this._buffers.delete(connection.id);
2201
+ this._buffer = Buffer.alloc(0);
2202
+ }
2203
+ else if (buffer === data) {
2204
+ // Copy into a right-sized buffer so we do not retain the potentially
2205
+ // large backing buffer of the original incoming data (Node.js pool).
2206
+ const copy = Buffer.allocUnsafe(buffer.length);
2207
+ buffer.copy(copy);
2208
+ this._buffer = copy;
1578
2209
  }
1579
2210
  else {
1580
- this._buffers.set(connection.id, buffer);
2211
+ this._buffer = buffer;
1581
2212
  }
1582
2213
  };
1583
- physicalLayer.on('data', handleData);
1584
- this._removeAllListeners.push(() => physicalLayer.removeListener('data', handleData));
1585
- const handleConnectionClose = (connection) => {
1586
- this._buffers.delete(connection.id);
1587
- };
1588
- physicalLayer.on('connection-close', handleConnectionClose);
1589
- this._removeAllListeners.push(() => physicalLayer.removeListener('connection-close', handleConnectionClose));
1590
- const handleClose = () => {
1591
- this._buffers.clear();
2214
+ connection.on('data', onData);
2215
+ this._cleanupFns.add(() => connection.off('data', onData));
2216
+ const onClose = () => {
2217
+ for (const fn of this._cleanupFns) {
2218
+ fn();
2219
+ }
2220
+ this._cleanupFns.clear();
1592
2221
  };
1593
- physicalLayer.on('close', handleClose);
1594
- this._removeAllListeners.push(() => physicalLayer.removeListener('close', handleClose));
2222
+ connection.on('close', onClose);
2223
+ this._cleanupFns.add(() => connection.off('close', onClose));
1595
2224
  }
1596
2225
  tryExtract(buffer) {
1597
2226
  if (buffer.length < 8) {
1598
2227
  return { kind: 'insufficient' };
1599
2228
  }
1600
2229
  if (buffer[2] !== 0 || buffer[3] !== 0) {
1601
- return { kind: 'error', error: new ModbusError(ModbusErrorCode.INVALID_DATA, 'Invalid data') };
2230
+ return { kind: 'error', error: new Error('Invalid data') };
1602
2231
  }
1603
2232
  const length = buffer.readUInt16BE(4);
1604
2233
  const total = 6 + length;
1605
2234
  if (total > MAX_TCP_FRAME || length < 2) {
1606
- return { kind: 'error', error: new ModbusError(ModbusErrorCode.INVALID_DATA, 'Invalid data') };
2235
+ return { kind: 'error', error: new Error('Invalid data') };
1607
2236
  }
1608
2237
  if (buffer.length < total) {
1609
2238
  return { kind: 'insufficient' };
1610
2239
  }
1611
2240
  return { kind: 'frame', frame: buffer.subarray(0, total), rest: buffer.subarray(total) };
1612
2241
  }
1613
- processFrame(buffer, response, connection) {
2242
+ processFrame(buffer) {
1614
2243
  const frame = {
1615
2244
  transaction: buffer.readUInt16BE(0),
1616
2245
  unit: buffer[6],
@@ -1618,12 +2247,15 @@ class TcpApplicationLayer extends AbstractApplicationLayer {
1618
2247
  data: buffer.subarray(8),
1619
2248
  buffer,
1620
2249
  };
1621
- this.emit('framing', frame, response, connection);
2250
+ this.emit('framing', frame);
2251
+ }
2252
+ flush() {
2253
+ this._buffer = Buffer.alloc(0);
1622
2254
  }
1623
2255
  encode(data) {
1624
2256
  var _a;
1625
2257
  const transaction = (_a = data.transaction) !== null && _a !== void 0 ? _a : this._transactionId;
1626
- const buffer = Buffer.alloc(data.data.length + 8);
2258
+ const buffer = Buffer.allocUnsafe(data.data.length + 8);
1627
2259
  buffer.writeUInt16BE(transaction, 0);
1628
2260
  buffer.writeUInt16BE(0, 2);
1629
2261
  buffer.writeUInt16BE(data.data.length + 2, 4);
@@ -1635,18 +2267,6 @@ class TcpApplicationLayer extends AbstractApplicationLayer {
1635
2267
  }
1636
2268
  return buffer;
1637
2269
  }
1638
- destroy() {
1639
- if (this._destroyed) {
1640
- return;
1641
- }
1642
- this._destroyed = true;
1643
- this.removeAllListeners();
1644
- for (const removeListener of this._removeAllListeners) {
1645
- removeListener();
1646
- }
1647
- this._removeAllListeners = [];
1648
- this._buffers.clear();
1649
- }
1650
2270
  }
1651
2271
 
1652
2272
  const FIFO_KEY = 'fifo';
@@ -1683,8 +2303,10 @@ class MasterSession {
1683
2303
  return;
1684
2304
  }
1685
2305
  if (!this.runPreChecks(waiter, frame)) {
2306
+ this._waiters.delete(key);
1686
2307
  return;
1687
2308
  }
2309
+ this._waiters.delete(key);
1688
2310
  waiter.callback(null, frame);
1689
2311
  }
1690
2312
  handleError(error) {
@@ -1695,22 +2317,22 @@ class MasterSession {
1695
2317
  for (const check of waiter.preCheck) {
1696
2318
  const res = check(frame);
1697
2319
  if (typeof res === 'undefined') {
1698
- waiter.callback(new ModbusError(ModbusErrorCode.INSUFFICIENT_DATA, 'Insufficient data length'));
2320
+ waiter.callback(new Error('Insufficient data length'));
1699
2321
  return false;
1700
2322
  }
1701
2323
  if (typeof res === 'number') {
1702
2324
  if (frame.data.length < res) {
1703
- waiter.callback(new ModbusError(ModbusErrorCode.INSUFFICIENT_DATA, 'Insufficient data length'));
2325
+ waiter.callback(new Error('Insufficient data length'));
1704
2326
  return false;
1705
2327
  }
1706
2328
  if (frame.data.length !== res) {
1707
- waiter.callback(new ModbusError(ModbusErrorCode.INVALID_RESPONSE, 'Invalid response'));
2329
+ waiter.callback(new Error('Invalid response'));
1708
2330
  return false;
1709
2331
  }
1710
2332
  continue;
1711
2333
  }
1712
2334
  if (!res) {
1713
- waiter.callback(new ModbusError(ModbusErrorCode.INVALID_RESPONSE, 'Invalid response'));
2335
+ waiter.callback(new Error('Invalid response'));
1714
2336
  return false;
1715
2337
  }
1716
2338
  }
@@ -1751,26 +2373,26 @@ typeof SuppressedError === "function" ? SuppressedError : function (error, suppr
1751
2373
  };
1752
2374
 
1753
2375
  class ModbusMaster extends EventEmitter {
1754
- get isOpen() {
1755
- return this.physicalLayer.isOpen;
2376
+ get state() {
2377
+ return this._physicalLayer.state;
1756
2378
  }
1757
- get destroyed() {
1758
- return this._cleanLevel === 'destroy' || this.physicalLayer.destroyed;
2379
+ get physicalLayer() {
2380
+ return this._physicalLayer;
1759
2381
  }
1760
- constructor(applicationLayer, physicalLayer, options = {}) {
2382
+ constructor(options) {
1761
2383
  var _a, _b;
1762
2384
  super();
1763
- Object.defineProperty(this, "applicationLayer", {
2385
+ Object.defineProperty(this, "timeout", {
1764
2386
  enumerable: true,
1765
2387
  configurable: true,
1766
2388
  writable: true,
1767
- value: applicationLayer
2389
+ value: void 0
1768
2390
  });
1769
- Object.defineProperty(this, "physicalLayer", {
2391
+ Object.defineProperty(this, "concurrent", {
1770
2392
  enumerable: true,
1771
2393
  configurable: true,
1772
2394
  writable: true,
1773
- value: physicalLayer
2395
+ value: void 0
1774
2396
  });
1775
2397
  Object.defineProperty(this, "_masterSession", {
1776
2398
  enumerable: true,
@@ -1778,6 +2400,30 @@ class ModbusMaster extends EventEmitter {
1778
2400
  writable: true,
1779
2401
  value: new MasterSession()
1780
2402
  });
2403
+ Object.defineProperty(this, "_physicalLayer", {
2404
+ enumerable: true,
2405
+ configurable: true,
2406
+ writable: true,
2407
+ value: void 0
2408
+ });
2409
+ Object.defineProperty(this, "_protocol", {
2410
+ enumerable: true,
2411
+ configurable: true,
2412
+ writable: true,
2413
+ value: void 0
2414
+ });
2415
+ Object.defineProperty(this, "_appLayer", {
2416
+ enumerable: true,
2417
+ configurable: true,
2418
+ writable: true,
2419
+ value: void 0
2420
+ });
2421
+ Object.defineProperty(this, "_customFunctionCodes", {
2422
+ enumerable: true,
2423
+ configurable: true,
2424
+ writable: true,
2425
+ value: new Map()
2426
+ });
1781
2427
  Object.defineProperty(this, "_queue", {
1782
2428
  enumerable: true,
1783
2429
  configurable: true,
@@ -1796,23 +2442,17 @@ class ModbusMaster extends EventEmitter {
1796
2442
  writable: true,
1797
2443
  value: 1
1798
2444
  });
1799
- Object.defineProperty(this, "_cleanLevel", {
1800
- enumerable: true,
1801
- configurable: true,
1802
- writable: true,
1803
- value: 'none'
1804
- });
1805
- Object.defineProperty(this, "timeout", {
2445
+ Object.defineProperty(this, "_cleanupFns", {
1806
2446
  enumerable: true,
1807
2447
  configurable: true,
1808
2448
  writable: true,
1809
- value: void 0
2449
+ value: new Set()
1810
2450
  });
1811
- Object.defineProperty(this, "concurrent", {
2451
+ Object.defineProperty(this, "_closePromise", {
1812
2452
  enumerable: true,
1813
2453
  configurable: true,
1814
2454
  writable: true,
1815
- value: void 0
2455
+ value: null
1816
2456
  });
1817
2457
  Object.defineProperty(this, "writeFC1", {
1818
2458
  enumerable: true,
@@ -1888,10 +2528,60 @@ class ModbusMaster extends EventEmitter {
1888
2528
  });
1889
2529
  this.timeout = (_a = options.timeout) !== null && _a !== void 0 ? _a : 1000;
1890
2530
  this.concurrent = (_b = options.concurrent) !== null && _b !== void 0 ? _b : false;
1891
- if (this.concurrent && this.applicationLayer.PROTOCOL !== 'TCP') {
1892
- throw new ModbusError(ModbusErrorCode.CONCURRENT_NOT_TCP, 'concurrent mode requires a Modbus TCP application layer');
2531
+ this._physicalLayer = createPhysicalLayer(options.physical);
2532
+ this._protocol = options.protocol;
2533
+ if (this.concurrent && options.protocol.type !== 'TCP') {
2534
+ throw new Error('concurrent mode requires a Modbus TCP application layer');
1893
2535
  }
1894
- this.applicationLayer.role = 'MASTER';
2536
+ const onOpen = () => {
2537
+ this.emit('open');
2538
+ };
2539
+ this._physicalLayer.on('open', onOpen);
2540
+ this._cleanupFns.add(() => this._physicalLayer.off('open', onOpen));
2541
+ const onConnect = (connection) => {
2542
+ const appLayer = this._createAppLayer(connection);
2543
+ this._appLayer = appLayer;
2544
+ for (const cfc of this._customFunctionCodes.values()) {
2545
+ appLayer.addCustomFunctionCode(cfc);
2546
+ }
2547
+ const cleanupFraming = () => appLayer.off('framing', onFraming);
2548
+ const onFraming = (frame) => {
2549
+ this._masterSession.handleFrame(frame);
2550
+ };
2551
+ appLayer.on('framing', onFraming);
2552
+ this._cleanupFns.add(cleanupFraming);
2553
+ const cleanupFramingError = () => appLayer.off('framing-error', onFramingError);
2554
+ const onFramingError = (error) => {
2555
+ this._masterSession.handleError(error);
2556
+ };
2557
+ appLayer.on('framing-error', onFramingError);
2558
+ this._cleanupFns.add(cleanupFramingError);
2559
+ const cleanupClose = () => connection.off('close', onClose);
2560
+ const onClose = () => {
2561
+ cleanupFraming();
2562
+ cleanupFramingError();
2563
+ cleanupClose();
2564
+ this._cleanupFns.delete(cleanupFraming);
2565
+ this._cleanupFns.delete(cleanupFramingError);
2566
+ this._cleanupFns.delete(cleanupClose);
2567
+ };
2568
+ connection.on('close', onClose);
2569
+ this._cleanupFns.add(cleanupClose);
2570
+ this.emit('connect', connection);
2571
+ };
2572
+ this._physicalLayer.on('connect', onConnect);
2573
+ this._cleanupFns.add(() => this._physicalLayer.off('connect', onConnect));
2574
+ const onClose = () => {
2575
+ this.emit('close');
2576
+ this.close();
2577
+ };
2578
+ this._physicalLayer.on('close', onClose);
2579
+ this._cleanupFns.add(() => this._physicalLayer.off('close', onClose));
2580
+ const onError = (err) => {
2581
+ this.emit('error', err);
2582
+ };
2583
+ this._physicalLayer.on('error', onError);
2584
+ this._cleanupFns.add(() => this._physicalLayer.off('error', onError));
1895
2585
  this.writeFC1 = this.readCoils;
1896
2586
  this.writeFC2 = this.readDiscreteInputs;
1897
2587
  this.writeFC3 = this.readHoldingRegisters;
@@ -1904,20 +2594,27 @@ class ModbusMaster extends EventEmitter {
1904
2594
  this.handleFC22 = this.maskWriteRegister;
1905
2595
  this.handleFC23 = this.readAndWriteMultipleRegisters;
1906
2596
  this.handleFC43_14 = this.readDeviceIdentification;
1907
- applicationLayer.on('framing', (frame) => {
1908
- this._masterSession.handleFrame(frame);
1909
- });
1910
- applicationLayer.on('framing-error', (error) => {
1911
- this._masterSession.handleError(error);
1912
- });
1913
- physicalLayer.on('error', (error) => {
1914
- this.emit('error', error);
1915
- });
1916
- physicalLayer.on('close', () => {
1917
- this.emit('close');
1918
- });
2597
+ }
2598
+ _createAppLayer(connection) {
2599
+ var _a;
2600
+ if (this._protocol.type === 'RTU') {
2601
+ const baudRate = this._physicalLayer.is('SERIAL') ? this._physicalLayer.baudRate : undefined;
2602
+ const { intervalBetweenFrames, interCharTimeout } = resolveRtuTiming(this._protocol.opts, baudRate);
2603
+ return new RtuApplicationLayer('MASTER', connection, {
2604
+ intervalBetweenFrames,
2605
+ interCharTimeout,
2606
+ poolSize: (_a = this._protocol.opts) === null || _a === void 0 ? void 0 : _a.poolSize,
2607
+ });
2608
+ }
2609
+ if (this._protocol.type === 'TCP') {
2610
+ return new TcpApplicationLayer('MASTER', connection);
2611
+ }
2612
+ return new AsciiApplicationLayer('MASTER', connection, this._protocol.opts);
1919
2613
  }
1920
2614
  send(adu, preCheck, timeout, broadcast) {
2615
+ if (this._physicalLayer.state !== exports.PhysicalState.OPEN) {
2616
+ return Promise.reject(new Error('Master is not open'));
2617
+ }
1921
2618
  const task = () => this._exchange(adu, preCheck, timeout, broadcast);
1922
2619
  if (this.concurrent) {
1923
2620
  return task();
@@ -1937,14 +2634,20 @@ class ModbusMaster extends EventEmitter {
1937
2634
  }
1938
2635
  _drain() {
1939
2636
  return __awaiter(this, void 0, void 0, function* () {
1940
- if (this._draining) {
2637
+ if (this._draining || this._physicalLayer.state !== exports.PhysicalState.OPEN) {
1941
2638
  return;
1942
2639
  }
1943
2640
  this._draining = true;
1944
2641
  try {
1945
2642
  while (this._queue.length > 0) {
2643
+ if (this._physicalLayer.state !== exports.PhysicalState.OPEN) {
2644
+ return;
2645
+ }
1946
2646
  const item = this._queue.shift();
1947
- yield item.run();
2647
+ try {
2648
+ yield item.run();
2649
+ }
2650
+ catch (_a) { }
1948
2651
  }
1949
2652
  }
1950
2653
  finally {
@@ -1954,18 +2657,26 @@ class ModbusMaster extends EventEmitter {
1954
2657
  }
1955
2658
  _exchange(adu, preCheck, timeout, broadcast) {
1956
2659
  return new Promise((resolve, reject) => {
1957
- if (this._cleanLevel !== 'none') {
1958
- reject(new ModbusError(ModbusErrorCode.MASTER_CLOSED, 'Master closed'));
2660
+ if (this._physicalLayer.state !== exports.PhysicalState.OPEN) {
2661
+ reject(new Error('Master is not open'));
2662
+ return;
2663
+ }
2664
+ const appLayer = this._appLayer;
2665
+ if (!appLayer) {
2666
+ reject(new Error('Connection not established'));
1959
2667
  return;
1960
2668
  }
1961
- const usesTid = this.applicationLayer.PROTOCOL === 'TCP' && !broadcast;
2669
+ const connection = appLayer.connection;
2670
+ const usesTid = appLayer.PROTOCOL === 'TCP' && !broadcast;
1962
2671
  let tid;
1963
2672
  if (usesTid) {
1964
- tid = this._nextTid;
1965
- this._nextTid = (this._nextTid + 1) % 65536 || 1;
2673
+ do {
2674
+ tid = this._nextTid;
2675
+ this._nextTid = (this._nextTid + 1) % 65536 || 1;
2676
+ } while (this._masterSession.has(tid));
1966
2677
  }
1967
2678
  const key = tid !== null && tid !== void 0 ? tid : FIFO_KEY;
1968
- const payload = this.applicationLayer.encode(Object.assign(Object.assign({}, adu), { transaction: tid }));
2679
+ const payload = appLayer.encode(Object.assign(Object.assign({}, adu), { transaction: tid }));
1969
2680
  let settled = false;
1970
2681
  const settle = (error, frame) => {
1971
2682
  if (settled) {
@@ -1981,18 +2692,22 @@ class ModbusMaster extends EventEmitter {
1981
2692
  resolve(frame);
1982
2693
  }
1983
2694
  };
1984
- const timer = setTimeout(() => settle(new ModbusError(ModbusErrorCode.TIMEOUT, 'Timeout')), timeout);
2695
+ const timer = setTimeout(() => settle(new Error('Timeout')), timeout);
1985
2696
  // FIFO mode: clear stale buffer state before this request.
1986
2697
  // Concurrent mode: must not flush — other in-flight requests share the buffer.
1987
2698
  if (!this.concurrent) {
1988
- this.applicationLayer.flush();
2699
+ appLayer.flush();
1989
2700
  }
1990
- this.physicalLayer
2701
+ connection
1991
2702
  .write(payload)
1992
2703
  .then(() => {
1993
2704
  if (settled) {
1994
2705
  return;
1995
2706
  }
2707
+ if (this._physicalLayer.state !== exports.PhysicalState.OPEN) {
2708
+ settle(new Error('Master is not open'));
2709
+ return;
2710
+ }
1996
2711
  if (broadcast) {
1997
2712
  settle(null);
1998
2713
  return;
@@ -2005,7 +2720,7 @@ class ModbusMaster extends EventEmitter {
2005
2720
  }
2006
2721
  writeFC1Or2(unit, fc, address, length, timeout) {
2007
2722
  const byteCount = Math.ceil(length / 8);
2008
- const bufferTx = Buffer.alloc(4);
2723
+ const bufferTx = Buffer.allocUnsafe(4);
2009
2724
  bufferTx.writeUInt16BE(address, 0);
2010
2725
  bufferTx.writeUInt16BE(length, 2);
2011
2726
  return this.send({ unit, fc, data: bufferTx }, [(frame) => frame.unit === unit && frame.fc === fc, () => 1 + byteCount, (frame) => frame.data[0] === byteCount], timeout, unit === 0).then((frame) => {
@@ -2022,7 +2737,7 @@ class ModbusMaster extends EventEmitter {
2022
2737
  }
2023
2738
  writeFC3Or4(unit, fc, address, length, timeout) {
2024
2739
  const byteCount = length * 2;
2025
- const bufferTx = Buffer.alloc(4);
2740
+ const bufferTx = Buffer.allocUnsafe(4);
2026
2741
  bufferTx.writeUInt16BE(address, 0);
2027
2742
  bufferTx.writeUInt16BE(length, 2);
2028
2743
  return this.send({ unit, fc, data: bufferTx }, [(frame) => frame.unit === unit && frame.fc === fc, () => 1 + byteCount, (frame) => frame.data[0] === byteCount], timeout, unit === 0).then((frame) => {
@@ -2040,7 +2755,7 @@ class ModbusMaster extends EventEmitter {
2040
2755
  }
2041
2756
  writeSingleCoil(unit, address, value, timeout = this.timeout) {
2042
2757
  const fc = exports.FunctionCode.WRITE_SINGLE_COIL;
2043
- const bufferTx = Buffer.alloc(4);
2758
+ const bufferTx = Buffer.allocUnsafe(4);
2044
2759
  bufferTx.writeUInt16BE(address, 0);
2045
2760
  bufferTx.writeUInt16BE(value ? COIL_ON : COIL_OFF, 2);
2046
2761
  return this.send({ unit, fc, data: bufferTx }, [(frame) => frame.unit === unit && frame.fc === fc, () => bufferTx.length, (frame) => frame.data.equals(bufferTx)], timeout, unit === 0).then((frame) => {
@@ -2051,7 +2766,7 @@ class ModbusMaster extends EventEmitter {
2051
2766
  }
2052
2767
  writeSingleRegister(unit, address, value, timeout = this.timeout) {
2053
2768
  const fc = exports.FunctionCode.WRITE_SINGLE_REGISTER;
2054
- const bufferTx = Buffer.alloc(4);
2769
+ const bufferTx = Buffer.allocUnsafe(4);
2055
2770
  bufferTx.writeUInt16BE(address, 0);
2056
2771
  bufferTx.writeUInt16BE(value, 2);
2057
2772
  return this.send({ unit, fc, data: bufferTx }, [(frame) => frame.unit === unit && frame.fc === fc, () => bufferTx.length, (frame) => frame.data.equals(bufferTx)], timeout, unit === 0).then((frame) => {
@@ -2081,7 +2796,7 @@ class ModbusMaster extends EventEmitter {
2081
2796
  writeMultipleRegisters(unit, address, value, timeout = this.timeout) {
2082
2797
  const fc = exports.FunctionCode.WRITE_MULTIPLE_REGISTERS;
2083
2798
  const byteCount = value.length * 2;
2084
- const bufferTx = Buffer.alloc(5 + byteCount);
2799
+ const bufferTx = Buffer.allocUnsafe(5 + byteCount);
2085
2800
  bufferTx.writeUInt16BE(address, 0);
2086
2801
  bufferTx.writeUInt16BE(value.length, 2);
2087
2802
  bufferTx.writeUInt8(byteCount, 4);
@@ -2116,7 +2831,7 @@ class ModbusMaster extends EventEmitter {
2116
2831
  }
2117
2832
  maskWriteRegister(unit, address, andMask, orMask, timeout = this.timeout) {
2118
2833
  const fc = exports.FunctionCode.MASK_WRITE_REGISTER;
2119
- const bufferTx = Buffer.alloc(6);
2834
+ const bufferTx = Buffer.allocUnsafe(6);
2120
2835
  bufferTx.writeUInt16BE(address, 0);
2121
2836
  bufferTx.writeUInt16BE(andMask, 2);
2122
2837
  bufferTx.writeUInt16BE(orMask, 4);
@@ -2130,7 +2845,7 @@ class ModbusMaster extends EventEmitter {
2130
2845
  const fc = exports.FunctionCode.READ_WRITE_MULTIPLE_REGISTERS;
2131
2846
  const byteCount = write.value.length * 2;
2132
2847
  const readByteCount = read.length * 2;
2133
- const bufferTx = Buffer.alloc(9 + byteCount);
2848
+ const bufferTx = Buffer.allocUnsafe(9 + byteCount);
2134
2849
  bufferTx.writeUInt16BE(read.address, 0);
2135
2850
  bufferTx.writeUInt16BE(read.length, 2);
2136
2851
  bufferTx.writeUInt16BE(write.address, 4);
@@ -2202,10 +2917,14 @@ class ModbusMaster extends EventEmitter {
2202
2917
  });
2203
2918
  }
2204
2919
  addCustomFunctionCode(cfc) {
2205
- this.applicationLayer.addCustomFunctionCode(cfc);
2920
+ var _a;
2921
+ this._customFunctionCodes.set(cfc.fc, cfc);
2922
+ (_a = this._appLayer) === null || _a === void 0 ? void 0 : _a.addCustomFunctionCode(cfc);
2206
2923
  }
2207
2924
  removeCustomFunctionCode(fc) {
2208
- this.applicationLayer.removeCustomFunctionCode(fc);
2925
+ var _a;
2926
+ this._customFunctionCodes.delete(fc);
2927
+ (_a = this._appLayer) === null || _a === void 0 ? void 0 : _a.removeCustomFunctionCode(fc);
2209
2928
  }
2210
2929
  sendCustomFC(unit, fc, data, timeout = this.timeout) {
2211
2930
  const payload = Buffer.isBuffer(data) ? data : Buffer.from(data);
@@ -2215,83 +2934,107 @@ class ModbusMaster extends EventEmitter {
2215
2934
  }
2216
2935
  });
2217
2936
  }
2218
- _clean(level) {
2219
- if (this._cleanLevel === 'destroy') {
2220
- return;
2221
- }
2222
- if (this._cleanLevel === 'close' && level === 'close') {
2223
- return;
2224
- }
2225
- const errorCode = level === 'destroy' ? ModbusErrorCode.MASTER_DESTROYED : ModbusErrorCode.MASTER_CLOSED;
2226
- const message = level === 'destroy' ? 'Master destroyed' : 'Master closed';
2937
+ _clean(message) {
2227
2938
  const queued = this._queue.splice(0);
2228
2939
  for (const item of queued) {
2229
- item.cancel(new ModbusError(errorCode, message));
2230
- }
2231
- this._masterSession.stopAll(new ModbusError(errorCode, message));
2232
- this._cleanLevel = level;
2233
- }
2940
+ item.cancel(new Error(message));
2941
+ }
2942
+ this._masterSession.stopAll(new Error(message));
2943
+ }
2944
+ /**
2945
+ * Open the underlying physical layer and begin accepting connections.
2946
+ *
2947
+ * A `ModbusMaster` instance can only be opened once. Once {@link close}
2948
+ * is called — explicitly or because the physical layer disconnected —
2949
+ * the instance is permanently closed and cannot be reopened.
2950
+ * Create a new `ModbusMaster` if a new connection is required.
2951
+ */
2234
2952
  open(...args) {
2235
- if (this._cleanLevel === 'destroy') {
2236
- return Promise.reject(new ModbusError(ModbusErrorCode.PORT_DESTROYED, 'Master is destroyed'));
2953
+ if (this._closePromise) {
2954
+ return Promise.reject(new Error('The port has been permanently closed and CANNOT be reopened'));
2237
2955
  }
2238
- this._cleanLevel = 'none';
2239
- this._nextTid = 1;
2240
- return this.physicalLayer.open(...args);
2956
+ return this._physicalLayer.open(...args);
2241
2957
  }
2242
- close() {
2243
- if (this._cleanLevel === 'destroy') {
2244
- return Promise.resolve();
2245
- }
2246
- this._clean('close');
2247
- return this.physicalLayer.close();
2958
+ _close() {
2959
+ return __awaiter(this, void 0, void 0, function* () {
2960
+ for (const fn of this._cleanupFns) {
2961
+ fn();
2962
+ }
2963
+ this._cleanupFns.clear();
2964
+ this._clean('Master is closing');
2965
+ let err = null;
2966
+ try {
2967
+ yield this._physicalLayer.close();
2968
+ }
2969
+ catch (error) {
2970
+ err = error;
2971
+ }
2972
+ this.removeAllListeners();
2973
+ if (err) {
2974
+ return Promise.reject(err);
2975
+ }
2976
+ });
2248
2977
  }
2249
- destroy() {
2250
- if (this._cleanLevel === 'destroy') {
2251
- return Promise.resolve();
2252
- }
2253
- this._clean('destroy');
2254
- this.removeAllListeners();
2255
- this.applicationLayer.destroy();
2256
- return this.physicalLayer.destroy();
2978
+ /**
2979
+ * Permanently close the master and release all resources.
2980
+ *
2981
+ * After calling this method the instance is considered dead:
2982
+ * - No further requests can be sent.
2983
+ * - The instance cannot be reopened via {@link open}.
2984
+ * - All event listeners registered on this master are removed.
2985
+ */
2986
+ close() {
2987
+ return __awaiter(this, void 0, void 0, function* () {
2988
+ if (this._closePromise) {
2989
+ return this._closePromise;
2990
+ }
2991
+ this._closePromise = this._close();
2992
+ return this._closePromise;
2993
+ });
2257
2994
  }
2258
2995
  }
2259
2996
 
2997
+ function prependByte(byte, buffer) {
2998
+ const result = Buffer.allocUnsafe(buffer.length + 1);
2999
+ result[0] = byte;
3000
+ buffer.copy(result, 1);
3001
+ return result;
3002
+ }
2260
3003
  class ModbusSlave extends EventEmitter {
2261
- get isOpen() {
2262
- return this.physicalLayer.isOpen;
3004
+ get state() {
3005
+ return this._physicalLayer.state;
2263
3006
  }
2264
- get destroyed() {
2265
- return this._cleanLevel === 'destroy' || this.physicalLayer.destroyed;
3007
+ get physicalLayer() {
3008
+ return this._physicalLayer;
2266
3009
  }
2267
- constructor(applicationLayer, physicalLayer, options = {}) {
3010
+ constructor(options) {
2268
3011
  var _a;
2269
3012
  super();
2270
- Object.defineProperty(this, "applicationLayer", {
3013
+ Object.defineProperty(this, "models", {
2271
3014
  enumerable: true,
2272
3015
  configurable: true,
2273
3016
  writable: true,
2274
- value: applicationLayer
3017
+ value: new Map()
2275
3018
  });
2276
- Object.defineProperty(this, "physicalLayer", {
3019
+ Object.defineProperty(this, "concurrent", {
2277
3020
  enumerable: true,
2278
3021
  configurable: true,
2279
3022
  writable: true,
2280
- value: physicalLayer
3023
+ value: void 0
2281
3024
  });
2282
- Object.defineProperty(this, "models", {
3025
+ Object.defineProperty(this, "_physicalLayer", {
2283
3026
  enumerable: true,
2284
3027
  configurable: true,
2285
3028
  writable: true,
2286
- value: new Map()
3029
+ value: void 0
2287
3030
  });
2288
- Object.defineProperty(this, "concurrent", {
3031
+ Object.defineProperty(this, "_protocol", {
2289
3032
  enumerable: true,
2290
3033
  configurable: true,
2291
3034
  writable: true,
2292
3035
  value: void 0
2293
3036
  });
2294
- Object.defineProperty(this, "_queues", {
3037
+ Object.defineProperty(this, "_appLayers", {
2295
3038
  enumerable: true,
2296
3039
  configurable: true,
2297
3040
  writable: true,
@@ -2309,72 +3052,112 @@ class ModbusSlave extends EventEmitter {
2309
3052
  writable: true,
2310
3053
  value: new Map()
2311
3054
  });
2312
- Object.defineProperty(this, "_cleanLevel", {
3055
+ Object.defineProperty(this, "_cleanupFns", {
2313
3056
  enumerable: true,
2314
3057
  configurable: true,
2315
3058
  writable: true,
2316
- value: 'none'
2317
- });
2318
- this.concurrent = (_a = options.concurrent) !== null && _a !== void 0 ? _a : false;
2319
- if (this.concurrent && this.applicationLayer.PROTOCOL !== 'TCP') {
2320
- throw new ModbusError(ModbusErrorCode.CONCURRENT_NOT_TCP, 'concurrent mode requires a Modbus TCP application layer');
2321
- }
2322
- this.applicationLayer.role = 'SLAVE';
2323
- applicationLayer.on('framing', (frame, response, connection) => {
2324
- if (!(frame.unit === 0x00 || this.models.has(frame.unit))) {
2325
- return;
2326
- }
2327
- if (this.concurrent) {
2328
- this._processFrame(frame, response).catch((error) => this.emit('error', error));
2329
- return;
2330
- }
2331
- let q = this._queues.get(connection.id);
2332
- if (!q) {
2333
- q = { items: [], processing: false };
2334
- this._queues.set(connection.id, q);
2335
- }
2336
- q.items.push({ frame, response });
2337
- this._drain(connection.id, q);
2338
- });
2339
- physicalLayer.on('error', (error) => {
2340
- this.emit('error', error);
3059
+ value: new Set()
2341
3060
  });
2342
- physicalLayer.on('connection-close', (connection) => {
2343
- const q = this._queues.get(connection.id);
2344
- if (!q) {
2345
- return;
2346
- }
2347
- q.items = [];
2348
- if (!q.processing) {
2349
- this._queues.delete(connection.id);
2350
- }
3061
+ Object.defineProperty(this, "_closePromise", {
3062
+ enumerable: true,
3063
+ configurable: true,
3064
+ writable: true,
3065
+ value: null
2351
3066
  });
2352
- physicalLayer.on('close', () => {
2353
- for (const q of this._queues.values()) {
2354
- q.items = [];
2355
- }
2356
- this._queues.clear();
3067
+ this.concurrent = (_a = options.concurrent) !== null && _a !== void 0 ? _a : false;
3068
+ this._physicalLayer = createPhysicalLayer(options.physical);
3069
+ this._protocol = options.protocol;
3070
+ if (this.concurrent && options.protocol.type !== 'TCP') {
3071
+ throw new Error('concurrent mode requires a Modbus TCP application layer');
3072
+ }
3073
+ const onOpen = () => this.emit('open');
3074
+ this._physicalLayer.on('open', onOpen);
3075
+ this._cleanupFns.add(() => this._physicalLayer.off('open', onOpen));
3076
+ const onConnect = (connection) => {
3077
+ const appLayer = this._createAppLayer(connection);
3078
+ const appLayerData = { queues: { frames: [], processing: false } };
3079
+ this._appLayers.set(appLayer, appLayerData);
3080
+ for (const cfc of this._customFunctionCodes.values()) {
3081
+ appLayer.addCustomFunctionCode(cfc);
3082
+ }
3083
+ const cleanupFraming = () => appLayer.off('framing', onFraming);
3084
+ const onFraming = (frame) => {
3085
+ if (this._physicalLayer.state !== exports.PhysicalState.OPEN) {
3086
+ return;
3087
+ }
3088
+ if (!(frame.unit === 0x00 || this.models.has(frame.unit))) {
3089
+ return;
3090
+ }
3091
+ if (this.concurrent) {
3092
+ this._processFrame(appLayer, frame);
3093
+ return;
3094
+ }
3095
+ appLayerData.queues.frames.push(frame);
3096
+ this._drain(appLayer, appLayerData.queues);
3097
+ };
3098
+ appLayer.on('framing', onFraming);
3099
+ this._cleanupFns.add(cleanupFraming);
3100
+ const cleanupClose = () => connection.off('close', onClose);
3101
+ const onClose = () => {
3102
+ cleanupFraming();
3103
+ cleanupClose();
3104
+ this._cleanupFns.delete(cleanupFraming);
3105
+ this._cleanupFns.delete(cleanupClose);
3106
+ this._appLayers.delete(appLayer);
3107
+ };
3108
+ connection.on('close', onClose);
3109
+ this._cleanupFns.add(cleanupClose);
3110
+ this.emit('connect', connection);
3111
+ };
3112
+ this._physicalLayer.on('connect', onConnect);
3113
+ this._cleanupFns.add(() => this._physicalLayer.off('connect', onConnect));
3114
+ const onClose = () => {
2357
3115
  this.emit('close');
2358
- });
3116
+ this.close();
3117
+ };
3118
+ this._physicalLayer.on('close', onClose);
3119
+ this._cleanupFns.add(() => this._physicalLayer.off('close', onClose));
3120
+ const onError = (err) => {
3121
+ this.emit('error', err);
3122
+ };
3123
+ this._physicalLayer.on('error', onError);
3124
+ this._cleanupFns.add(() => this._physicalLayer.off('error', onError));
3125
+ }
3126
+ _createAppLayer(connection) {
3127
+ var _a;
3128
+ if (this._protocol.type === 'RTU') {
3129
+ const baudRate = this._physicalLayer.is('SERIAL') ? this._physicalLayer.baudRate : undefined;
3130
+ const { intervalBetweenFrames, interCharTimeout } = resolveRtuTiming(this._protocol.opts, baudRate);
3131
+ return new RtuApplicationLayer('SLAVE', connection, {
3132
+ intervalBetweenFrames,
3133
+ interCharTimeout,
3134
+ poolSize: (_a = this._protocol.opts) === null || _a === void 0 ? void 0 : _a.poolSize,
3135
+ });
3136
+ }
3137
+ if (this._protocol.type === 'TCP') {
3138
+ return new TcpApplicationLayer('SLAVE', connection);
3139
+ }
3140
+ return new AsciiApplicationLayer('SLAVE', connection, this._protocol.opts);
2359
3141
  }
2360
- handleFC1(model, frame, response) {
3142
+ handleFC1(appLayer, model, frame, response) {
2361
3143
  return __awaiter(this, void 0, void 0, function* () {
2362
3144
  var _a;
2363
3145
  if (frame.data.length !== 4) {
3146
+ yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
2364
3147
  return;
2365
3148
  }
2366
3149
  if (!model.readCoils) {
2367
- yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
3150
+ yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
2368
3151
  return;
2369
3152
  }
2370
3153
  const address = (frame.data[0] << 8) | frame.data[1];
2371
3154
  const length = (frame.data[2] << 8) | frame.data[3];
2372
3155
  if (length < LIMITS.READ_COILS_MIN || length > LIMITS.READ_COILS_MAX) {
2373
- yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
3156
+ yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
2374
3157
  return;
2375
3158
  }
2376
3159
  if (!checkRange([address, address + length], (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).coils)) {
2377
- yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
3160
+ yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
2378
3161
  return;
2379
3162
  }
2380
3163
  try {
@@ -2385,31 +3168,32 @@ class ModbusSlave extends EventEmitter {
2385
3168
  bufferTx[Math.floor(index / 8)] |= 1 << index % 8;
2386
3169
  }
2387
3170
  });
2388
- yield response(this.applicationLayer.encode(Object.assign(Object.assign({}, frame), { data: Buffer.concat([Buffer.from([bufferTx.length]), bufferTx]) })));
3171
+ yield response(appLayer.encode(Object.assign(Object.assign({}, frame), { data: prependByte(bufferTx.length, bufferTx) })));
2389
3172
  }
2390
3173
  catch (error) {
2391
- yield this.responseError(frame, response, error);
3174
+ yield this.responseError(appLayer, frame, response, error);
2392
3175
  }
2393
3176
  });
2394
3177
  }
2395
- handleFC2(model, frame, response) {
3178
+ handleFC2(appLayer, model, frame, response) {
2396
3179
  return __awaiter(this, void 0, void 0, function* () {
2397
3180
  var _a;
2398
3181
  if (frame.data.length !== 4) {
3182
+ yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
2399
3183
  return;
2400
3184
  }
2401
3185
  if (!model.readDiscreteInputs) {
2402
- yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
3186
+ yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
2403
3187
  return;
2404
3188
  }
2405
3189
  const address = (frame.data[0] << 8) | frame.data[1];
2406
3190
  const length = (frame.data[2] << 8) | frame.data[3];
2407
3191
  if (length < LIMITS.READ_COILS_MIN || length > LIMITS.READ_COILS_MAX) {
2408
- yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
3192
+ yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
2409
3193
  return;
2410
3194
  }
2411
3195
  if (!checkRange([address, address + length], (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).discreteInputs)) {
2412
- yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
3196
+ yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
2413
3197
  return;
2414
3198
  }
2415
3199
  try {
@@ -2420,152 +3204,157 @@ class ModbusSlave extends EventEmitter {
2420
3204
  bufferTx[Math.floor(index / 8)] |= 1 << index % 8;
2421
3205
  }
2422
3206
  });
2423
- yield response(this.applicationLayer.encode(Object.assign(Object.assign({}, frame), { data: Buffer.concat([Buffer.from([bufferTx.length]), bufferTx]) })));
3207
+ yield response(appLayer.encode(Object.assign(Object.assign({}, frame), { data: prependByte(bufferTx.length, bufferTx) })));
2424
3208
  }
2425
3209
  catch (error) {
2426
- yield this.responseError(frame, response, error);
3210
+ yield this.responseError(appLayer, frame, response, error);
2427
3211
  }
2428
3212
  });
2429
3213
  }
2430
- handleFC3(model, frame, response) {
3214
+ handleFC3(appLayer, model, frame, response) {
2431
3215
  return __awaiter(this, void 0, void 0, function* () {
2432
3216
  var _a;
2433
3217
  if (frame.data.length !== 4) {
3218
+ yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
2434
3219
  return;
2435
3220
  }
2436
3221
  if (!model.readHoldingRegisters) {
2437
- yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
3222
+ yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
2438
3223
  return;
2439
3224
  }
2440
3225
  const address = (frame.data[0] << 8) | frame.data[1];
2441
3226
  const length = (frame.data[2] << 8) | frame.data[3];
2442
3227
  if (length < LIMITS.READ_REGISTERS_MIN || length > LIMITS.READ_REGISTERS_MAX) {
2443
- yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
3228
+ yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
2444
3229
  return;
2445
3230
  }
2446
3231
  if (!checkRange([address, address + length], (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).holdingRegisters)) {
2447
- yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
3232
+ yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
2448
3233
  return;
2449
3234
  }
2450
3235
  try {
2451
3236
  const registers = yield model.readHoldingRegisters(address, length);
2452
- const bufferTx = Buffer.alloc(length * 2);
3237
+ const bufferTx = Buffer.allocUnsafe(length * 2);
2453
3238
  registers.forEach((register, index) => {
2454
3239
  bufferTx.writeUInt16BE(register, index * 2);
2455
3240
  });
2456
- yield response(this.applicationLayer.encode(Object.assign(Object.assign({}, frame), { data: Buffer.concat([Buffer.from([bufferTx.length]), bufferTx]) })));
3241
+ yield response(appLayer.encode(Object.assign(Object.assign({}, frame), { data: prependByte(bufferTx.length, bufferTx) })));
2457
3242
  }
2458
3243
  catch (error) {
2459
- yield this.responseError(frame, response, error);
3244
+ yield this.responseError(appLayer, frame, response, error);
2460
3245
  }
2461
3246
  });
2462
3247
  }
2463
- handleFC4(model, frame, response) {
3248
+ handleFC4(appLayer, model, frame, response) {
2464
3249
  return __awaiter(this, void 0, void 0, function* () {
2465
3250
  var _a;
2466
3251
  if (frame.data.length !== 4) {
3252
+ yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
2467
3253
  return;
2468
3254
  }
2469
3255
  if (!model.readInputRegisters) {
2470
- yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
3256
+ yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
2471
3257
  return;
2472
3258
  }
2473
3259
  const address = (frame.data[0] << 8) | frame.data[1];
2474
3260
  const length = (frame.data[2] << 8) | frame.data[3];
2475
3261
  if (length < LIMITS.READ_REGISTERS_MIN || length > LIMITS.READ_REGISTERS_MAX) {
2476
- yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
3262
+ yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
2477
3263
  return;
2478
3264
  }
2479
3265
  if (!checkRange([address, address + length], (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).inputRegisters)) {
2480
- yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
3266
+ yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
2481
3267
  return;
2482
3268
  }
2483
3269
  try {
2484
3270
  const registers = yield model.readInputRegisters(address, length);
2485
- const bufferTx = Buffer.alloc(length * 2);
3271
+ const bufferTx = Buffer.allocUnsafe(length * 2);
2486
3272
  registers.forEach((register, index) => {
2487
3273
  bufferTx.writeUInt16BE(register, index * 2);
2488
3274
  });
2489
- yield response(this.applicationLayer.encode(Object.assign(Object.assign({}, frame), { data: Buffer.concat([Buffer.from([bufferTx.length]), bufferTx]) })));
3275
+ yield response(appLayer.encode(Object.assign(Object.assign({}, frame), { data: prependByte(bufferTx.length, bufferTx) })));
2490
3276
  }
2491
3277
  catch (error) {
2492
- yield this.responseError(frame, response, error);
3278
+ yield this.responseError(appLayer, frame, response, error);
2493
3279
  }
2494
3280
  });
2495
3281
  }
2496
- handleFC5(model, frame, response) {
3282
+ handleFC5(appLayer, model, frame, response) {
2497
3283
  return __awaiter(this, void 0, void 0, function* () {
2498
3284
  var _a;
2499
3285
  if (frame.data.length !== 4) {
3286
+ yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
2500
3287
  return;
2501
3288
  }
2502
3289
  if (!model.writeSingleCoil) {
2503
- yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
3290
+ yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
2504
3291
  return;
2505
3292
  }
2506
3293
  const address = (frame.data[0] << 8) | frame.data[1];
2507
3294
  const value = (frame.data[2] << 8) | frame.data[3];
2508
3295
  if (value !== COIL_OFF && value !== COIL_ON) {
2509
- yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
3296
+ yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
2510
3297
  return;
2511
3298
  }
2512
3299
  if (!checkRange(address, (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).coils)) {
2513
- yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
3300
+ yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
2514
3301
  return;
2515
3302
  }
2516
3303
  try {
2517
3304
  yield model.writeSingleCoil(address, value === COIL_ON);
2518
- yield response(this.applicationLayer.encode(frame));
3305
+ yield response(appLayer.encode(frame));
2519
3306
  }
2520
3307
  catch (error) {
2521
- yield this.responseError(frame, response, error);
3308
+ yield this.responseError(appLayer, frame, response, error);
2522
3309
  }
2523
3310
  });
2524
3311
  }
2525
- handleFC6(model, frame, response) {
3312
+ handleFC6(appLayer, model, frame, response) {
2526
3313
  return __awaiter(this, void 0, void 0, function* () {
2527
3314
  var _a;
2528
3315
  if (frame.data.length !== 4) {
3316
+ yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
2529
3317
  return;
2530
3318
  }
2531
3319
  if (!model.writeSingleRegister) {
2532
- yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
3320
+ yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
2533
3321
  return;
2534
3322
  }
2535
3323
  const address = (frame.data[0] << 8) | frame.data[1];
2536
3324
  const value = (frame.data[2] << 8) | frame.data[3];
2537
3325
  if (!checkRange(address, (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).holdingRegisters)) {
2538
- yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
3326
+ yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
2539
3327
  return;
2540
3328
  }
2541
3329
  try {
2542
3330
  yield model.writeSingleRegister(address, value);
2543
- yield response(this.applicationLayer.encode(frame));
3331
+ yield response(appLayer.encode(frame));
2544
3332
  }
2545
3333
  catch (error) {
2546
- yield this.responseError(frame, response, error);
3334
+ yield this.responseError(appLayer, frame, response, error);
2547
3335
  }
2548
3336
  });
2549
3337
  }
2550
- handleFC15(model, frame, response) {
3338
+ handleFC15(appLayer, model, frame, response) {
2551
3339
  return __awaiter(this, void 0, void 0, function* () {
2552
3340
  var _a;
2553
3341
  if (frame.data.length <= 5 || frame.data.length !== 5 + frame.data[4]) {
3342
+ yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
2554
3343
  return;
2555
3344
  }
2556
3345
  if (!model.writeMultipleCoils && !model.writeSingleCoil) {
2557
- yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
3346
+ yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
2558
3347
  return;
2559
3348
  }
2560
3349
  const address = (frame.data[0] << 8) | frame.data[1];
2561
3350
  const length = (frame.data[2] << 8) | frame.data[3];
2562
3351
  const byteCount = frame.data[4];
2563
3352
  if (length < LIMITS.READ_COILS_MIN || length > LIMITS.WRITE_COILS_MAX || byteCount !== Math.ceil(length / 8)) {
2564
- yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
3353
+ yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
2565
3354
  return;
2566
3355
  }
2567
3356
  if (!checkRange([address, address + length], (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).coils)) {
2568
- yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
3357
+ yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
2569
3358
  return;
2570
3359
  }
2571
3360
  const value = new Array(length);
@@ -2581,32 +3370,33 @@ class ModbusSlave extends EventEmitter {
2581
3370
  yield model.writeSingleCoil(address + i, value[i]);
2582
3371
  }
2583
3372
  }
2584
- yield response(this.applicationLayer.encode(Object.assign(Object.assign({}, frame), { data: frame.data.subarray(0, 4) })));
3373
+ yield response(appLayer.encode(Object.assign(Object.assign({}, frame), { data: frame.data.subarray(0, 4) })));
2585
3374
  }
2586
3375
  catch (error) {
2587
- yield this.responseError(frame, response, error);
3376
+ yield this.responseError(appLayer, frame, response, error);
2588
3377
  }
2589
3378
  });
2590
3379
  }
2591
- handleFC16(model, frame, response) {
3380
+ handleFC16(appLayer, model, frame, response) {
2592
3381
  return __awaiter(this, void 0, void 0, function* () {
2593
3382
  var _a;
2594
3383
  if (frame.data.length <= 5 || frame.data.length !== 5 + frame.data[4]) {
3384
+ yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
2595
3385
  return;
2596
3386
  }
2597
3387
  if (!model.writeMultipleRegisters && !model.writeSingleRegister) {
2598
- yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
3388
+ yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
2599
3389
  return;
2600
3390
  }
2601
3391
  const address = (frame.data[0] << 8) | frame.data[1];
2602
3392
  const length = (frame.data[2] << 8) | frame.data[3];
2603
3393
  const byteCount = frame.data[4];
2604
3394
  if (length < LIMITS.READ_REGISTERS_MIN || length > LIMITS.WRITE_REGISTERS_MAX || byteCount !== length * 2) {
2605
- yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
3395
+ yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
2606
3396
  return;
2607
3397
  }
2608
3398
  if (!checkRange([address, address + length], (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).holdingRegisters)) {
2609
- yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
3399
+ yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
2610
3400
  return;
2611
3401
  }
2612
3402
  const value = new Array(length);
@@ -2622,21 +3412,22 @@ class ModbusSlave extends EventEmitter {
2622
3412
  yield model.writeSingleRegister(address + i, value[i]);
2623
3413
  }
2624
3414
  }
2625
- yield response(this.applicationLayer.encode(Object.assign(Object.assign({}, frame), { data: frame.data.subarray(0, 4) })));
3415
+ yield response(appLayer.encode(Object.assign(Object.assign({}, frame), { data: frame.data.subarray(0, 4) })));
2626
3416
  }
2627
3417
  catch (error) {
2628
- yield this.responseError(frame, response, error);
3418
+ yield this.responseError(appLayer, frame, response, error);
2629
3419
  }
2630
3420
  });
2631
3421
  }
2632
- handleFC17(model, frame, response) {
3422
+ handleFC17(appLayer, model, frame, response) {
2633
3423
  return __awaiter(this, void 0, void 0, function* () {
2634
3424
  var _a;
2635
3425
  if (frame.data.length !== 0) {
3426
+ yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
2636
3427
  return;
2637
3428
  }
2638
3429
  if (!model.reportServerId) {
2639
- yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
3430
+ yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
2640
3431
  return;
2641
3432
  }
2642
3433
  try {
@@ -2644,40 +3435,44 @@ class ModbusSlave extends EventEmitter {
2644
3435
  const serverIdBytes = serverId;
2645
3436
  const byteCount = serverIdBytes.length + 1 + additionalData.length;
2646
3437
  if (byteCount > 255) {
2647
- yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.SERVER_DEVICE_FAILURE));
3438
+ yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.SERVER_DEVICE_FAILURE));
2648
3439
  return;
2649
3440
  }
2650
3441
  const allBytes = [...serverIdBytes, runIndicatorStatus ? 0xff : 0x00, ...additionalData];
2651
3442
  if (allBytes.some((b) => !isUint8(b))) {
2652
- yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.SERVER_DEVICE_FAILURE));
3443
+ yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.SERVER_DEVICE_FAILURE));
2653
3444
  return;
2654
3445
  }
2655
- yield response(this.applicationLayer.encode(Object.assign(Object.assign({}, frame), { data: Buffer.concat([Buffer.from([byteCount]), Buffer.from(allBytes)]) })));
3446
+ const data = Buffer.allocUnsafe(byteCount + 1);
3447
+ data[0] = byteCount;
3448
+ data.set(allBytes, 1);
3449
+ yield response(appLayer.encode(Object.assign(Object.assign({}, frame), { data })));
2656
3450
  }
2657
3451
  catch (error) {
2658
- yield this.responseError(frame, response, error);
3452
+ yield this.responseError(appLayer, frame, response, error);
2659
3453
  }
2660
3454
  });
2661
3455
  }
2662
- handleFC22(model, frame, response) {
3456
+ handleFC22(appLayer, model, frame, response) {
2663
3457
  return __awaiter(this, void 0, void 0, function* () {
2664
3458
  var _a;
2665
3459
  if (frame.data.length !== 6) {
3460
+ yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
2666
3461
  return;
2667
3462
  }
2668
3463
  if (!model.maskWriteRegister && !(model.readHoldingRegisters && model.writeSingleRegister)) {
2669
- yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
3464
+ yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
2670
3465
  return;
2671
3466
  }
2672
3467
  const address = (frame.data[0] << 8) | frame.data[1];
2673
3468
  const andMask = (frame.data[2] << 8) | frame.data[3];
2674
3469
  const orMask = (frame.data[4] << 8) | frame.data[5];
2675
3470
  if (!checkRange(address, (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).holdingRegisters)) {
2676
- yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
3471
+ yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
2677
3472
  return;
2678
3473
  }
2679
3474
  try {
2680
- yield this.withAddressLock([address], () => __awaiter(this, void 0, void 0, function* () {
3475
+ yield this._withAddressLock([address], () => __awaiter(this, void 0, void 0, function* () {
2681
3476
  if (model.maskWriteRegister) {
2682
3477
  yield model.maskWriteRegister(address, andMask, orMask);
2683
3478
  }
@@ -2686,21 +3481,22 @@ class ModbusSlave extends EventEmitter {
2686
3481
  yield model.writeSingleRegister(address, (value & andMask) | (orMask & (~andMask & 0xffff)));
2687
3482
  }
2688
3483
  }));
2689
- yield response(this.applicationLayer.encode(frame));
3484
+ yield response(appLayer.encode(frame));
2690
3485
  }
2691
3486
  catch (error) {
2692
- yield this.responseError(frame, response, error);
3487
+ yield this.responseError(appLayer, frame, response, error);
2693
3488
  }
2694
3489
  });
2695
3490
  }
2696
- handleFC23(model, frame, response) {
3491
+ handleFC23(appLayer, model, frame, response) {
2697
3492
  return __awaiter(this, void 0, void 0, function* () {
2698
3493
  var _a;
2699
3494
  if (frame.data.length <= 9 || frame.data.length !== 9 + frame.data[8]) {
3495
+ yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
2700
3496
  return;
2701
3497
  }
2702
3498
  if (!model.readHoldingRegisters || (!model.writeMultipleRegisters && !model.writeSingleRegister)) {
2703
- yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
3499
+ yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
2704
3500
  return;
2705
3501
  }
2706
3502
  const address = {
@@ -2717,11 +3513,11 @@ class ModbusSlave extends EventEmitter {
2717
3513
  length.write < LIMITS.READ_REGISTERS_MIN ||
2718
3514
  length.write > LIMITS.RW_REGISTERS_WRITE_MAX ||
2719
3515
  byteCount !== length.write * 2) {
2720
- yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
3516
+ yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
2721
3517
  return;
2722
3518
  }
2723
3519
  if (!checkRange([address.read, address.read + length.read, address.write, address.write + length.write], (_a = model.getAddressRange) === null || _a === void 0 ? void 0 : _a.call(model).holdingRegisters)) {
2724
- yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
3520
+ yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
2725
3521
  return;
2726
3522
  }
2727
3523
  const value = new Array(length.write);
@@ -2730,7 +3526,7 @@ class ModbusSlave extends EventEmitter {
2730
3526
  }
2731
3527
  try {
2732
3528
  const writeAddresses = Array.from({ length: length.write }, (_, i) => address.write + i);
2733
- yield this.withAddressLock(writeAddresses, () => __awaiter(this, void 0, void 0, function* () {
3529
+ yield this._withAddressLock(writeAddresses, () => __awaiter(this, void 0, void 0, function* () {
2734
3530
  if (model.writeMultipleRegisters) {
2735
3531
  yield model.writeMultipleRegisters(address.write, value);
2736
3532
  }
@@ -2741,18 +3537,18 @@ class ModbusSlave extends EventEmitter {
2741
3537
  }
2742
3538
  }));
2743
3539
  const registers = yield model.readHoldingRegisters(address.read, length.read);
2744
- const bufferTx = Buffer.alloc(length.read * 2);
3540
+ const bufferTx = Buffer.allocUnsafe(length.read * 2);
2745
3541
  registers.forEach((register, index) => {
2746
3542
  bufferTx.writeUInt16BE(register, index * 2);
2747
3543
  });
2748
- yield response(this.applicationLayer.encode(Object.assign(Object.assign({}, frame), { data: Buffer.concat([Buffer.from([bufferTx.length]), bufferTx]) })));
3544
+ yield response(appLayer.encode(Object.assign(Object.assign({}, frame), { data: prependByte(bufferTx.length, bufferTx) })));
2749
3545
  }
2750
3546
  catch (error) {
2751
- yield this.responseError(frame, response, error);
3547
+ yield this.responseError(appLayer, frame, response, error);
2752
3548
  }
2753
3549
  });
2754
3550
  }
2755
- handleFC43_14(model, frame, response) {
3551
+ handleFC43_14(appLayer, model, frame, response) {
2756
3552
  return __awaiter(this, void 0, void 0, function* () {
2757
3553
  if (frame.data.length === 3) {
2758
3554
  if (frame.data[0] === MEI_READ_DEVICE_ID && model.readDeviceIdentification) {
@@ -2779,13 +3575,13 @@ class ModbusSlave extends EventEmitter {
2779
3575
  }
2780
3576
  case exports.ReadDeviceIDCode.SPECIFIC_ACCESS: {
2781
3577
  if (objectID > 0x06 && objectID < 0x80) {
2782
- yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
3578
+ yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
2783
3579
  return;
2784
3580
  }
2785
3581
  break;
2786
3582
  }
2787
3583
  default: {
2788
- yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
3584
+ yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_VALUE));
2789
3585
  return;
2790
3586
  }
2791
3587
  }
@@ -2805,7 +3601,7 @@ class ModbusSlave extends EventEmitter {
2805
3601
  }
2806
3602
  if (!objects.has(objectID)) {
2807
3603
  if (readDeviceIDCode === exports.ReadDeviceIDCode.SPECIFIC_ACCESS) {
2808
- yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
3604
+ yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_DATA_ADDRESS));
2809
3605
  return;
2810
3606
  }
2811
3607
  objectID = 0x00;
@@ -2813,7 +3609,7 @@ class ModbusSlave extends EventEmitter {
2813
3609
  let maxId = 0;
2814
3610
  for (const id of objects.keys()) {
2815
3611
  if ((id >= 0x07 && id <= 0x7f) || id > 0xff) {
2816
- yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.SERVER_DEVICE_FAILURE));
3612
+ yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.SERVER_DEVICE_FAILURE));
2817
3613
  return;
2818
3614
  }
2819
3615
  if (id > maxId) {
@@ -2830,97 +3626,104 @@ class ModbusSlave extends EventEmitter {
2830
3626
  }
2831
3627
  const byteLength = Buffer.byteLength(value);
2832
3628
  if (byteLength > 245) {
2833
- yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.SERVER_DEVICE_FAILURE));
3629
+ yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.SERVER_DEVICE_FAILURE));
2834
3630
  return;
2835
3631
  }
2836
3632
  if (lastID !== 0) {
2837
3633
  continue;
2838
3634
  }
2839
3635
  if (byteLength + 2 > 253 - totalLength) {
2840
- if (lastID === 0) {
2841
- lastID = id;
2842
- }
3636
+ lastID = id;
2843
3637
  }
2844
3638
  else {
2845
3639
  totalLength += byteLength + 2;
2846
- ids.push(id);
3640
+ ids.push({ id, byteLength });
2847
3641
  if (readDeviceIDCode === exports.ReadDeviceIDCode.SPECIFIC_ACCESS) {
2848
3642
  break;
2849
3643
  }
2850
3644
  }
2851
3645
  }
2852
- ids.sort((a, b) => a - b);
2853
- const parts = [];
2854
- parts.push(Buffer.from([MEI_READ_DEVICE_ID, readDeviceIDCode, conformityLevel, lastID === 0 ? 0x00 : 0xff, lastID, ids.length]));
2855
- for (const id of ids) {
3646
+ ids.sort((a, b) => a.id - b.id);
3647
+ let dataLength = 6;
3648
+ for (const { byteLength } of ids) {
3649
+ dataLength += 2 + byteLength;
3650
+ }
3651
+ const data = Buffer.allocUnsafe(dataLength);
3652
+ let offset = 0;
3653
+ data[offset++] = MEI_READ_DEVICE_ID;
3654
+ data[offset++] = readDeviceIDCode;
3655
+ data[offset++] = conformityLevel;
3656
+ data[offset++] = lastID === 0 ? 0x00 : 0xff;
3657
+ data[offset++] = lastID;
3658
+ data[offset++] = ids.length;
3659
+ for (const { id, byteLength } of ids) {
2856
3660
  const value = objects.get(id);
2857
- parts.push(Buffer.from([id, Buffer.byteLength(value)]));
2858
- parts.push(Buffer.from(value));
3661
+ data[offset++] = id;
3662
+ data[offset++] = byteLength;
3663
+ offset += data.write(value, offset);
2859
3664
  }
2860
- yield response(this.applicationLayer.encode(Object.assign(Object.assign({}, frame), { data: Buffer.concat(parts) })));
3665
+ yield response(appLayer.encode(Object.assign(Object.assign({}, frame), { data })));
2861
3666
  }
2862
3667
  catch (error) {
2863
- yield this.responseError(frame, response, error);
3668
+ yield this.responseError(appLayer, frame, response, error);
2864
3669
  }
2865
3670
  }
2866
3671
  else {
2867
- yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
3672
+ yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
2868
3673
  }
2869
3674
  }
2870
3675
  });
2871
3676
  }
2872
- responseError(frame, response, error) {
3677
+ responseError(appLayer, frame, response, error) {
2873
3678
  return __awaiter(this, void 0, void 0, function* () {
2874
- yield response(this.applicationLayer.encode(Object.assign(Object.assign({}, frame), { fc: frame.fc | EXCEPTION_OFFSET, data: Buffer.from([getCodeByError(error)]) })));
3679
+ yield response(appLayer.encode(Object.assign(Object.assign({}, frame), { fc: frame.fc | EXCEPTION_OFFSET, data: Buffer.from([getCodeByError(error)]) })));
2875
3680
  });
2876
3681
  }
2877
- _drain(key, q) {
3682
+ _drain(appLayer, q) {
2878
3683
  return __awaiter(this, void 0, void 0, function* () {
2879
- if (q.processing) {
3684
+ if (q.processing || this._physicalLayer.state !== exports.PhysicalState.OPEN) {
2880
3685
  return;
2881
3686
  }
2882
3687
  q.processing = true;
2883
3688
  try {
2884
- while (q.items.length > 0) {
2885
- const { frame, response } = q.items.shift();
2886
- try {
2887
- yield this._processFrame(frame, response);
2888
- }
2889
- catch (error) {
2890
- this.emit('error', error);
3689
+ while (q.frames.length > 0) {
3690
+ if (this._physicalLayer.state !== exports.PhysicalState.OPEN) {
3691
+ return;
2891
3692
  }
3693
+ const frame = q.frames.shift();
3694
+ yield this._processFrame(appLayer, frame);
2892
3695
  }
2893
3696
  }
2894
3697
  finally {
2895
3698
  q.processing = false;
2896
- // Cleanup empty queue entries so the map doesn't grow unbounded across
2897
- // ephemeral connections (UDP rinfo, brief TCP clients).
2898
- if (q.items.length === 0 && this._queues.get(key) === q) {
2899
- this._queues.delete(key);
2900
- }
2901
3699
  }
2902
3700
  });
2903
3701
  }
2904
- _processFrame(frame, _response) {
3702
+ _processFrame(appLayer, frame) {
2905
3703
  return __awaiter(this, void 0, void 0, function* () {
2906
3704
  const response = (data) => __awaiter(this, void 0, void 0, function* () {
2907
- if (frame.unit === 0x00) {
3705
+ if (frame.unit === 0x00 || this._physicalLayer.state !== exports.PhysicalState.OPEN) {
2908
3706
  return;
2909
3707
  }
2910
3708
  try {
2911
- yield _response(data);
3709
+ yield appLayer.connection.write(data);
3710
+ }
3711
+ catch (_a) {
3712
+ /* A write failure usually means the connection is already broken; let the connection's close event handle cleanup. */
2912
3713
  }
2913
- catch (error) { }
2914
3714
  });
2915
3715
  for (const model of frame.unit === 0x00 ? this.models.values() : [this.models.get(frame.unit)]) {
2916
- const intercepted = yield this._intercept(model, frame, response);
3716
+ if (this._physicalLayer.state !== exports.PhysicalState.OPEN) {
3717
+ return;
3718
+ }
3719
+ const intercepted = yield this._intercept(appLayer, model, frame, response);
2917
3720
  if (!intercepted) {
2918
- yield this._handleFC(model, frame, response);
3721
+ yield this._handleFC(appLayer, model, frame, response);
2919
3722
  }
2920
3723
  }
2921
3724
  });
2922
3725
  }
2923
- _intercept(model, frame, response) {
3726
+ _intercept(appLayer, model, frame, response) {
2924
3727
  return __awaiter(this, void 0, void 0, function* () {
2925
3728
  if (!model.interceptor) {
2926
3729
  return false;
@@ -2928,18 +3731,18 @@ class ModbusSlave extends EventEmitter {
2928
3731
  try {
2929
3732
  const data = yield model.interceptor(frame.fc, frame.data);
2930
3733
  if (data !== undefined) {
2931
- yield response(this.applicationLayer.encode(Object.assign(Object.assign({}, frame), { data })));
3734
+ yield response(appLayer.encode(Object.assign(Object.assign({}, frame), { data })));
2932
3735
  return true;
2933
3736
  }
2934
3737
  return false;
2935
3738
  }
2936
3739
  catch (error) {
2937
- yield this.responseError(frame, response, error);
3740
+ yield this.responseError(appLayer, frame, response, error);
2938
3741
  return true;
2939
3742
  }
2940
3743
  });
2941
3744
  }
2942
- withAddressLock(addresses, fn) {
3745
+ _withAddressLock(addresses, fn) {
2943
3746
  return __awaiter(this, void 0, void 0, function* () {
2944
3747
  const sorted = [...new Set(addresses)].sort((a, b) => a - b);
2945
3748
  const previous = sorted.map((addr) => { var _a; return (_a = this._locks.get(addr)) !== null && _a !== void 0 ? _a : Promise.resolve(); });
@@ -2962,55 +3765,55 @@ class ModbusSlave extends EventEmitter {
2962
3765
  }
2963
3766
  });
2964
3767
  }
2965
- _handleFC(model, frame, response) {
3768
+ _handleFC(appLayer, model, frame, response) {
2966
3769
  return __awaiter(this, void 0, void 0, function* () {
2967
3770
  switch (frame.fc) {
2968
3771
  case exports.FunctionCode.READ_COILS: {
2969
- yield this.handleFC1(model, frame, response);
3772
+ yield this.handleFC1(appLayer, model, frame, response);
2970
3773
  break;
2971
3774
  }
2972
3775
  case exports.FunctionCode.READ_DISCRETE_INPUTS: {
2973
- yield this.handleFC2(model, frame, response);
3776
+ yield this.handleFC2(appLayer, model, frame, response);
2974
3777
  break;
2975
3778
  }
2976
3779
  case exports.FunctionCode.READ_HOLDING_REGISTERS: {
2977
- yield this.handleFC3(model, frame, response);
3780
+ yield this.handleFC3(appLayer, model, frame, response);
2978
3781
  break;
2979
3782
  }
2980
3783
  case exports.FunctionCode.READ_INPUT_REGISTERS: {
2981
- yield this.handleFC4(model, frame, response);
3784
+ yield this.handleFC4(appLayer, model, frame, response);
2982
3785
  break;
2983
3786
  }
2984
3787
  case exports.FunctionCode.WRITE_SINGLE_COIL: {
2985
- yield this.handleFC5(model, frame, response);
3788
+ yield this.handleFC5(appLayer, model, frame, response);
2986
3789
  break;
2987
3790
  }
2988
3791
  case exports.FunctionCode.WRITE_SINGLE_REGISTER: {
2989
- yield this.handleFC6(model, frame, response);
3792
+ yield this.handleFC6(appLayer, model, frame, response);
2990
3793
  break;
2991
3794
  }
2992
3795
  case exports.FunctionCode.WRITE_MULTIPLE_COILS: {
2993
- yield this.handleFC15(model, frame, response);
3796
+ yield this.handleFC15(appLayer, model, frame, response);
2994
3797
  break;
2995
3798
  }
2996
3799
  case exports.FunctionCode.WRITE_MULTIPLE_REGISTERS: {
2997
- yield this.handleFC16(model, frame, response);
3800
+ yield this.handleFC16(appLayer, model, frame, response);
2998
3801
  break;
2999
3802
  }
3000
3803
  case exports.FunctionCode.REPORT_SERVER_ID: {
3001
- yield this.handleFC17(model, frame, response);
3804
+ yield this.handleFC17(appLayer, model, frame, response);
3002
3805
  break;
3003
3806
  }
3004
3807
  case exports.FunctionCode.MASK_WRITE_REGISTER: {
3005
- yield this.handleFC22(model, frame, response);
3808
+ yield this.handleFC22(appLayer, model, frame, response);
3006
3809
  break;
3007
3810
  }
3008
3811
  case exports.FunctionCode.READ_WRITE_MULTIPLE_REGISTERS: {
3009
- yield this.handleFC23(model, frame, response);
3812
+ yield this.handleFC23(appLayer, model, frame, response);
3010
3813
  break;
3011
3814
  }
3012
3815
  case exports.FunctionCode.READ_DEVICE_IDENTIFICATION: {
3013
- yield this.handleFC43_14(model, frame, response);
3816
+ yield this.handleFC43_14(appLayer, model, frame, response);
3014
3817
  break;
3015
3818
  }
3016
3819
  default: {
@@ -3018,14 +3821,14 @@ class ModbusSlave extends EventEmitter {
3018
3821
  if (cfc === null || cfc === void 0 ? void 0 : cfc.handle) {
3019
3822
  try {
3020
3823
  const responseData = yield cfc.handle(frame.data, frame.unit);
3021
- yield response(this.applicationLayer.encode(Object.assign(Object.assign({}, frame), { data: responseData })));
3824
+ yield response(appLayer.encode(Object.assign(Object.assign({}, frame), { data: responseData })));
3022
3825
  }
3023
3826
  catch (error) {
3024
- yield this.responseError(frame, response, error);
3827
+ yield this.responseError(appLayer, frame, response, error);
3025
3828
  }
3026
3829
  }
3027
3830
  else {
3028
- yield this.responseError(frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
3831
+ yield this.responseError(appLayer, frame, response, getErrorByCode(exports.ErrorCode.ILLEGAL_FUNCTION));
3029
3832
  }
3030
3833
  break;
3031
3834
  }
@@ -3041,53 +3844,70 @@ class ModbusSlave extends EventEmitter {
3041
3844
  }
3042
3845
  addCustomFunctionCode(cfc) {
3043
3846
  this._customFunctionCodes.set(cfc.fc, cfc);
3044
- this.applicationLayer.addCustomFunctionCode(cfc);
3847
+ for (const appLayer of this._appLayers.keys()) {
3848
+ appLayer.addCustomFunctionCode(cfc);
3849
+ }
3045
3850
  }
3046
3851
  removeCustomFunctionCode(fc) {
3047
3852
  this._customFunctionCodes.delete(fc);
3048
- this.applicationLayer.removeCustomFunctionCode(fc);
3049
- }
3050
- _clean(level) {
3051
- if (this._cleanLevel === 'destroy') {
3052
- return;
3053
- }
3054
- if (this._cleanLevel === 'close' && level === 'close') {
3055
- return;
3056
- }
3057
- for (const q of this._queues.values()) {
3058
- q.items = [];
3059
- }
3060
- this._queues.clear();
3061
- this._locks.clear();
3062
- if (level === 'destroy') {
3063
- this._customFunctionCodes.clear();
3064
- this.models.clear();
3853
+ for (const appLayer of this._appLayers.keys()) {
3854
+ appLayer.removeCustomFunctionCode(fc);
3855
+ }
3856
+ }
3857
+ /**
3858
+ * Open the underlying physical layer and begin accepting connections.
3859
+ *
3860
+ * A `ModbusSlave` instance can only be opened once. Once {@link close}
3861
+ * is called — explicitly or because the physical layer disconnected —
3862
+ * the instance is permanently closed and cannot be reopened.
3863
+ * Create a new `ModbusSlave` if a new server is required.
3864
+ */
3865
+ open(...args) {
3866
+ if (this._closePromise) {
3867
+ return Promise.reject(new Error('The port has been permanently closed and CANNOT be reopened'));
3065
3868
  }
3066
- this._cleanLevel = level;
3869
+ return this._physicalLayer.open(...args);
3067
3870
  }
3068
- open(...args) {
3069
- this._cleanLevel = 'none';
3070
- return this.physicalLayer.open(...args);
3871
+ _close() {
3872
+ return __awaiter(this, void 0, void 0, function* () {
3873
+ for (const fn of this._cleanupFns) {
3874
+ fn();
3875
+ }
3876
+ this._cleanupFns.clear();
3877
+ let err = null;
3878
+ try {
3879
+ yield this._physicalLayer.close();
3880
+ }
3881
+ catch (error) {
3882
+ err = error;
3883
+ }
3884
+ this.removeAllListeners();
3885
+ if (err) {
3886
+ return Promise.reject(err);
3887
+ }
3888
+ });
3071
3889
  }
3890
+ /**
3891
+ * Permanently close the slave and release all resources.
3892
+ *
3893
+ * After calling this method the instance is considered dead:
3894
+ * - No further requests can be processed.
3895
+ * - The instance cannot be reopened via {@link open}.
3896
+ * - All event listeners registered on this slave are removed.
3897
+ */
3072
3898
  close() {
3073
- if (this._cleanLevel === 'destroy') {
3074
- return Promise.resolve();
3075
- }
3076
- this._clean('close');
3077
- return this.physicalLayer.close();
3078
- }
3079
- destroy() {
3080
- if (this._cleanLevel === 'destroy') {
3081
- return Promise.resolve();
3082
- }
3083
- this._clean('destroy');
3084
- this.removeAllListeners();
3085
- this.applicationLayer.destroy();
3086
- return this.physicalLayer.destroy();
3899
+ return __awaiter(this, void 0, void 0, function* () {
3900
+ if (this._closePromise) {
3901
+ return this._closePromise;
3902
+ }
3903
+ this._closePromise = this._close();
3904
+ return this._closePromise;
3905
+ });
3087
3906
  }
3088
3907
  }
3089
3908
 
3090
3909
  exports.AbstractApplicationLayer = AbstractApplicationLayer;
3910
+ exports.AbstractPhysicalConnection = AbstractPhysicalConnection;
3091
3911
  exports.AbstractPhysicalLayer = AbstractPhysicalLayer;
3092
3912
  exports.AsciiApplicationLayer = AsciiApplicationLayer;
3093
3913
  exports.COIL_OFF = COIL_OFF;
@@ -3097,7 +3917,6 @@ exports.LIMITS = LIMITS;
3097
3917
  exports.MEI_READ_DEVICE_ID = MEI_READ_DEVICE_ID;
3098
3918
  exports.MasterSession = MasterSession;
3099
3919
  exports.ModbusError = ModbusError;
3100
- exports.ModbusErrorCode = ModbusErrorCode;
3101
3920
  exports.ModbusMaster = ModbusMaster;
3102
3921
  exports.ModbusSlave = ModbusSlave;
3103
3922
  exports.RtuApplicationLayer = RtuApplicationLayer;
@@ -3105,6 +3924,8 @@ exports.SerialPhysicalLayer = SerialPhysicalLayer;
3105
3924
  exports.TcpApplicationLayer = TcpApplicationLayer;
3106
3925
  exports.TcpClientPhysicalLayer = TcpClientPhysicalLayer;
3107
3926
  exports.TcpServerPhysicalLayer = TcpServerPhysicalLayer;
3108
- exports.UdpPhysicalLayer = UdpPhysicalLayer;
3927
+ exports.UdpClientPhysicalLayer = UdpClientPhysicalLayer;
3928
+ exports.UdpServerPhysicalLayer = UdpServerPhysicalLayer;
3929
+ exports.createPhysicalLayer = createPhysicalLayer;
3109
3930
  exports.getCodeByError = getCodeByError;
3110
3931
  exports.getErrorByCode = getErrorByCode;