@decentnetwork/peer 0.1.101 → 0.1.104
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/compat/filetransfer.js +26 -5
- package/dist/peer.js +26 -2
- package/package.json +1 -1
|
@@ -96,6 +96,14 @@ const PACE_CYCLE_GAINS = [1.25, 0.8, 1, 1, 1, 1, 1, 1]; // steady PROBE_BW cycle
|
|
|
96
96
|
const PACE_BW_WINDOW = 10; // rounds of delivered-rate history for the max-filter
|
|
97
97
|
const PACE_SAMPLE_MS = 200; // aggregate ACKs so one scheduler-jittered packet is not a "round"
|
|
98
98
|
const PACE_INIT_BPS = 120_000; // initial pace rate (~120 KB/s) before any bandwidth estimate
|
|
99
|
+
// A subnet-validated endpoint is not an unknown Internet/relay path. Starting
|
|
100
|
+
// it at 120 KB/s made a Windows ACK-scheduling wobble terminate STARTUP before
|
|
101
|
+
// the controller had ever offered meaningful LAN load; transfers then stayed
|
|
102
|
+
// around 200-500 KB/s despite a multi-MB/s physical path. 2 MB/s is a starting
|
|
103
|
+
// probe, not a cap: the normal feedback loop can grow above it or immediately
|
|
104
|
+
// drain below it on real loss/queueing. The 20 ms pacer limits the initial burst
|
|
105
|
+
// to ~40 KB, so this does not recreate the old 1 MB window blast.
|
|
106
|
+
const LAN_PACE_INIT_BPS = 2_000_000;
|
|
99
107
|
const PACE_BURST_MS = 20; // token-bucket burst allowance (keeps sends smooth, not bunched)
|
|
100
108
|
// A LAN receiver can coalesce a backlog of ACKs after its event loop wakes. The
|
|
101
109
|
// resulting delivery sample is ACK rate, not link capacity: in practice a
|
|
@@ -111,7 +119,11 @@ const LAN_STARTUP_QUEUE_MS = 500; // unmistakable raw queue spike; smaller app-l
|
|
|
111
119
|
const LAN_STARTUP_HARD_QUEUE_MS = 2000; // one multi-second queue sample is unsafe
|
|
112
120
|
const LAN_STARTUP_QUEUE_ROUNDS = 2; // tolerate one scheduler/session-handover outlier on LAN
|
|
113
121
|
const LAN_STARTUP_KEEPUP = 0.70; // delivered goodput below 70% of pace means the receiver is no longer keeping up
|
|
114
|
-
|
|
122
|
+
// Five 200 ms samples was still only about one Windows ACK scheduling cycle in
|
|
123
|
+
// practice: a clean LAN (0% loss) exited STARTUP after ~3 MB and threw away a
|
|
124
|
+
// healthy 2.3 MB/s probe. Require a few seconds of sustained under-delivery;
|
|
125
|
+
// the independent queue/loss brakes still react immediately to a real overload.
|
|
126
|
+
const LAN_STARTUP_SLOW_ROUNDS = 15;
|
|
115
127
|
// FILE ACKs are coalesced and handled by the remote JS event loop. On Windows
|
|
116
128
|
// the effective ACK clock is commonly 20–120 ms even when an occasional probe
|
|
117
129
|
// reports a 7 ms network RTT. Sizing cwnd from that lucky minimum made a
|
|
@@ -119,6 +131,7 @@ const LAN_STARTUP_SLOW_ROUNDS = 5; // require sustained saturation across Window
|
|
|
119
131
|
// and falsely ended startup. Pacing remains the primary throttle, so this floor
|
|
120
132
|
// only prevents the safety window from starving a validated LAN flow.
|
|
121
133
|
const LAN_BDP_RTT_FLOOR_MS = 50;
|
|
134
|
+
const LAN_CWND_INIT_CHUNKS = Math.ceil((LAN_PACE_INIT_BPS * (LAN_BDP_RTT_FLOOR_MS / 1000) * 2) / MAX_FILE_DATA_SIZE);
|
|
122
135
|
const WATCHDOG_MS = 150; // stall poll cadence / base no-progress patience
|
|
123
136
|
const WATCHDOG_MAX_MS = 8000; // cap the RTT-adaptive stall patience (must exceed a slow path's RTT so acks aren't mistaken for a stall)
|
|
124
137
|
const FAST_RT_MIN_MS = 40; // min gap between fast-retransmits of the same hole (≈1 RTT)
|
|
@@ -245,16 +258,19 @@ export class FileTransferManager {
|
|
|
245
258
|
? new Uint8Array(createHash("sha256").update(data).digest()).slice(0, FILE_ID_LENGTH)
|
|
246
259
|
: randomBytes(FILE_ID_LENGTH);
|
|
247
260
|
const req = encodeFileSendRequest(fileNumber, opts.kind ?? FILEKIND.DATA, BigInt(data.length), fileId, opts.name);
|
|
261
|
+
const lanPath = this.isLanPath(friendId);
|
|
248
262
|
const st = {
|
|
249
263
|
fileNumber, fileId, name: opts.name, data, size: data.length,
|
|
250
264
|
acked: 0, nextSend: 0, pumping: false, started: false, req, startMs: nowMs(),
|
|
251
265
|
lastProgressAcked: 0, lastAckAdvanceMs: nowMs(), lastResendMs: 0,
|
|
252
|
-
cwnd:
|
|
266
|
+
cwnd: lanPath ? LAN_CWND_INIT_CHUNKS : CWND_INIT_CHUNKS,
|
|
267
|
+
stalls: 0, parityHighBlock: -1, lastLossMs: 0, lossEwma: 0,
|
|
253
268
|
minRttMs: Infinity, srttMs: 0, probeOffset: -1, probeSentMs: 0,
|
|
254
|
-
paceRateBps:
|
|
269
|
+
paceRateBps: lanPath ? LAN_PACE_INIT_BPS : PACE_INIT_BPS,
|
|
270
|
+
paceTokens: 0, lastPaceMs: nowMs(),
|
|
255
271
|
btlBwBps: 0, bwSamples: [], fullBwRounds: 0, startupQueueRounds: 0, bbrPhase: 0, roundCount: 0,
|
|
256
272
|
rateMarkMs: nowMs(), rateMarkAcked: 0, lowRttCount: 0, lowRttMinMs: Infinity, lastCcLogMs: 0,
|
|
257
|
-
lanPath
|
|
273
|
+
lanPath,
|
|
258
274
|
};
|
|
259
275
|
this.#map(this.#sending, friendId).set(fileNumber, st);
|
|
260
276
|
void this.send(friendId, PACKET_ID_FILE_SENDREQUEST, req).catch(() => undefined);
|
|
@@ -786,10 +802,15 @@ export class FileTransferManager {
|
|
|
786
802
|
const lossMult = lanNow ? 1 : 1 / (1 - Math.min(0.5, st.lossEwma));
|
|
787
803
|
if (lanNow && !st.lanPath) {
|
|
788
804
|
// A transfer can begin on relay and then promote to a validated
|
|
789
|
-
// physical-LAN endpoint.
|
|
805
|
+
// physical-LAN endpoint. Relay samples must not pin the new LAN
|
|
806
|
+
// path near 120 KB/s: discard them and begin a safe LAN probe.
|
|
790
807
|
st.bbrPhase = 0;
|
|
791
808
|
st.fullBwRounds = 0;
|
|
792
809
|
st.startupQueueRounds = 0;
|
|
810
|
+
st.bwSamples = [];
|
|
811
|
+
st.btlBwBps = 0;
|
|
812
|
+
st.paceRateBps = Math.max(st.paceRateBps, LAN_PACE_INIT_BPS);
|
|
813
|
+
st.cwnd = Math.max(st.cwnd, LAN_CWND_INIT_CHUNKS);
|
|
793
814
|
}
|
|
794
815
|
st.lanPath = lanNow;
|
|
795
816
|
if (!lanNow) {
|
package/dist/peer.js
CHANGED
|
@@ -210,8 +210,22 @@ const RECV_REQUEST_MIN_INTERVAL_MS = 200;
|
|
|
210
210
|
export class Peer {
|
|
211
211
|
#opts;
|
|
212
212
|
#events = new EventEmitter();
|
|
213
|
+
/** While negotiating a non-LAN file, temporarily stop sending this friend
|
|
214
|
+
* UDP control traffic. Peer <=0.1.94 routes FILE_CONTROL UDP-only whenever
|
|
215
|
+
* it has seen recent inbound UDP, even when that public/NAT path is one-way.
|
|
216
|
+
* Repeated relay-only offers let that 4s freshness expire, after which the
|
|
217
|
+
* old peer returns ACCEPT/ACK over its reliable TCP relay. */
|
|
218
|
+
#fileRelayNegotiationUntil = new Map();
|
|
213
219
|
// Toxcore-standard file transfer (wire-compatible with native Carrier/toxcore).
|
|
214
|
-
#fileTransfer = new FileTransferManager((friendId, packetId, payload) =>
|
|
220
|
+
#fileTransfer = new FileTransferManager((friendId, packetId, payload) => {
|
|
221
|
+
if (packetId === PACKET_ID_FILE_SENDREQUEST) {
|
|
222
|
+
const session = this.#friendSessions.get(friendId);
|
|
223
|
+
const lan = !!session?.lanRemoteHost && session.remote?.host === session.lanRemoteHost;
|
|
224
|
+
if (!lan)
|
|
225
|
+
this.#fileRelayNegotiationUntil.set(friendId, Date.now() + 6_000);
|
|
226
|
+
}
|
|
227
|
+
return this.#sendMessengerPacket(friendId, packetId, payload);
|
|
228
|
+
}, (event, payload) => { this.#events.emit(event, payload); }, (friendId) => {
|
|
215
229
|
const session = this.#friendSessions.get(friendId);
|
|
216
230
|
return !!session?.lanRemoteHost && session.remote?.host === session.lanRemoteHost;
|
|
217
231
|
});
|
|
@@ -604,6 +618,7 @@ export class Peer {
|
|
|
604
618
|
clearTimeout(timer);
|
|
605
619
|
this.#profileRetryTimers.clear();
|
|
606
620
|
this.#profileRetryAttempts.clear();
|
|
621
|
+
this.#fileRelayNegotiationUntil.clear();
|
|
607
622
|
if (this.#expressPollTimer) {
|
|
608
623
|
clearInterval(this.#expressPollTimer);
|
|
609
624
|
this.#expressPollTimer = undefined;
|
|
@@ -980,6 +995,7 @@ export class Peer {
|
|
|
980
995
|
}
|
|
981
996
|
const existed = this.#friends.delete(friendId);
|
|
982
997
|
this.#friendSessions.delete(friendId);
|
|
998
|
+
this.#fileRelayNegotiationUntil.delete(friendId);
|
|
983
999
|
this.#pendingFriendRequests.delete(friendId);
|
|
984
1000
|
this.#cookieRetryCount.delete(friendId);
|
|
985
1001
|
this.#lastCookieSentKey.delete(friendId);
|
|
@@ -3345,6 +3361,11 @@ export class Peer {
|
|
|
3345
3361
|
* buffered packets through the exact same dispatch. `kind` is the first
|
|
3346
3362
|
* payload byte (never undefined here — the caller defaults it). */
|
|
3347
3363
|
#deliverLosslessPayload(friendId, state, kind, inner, remote, packetNumber = 0) {
|
|
3364
|
+
// Any file response proves the receiver can hear this session. Release the
|
|
3365
|
+
// old-peer relay-only negotiation guard before the data pump chooses its
|
|
3366
|
+
// normal LAN/reliable-public path.
|
|
3367
|
+
if (kind === PACKET_ID_FILE_CONTROL)
|
|
3368
|
+
this.#fileRelayNegotiationUntil.delete(friendId);
|
|
3348
3369
|
// Toxcore file transfer (FILE_SENDREQUEST/CONTROL/DATA = 80/81/82).
|
|
3349
3370
|
if (this.#fileTransfer.handlePacket(friendId, kind, inner))
|
|
3350
3371
|
return;
|
|
@@ -3964,6 +3985,8 @@ export class Peer {
|
|
|
3964
3985
|
const udpFresh = !!realUdpRemote &&
|
|
3965
3986
|
s?.lastUdpRecvMs !== undefined &&
|
|
3966
3987
|
Date.now() - s.lastUdpRecvMs < 4_000;
|
|
3988
|
+
const forceFileRelay = (this.#fileRelayNegotiationUntil.get(friendId) ?? 0) > Date.now() &&
|
|
3989
|
+
!(s?.lanRemoteHost && s.remote?.host === s.lanRemoteHost);
|
|
3967
3990
|
// A freshly-received UDP packet proves only the peer -> us direction.
|
|
3968
3991
|
// That is enough for lossy IP/video bulk, whose inner TCP stream can
|
|
3969
3992
|
// recover a dropped packet, but not for reliable file data: returning
|
|
@@ -3980,7 +4003,8 @@ export class Peer {
|
|
|
3980
4003
|
// lastUdpRecvMs). This converts a multi-second blackout into a few
|
|
3981
4004
|
// seconds of higher relay latency. Control packets always probe UDP
|
|
3982
4005
|
// (to keep the NAT mapping warm) so we still try UDP for them.
|
|
3983
|
-
const tryUdp =
|
|
4006
|
+
const tryUdp = !forceFileRelay &&
|
|
4007
|
+
realUdpRemote &&
|
|
3984
4008
|
(isBulkData ? udpFresh && fileUdpOnlyConfirmed : true);
|
|
3985
4009
|
if (tryUdp && s?.remote) {
|
|
3986
4010
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decentnetwork/peer",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.104",
|
|
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",
|