njs-modbus 3.4.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/utils.mjs DELETED
@@ -1,547 +0,0 @@
1
- /**
2
- * Convert a number of bits to milliseconds at a given baud rate.
3
- *
4
- * Used to derive Modbus RTU timing intervals from bit counts — e.g. 38.5 bits
5
- * = 3.5 character times at 11 bits/char (t3.5 inter-frame silence), or 16.5
6
- * bits = 1.5 character times (t1.5 inter-character timeout), per Modbus V1.02
7
- * §2.5.1.1.
8
- *
9
- * @param baudRate Serial port baud rate.
10
- * @param bits Number of bits to convert.
11
- * @returns Duration in milliseconds.
12
- */
13
- function bitsToMs(baudRate, bits) {
14
- return (bits * 1000) / baudRate;
15
- }
16
-
17
- /**
18
- * Drain a pending-callback array: invoke each callback with the given error (or null).
19
- *
20
- * Handles `null`/`undefined` entries (from optional `cb?` parameters) gracefully.
21
- * Used by physical layers and connections to resolve queued
22
- * open / close / destroy callbacks.
23
- */
24
- function drainCbs(cbs, err) {
25
- if (!cbs) {
26
- return;
27
- }
28
- for (const cb of cbs) {
29
- cb?.(err);
30
- }
31
- }
32
-
33
- function checkRange(value, range) {
34
- if (!range || range.length === 0) {
35
- return true;
36
- }
37
- const isMultiRange = Array.isArray(range[0]);
38
- const isValueArray = Array.isArray(value);
39
- if (!isValueArray && !isMultiRange) {
40
- const r = range;
41
- const min = r[0], max = r[1];
42
- const v = value;
43
- return min <= max ? v >= min && v <= max : v >= max && v <= min;
44
- }
45
- if (!isValueArray && isMultiRange) {
46
- const ranges = range;
47
- const v = value;
48
- for (let i = 0; i < ranges.length; i++) {
49
- const min = ranges[i][0], max = ranges[i][1];
50
- const lo = min <= max ? min : max;
51
- const hi = min <= max ? max : min;
52
- if (v >= lo && v <= hi) {
53
- return true;
54
- }
55
- }
56
- return false;
57
- }
58
- const values = value;
59
- if (values.length === 0) {
60
- return true;
61
- }
62
- if (!isMultiRange) {
63
- const r = range;
64
- const min = r[0], max = r[1];
65
- const lo = min <= max ? min : max;
66
- const hi = min <= max ? max : min;
67
- for (let i = 0; i < values.length; i++) {
68
- if (values[i] < lo || values[i] > hi) {
69
- return false;
70
- }
71
- }
72
- return true;
73
- }
74
- const ranges = range;
75
- for (let i = 0; i < ranges.length; i++) {
76
- const min = ranges[i][0], max = ranges[i][1];
77
- const lo = min <= max ? min : max;
78
- const hi = min <= max ? max : min;
79
- let allInRange = true;
80
- for (let j = 0; j < values.length; j++) {
81
- if (values[j] < lo || values[j] > hi) {
82
- allInRange = false;
83
- break;
84
- }
85
- }
86
- if (allInRange) {
87
- return true;
88
- }
89
- }
90
- return false;
91
- }
92
-
93
- const CRC_TABLE = new Uint16Array([
94
- 0x0000, 0xc0c1, 0xc181, 0x0140, 0xc301, 0x03c0, 0x0280, 0xc241, 0xc601, 0x06c0, 0x0780, 0xc741, 0x0500, 0xc5c1, 0xc481, 0x0440, 0xcc01,
95
- 0x0cc0, 0x0d80, 0xcd41, 0x0f00, 0xcfc1, 0xce81, 0x0e40, 0x0a00, 0xcac1, 0xcb81, 0x0b40, 0xc901, 0x09c0, 0x0880, 0xc841, 0xd801, 0x18c0,
96
- 0x1980, 0xd941, 0x1b00, 0xdbc1, 0xda81, 0x1a40, 0x1e00, 0xdec1, 0xdf81, 0x1f40, 0xdd01, 0x1dc0, 0x1c80, 0xdc41, 0x1400, 0xd4c1, 0xd581,
97
- 0x1540, 0xd701, 0x17c0, 0x1680, 0xd641, 0xd201, 0x12c0, 0x1380, 0xd341, 0x1100, 0xd1c1, 0xd081, 0x1040, 0xf001, 0x30c0, 0x3180, 0xf141,
98
- 0x3300, 0xf3c1, 0xf281, 0x3240, 0x3600, 0xf6c1, 0xf781, 0x3740, 0xf501, 0x35c0, 0x3480, 0xf441, 0x3c00, 0xfcc1, 0xfd81, 0x3d40, 0xff01,
99
- 0x3fc0, 0x3e80, 0xfe41, 0xfa01, 0x3ac0, 0x3b80, 0xfb41, 0x3900, 0xf9c1, 0xf881, 0x3840, 0x2800, 0xe8c1, 0xe981, 0x2940, 0xeb01, 0x2bc0,
100
- 0x2a80, 0xea41, 0xee01, 0x2ec0, 0x2f80, 0xef41, 0x2d00, 0xedc1, 0xec81, 0x2c40, 0xe401, 0x24c0, 0x2580, 0xe541, 0x2700, 0xe7c1, 0xe681,
101
- 0x2640, 0x2200, 0xe2c1, 0xe381, 0x2340, 0xe101, 0x21c0, 0x2080, 0xe041, 0xa001, 0x60c0, 0x6180, 0xa141, 0x6300, 0xa3c1, 0xa281, 0x6240,
102
- 0x6600, 0xa6c1, 0xa781, 0x6740, 0xa501, 0x65c0, 0x6480, 0xa441, 0x6c00, 0xacc1, 0xad81, 0x6d40, 0xaf01, 0x6fc0, 0x6e80, 0xae41, 0xaa01,
103
- 0x6ac0, 0x6b80, 0xab41, 0x6900, 0xa9c1, 0xa881, 0x6840, 0x7800, 0xb8c1, 0xb981, 0x7940, 0xbb01, 0x7bc0, 0x7a80, 0xba41, 0xbe01, 0x7ec0,
104
- 0x7f80, 0xbf41, 0x7d00, 0xbdc1, 0xbc81, 0x7c40, 0xb401, 0x74c0, 0x7580, 0xb541, 0x7700, 0xb7c1, 0xb681, 0x7640, 0x7200, 0xb2c1, 0xb381,
105
- 0x7340, 0xb101, 0x71c0, 0x7080, 0xb041, 0x5000, 0x90c1, 0x9181, 0x5140, 0x9301, 0x53c0, 0x5280, 0x9241, 0x9601, 0x56c0, 0x5780, 0x9741,
106
- 0x5500, 0x95c1, 0x9481, 0x5440, 0x9c01, 0x5cc0, 0x5d80, 0x9d41, 0x5f00, 0x9fc1, 0x9e81, 0x5e40, 0x5a00, 0x9ac1, 0x9b81, 0x5b40, 0x9901,
107
- 0x59c0, 0x5880, 0x9841, 0x8801, 0x48c0, 0x4980, 0x8941, 0x4b00, 0x8bc1, 0x8a81, 0x4a40, 0x4e00, 0x8ec1, 0x8f81, 0x4f40, 0x8d01, 0x4dc0,
108
- 0x4c80, 0x8c41, 0x4400, 0x84c1, 0x8581, 0x4540, 0x8701, 0x47c0, 0x4680, 0x8641, 0x8201, 0x42c0, 0x4380, 0x8341, 0x4100, 0x81c1, 0x8081,
109
- 0x4040,
110
- ]);
111
- /** CRC-16 (Modbus) over a single contiguous buffer. */
112
- function crcFixed(data, start, end) {
113
- let crc = 0xffff;
114
- for (let index = start; index < end; index++) {
115
- crc = CRC_TABLE[(crc ^ data[index]) & 0xff] ^ (crc >> 8);
116
- }
117
- return crc;
118
- }
119
- /**
120
- * CRC-16 (Modbus) over two contiguous buffer segments.
121
- * Computes CRC(head[headOff:headOff+headLen]) followed by CRC(tail[tailOff:tailOff+tailLen]).
122
- */
123
- function crcDual(head, headOff, headLen, tail, tailOff, tailLen) {
124
- let crc = 0xffff;
125
- const headEnd = headOff + headLen;
126
- for (let i = headOff; i < headEnd; i++) {
127
- crc = CRC_TABLE[(crc ^ head[i]) & 0xff] ^ (crc >> 8);
128
- }
129
- const tailEnd = tailOff + tailLen;
130
- for (let i = tailOff; i < tailEnd; i++) {
131
- crc = CRC_TABLE[(crc ^ tail[i]) & 0xff] ^ (crc >> 8);
132
- }
133
- return crc;
134
- }
135
-
136
- function lrc(data, start, end) {
137
- let sum = 0;
138
- for (let i = start; i < end; i++) {
139
- sum += data[i];
140
- }
141
- return -sum & 0xff;
142
- }
143
-
144
- /**
145
- * Standard Modbus function codes (V1.1b3 §6).
146
- */
147
- var FunctionCode;
148
- (function (FunctionCode) {
149
- FunctionCode[FunctionCode["READ_COILS"] = 1] = "READ_COILS";
150
- FunctionCode[FunctionCode["READ_DISCRETE_INPUTS"] = 2] = "READ_DISCRETE_INPUTS";
151
- FunctionCode[FunctionCode["READ_HOLDING_REGISTERS"] = 3] = "READ_HOLDING_REGISTERS";
152
- FunctionCode[FunctionCode["READ_INPUT_REGISTERS"] = 4] = "READ_INPUT_REGISTERS";
153
- FunctionCode[FunctionCode["WRITE_SINGLE_COIL"] = 5] = "WRITE_SINGLE_COIL";
154
- FunctionCode[FunctionCode["WRITE_SINGLE_REGISTER"] = 6] = "WRITE_SINGLE_REGISTER";
155
- FunctionCode[FunctionCode["WRITE_MULTIPLE_COILS"] = 15] = "WRITE_MULTIPLE_COILS";
156
- FunctionCode[FunctionCode["WRITE_MULTIPLE_REGISTERS"] = 16] = "WRITE_MULTIPLE_REGISTERS";
157
- FunctionCode[FunctionCode["REPORT_SERVER_ID"] = 17] = "REPORT_SERVER_ID";
158
- FunctionCode[FunctionCode["MASK_WRITE_REGISTER"] = 22] = "MASK_WRITE_REGISTER";
159
- FunctionCode[FunctionCode["READ_WRITE_MULTIPLE_REGISTERS"] = 23] = "READ_WRITE_MULTIPLE_REGISTERS";
160
- FunctionCode[FunctionCode["READ_DEVICE_IDENTIFICATION"] = 43] = "READ_DEVICE_IDENTIFICATION";
161
- })(FunctionCode || (FunctionCode = {}));
162
- /** Exception response FC = request FC | EXCEPTION_OFFSET (V1.1b3 §7). */
163
- const EXCEPTION_OFFSET = 0x80;
164
- /** FC 0x2B MEI sub-function selecting Read Device Identification (V1.1b3 §6.21). */
165
- const MEI_READ_DEVICE_ID = 0x0e;
166
- /** Read Device ID code values inside an FC 0x2B / MEI 0x0E request. */
167
- var ReadDeviceIDCode;
168
- (function (ReadDeviceIDCode) {
169
- ReadDeviceIDCode[ReadDeviceIDCode["BASIC_STREAM"] = 1] = "BASIC_STREAM";
170
- ReadDeviceIDCode[ReadDeviceIDCode["REGULAR_STREAM"] = 2] = "REGULAR_STREAM";
171
- ReadDeviceIDCode[ReadDeviceIDCode["EXTENDED_STREAM"] = 3] = "EXTENDED_STREAM";
172
- ReadDeviceIDCode[ReadDeviceIDCode["SPECIFIC_ACCESS"] = 4] = "SPECIFIC_ACCESS";
173
- })(ReadDeviceIDCode || (ReadDeviceIDCode = {}));
174
- /** Conformity level reported in an FC 0x2B / MEI 0x0E response. */
175
- var ConformityLevel;
176
- (function (ConformityLevel) {
177
- ConformityLevel[ConformityLevel["BASIC"] = 129] = "BASIC";
178
- ConformityLevel[ConformityLevel["REGULAR"] = 130] = "REGULAR";
179
- ConformityLevel[ConformityLevel["EXTENDED"] = 131] = "EXTENDED";
180
- })(ConformityLevel || (ConformityLevel = {}));
181
- /** Shared empty Buffer to avoid repeated allocations. */
182
- Buffer.alloc(0);
183
-
184
- const PREDICT_NEED_MORE = 0;
185
- const PREDICT_UNKNOWN = -1;
186
- const REQ_TABLE = new Int32Array(256);
187
- const RES_TABLE = new Int32Array(256);
188
- (function initTables() {
189
- REQ_TABLE[FunctionCode.READ_COILS] = 8;
190
- REQ_TABLE[FunctionCode.READ_DISCRETE_INPUTS] = 8;
191
- REQ_TABLE[FunctionCode.READ_HOLDING_REGISTERS] = 8;
192
- REQ_TABLE[FunctionCode.READ_INPUT_REGISTERS] = 8;
193
- REQ_TABLE[FunctionCode.WRITE_SINGLE_COIL] = 8;
194
- REQ_TABLE[FunctionCode.WRITE_SINGLE_REGISTER] = 8;
195
- REQ_TABLE[FunctionCode.REPORT_SERVER_ID] = 4;
196
- REQ_TABLE[FunctionCode.MASK_WRITE_REGISTER] = 10;
197
- REQ_TABLE[FunctionCode.READ_DEVICE_IDENTIFICATION] = 7;
198
- REQ_TABLE[FunctionCode.WRITE_MULTIPLE_COILS] = -1545;
199
- REQ_TABLE[FunctionCode.WRITE_MULTIPLE_REGISTERS] = -1545;
200
- REQ_TABLE[FunctionCode.READ_WRITE_MULTIPLE_REGISTERS] = -2573;
201
- RES_TABLE[FunctionCode.WRITE_SINGLE_COIL] = 8;
202
- RES_TABLE[FunctionCode.WRITE_SINGLE_REGISTER] = 8;
203
- RES_TABLE[FunctionCode.WRITE_MULTIPLE_COILS] = 8;
204
- RES_TABLE[FunctionCode.WRITE_MULTIPLE_REGISTERS] = 8;
205
- RES_TABLE[FunctionCode.MASK_WRITE_REGISTER] = 10;
206
- RES_TABLE[FunctionCode.READ_COILS] = -517;
207
- RES_TABLE[FunctionCode.READ_DISCRETE_INPUTS] = -517;
208
- RES_TABLE[FunctionCode.READ_HOLDING_REGISTERS] = -517;
209
- RES_TABLE[FunctionCode.READ_INPUT_REGISTERS] = -517;
210
- RES_TABLE[FunctionCode.REPORT_SERVER_ID] = -517;
211
- RES_TABLE[FunctionCode.READ_WRITE_MULTIPLE_REGISTERS] = -517;
212
- RES_TABLE[FunctionCode.READ_DEVICE_IDENTIFICATION] = -999;
213
- })();
214
- function predictRtuFrameLength(residual, data, residualLen, start, end, isResponse) {
215
- const len = end - start;
216
- if (len < 2) {
217
- return PREDICT_NEED_MORE;
218
- }
219
- const fc = start + 1 < residualLen ? residual[start + 1] : data[start + 1 - residualLen];
220
- if (isResponse) {
221
- if ((fc & EXCEPTION_OFFSET) !== 0) {
222
- return 5;
223
- }
224
- const val = RES_TABLE[fc];
225
- if (val > 0) {
226
- return val;
227
- }
228
- if (val < 0) {
229
- if (val === -999) {
230
- // FC 43 / MEI 14 response — inline to avoid function-call overhead on
231
- // the framing hot path (even though this FC is uncommon).
232
- if (end - start < 8) {
233
- return PREDICT_NEED_MORE;
234
- }
235
- if ((start + 2 < residualLen ? residual[start + 2] : data[start + 2 - residualLen]) !== MEI_READ_DEVICE_ID) {
236
- return PREDICT_UNKNOWN;
237
- }
238
- const numObjs = start + 7 < residualLen ? residual[start + 7] : data[start + 7 - residualLen];
239
- let cursor = start + 8;
240
- for (let i = 0; i < numObjs; i++) {
241
- if (end < cursor + 2) {
242
- return PREDICT_NEED_MORE;
243
- }
244
- cursor += 2 + (cursor + 1 < residualLen ? residual[cursor + 1] : data[cursor + 1 - residualLen]);
245
- }
246
- return cursor - start + 2;
247
- }
248
- const decode = -val;
249
- const offset = decode >> 8;
250
- if (len <= offset) {
251
- return PREDICT_NEED_MORE;
252
- }
253
- return (decode & 0xff) + (start + offset < residualLen ? residual[start + offset] : data[start + offset - residualLen]);
254
- }
255
- }
256
- else {
257
- const val = REQ_TABLE[fc];
258
- if (val > 0) {
259
- return val;
260
- }
261
- if (val < 0) {
262
- const decode = -val;
263
- const offset = decode >> 8;
264
- if (len <= offset) {
265
- return PREDICT_NEED_MORE;
266
- }
267
- return (decode & 0xff) + (start + offset < residualLen ? residual[start + offset] : data[start + offset - residualLen]);
268
- }
269
- }
270
- return PREDICT_UNKNOWN;
271
- }
272
-
273
- /**
274
- * Convert a callback-style `(cb: (err?) => void) => void` call into a Promise.
275
- */
276
- function promisifyCb(fn) {
277
- return new Promise((resolve, reject) => {
278
- fn((err) => {
279
- if (err) {
280
- reject(err);
281
- }
282
- else {
283
- resolve();
284
- }
285
- });
286
- });
287
- }
288
-
289
- /**
290
- * Resolve a single RTU timing parameter to milliseconds.
291
- *
292
- * Returns `undefined` when the caller did not supply the parameter at all
293
- * (so the caller knows to fall back to a spec default). An explicit `0` or
294
- * `{ value: 0 }` returns `0` — the parameter is set, just disabled.
295
- */
296
- function resolveOne(value, baudRate, fastBaudMs) {
297
- if (value === undefined) {
298
- return undefined;
299
- }
300
- if (typeof value === 'number') {
301
- return value;
302
- }
303
- if (value.unit === 'ms') {
304
- return value.value;
305
- }
306
- // unit === 'bit' — needs baudRate to convert
307
- if (baudRate === undefined) {
308
- return undefined;
309
- }
310
- if (baudRate > 19200) {
311
- return fastBaudMs;
312
- }
313
- const ms = bitsToMs(baudRate, value.value);
314
- const trunc = ms | 0;
315
- return trunc + (ms > trunc ? 1 : 0);
316
- }
317
- /**
318
- * Resolve Modbus RTU timing parameters from user options into milliseconds.
319
- *
320
- * - `intervalBetweenFrames` (t3.5): if omitted and `baudRate` is present,
321
- * defaults to 38.5 bits per Modbus V1.02 §2.5.1.1. Pass `0` to disable.
322
- * - `interCharTimeout` (t1.5): opt-in; only resolved when explicitly provided.
323
- *
324
- * Per the spec, at baud rates > 19200 fixed values are used
325
- * (1.75 ms for t3.5, 0.75 ms for t1.5) regardless of the bit value.
326
- */
327
- function resolveRtuTiming(opts = {}, baudRate) {
328
- let intervalBetweenFrames = resolveOne(opts.intervalBetweenFrames, baudRate, 1.75);
329
- if (intervalBetweenFrames === undefined) {
330
- // Spec default: t3.5 derived from baudRate, or 0 when neither option nor
331
- // baudRate were supplied.
332
- if (baudRate === undefined) {
333
- intervalBetweenFrames = 0;
334
- }
335
- else if (baudRate > 19200) {
336
- intervalBetweenFrames = 1.75;
337
- }
338
- else {
339
- const ms = bitsToMs(baudRate, 38.5);
340
- const trunc = ms | 0;
341
- intervalBetweenFrames = trunc + (ms > trunc ? 1 : 0);
342
- }
343
- }
344
- let interCharTimeout = resolveOne(opts.interCharTimeout, baudRate, 0.75);
345
- if (interCharTimeout === undefined) {
346
- // t1.5 is opt-in — no spec-default fallback.
347
- interCharTimeout = 0;
348
- }
349
- return { intervalBetweenFrames, interCharTimeout };
350
- }
351
-
352
- /**
353
- * Hybrid timer manager: uses native `setTimeout` for low concurrency
354
- * and switches to a binary min-heap when concurrency exceeds the threshold.
355
- *
356
- * Benchmarks (add + clear throughput, Node 24, x64):
357
- * 1 concurrent: setTimeout ~1.7× faster than heap
358
- * 2 concurrent: setTimeout ~1.6× faster than heap
359
- * 5 concurrent: setTimeout ~1.5-1.9× faster than heap
360
- * 10 concurrent: roughly equal
361
- * 20 concurrent: heap ~1.3× faster than setTimeout[]
362
- * 50 concurrent: heap ~1.4-1.7× faster than setTimeout[]
363
- *
364
- * The crossover point is around 10 concurrent timers, so the default
365
- * `concurrentThreshold = 2` keeps the common 1-2 request case on the
366
- * fast direct path while delegating to the heap for larger batches.
367
- */
368
- class TimerHeap {
369
- _deadlines = [];
370
- _ids = [];
371
- _seqs = [];
372
- _counter = 0;
373
- _timer = null;
374
- _onFire;
375
- _boundTick;
376
- _threshold;
377
- _mode = 'direct';
378
- _directTimers = new Map();
379
- /**
380
- * @param onFire Callback invoked with the timer id when it expires.
381
- * @param concurrentThreshold Maximum number of timers kept as individual
382
- * native `setTimeout` handles. Once exceeded, all timers migrate to
383
- * the internal heap and share a single native timer. Default is 2.
384
- */
385
- constructor(onFire, concurrentThreshold = 2) {
386
- this._onFire = onFire;
387
- this._boundTick = this._onTick.bind(this);
388
- this._threshold = concurrentThreshold;
389
- }
390
- get size() {
391
- return this._mode === 'direct' ? this._directTimers.size : this._deadlines.length;
392
- }
393
- add(id, ms) {
394
- if (this._mode === 'direct' && this._directTimers.size + 1 <= this._threshold) {
395
- const deadline = performance.now() + ms;
396
- const handle = setTimeout(() => {
397
- if (this._mode !== 'direct') {
398
- return;
399
- }
400
- this._directTimers.delete(id);
401
- this._onFire(id);
402
- }, ms);
403
- this._directTimers.set(id, { handle, deadline });
404
- return;
405
- }
406
- if (this._mode === 'direct') {
407
- this._mode = 'heap';
408
- for (const [existingId, { handle, deadline }] of this._directTimers) {
409
- clearTimeout(handle);
410
- const diff = deadline - performance.now();
411
- const trunc = diff | 0;
412
- const remaining = diff > 0 ? trunc + (diff > trunc ? 1 : 0) : 0;
413
- if (remaining === 0) {
414
- this._onFire(existingId);
415
- }
416
- else {
417
- this._heapAdd(existingId, remaining);
418
- }
419
- }
420
- this._directTimers.clear();
421
- }
422
- this._heapAdd(id, ms);
423
- }
424
- clear() {
425
- for (const { handle } of this._directTimers.values()) {
426
- clearTimeout(handle);
427
- }
428
- this._directTimers.clear();
429
- this._mode = 'direct';
430
- if (this._timer) {
431
- clearTimeout(this._timer);
432
- this._timer = null;
433
- }
434
- this._deadlines.length = 0;
435
- this._ids.length = 0;
436
- this._seqs.length = 0;
437
- this._counter = 0;
438
- }
439
- _heapAdd(id, ms) {
440
- const deadline = performance.now() + ms;
441
- const seq = this._counter++;
442
- let i = this._deadlines.length;
443
- this._deadlines.push(deadline);
444
- this._ids.push(id);
445
- this._seqs.push(seq);
446
- while (i > 0) {
447
- const p = (i - 1) >> 1;
448
- const parentComesFirst = this._deadlines[p] < deadline || (this._deadlines[p] === deadline && this._seqs[p] < seq);
449
- if (parentComesFirst) {
450
- break;
451
- }
452
- this._deadlines[i] = this._deadlines[p];
453
- this._ids[i] = this._ids[p];
454
- this._seqs[i] = this._seqs[p];
455
- i = p;
456
- }
457
- this._deadlines[i] = deadline;
458
- this._ids[i] = id;
459
- this._seqs[i] = seq;
460
- if (i === 0) {
461
- this._refresh();
462
- }
463
- }
464
- _refresh() {
465
- if (this._timer) {
466
- clearTimeout(this._timer);
467
- this._timer = null;
468
- }
469
- if (this._deadlines.length === 0) {
470
- return;
471
- }
472
- const diff = this._deadlines[0] - performance.now();
473
- const trunc = diff | 0;
474
- const delay = diff > 0 ? trunc + (diff > trunc ? 1 : 0) : 0;
475
- const safeDelay = delay < 2147483647 ? delay : 2147483647;
476
- this._timer = setTimeout(this._boundTick, safeDelay);
477
- }
478
- _onTick() {
479
- this._timer = null;
480
- const now = performance.now();
481
- try {
482
- while (this._deadlines.length > 0 && this._deadlines[0] <= now) {
483
- const id = this._pop();
484
- this._onFire(id);
485
- }
486
- }
487
- finally {
488
- this._refresh();
489
- }
490
- }
491
- _pop() {
492
- const topId = this._ids[0];
493
- const lastId = this._ids.pop();
494
- const lastDeadline = this._deadlines.pop();
495
- const lastSeq = this._seqs.pop();
496
- const n = this._deadlines.length;
497
- if (n > 0) {
498
- let i = 0;
499
- const half = n >> 1;
500
- while (i < half) {
501
- let minChild = (i << 1) + 1;
502
- const rightChild = minChild + 1;
503
- if (rightChild < n) {
504
- const rightComesFirst = this._deadlines[rightChild] < this._deadlines[minChild] ||
505
- (this._deadlines[rightChild] === this._deadlines[minChild] && this._seqs[rightChild] < this._seqs[minChild]);
506
- if (rightComesFirst) {
507
- minChild = rightChild;
508
- }
509
- }
510
- const lastComesFirst = lastDeadline < this._deadlines[minChild] || (lastDeadline === this._deadlines[minChild] && lastSeq < this._seqs[minChild]);
511
- if (lastComesFirst) {
512
- break;
513
- }
514
- this._deadlines[i] = this._deadlines[minChild];
515
- this._ids[i] = this._ids[minChild];
516
- this._seqs[i] = this._seqs[minChild];
517
- i = minChild;
518
- }
519
- this._deadlines[i] = lastDeadline;
520
- this._ids[i] = lastId;
521
- this._seqs[i] = lastSeq;
522
- }
523
- return topId;
524
- }
525
- }
526
-
527
- /**
528
- * Normalize an IP address by stripping the IPv4-mapped IPv6 prefix.
529
- * This ensures consistent comparison of addresses like `::ffff:192.168.1.1`.
530
- */
531
- function normalizeAddress(address = '') {
532
- return address.replace(/^::ffff:/, '');
533
- }
534
- /**
535
- * Check whether a remote address is allowed by the given whitelist.
536
- * IPv4-mapped IPv6 addresses are normalized before comparison.
537
- * Returns `true` when whitelist is absent or empty.
538
- */
539
- function isWhitelisted(address, whitelist) {
540
- if (!whitelist || whitelist.length === 0) {
541
- return true;
542
- }
543
- const normalized = normalizeAddress(address);
544
- return whitelist.includes(normalized);
545
- }
546
-
547
- export { CRC_TABLE, PREDICT_NEED_MORE, PREDICT_UNKNOWN, REQ_TABLE, RES_TABLE, TimerHeap, bitsToMs, checkRange, crcDual, crcFixed, drainCbs, isWhitelisted, lrc, predictRtuFrameLength, promisifyCb, resolveRtuTiming };