@mega-yfue/eufy-sdk 0.3.0-beta.2 → 0.3.0-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -15141,6 +15141,8 @@ var LIVE_START_ACK_DEADLINE_MS = 3e3;
15141
15141
  var SEQUENCE_LOOKBACK = 32768;
15142
15142
  var STALE_RETRANSMIT_DEPTH = 1024;
15143
15143
  var PUNCH_PROBE_SOCKETS = 7;
15144
+ var REORDER_WAIT_MS = 250;
15145
+ var REORDER_MAX_DATAGRAMS = 128;
15144
15146
  var MAX_TRACED_DATAGRAM_GAPS = 8;
15145
15147
  var CMD_SET_PAYLOAD = 1350;
15146
15148
  var CMD_NOTIFY_PAYLOAD = 1351;
@@ -15210,9 +15212,10 @@ var P2PSession = class _P2PSession extends EventEmitter2 {
15210
15212
  selfHost;
15211
15213
  /** In-flight multi-datagram frame per data channel (see onData). */
15212
15214
  pendingByDataType = /* @__PURE__ */ new Map();
15213
- /** Last datagram sequence number seen per dataType — used to detect a lost/reordered datagram
15214
- * mid-frame and drop the (now unrecoverable) partial frame instead of splicing wrong bytes. */
15215
+ /** Last delivered datagram sequence number per data type. */
15215
15216
  lastSeqByType = /* @__PURE__ */ new Map();
15217
+ /** Held datagrams and their active gap timer, grouped by data type. */
15218
+ reorderByType = /* @__PURE__ */ new Map();
15216
15219
  tracedDatagramGaps = 0;
15217
15220
  level1Key;
15218
15221
  /** Negotiated 32-byte level-2/gateway key (AES-256-GCM). Set via setLevel2Key once known. */
@@ -16467,8 +16470,7 @@ var P2PSession = class _P2PSession extends EventEmitter2 {
16467
16470
  * Acknowledge and reassemble one DATA datagram, sequenced independently per data type.
16468
16471
  *
16469
16472
  * The device numbers each data type's datagrams in its own 16-bit space and repeats what it thinks was
16470
- * lost, so a datagram that does not advance the sequence — a duplicate, or one already superseded — is a
16471
- * retransmission of something already reassembled: it is acknowledged, then ignored. Distance is measured
16473
+ * lost. A repeat of something already delivered is acknowledged, then ignored. Distance is measured
16472
16474
  * modulo the sequence space and read as backwards beyond {@link SEQUENCE_LOOKBACK}, which is what lets the
16473
16475
  * numbering wrap without the next datagram looking like a jump of nearly a full space.
16474
16476
  *
@@ -16478,9 +16480,13 @@ var P2PSession = class _P2PSession extends EventEmitter2 {
16478
16480
  * numbering and the half-assembled frame goes — because ignoring it would freeze the mark, and every
16479
16481
  * datagram of the new numbering would then be read as behind it too, for as long as it took to climb back.
16480
16482
  *
16481
- * Only a forward gap means a datagram is genuinely missing. A logical frame's payload spans datagrams that
16482
- * carry no header of their own, so the bytes cannot be reassembled around the hole: whatever was pending
16483
- * for that data type is discarded, and the frame is rebuilt from the next header.
16483
+ * A forward gap means a datagram has not arrived YET, which is not the same as lost. The device repeats
16484
+ * unacknowledged datagrams, so successors are held for a bounded wait and delivered strictly in sequence
16485
+ * if the missing one arrives. A logical frame's payload spans datagrams that carry no header of their
16486
+ * own, so reassembling around a hole is impossible; waiting for it is what keeps the frame whole.
16487
+ *
16488
+ * Only once {@link REORDER_WAIT_MS} passes, or {@link REORDER_MAX_DATAGRAMS} pile up, is the datagram
16489
+ * treated as lost: a pending frame is discarded and delivery resumes from the earliest held datagram.
16484
16490
  */
16485
16491
  onData(msg, addr) {
16486
16492
  const dataTypeBuffer = msg.subarray(4, 6);
@@ -16491,18 +16497,98 @@ var P2PSession = class _P2PSession extends EventEmitter2 {
16491
16497
  const advance = prevSeq === void 0 ? 1 : seqNo - prevSeq & 65535;
16492
16498
  if (advance === 0)
16493
16499
  return;
16494
- const restarted = advance > SEQUENCE_LOOKBACK && 65536 - advance > STALE_RETRANSMIT_DEPTH;
16495
- if (advance > SEQUENCE_LOOKBACK && !restarted)
16496
- return;
16497
- this.lastSeqByType.set(dataType, seqNo);
16498
- if ((advance > 1 || restarted) && this.pendingByDataType.has(dataType)) {
16499
- if (this.tracedDatagramGaps++ < MAX_TRACED_DATAGRAM_GAPS) {
16500
- this.trace({ phase: restarted ? "sequence-restart" : "datagram-gap", dataType });
16501
- }
16500
+ if (advance > SEQUENCE_LOOKBACK) {
16501
+ if (65536 - advance <= STALE_RETRANSMIT_DEPTH)
16502
+ return;
16503
+ if (this.pendingByDataType.has(dataType) && this.tracedDatagramGaps++ < MAX_TRACED_DATAGRAM_GAPS)
16504
+ this.trace({ phase: "sequence-restart", dataType });
16505
+ this.clearReorderTimer(dataType);
16506
+ this.reorderByType.delete(dataType);
16502
16507
  this.pendingByDataType.delete(dataType);
16508
+ this.lastSeqByType.set(dataType, seqNo);
16509
+ this.reassemble(dataType, msg.subarray(8));
16510
+ return;
16511
+ }
16512
+ if (advance === 1) {
16513
+ this.lastSeqByType.set(dataType, seqNo);
16514
+ this.reassemble(dataType, msg.subarray(8));
16515
+ this.drainReorder(dataType);
16516
+ return;
16503
16517
  }
16518
+ this.holdForRetransmit(dataType, seqNo, msg.subarray(8));
16519
+ }
16520
+ /** Hold a datagram that arrived past a hole, and arm the wait for the missing one to be repeated. */
16521
+ holdForRetransmit(dataType, seqNo, body) {
16522
+ let reorder = this.reorderByType.get(dataType);
16523
+ if (!reorder) {
16524
+ reorder = { held: /* @__PURE__ */ new Map() };
16525
+ this.reorderByType.set(dataType, reorder);
16526
+ }
16527
+ if (!reorder.held.has(seqNo))
16528
+ reorder.held.set(seqNo, body);
16529
+ if (reorder.held.size > REORDER_MAX_DATAGRAMS) {
16530
+ this.abandonHole(dataType);
16531
+ return;
16532
+ }
16533
+ this.armReorderTimer(dataType);
16534
+ }
16535
+ /** Start a full wait for the current hole, leaving an existing wait undisturbed. */
16536
+ armReorderTimer(dataType) {
16537
+ const reorder = this.reorderByType.get(dataType);
16538
+ if (!reorder?.held.size || reorder.timer)
16539
+ return;
16540
+ const timer = setTimeout(() => {
16541
+ reorder.timer = void 0;
16542
+ this.abandonHole(dataType);
16543
+ }, REORDER_WAIT_MS);
16544
+ timer.unref?.();
16545
+ reorder.timer = timer;
16546
+ }
16547
+ /** Deliver held datagrams that now follow directly on from the last one delivered. */
16548
+ drainReorder(dataType) {
16549
+ const reorder = this.reorderByType.get(dataType);
16550
+ if (!reorder?.held.size)
16551
+ return;
16552
+ this.clearReorderTimer(dataType);
16553
+ for (; ; ) {
16554
+ const next = this.lastSeqByType.get(dataType) + 1 & 65535;
16555
+ const body = reorder.held.get(next);
16556
+ if (body === void 0)
16557
+ break;
16558
+ reorder.held.delete(next);
16559
+ this.lastSeqByType.set(dataType, next);
16560
+ this.reassemble(dataType, body);
16561
+ }
16562
+ if (reorder.held.size)
16563
+ this.armReorderTimer(dataType);
16564
+ else
16565
+ this.reorderByType.delete(dataType);
16566
+ }
16567
+ /**
16568
+ * The missing datagram was not repeated in time: report the gap, drop the frame it belonged to, and
16569
+ * resume from the earliest datagram still held so the stream keeps moving.
16570
+ */
16571
+ abandonHole(dataType) {
16572
+ const held = this.reorderByType.get(dataType).held;
16573
+ if (this.pendingByDataType.has(dataType) && this.tracedDatagramGaps++ < MAX_TRACED_DATAGRAM_GAPS)
16574
+ this.trace({ phase: "datagram-gap", dataType });
16575
+ this.pendingByDataType.delete(dataType);
16576
+ const last = this.lastSeqByType.get(dataType);
16577
+ const earliest = [...held.keys()].sort((a, b) => (a - last & 65535) - (b - last & 65535))[0];
16578
+ this.lastSeqByType.set(dataType, earliest - 1 & 65535);
16579
+ this.drainReorder(dataType);
16580
+ }
16581
+ clearReorderTimer(dataType) {
16582
+ const reorder = this.reorderByType.get(dataType);
16583
+ if (!reorder?.timer)
16584
+ return;
16585
+ clearTimeout(reorder.timer);
16586
+ reorder.timer = void 0;
16587
+ }
16588
+ /** Reassemble one in-sequence datagram body into logical frames. */
16589
+ reassemble(dataType, datagramBody) {
16504
16590
  const pending = this.pendingByDataType.get(dataType);
16505
- let body = pending ? Buffer.concat([pending.buf, msg.subarray(8)]) : msg.subarray(8);
16591
+ let body = pending ? Buffer.concat([pending.buf, datagramBody]) : datagramBody;
16506
16592
  const carryHeader = pending?.header;
16507
16593
  this.pendingByDataType.delete(dataType);
16508
16594
  if (carryHeader) {
@@ -16533,6 +16619,9 @@ var P2PSession = class _P2PSession extends EventEmitter2 {
16533
16619
  * completed either.
16534
16620
  */
16535
16621
  resetInboundSequencing() {
16622
+ for (const dataType of this.reorderByType.keys())
16623
+ this.clearReorderTimer(dataType);
16624
+ this.reorderByType.clear();
16536
16625
  this.lastSeqByType.clear();
16537
16626
  this.pendingByDataType.clear();
16538
16627
  this.tracedDatagramGaps = 0;