@decentnetwork/peer 0.1.70 → 0.1.71

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.
@@ -30,15 +30,23 @@ export const MAX_CONCURRENT_FILE_PIPES = 256;
30
30
  export const MAX_FILE_DATA_SIZE = 1367;
31
31
  export const FILECONTROL = { ACCEPT: 0, PAUSE: 1, KILL: 2, SEEK: 3, ACK: 4 };
32
32
  export const FILEKIND = { DATA: 0, AVATAR: 1 };
33
- // Reliability tuning. We use a FIXED in-flight window (not TCP-style AIMD): a
34
- // file transfer over a private direct path should treat packet loss as RANDOM
35
- // (lossy Wi-Fi / long-haul jitter), NOT as congestion backing the window off
36
- // on every random drop collapsed throughput to a crawl (minutes for a big
37
- // file). Instead, keep the window large and just retransmit the gaps fast. The
38
- // 4 MB UDP socket buffer absorbs the burst; the receiver buffers by offset, so
39
- // reordering is free.
40
- const WINDOW_CHUNKS = 768; // ~1 MB in flight — fills a 200ms-RTT path at ~5 MB/s
41
- const WATCHDOG_MS = 150; // resend from the ack point if no progress this long
33
+ // Reliability tuning. We RAMP an in-flight window (AIMD-lite) rather than pin it:
34
+ // - RANDOM single-packet loss (dup-ack with a gap) is NOT congestion fast-
35
+ // retransmit the hole and DON'T shrink the window, so lossy-but-fat paths
36
+ // (Wi-Fi / long-haul jitter) keep full throughput (the original intent).
37
+ // - A SUSTAINED stall (no ack progress for ~RTT) IS congestion halve the
38
+ // window so we stop flooding the path. This is what keeps the *session* alive
39
+ // during a big transfer over a thin/relayed hop: without it, a fixed 1 MB
40
+ // window blasted go-back-N saturates the path, starves the net_crypto
41
+ // keepalive, and the whole session (ping included) times out. On a good path
42
+ // the window ramps back to WINDOW_CHUNKS within a few RTTs → same speed.
43
+ // The 4 MB UDP socket buffer absorbs bursts; the receiver buffers by offset.
44
+ const WINDOW_CHUNKS = 768; // ~1 MB — the window CAP (fills a 200ms-RTT path at ~5 MB/s)
45
+ const CWND_INIT_CHUNKS = 96; // ~131 KB start — ramps to the cap fast on a clean path
46
+ const CWND_MIN_CHUNKS = 24; // ~33 KB floor — still enough to keep a thin path busy
47
+ const CWND_GROW_CHUNKS = 48; // additive increase per ack-progress event
48
+ const WATCHDOG_MS = 150; // stall poll cadence / base no-progress patience
49
+ const WATCHDOG_MAX_MS = 2400; // cap the RTT-adaptive stall patience (high-RTT relay paths)
42
50
  const FAST_RT_MIN_MS = 40; // min gap between fast-retransmits of the same hole (≈1 RTT)
43
51
  const ACK_THROTTLE_MS = 15; // coalesce receiver acks (fast enough to drive fast-retransmit)
44
52
  const REACK_MS = 200; // receiver keep-alive re-ack cadence
@@ -140,6 +148,7 @@ export class FileTransferManager {
140
148
  fileNumber, fileId, name: opts.name, data, size: data.length,
141
149
  acked: 0, nextSend: 0, pumping: false, started: false, req, startMs: nowMs(),
142
150
  lastProgressAcked: 0, lastAckAdvanceMs: nowMs(), lastResendMs: 0,
151
+ cwnd: CWND_INIT_CHUNKS, stalls: 0,
143
152
  };
144
153
  this.#map(this.#sending, friendId).set(fileNumber, st);
145
154
  void this.send(friendId, PACKET_ID_FILE_SENDREQUEST, req).catch(() => undefined);
@@ -360,9 +369,12 @@ export class FileTransferManager {
360
369
  const ackedOffset = readU32be(data, 0);
361
370
  const maxRecv = data.length >= 8 ? readU32be(data, 4) : ackedOffset;
362
371
  if (ackedOffset > st.acked) {
363
- // Progress.
372
+ // Progress. The path is delivering → additively re-open the window (up to
373
+ // the cap) and clear the stall streak (patience shrinks back to the base).
364
374
  st.acked = Math.min(ackedOffset, st.size);
365
375
  st.lastAckAdvanceMs = nowMs();
376
+ st.cwnd = Math.min(WINDOW_CHUNKS, st.cwnd + CWND_GROW_CHUNKS);
377
+ st.stalls = 0;
366
378
  if (st.acked - st.lastProgressAcked >= 256 * 1024 || st.acked >= st.size) {
367
379
  st.lastProgressAcked = st.acked;
368
380
  this.emit("file-progress", { friendId, fileId: hex(st.fileId), received: st.acked, total: st.size, sending: true });
@@ -402,7 +414,12 @@ export class FileTransferManager {
402
414
  }
403
415
  if (st.acked >= st.size)
404
416
  return;
405
- if (nowMs() - st.lastAckAdvanceMs < WATCHDOG_MS) {
417
+ // RTT-adaptive patience: after consecutive stalls, wait longer before
418
+ // deciding the path stalled — so a high-RTT relay hop doesn't trigger a
419
+ // spurious go-back-N re-blast before an ACK could even have returned
420
+ // (that spurious re-blast is exactly what collapses a thin path).
421
+ const patience = Math.min(WATCHDOG_MAX_MS, WATCHDOG_MS << Math.min(st.stalls, 4));
422
+ if (nowMs() - st.lastAckAdvanceMs < patience) {
406
423
  stalledSinceMs = nowMs();
407
424
  return;
408
425
  }
@@ -411,6 +428,12 @@ export class FileTransferManager {
411
428
  this.emit("file-cancel", { friendId, fileId: hex(st.fileId), sending: true, reason: "timeout" });
412
429
  return;
413
430
  }
431
+ // Sustained stall = congestion/path signal → multiplicatively cut the
432
+ // window so we STOP flooding. This frees the path for the net_crypto
433
+ // keepalive + acks, keeping the session (and ping) alive; the transfer
434
+ // then self-clocks back up as acks resume.
435
+ st.stalls++;
436
+ st.cwnd = Math.max(CWND_MIN_CHUNKS, Math.floor(st.cwnd / 2));
414
437
  st.lastAckAdvanceMs = nowMs();
415
438
  st.lastResendMs = nowMs();
416
439
  if (st.nextSend > st.acked)
@@ -425,7 +448,7 @@ export class FileTransferManager {
425
448
  st.pumping = true;
426
449
  try {
427
450
  while (this.#map(this.#sending, friendId).has(st.fileNumber) && st.acked < st.size) {
428
- const windowEnd = Math.min(st.acked + WINDOW_CHUNKS * MAX_FILE_DATA_SIZE, st.size);
451
+ const windowEnd = Math.min(st.acked + st.cwnd * MAX_FILE_DATA_SIZE, st.size);
429
452
  if (st.nextSend >= windowEnd)
430
453
  break; // window full / all sent — wait for acks
431
454
  const off = st.nextSend;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decentnetwork/peer",
3
- "version": "0.1.70",
3
+ "version": "0.1.71",
4
4
  "description": "Pure TypeScript port of Elastos Carrier (toxcore-derived) P2P messaging. DHT, onion routing, TCP relay, FlatBuffers app payloads, Express offline relay. Wire-compatible with iOS Beagle and the Carrier C SDK.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",