njs-modbus 3.2.0 → 3.4.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.cjs CHANGED
@@ -24,40 +24,75 @@ function bitsToMs(baudRate, bits) {
24
24
  * open / close / destroy callbacks.
25
25
  */
26
26
  function drainCbs(cbs, err) {
27
- if (!cbs)
27
+ if (!cbs) {
28
28
  return;
29
+ }
29
30
  for (const cb of cbs) {
30
31
  cb?.(err);
31
32
  }
32
33
  }
33
34
 
34
- function inRange(n, [min, max]) {
35
- return n >= min && n <= max;
36
- }
37
- function isRangeArray(range) {
38
- return Array.isArray(range[0]);
39
- }
40
35
  function checkRange(value, range) {
41
36
  if (!range || range.length === 0) {
42
37
  return true;
43
38
  }
44
- const values = Array.isArray(value) ? value : [value];
45
- if (isRangeArray(range)) {
46
- for (const r of range) {
47
- const [min, max] = r;
48
- const [lo, hi] = min <= max ? [min, max] : [max, min];
49
- if (values.every((n) => inRange(n, [lo, hi]))) {
39
+ const isMultiRange = Array.isArray(range[0]);
40
+ const isValueArray = Array.isArray(value);
41
+ if (!isValueArray && !isMultiRange) {
42
+ const r = range;
43
+ const min = r[0], max = r[1];
44
+ const v = value;
45
+ return min <= max ? v >= min && v <= max : v >= max && v <= min;
46
+ }
47
+ if (!isValueArray && isMultiRange) {
48
+ const ranges = range;
49
+ const v = value;
50
+ for (let i = 0; i < ranges.length; i++) {
51
+ const min = ranges[i][0], max = ranges[i][1];
52
+ const lo = min <= max ? min : max;
53
+ const hi = min <= max ? max : min;
54
+ if (v >= lo && v <= hi) {
50
55
  return true;
51
56
  }
52
57
  }
53
58
  return false;
54
59
  }
55
- const [min, max] = range;
56
- const [lo, hi] = min <= max ? [min, max] : [max, min];
57
- return values.every((n) => inRange(n, [lo, hi]));
60
+ const values = value;
61
+ if (values.length === 0) {
62
+ return true;
63
+ }
64
+ if (!isMultiRange) {
65
+ const r = range;
66
+ const min = r[0], max = r[1];
67
+ const lo = min <= max ? min : max;
68
+ const hi = min <= max ? max : min;
69
+ for (let i = 0; i < values.length; i++) {
70
+ if (values[i] < lo || values[i] > hi) {
71
+ return false;
72
+ }
73
+ }
74
+ return true;
75
+ }
76
+ const ranges = range;
77
+ for (let i = 0; i < ranges.length; i++) {
78
+ const min = ranges[i][0], max = ranges[i][1];
79
+ const lo = min <= max ? min : max;
80
+ const hi = min <= max ? max : min;
81
+ let allInRange = true;
82
+ for (let j = 0; j < values.length; j++) {
83
+ if (values[j] < lo || values[j] > hi) {
84
+ allInRange = false;
85
+ break;
86
+ }
87
+ }
88
+ if (allInRange) {
89
+ return true;
90
+ }
91
+ }
92
+ return false;
58
93
  }
59
94
 
60
- const TABLE = [
95
+ const CRC_TABLE = new Uint16Array([
61
96
  0x0000, 0xc0c1, 0xc181, 0x0140, 0xc301, 0x03c0, 0x0280, 0xc241, 0xc601, 0x06c0, 0x0780, 0xc741, 0x0500, 0xc5c1, 0xc481, 0x0440, 0xcc01,
62
97
  0x0cc0, 0x0d80, 0xcd41, 0x0f00, 0xcfc1, 0xce81, 0x0e40, 0x0a00, 0xcac1, 0xcb81, 0x0b40, 0xc901, 0x09c0, 0x0880, 0xc841, 0xd801, 0x18c0,
63
98
  0x1980, 0xd941, 0x1b00, 0xdbc1, 0xda81, 0x1a40, 0x1e00, 0xdec1, 0xdf81, 0x1f40, 0xdd01, 0x1dc0, 0x1c80, 0xdc41, 0x1400, 0xd4c1, 0xd581,
@@ -74,32 +109,38 @@ const TABLE = [
74
109
  0x59c0, 0x5880, 0x9841, 0x8801, 0x48c0, 0x4980, 0x8941, 0x4b00, 0x8bc1, 0x8a81, 0x4a40, 0x4e00, 0x8ec1, 0x8f81, 0x4f40, 0x8d01, 0x4dc0,
75
110
  0x4c80, 0x8c41, 0x4400, 0x84c1, 0x8581, 0x4540, 0x8701, 0x47c0, 0x4680, 0x8641, 0x8201, 0x42c0, 0x4380, 0x8341, 0x4100, 0x81c1, 0x8081,
76
111
  0x4040,
77
- ];
78
- function crc(data, start = 0, end = data.length) {
112
+ ]);
113
+ /** CRC-16 (Modbus) over a single contiguous buffer. */
114
+ function crcFixed(data, start, end) {
79
115
  let crc = 0xffff;
80
116
  for (let index = start; index < end; index++) {
81
- crc = (TABLE[(crc ^ data[index]) & 0xff] ^ (crc >> 8)) & 0xffff;
117
+ crc = CRC_TABLE[(crc ^ data[index]) & 0xff] ^ (crc >> 8);
82
118
  }
83
119
  return crc;
84
120
  }
85
-
86
121
  /**
87
- * Returns true when `n` is an integer in the unsigned-byte range [0, 255].
88
- *
89
- * Used for byte-level Modbus payload validation (function-code values, raw
90
- * byte arrays in FC17/FC43 responses) — rejects negative, fractional, NaN,
91
- * Infinity, and out-of-range values uniformly.
122
+ * CRC-16 (Modbus) over two contiguous buffer segments.
123
+ * Computes CRC(head[headOff:headOff+headLen]) followed by CRC(tail[tailOff:tailOff+tailLen]).
92
124
  */
93
- function isUint8(n) {
94
- return Number.isInteger(n) && n >= 0 && n <= 255;
125
+ function crcDual(head, headOff, headLen, tail, tailOff, tailLen) {
126
+ let crc = 0xffff;
127
+ const headEnd = headOff + headLen;
128
+ for (let i = headOff; i < headEnd; i++) {
129
+ crc = CRC_TABLE[(crc ^ head[i]) & 0xff] ^ (crc >> 8);
130
+ }
131
+ const tailEnd = tailOff + tailLen;
132
+ for (let i = tailOff; i < tailEnd; i++) {
133
+ crc = CRC_TABLE[(crc ^ tail[i]) & 0xff] ^ (crc >> 8);
134
+ }
135
+ return crc;
95
136
  }
96
137
 
97
- function lrc(data, start = 0, end = data.length) {
138
+ function lrc(data, start, end) {
98
139
  let sum = 0;
99
140
  for (let i = start; i < end; i++) {
100
141
  sum += data[i];
101
142
  }
102
- return (~sum + 1) & 0xff;
143
+ return -sum & 0xff;
103
144
  }
104
145
 
105
146
  /**
@@ -142,103 +183,93 @@ var ConformityLevel;
142
183
  /** Shared empty Buffer to avoid repeated allocations. */
143
184
  Buffer.alloc(0);
144
185
 
145
- const REQUEST_FIXED_LENGTHS = {
146
- [FunctionCode.READ_COILS]: 8,
147
- [FunctionCode.READ_DISCRETE_INPUTS]: 8,
148
- [FunctionCode.READ_HOLDING_REGISTERS]: 8,
149
- [FunctionCode.READ_INPUT_REGISTERS]: 8,
150
- [FunctionCode.WRITE_SINGLE_COIL]: 8,
151
- [FunctionCode.WRITE_SINGLE_REGISTER]: 8,
152
- [FunctionCode.REPORT_SERVER_ID]: 4,
153
- [FunctionCode.MASK_WRITE_REGISTER]: 10,
154
- [FunctionCode.READ_DEVICE_IDENTIFICATION]: 7,
155
- };
156
- const REQUEST_BYTE_COUNT = {
157
- [FunctionCode.WRITE_MULTIPLE_COILS]: { offset: 6, extra: 9 },
158
- [FunctionCode.WRITE_MULTIPLE_REGISTERS]: { offset: 6, extra: 9 },
159
- [FunctionCode.READ_WRITE_MULTIPLE_REGISTERS]: { offset: 10, extra: 13 },
160
- };
161
- const RESPONSE_FIXED_LENGTHS = {
162
- [FunctionCode.WRITE_SINGLE_COIL]: 8,
163
- [FunctionCode.WRITE_SINGLE_REGISTER]: 8,
164
- [FunctionCode.WRITE_MULTIPLE_COILS]: 8,
165
- [FunctionCode.WRITE_MULTIPLE_REGISTERS]: 8,
166
- [FunctionCode.MASK_WRITE_REGISTER]: 10,
167
- };
168
- const RESPONSE_BYTE_COUNT = {
169
- [FunctionCode.READ_COILS]: { offset: 2, extra: 5 },
170
- [FunctionCode.READ_DISCRETE_INPUTS]: { offset: 2, extra: 5 },
171
- [FunctionCode.READ_HOLDING_REGISTERS]: { offset: 2, extra: 5 },
172
- [FunctionCode.READ_INPUT_REGISTERS]: { offset: 2, extra: 5 },
173
- [FunctionCode.REPORT_SERVER_ID]: { offset: 2, extra: 5 },
174
- [FunctionCode.READ_WRITE_MULTIPLE_REGISTERS]: { offset: 2, extra: 5 },
175
- };
176
- /** Sentinel: caller needs to feed more bytes before length can be determined. */
177
186
  const PREDICT_NEED_MORE = 0;
178
- /** Sentinel: function code is not in the standard tables. */
179
187
  const PREDICT_UNKNOWN = -1;
180
- /**
181
- * Predict the total RTU frame length (PDU + 2-byte CRC) given the leading bytes.
182
- *
183
- * Returns a sentinel-encoded number to avoid per-call object allocation on the
184
- * RTU decode hot path:
185
- * - Positive integer (>= 4): total frame length, function code is known.
186
- * - {@link PREDICT_NEED_MORE} (0): function code is known but more bytes are
187
- * required (typically waiting on the byteCount byte).
188
- * - {@link PREDICT_UNKNOWN} (-1): function code is not in the standard tables —
189
- * the framing layer must defer to a registered `CustomFunctionCode` or treat
190
- * this as a framing error.
191
- */
192
- function predictRtuFrameLength(buffer, start, end, isResponse) {
193
- if (end - start < 2) {
188
+ const REQ_TABLE = new Int32Array(256);
189
+ const RES_TABLE = new Int32Array(256);
190
+ (function initTables() {
191
+ REQ_TABLE[FunctionCode.READ_COILS] = 8;
192
+ REQ_TABLE[FunctionCode.READ_DISCRETE_INPUTS] = 8;
193
+ REQ_TABLE[FunctionCode.READ_HOLDING_REGISTERS] = 8;
194
+ REQ_TABLE[FunctionCode.READ_INPUT_REGISTERS] = 8;
195
+ REQ_TABLE[FunctionCode.WRITE_SINGLE_COIL] = 8;
196
+ REQ_TABLE[FunctionCode.WRITE_SINGLE_REGISTER] = 8;
197
+ REQ_TABLE[FunctionCode.REPORT_SERVER_ID] = 4;
198
+ REQ_TABLE[FunctionCode.MASK_WRITE_REGISTER] = 10;
199
+ REQ_TABLE[FunctionCode.READ_DEVICE_IDENTIFICATION] = 7;
200
+ REQ_TABLE[FunctionCode.WRITE_MULTIPLE_COILS] = -1545;
201
+ REQ_TABLE[FunctionCode.WRITE_MULTIPLE_REGISTERS] = -1545;
202
+ REQ_TABLE[FunctionCode.READ_WRITE_MULTIPLE_REGISTERS] = -2573;
203
+ RES_TABLE[FunctionCode.WRITE_SINGLE_COIL] = 8;
204
+ RES_TABLE[FunctionCode.WRITE_SINGLE_REGISTER] = 8;
205
+ RES_TABLE[FunctionCode.WRITE_MULTIPLE_COILS] = 8;
206
+ RES_TABLE[FunctionCode.WRITE_MULTIPLE_REGISTERS] = 8;
207
+ RES_TABLE[FunctionCode.MASK_WRITE_REGISTER] = 10;
208
+ RES_TABLE[FunctionCode.READ_COILS] = -517;
209
+ RES_TABLE[FunctionCode.READ_DISCRETE_INPUTS] = -517;
210
+ RES_TABLE[FunctionCode.READ_HOLDING_REGISTERS] = -517;
211
+ RES_TABLE[FunctionCode.READ_INPUT_REGISTERS] = -517;
212
+ RES_TABLE[FunctionCode.REPORT_SERVER_ID] = -517;
213
+ RES_TABLE[FunctionCode.READ_WRITE_MULTIPLE_REGISTERS] = -517;
214
+ RES_TABLE[FunctionCode.READ_DEVICE_IDENTIFICATION] = -999;
215
+ })();
216
+ function predictRtuFrameLength(residual, data, residualLen, start, end, isResponse) {
217
+ const len = end - start;
218
+ if (len < 2) {
194
219
  return PREDICT_NEED_MORE;
195
220
  }
196
- const fc = buffer[start + 1];
197
- if (isResponse && (fc & EXCEPTION_OFFSET) !== 0) {
198
- return 5;
199
- }
200
- const fixed = (isResponse ? RESPONSE_FIXED_LENGTHS : REQUEST_FIXED_LENGTHS)[fc];
201
- if (fixed !== undefined) {
202
- return fixed;
203
- }
204
- const bc = (isResponse ? RESPONSE_BYTE_COUNT : REQUEST_BYTE_COUNT)[fc];
205
- if (bc !== undefined) {
206
- if (end - start <= bc.offset) {
207
- return PREDICT_NEED_MORE;
221
+ const fc = start + 1 < residualLen ? residual[start + 1] : data[start + 1 - residualLen];
222
+ if (isResponse) {
223
+ if ((fc & EXCEPTION_OFFSET) !== 0) {
224
+ return 5;
225
+ }
226
+ const val = RES_TABLE[fc];
227
+ if (val > 0) {
228
+ return val;
229
+ }
230
+ if (val < 0) {
231
+ if (val === -999) {
232
+ // FC 43 / MEI 14 response — inline to avoid function-call overhead on
233
+ // the framing hot path (even though this FC is uncommon).
234
+ if (end - start < 8) {
235
+ return PREDICT_NEED_MORE;
236
+ }
237
+ if ((start + 2 < residualLen ? residual[start + 2] : data[start + 2 - residualLen]) !== MEI_READ_DEVICE_ID) {
238
+ return PREDICT_UNKNOWN;
239
+ }
240
+ const numObjs = start + 7 < residualLen ? residual[start + 7] : data[start + 7 - residualLen];
241
+ let cursor = start + 8;
242
+ for (let i = 0; i < numObjs; i++) {
243
+ if (end < cursor + 2) {
244
+ return PREDICT_NEED_MORE;
245
+ }
246
+ cursor += 2 + (cursor + 1 < residualLen ? residual[cursor + 1] : data[cursor + 1 - residualLen]);
247
+ }
248
+ return cursor - start + 2;
249
+ }
250
+ const decode = -val;
251
+ const offset = decode >> 8;
252
+ if (len <= offset) {
253
+ return PREDICT_NEED_MORE;
254
+ }
255
+ return (decode & 0xff) + (start + offset < residualLen ? residual[start + offset] : data[start + offset - residualLen]);
208
256
  }
209
- return bc.extra + buffer[start + bc.offset];
210
- }
211
- if (isResponse && fc === FunctionCode.READ_DEVICE_IDENTIFICATION) {
212
- return predictFc43_14Response(buffer, start, end);
213
- }
214
- return PREDICT_UNKNOWN;
215
- }
216
- /**
217
- * Walk the variable-length FC 0x2B / MEI 0x0E (Read Device Identification)
218
- * response structure per Modbus V1.1b3 §6.21.
219
- *
220
- * Layout (after unit and fc):
221
- * mei(1) rdic(1) conformity(1) more(1) nextObjId(1) numObjs(1)
222
- * [objId(1) objLen(1) objData(objLen)] × numObjs
223
- * CRC(2)
224
- */
225
- function predictFc43_14Response(buffer, start, end) {
226
- if (end - start < 8) {
227
- return PREDICT_NEED_MORE;
228
- }
229
- if (buffer[start + 2] !== MEI_READ_DEVICE_ID) {
230
- return PREDICT_UNKNOWN;
231
257
  }
232
- const numObjs = buffer[start + 7];
233
- let cursor = start + 8;
234
- for (let i = 0; i < numObjs; i++) {
235
- if (end < cursor + 2) {
236
- return PREDICT_NEED_MORE;
258
+ else {
259
+ const val = REQ_TABLE[fc];
260
+ if (val > 0) {
261
+ return val;
262
+ }
263
+ if (val < 0) {
264
+ const decode = -val;
265
+ const offset = decode >> 8;
266
+ if (len <= offset) {
267
+ return PREDICT_NEED_MORE;
268
+ }
269
+ return (decode & 0xff) + (start + offset < residualLen ? residual[start + offset] : data[start + offset - residualLen]);
237
270
  }
238
- const objLen = buffer[cursor + 1];
239
- cursor += 2 + objLen;
240
271
  }
241
- return cursor - start + 2;
272
+ return PREDICT_UNKNOWN;
242
273
  }
243
274
 
244
275
  /**
@@ -247,10 +278,12 @@ function predictFc43_14Response(buffer, start, end) {
247
278
  function promisifyCb(fn) {
248
279
  return new Promise((resolve, reject) => {
249
280
  fn((err) => {
250
- if (err)
281
+ if (err) {
251
282
  reject(err);
252
- else
283
+ }
284
+ else {
253
285
  resolve();
286
+ }
254
287
  });
255
288
  });
256
289
  }
@@ -276,7 +309,12 @@ function resolveOne(value, baudRate, fastBaudMs) {
276
309
  if (baudRate === undefined) {
277
310
  return undefined;
278
311
  }
279
- return baudRate > 19200 ? fastBaudMs : Math.ceil(bitsToMs(baudRate, value.value));
312
+ if (baudRate > 19200) {
313
+ return fastBaudMs;
314
+ }
315
+ const ms = bitsToMs(baudRate, value.value);
316
+ const trunc = ms | 0;
317
+ return trunc + (ms > trunc ? 1 : 0);
280
318
  }
281
319
  /**
282
320
  * Resolve Modbus RTU timing parameters from user options into milliseconds.
@@ -293,7 +331,17 @@ function resolveRtuTiming(opts = {}, baudRate) {
293
331
  if (intervalBetweenFrames === undefined) {
294
332
  // Spec default: t3.5 derived from baudRate, or 0 when neither option nor
295
333
  // baudRate were supplied.
296
- intervalBetweenFrames = baudRate !== undefined ? (baudRate > 19200 ? 1.75 : Math.ceil(bitsToMs(baudRate, 38.5))) : 0;
334
+ if (baudRate === undefined) {
335
+ intervalBetweenFrames = 0;
336
+ }
337
+ else if (baudRate > 19200) {
338
+ intervalBetweenFrames = 1.75;
339
+ }
340
+ else {
341
+ const ms = bitsToMs(baudRate, 38.5);
342
+ const trunc = ms | 0;
343
+ intervalBetweenFrames = trunc + (ms > trunc ? 1 : 0);
344
+ }
297
345
  }
298
346
  let interCharTimeout = resolveOne(opts.interCharTimeout, baudRate, 0.75);
299
347
  if (interCharTimeout === undefined) {
@@ -303,102 +351,176 @@ function resolveRtuTiming(opts = {}, baudRate) {
303
351
  return { intervalBetweenFrames, interCharTimeout };
304
352
  }
305
353
 
306
- /** @internal
307
- * Zero-allocation binary min-heap for coalescing per-request timeouts.
354
+ /**
355
+ * Hybrid timer manager: uses native `setTimeout` for low concurrency
356
+ * and switches to a binary min-heap when concurrency exceeds the threshold.
357
+ *
358
+ * Benchmarks (add + clear throughput, Node 24, x64):
359
+ * 1 concurrent: setTimeout ~1.7× faster than heap
360
+ * 2 concurrent: setTimeout ~1.6× faster than heap
361
+ * 5 concurrent: setTimeout ~1.5-1.9× faster than heap
362
+ * 10 concurrent: roughly equal
363
+ * 20 concurrent: heap ~1.3× faster than setTimeout[]
364
+ * 50 concurrent: heap ~1.4-1.7× faster than setTimeout[]
308
365
  *
309
- * Uses two parallel numeric arrays (no object allocation per entry).
310
- * Lazy deletion: callers never remove from the heap; expired entries
311
- * are silently dropped when they surface at the top.
366
+ * The crossover point is around 10 concurrent timers, so the default
367
+ * `concurrentThreshold = 2` keeps the common 1-2 request case on the
368
+ * fast direct path while delegating to the heap for larger batches.
312
369
  */
313
370
  class TimerHeap {
314
371
  _deadlines = [];
315
372
  _ids = [];
373
+ _seqs = [];
374
+ _counter = 0;
316
375
  _timer = null;
317
376
  _onFire;
318
- /** Pre-bound tick handler to avoid creating a new arrow function on every setTimeout. */
319
377
  _boundTick;
320
- constructor(onFire) {
378
+ _threshold;
379
+ _mode = 'direct';
380
+ _directTimers = new Map();
381
+ /**
382
+ * @param onFire Callback invoked with the timer id when it expires.
383
+ * @param concurrentThreshold Maximum number of timers kept as individual
384
+ * native `setTimeout` handles. Once exceeded, all timers migrate to
385
+ * the internal heap and share a single native timer. Default is 2.
386
+ */
387
+ constructor(onFire, concurrentThreshold = 2) {
321
388
  this._onFire = onFire;
322
389
  this._boundTick = this._onTick.bind(this);
390
+ this._threshold = concurrentThreshold;
323
391
  }
324
- /** Number of pending timers in the heap. */
325
392
  get size() {
326
- return this._deadlines.length;
393
+ return this._mode === 'direct' ? this._directTimers.size : this._deadlines.length;
327
394
  }
328
- /** Schedule an entry. If it becomes the new top, the global timer is re-scheduled (pre-emption). */
329
395
  add(id, ms) {
396
+ if (this._mode === 'direct' && this._directTimers.size + 1 <= this._threshold) {
397
+ const deadline = performance.now() + ms;
398
+ const handle = setTimeout(() => {
399
+ if (this._mode !== 'direct') {
400
+ return;
401
+ }
402
+ this._directTimers.delete(id);
403
+ this._onFire(id);
404
+ }, ms);
405
+ this._directTimers.set(id, { handle, deadline });
406
+ return;
407
+ }
408
+ if (this._mode === 'direct') {
409
+ this._mode = 'heap';
410
+ for (const [existingId, { handle, deadline }] of this._directTimers) {
411
+ clearTimeout(handle);
412
+ const diff = deadline - performance.now();
413
+ const trunc = diff | 0;
414
+ const remaining = diff > 0 ? trunc + (diff > trunc ? 1 : 0) : 0;
415
+ if (remaining === 0) {
416
+ this._onFire(existingId);
417
+ }
418
+ else {
419
+ this._heapAdd(existingId, remaining);
420
+ }
421
+ }
422
+ this._directTimers.clear();
423
+ }
424
+ this._heapAdd(id, ms);
425
+ }
426
+ clear() {
427
+ for (const { handle } of this._directTimers.values()) {
428
+ clearTimeout(handle);
429
+ }
430
+ this._directTimers.clear();
431
+ this._mode = 'direct';
432
+ if (this._timer) {
433
+ clearTimeout(this._timer);
434
+ this._timer = null;
435
+ }
436
+ this._deadlines.length = 0;
437
+ this._ids.length = 0;
438
+ this._seqs.length = 0;
439
+ this._counter = 0;
440
+ }
441
+ _heapAdd(id, ms) {
330
442
  const deadline = performance.now() + ms;
443
+ const seq = this._counter++;
331
444
  let i = this._deadlines.length;
332
445
  this._deadlines.push(deadline);
333
446
  this._ids.push(id);
334
- // sift up
447
+ this._seqs.push(seq);
335
448
  while (i > 0) {
336
449
  const p = (i - 1) >> 1;
337
- if (this._deadlines[p] <= deadline)
450
+ const parentComesFirst = this._deadlines[p] < deadline || (this._deadlines[p] === deadline && this._seqs[p] < seq);
451
+ if (parentComesFirst) {
338
452
  break;
453
+ }
339
454
  this._deadlines[i] = this._deadlines[p];
340
455
  this._ids[i] = this._ids[p];
456
+ this._seqs[i] = this._seqs[p];
341
457
  i = p;
342
458
  }
343
459
  this._deadlines[i] = deadline;
344
460
  this._ids[i] = id;
345
- // Only reschedule when the new entry became the heap top.
346
- if (i === 0)
461
+ this._seqs[i] = seq;
462
+ if (i === 0) {
347
463
  this._refresh();
348
- }
349
- /** Dispose without firing callbacks. */
350
- clear() {
351
- if (this._timer) {
352
- clearTimeout(this._timer);
353
- this._timer = null;
354
464
  }
355
- this._deadlines.length = 0;
356
- this._ids.length = 0;
357
465
  }
358
466
  _refresh() {
359
467
  if (this._timer) {
360
468
  clearTimeout(this._timer);
361
469
  this._timer = null;
362
470
  }
363
- if (this._deadlines.length === 0)
471
+ if (this._deadlines.length === 0) {
364
472
  return;
365
- const delay = Math.max(0, Math.ceil(this._deadlines[0] - performance.now()));
366
- this._timer = setTimeout(this._boundTick, delay);
473
+ }
474
+ const diff = this._deadlines[0] - performance.now();
475
+ const trunc = diff | 0;
476
+ const delay = diff > 0 ? trunc + (diff > trunc ? 1 : 0) : 0;
477
+ const safeDelay = delay < 2147483647 ? delay : 2147483647;
478
+ this._timer = setTimeout(this._boundTick, safeDelay);
367
479
  }
368
480
  _onTick() {
369
481
  this._timer = null;
370
482
  const now = performance.now();
371
- while (this._deadlines.length > 0 && this._deadlines[0] <= now) {
372
- const id = this._pop();
373
- this._onFire(id);
483
+ try {
484
+ while (this._deadlines.length > 0 && this._deadlines[0] <= now) {
485
+ const id = this._pop();
486
+ this._onFire(id);
487
+ }
488
+ }
489
+ finally {
490
+ this._refresh();
374
491
  }
375
- this._refresh();
376
492
  }
377
- /** Pop and return the id with the earliest deadline. Assumes heap is non-empty. */
378
493
  _pop() {
379
494
  const topId = this._ids[0];
380
495
  const lastId = this._ids.pop();
381
496
  const lastDeadline = this._deadlines.pop();
497
+ const lastSeq = this._seqs.pop();
382
498
  const n = this._deadlines.length;
383
499
  if (n > 0) {
384
500
  let i = 0;
385
- // sift down
386
- while (true) {
387
- let min = i;
388
- const l = i * 2 + 1;
389
- const r = l + 1;
390
- if (l < n && this._deadlines[l] < this._deadlines[min])
391
- min = l;
392
- if (r < n && this._deadlines[r] < this._deadlines[min])
393
- min = r;
394
- if (min === i)
501
+ const half = n >> 1;
502
+ while (i < half) {
503
+ let minChild = (i << 1) + 1;
504
+ const rightChild = minChild + 1;
505
+ if (rightChild < n) {
506
+ const rightComesFirst = this._deadlines[rightChild] < this._deadlines[minChild] ||
507
+ (this._deadlines[rightChild] === this._deadlines[minChild] && this._seqs[rightChild] < this._seqs[minChild]);
508
+ if (rightComesFirst) {
509
+ minChild = rightChild;
510
+ }
511
+ }
512
+ const lastComesFirst = lastDeadline < this._deadlines[minChild] || (lastDeadline === this._deadlines[minChild] && lastSeq < this._seqs[minChild]);
513
+ if (lastComesFirst) {
395
514
  break;
396
- this._deadlines[i] = this._deadlines[min];
397
- this._ids[i] = this._ids[min];
398
- i = min;
515
+ }
516
+ this._deadlines[i] = this._deadlines[minChild];
517
+ this._ids[i] = this._ids[minChild];
518
+ this._seqs[i] = this._seqs[minChild];
519
+ i = minChild;
399
520
  }
400
521
  this._deadlines[i] = lastDeadline;
401
522
  this._ids[i] = lastId;
523
+ this._seqs[i] = lastSeq;
402
524
  }
403
525
  return topId;
404
526
  }
@@ -424,14 +546,17 @@ function isWhitelisted(address, whitelist) {
424
546
  return whitelist.includes(normalized);
425
547
  }
426
548
 
549
+ exports.CRC_TABLE = CRC_TABLE;
427
550
  exports.PREDICT_NEED_MORE = PREDICT_NEED_MORE;
428
551
  exports.PREDICT_UNKNOWN = PREDICT_UNKNOWN;
552
+ exports.REQ_TABLE = REQ_TABLE;
553
+ exports.RES_TABLE = RES_TABLE;
429
554
  exports.TimerHeap = TimerHeap;
430
555
  exports.bitsToMs = bitsToMs;
431
556
  exports.checkRange = checkRange;
432
- exports.crc = crc;
557
+ exports.crcDual = crcDual;
558
+ exports.crcFixed = crcFixed;
433
559
  exports.drainCbs = drainCbs;
434
- exports.isUint8 = isUint8;
435
560
  exports.isWhitelisted = isWhitelisted;
436
561
  exports.lrc = lrc;
437
562
  exports.predictRtuFrameLength = predictRtuFrameLength;