@decentnetwork/peer 0.1.108 → 0.1.110
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.d.ts +4 -1
- package/dist/compat/filetransfer.js +41 -6
- package/dist/peer.js +36 -20
- package/package.json +1 -1
|
@@ -41,12 +41,15 @@ export declare function parseFileData(p: Uint8Array): {
|
|
|
41
41
|
export type FtSend = (friendId: string, packetId: number, payload: Uint8Array) => Promise<unknown>;
|
|
42
42
|
export type FtEmit = (event: string, payload: Record<string, unknown>) => void;
|
|
43
43
|
export type FtIsLanPath = (friendId: string) => boolean;
|
|
44
|
+
export type FtPathKind = "lan" | "udp-direct" | "relay";
|
|
45
|
+
export type FtPathKindFn = (friendId: string) => FtPathKind;
|
|
44
46
|
export declare class FileTransferManager {
|
|
45
47
|
#private;
|
|
46
48
|
private readonly send;
|
|
47
49
|
private readonly emit;
|
|
48
50
|
private readonly isLanPath;
|
|
49
|
-
|
|
51
|
+
private readonly pathKind;
|
|
52
|
+
constructor(send: FtSend, emit: FtEmit, isLanPath?: FtIsLanPath, pathKind?: FtPathKindFn);
|
|
50
53
|
/** Enable resumable receives: partials are persisted under `dir` and matched
|
|
51
54
|
* on re-offer by content-hash fileId. Unset = in-memory only (no resume). */
|
|
52
55
|
setResumeDir(dir: string): void;
|
|
@@ -209,15 +209,17 @@ export class FileTransferManager {
|
|
|
209
209
|
send;
|
|
210
210
|
emit;
|
|
211
211
|
isLanPath;
|
|
212
|
+
pathKind;
|
|
212
213
|
#sending = new Map();
|
|
213
214
|
#receiving = new Map();
|
|
214
215
|
// When set, incoming transfers persist their contiguous prefix here so a
|
|
215
216
|
// dropped/restarted transfer resumes instead of restarting (断点续传).
|
|
216
217
|
#resumeDir;
|
|
217
|
-
constructor(send, emit, isLanPath = () => false) {
|
|
218
|
+
constructor(send, emit, isLanPath = () => false, pathKind = (friendId) => (this.isLanPath(friendId) ? "lan" : "relay")) {
|
|
218
219
|
this.send = send;
|
|
219
220
|
this.emit = emit;
|
|
220
221
|
this.isLanPath = isLanPath;
|
|
222
|
+
this.pathKind = pathKind;
|
|
221
223
|
}
|
|
222
224
|
/** Enable resumable receives: partials are persisted under `dir` and matched
|
|
223
225
|
* on re-offer by content-hash fileId. Unset = in-memory only (no resume). */
|
|
@@ -279,7 +281,7 @@ export class FileTransferManager {
|
|
|
279
281
|
minRttMs: Infinity, srttMs: 0, probeOffset: -1, probeSentMs: 0,
|
|
280
282
|
paceRateBps: lanPath ? LAN_PACE_INIT_BPS : PACE_INIT_BPS,
|
|
281
283
|
paceTokens: 0, lastPaceMs: nowMs(),
|
|
282
|
-
btlBwBps: 0, bwSamples: [], fullBwRounds: 0, startupQueueRounds: 0, bbrPhase: 0, roundCount: 0,
|
|
284
|
+
btlBwBps: 0, bwSamples: [], fullBwBps: 0, fullBwRounds: 0, startupQueueRounds: 0, bbrPhase: 0, roundCount: 0,
|
|
283
285
|
rateMarkMs: nowMs(), rateMarkAcked: 0, lowRttCount: 0, lowRttMinMs: Infinity, lastCcLogMs: 0,
|
|
284
286
|
lanPath,
|
|
285
287
|
dbgLanNow: lanPath ? 1 : 0, dbgSampleMs: 0, dbgRoundRate: 0, dbgMeasuredRate: 0,
|
|
@@ -821,7 +823,9 @@ export class FileTransferManager {
|
|
|
821
823
|
const roundRate = ((st.acked - st.rateMarkAcked) * 1000) / sampleMs;
|
|
822
824
|
st.rateMarkMs = nowT;
|
|
823
825
|
st.rateMarkAcked = st.acked;
|
|
824
|
-
const
|
|
826
|
+
const pathKind = this.pathKind(friendId);
|
|
827
|
+
const lanNow = pathKind === "lan";
|
|
828
|
+
const udpDirectNow = pathKind === "udp-direct";
|
|
825
829
|
// On a paced LAN flow, sustainable delivery cannot suddenly be
|
|
826
830
|
// many times faster than the bytes we are putting on the wire.
|
|
827
831
|
// Such a sample is delayed/coalesced ACKs draining, not new
|
|
@@ -861,15 +865,46 @@ export class FileTransferManager {
|
|
|
861
865
|
st.lanPath = lanNow;
|
|
862
866
|
const rawQueue = st.minRttMs !== Infinity ? acceptedRtt - st.minRttMs : 0;
|
|
863
867
|
st.dbgRawQueue = Math.round(rawQueue);
|
|
864
|
-
if (!lanNow) {
|
|
865
|
-
//
|
|
868
|
+
if (!lanNow && !udpDirectNow) {
|
|
869
|
+
// TCP-relay-only file traffic must remain conservative. A short
|
|
866
870
|
// ACK burst can wildly overstate bandwidth there; allowing LAN's
|
|
867
871
|
// exponential STARTUP filled the TCP relay with an 8.7 s queue.
|
|
868
|
-
//
|
|
872
|
+
// A confirmed public UDP-direct path is different: it is the old
|
|
873
|
+
// long-haul file path with FEC and BBR-style pacing, so do not
|
|
874
|
+
// pin it to 120-240 KB/s.
|
|
869
875
|
st.bbrPhase = 2;
|
|
870
876
|
const gain = PACE_CYCLE_GAINS[st.roundCount % PACE_CYCLE_GAINS.length];
|
|
871
877
|
st.paceRateBps = Math.max(PACE_INIT_BPS, Math.min(PACE_INIT_BPS * 2, st.btlBwBps * gain * lossMult));
|
|
872
878
|
}
|
|
879
|
+
else if (udpDirectNow) {
|
|
880
|
+
// Public UDP direct: restore the pre-LAN-regression remote path.
|
|
881
|
+
// It keeps FEC loss compensation and paced BBR startup/probe, but
|
|
882
|
+
// does NOT use LAN's aggressive 2 MB/s initial probe or ACK-burst
|
|
883
|
+
// sample cap. Queue growth still exits STARTUP quickly, so this
|
|
884
|
+
// does not recreate the deep TCP-relay bufferbloat issue.
|
|
885
|
+
if (st.bbrPhase === 0) {
|
|
886
|
+
if (st.fullBwBps === 0 || st.btlBwBps >= st.fullBwBps * 1.20) {
|
|
887
|
+
st.fullBwBps = st.btlBwBps;
|
|
888
|
+
st.fullBwRounds = 0;
|
|
889
|
+
}
|
|
890
|
+
else {
|
|
891
|
+
st.fullBwRounds++;
|
|
892
|
+
}
|
|
893
|
+
if (st.fullBwRounds >= 3 || rawQueue > 350) {
|
|
894
|
+
st.bbrPhase = 2;
|
|
895
|
+
}
|
|
896
|
+
if (st.bbrPhase === 0) {
|
|
897
|
+
st.paceRateBps = Math.max(st.paceRateBps * 1.4, st.btlBwBps * 2.0 * lossMult);
|
|
898
|
+
}
|
|
899
|
+
else {
|
|
900
|
+
st.paceRateBps = Math.max(PACE_INIT_BPS * 0.5, st.btlBwBps * lossMult);
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
else {
|
|
904
|
+
const gain = PACE_CYCLE_GAINS[st.roundCount % PACE_CYCLE_GAINS.length];
|
|
905
|
+
st.paceRateBps = Math.max(PACE_INIT_BPS * 0.5, st.btlBwBps * gain * lossMult);
|
|
906
|
+
}
|
|
907
|
+
}
|
|
873
908
|
else if (st.bbrPhase === 0) {
|
|
874
909
|
// BBR full-bandwidth detection: remain in STARTUP while delivered
|
|
875
910
|
// bandwidth grows. A relative RTT threshold is unusable on LAN —
|
package/dist/peer.js
CHANGED
|
@@ -230,6 +230,16 @@ export class Peer {
|
|
|
230
230
|
}, (event, payload) => { this.#events.emit(event, payload); }, (friendId) => {
|
|
231
231
|
const session = this.#friendSessions.get(friendId);
|
|
232
232
|
return !!session?.lanRemoteHost && session.remote?.host === session.lanRemoteHost;
|
|
233
|
+
}, (friendId) => {
|
|
234
|
+
const session = this.#friendSessions.get(friendId);
|
|
235
|
+
const realUdpRemote = session?.remote && !session.remote.host?.startsWith("tcp:") && session.remote.port !== 0;
|
|
236
|
+
if (realUdpRemote && session.lanRemoteHost && session.remote?.host === session.lanRemoteHost)
|
|
237
|
+
return "lan";
|
|
238
|
+
if (realUdpRemote &&
|
|
239
|
+
session.lastUdpRecvMs !== undefined &&
|
|
240
|
+
Date.now() - session.lastUdpRecvMs < 4_000)
|
|
241
|
+
return "udp-direct";
|
|
242
|
+
return "relay";
|
|
233
243
|
});
|
|
234
244
|
#keyPair;
|
|
235
245
|
#udp = new UdpTransport();
|
|
@@ -2169,17 +2179,21 @@ export class Peer {
|
|
|
2169
2179
|
// syscall (that was the CCTV CPU regression) — safe at CCTV packet
|
|
2170
2180
|
// rates.
|
|
2171
2181
|
this.#adoptRemote(state, remote.address, remote.port);
|
|
2172
|
-
// A successfully decrypted packet arriving from a private address
|
|
2173
|
-
//
|
|
2174
|
-
//
|
|
2175
|
-
//
|
|
2176
|
-
//
|
|
2177
|
-
//
|
|
2178
|
-
//
|
|
2182
|
+
// A successfully decrypted packet arriving from a private address is
|
|
2183
|
+
// stronger proof than an endpoint hint: it demonstrates that this
|
|
2184
|
+
// exact host:port already reaches us. Do not require the address to be
|
|
2185
|
+
// in one of this process' physical subnets. WSL commonly has only a
|
|
2186
|
+
// 172.16/12 vNIC while the real LAN peer is 10.0.0.x via the Windows
|
|
2187
|
+
// host NAT; direct UDP is confirmed, but a strict same-subnet check
|
|
2188
|
+
// leaves file transfer on the conservative public/relay pacer.
|
|
2189
|
+
//
|
|
2190
|
+
// Unverified private candidates still require same-subnet matching at
|
|
2191
|
+
// candidate-learn time. This relaxed lock is only for decrypted UDP,
|
|
2192
|
+
// so it cannot promote a spoofed or merely advertised private host.
|
|
2179
2193
|
if (!state.lanRemoteHost &&
|
|
2180
2194
|
isPrivateAddress(remote.address) &&
|
|
2181
2195
|
!isCgnatAddress(remote.address) &&
|
|
2182
|
-
|
|
2196
|
+
!this.#isUnroutableSelfSource(remote.address, remote.port)) {
|
|
2183
2197
|
state.lanRemoteHost = remote.address;
|
|
2184
2198
|
this.#rememberEndpointCandidate(state, remote.address, remote.port);
|
|
2185
2199
|
this.#debugLog(`lan_lock_confirmed friend=${friendId} host=${remote.address}:${remote.port} (decrypted UDP source)`);
|
|
@@ -4008,15 +4022,16 @@ export class Peer {
|
|
|
4008
4022
|
Date.now() - s.lastUdpRecvMs < 4_000;
|
|
4009
4023
|
const forceFileRelay = (this.#fileRelayNegotiationUntil.get(friendId) ?? 0) > Date.now() &&
|
|
4010
4024
|
!(s?.lanRemoteHost && s.remote?.host === s.lanRemoteHost);
|
|
4011
|
-
//
|
|
4012
|
-
//
|
|
4013
|
-
//
|
|
4014
|
-
//
|
|
4015
|
-
//
|
|
4016
|
-
//
|
|
4017
|
-
//
|
|
4018
|
-
|
|
4019
|
-
|
|
4025
|
+
// File DATA/FEC is reliable at the file layer: FEC + ACK/retransmit repair
|
|
4026
|
+
// loss. It should use a fresh decrypted UDP-direct path, including public
|
|
4027
|
+
// long-haul peers. A previous LAN fix accidentally required a LAN lock here,
|
|
4028
|
+
// which forced China/US file sends back onto TCP relay and destroyed the
|
|
4029
|
+
// already-tuned remote-transfer path.
|
|
4030
|
+
//
|
|
4031
|
+
// The one-way-public-UDP blackhole is handled by keeping SENDREQUEST/CONTROL
|
|
4032
|
+
// on relay during negotiation (`forceFileRelay`) until a CONTROL packet
|
|
4033
|
+
// proves the receiver is responding. Once `lastUdpRecvMs` is fresh, bulk may
|
|
4034
|
+
// ride UDP only; if it goes stale, we fall back to relay/TCP.
|
|
4020
4035
|
// Bulk IP data rides the direct path ONLY while it's fresh. When it
|
|
4021
4036
|
// goes stale (likely a NAT remap mid-stream), we do NOT keep firing
|
|
4022
4037
|
// into the dead endpoint and we do NOT duplicate onto UDP — we bridge
|
|
@@ -4026,7 +4041,7 @@ export class Peer {
|
|
|
4026
4041
|
// (to keep the NAT mapping warm) so we still try UDP for them.
|
|
4027
4042
|
const tryUdp = !forceFileRelay &&
|
|
4028
4043
|
realUdpRemote &&
|
|
4029
|
-
(isBulkData ? udpFresh
|
|
4044
|
+
(isBulkData ? udpFresh : true);
|
|
4030
4045
|
if (tryUdp && s?.remote) {
|
|
4031
4046
|
try {
|
|
4032
4047
|
await this.#sendPacket(packet, s.remote);
|
|
@@ -4037,8 +4052,9 @@ export class Peer {
|
|
|
4037
4052
|
}
|
|
4038
4053
|
}
|
|
4039
4054
|
// Fresh confirmed direct path → send over UDP only, but ONLY for bulk.
|
|
4040
|
-
// For files
|
|
4041
|
-
//
|
|
4055
|
+
// For files this includes public UDP-direct, not only LAN; the file layer
|
|
4056
|
+
// handles loss and retransmit. Sparse control/chat still also go over relay
|
|
4057
|
+
// because inbound UDP freshness alone does not prove outbound reachability.
|
|
4042
4058
|
//
|
|
4043
4059
|
// Low-volume traffic (chat 64 + control keepalives / endpoint offers /
|
|
4044
4060
|
// profile) instead ALSO goes over the relay even when the direct path
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decentnetwork/peer",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.110",
|
|
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",
|