@decentnetwork/peer 0.1.52 → 0.1.53
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 +42 -8
- package/package.json +1 -1
|
@@ -30,8 +30,16 @@ 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.
|
|
34
|
-
|
|
33
|
+
// Reliability + congestion control tuning. The window is a CONGESTION WINDOW
|
|
34
|
+
// (cwnd, in chunks) that slow-starts from small and grows with acks, halving on
|
|
35
|
+
// a loss/stall — TCP-style AIMD. Starting at a fixed large window blasted an
|
|
36
|
+
// initial burst that a slow/lossy long-haul path (China↔US) couldn't absorb, so
|
|
37
|
+
// the front chunks kept dropping and go-back-N re-burst the same → a big file
|
|
38
|
+
// stuck at 0%. Slow-start makes the first send tiny (fits any path), then it
|
|
39
|
+
// self-clocks to the path's capacity via the ack stream.
|
|
40
|
+
const INITIAL_CWND = 8; // chunks (~11 KB) — small enough for any path
|
|
41
|
+
const MIN_CWND = 4;
|
|
42
|
+
const MAX_CWND = 1024; // ~1.4 MB in flight cap
|
|
35
43
|
const RETRANSMIT_MS = 600; // resend window from the ack point if no progress in this long
|
|
36
44
|
const ACK_THROTTLE_MS = 25; // coalesce receiver acks
|
|
37
45
|
const SEND_GIVEUP_MS = 60000; // abandon a send after this long with no ack progress (peer gone)
|
|
@@ -129,7 +137,8 @@ export class FileTransferManager {
|
|
|
129
137
|
const fileId = randomBytes(FILE_ID_LENGTH);
|
|
130
138
|
const st = {
|
|
131
139
|
fileNumber, fileId, name: opts.name, data, size: data.length,
|
|
132
|
-
acked: 0,
|
|
140
|
+
acked: 0, cwnd: INITIAL_CWND, ssthresh: MAX_CWND,
|
|
141
|
+
pumping: false, lastProgressAcked: 0, lastAckAdvanceMs: nowMs(),
|
|
133
142
|
};
|
|
134
143
|
this.#map(this.#sending, friendId).set(fileNumber, st);
|
|
135
144
|
const req = encodeFileSendRequest(fileNumber, opts.kind ?? FILEKIND.DATA, BigInt(data.length), fileId, opts.name);
|
|
@@ -194,7 +203,7 @@ export class FileTransferManager {
|
|
|
194
203
|
const st = {
|
|
195
204
|
fileNumber: r.fileNumber, fileId: r.fileId, name: r.filename, size,
|
|
196
205
|
buf: new Uint8Array(size), got: new Uint8Array(chunkCount(size)),
|
|
197
|
-
contiguous: 0, done: false, ackDirty: false,
|
|
206
|
+
contiguous: 0, maxByte: 0, done: false, ackDirty: false,
|
|
198
207
|
};
|
|
199
208
|
this.#map(this.#receiving, friendId).set(r.fileNumber, st);
|
|
200
209
|
this.emit("file-offer", { friendId, fileNumber: r.fileNumber, fileId: hex(r.fileId), name: r.filename, size, kind: r.fileType });
|
|
@@ -249,6 +258,8 @@ export class FileTransferManager {
|
|
|
249
258
|
const idx = Math.floor(offset / MAX_FILE_DATA_SIZE);
|
|
250
259
|
if (offset % MAX_FILE_DATA_SIZE !== 0 || idx >= st.got.length)
|
|
251
260
|
return; // not a chunk boundary
|
|
261
|
+
if (offset + data.length > st.maxByte)
|
|
262
|
+
st.maxByte = offset + data.length;
|
|
252
263
|
if (!st.got[idx]) {
|
|
253
264
|
st.buf.set(data, offset);
|
|
254
265
|
st.got[idx] = 1;
|
|
@@ -304,17 +315,36 @@ export class FileTransferManager {
|
|
|
304
315
|
#sendAck(friendId, st) {
|
|
305
316
|
st.ackDirty = false;
|
|
306
317
|
// send_receive=1: the ACK refers to a file the PEER is SENDING (it lives in
|
|
307
|
-
// the peer's #sending table)
|
|
308
|
-
|
|
318
|
+
// the peer's #sending table). Carries [contiguous][maxByte] so the sender
|
|
319
|
+
// sees the gap (maxByte − contiguous) = loss/reorder, for congestion control.
|
|
320
|
+
const body = new Uint8Array(8);
|
|
321
|
+
body.set(u32be(st.contiguous), 0);
|
|
322
|
+
body.set(u32be(st.maxByte), 4);
|
|
323
|
+
void this.send(friendId, PACKET_ID_FILE_CONTROL, encodeFileControl(1, st.fileNumber, FILECONTROL.ACK, body)).catch(() => undefined);
|
|
309
324
|
}
|
|
310
325
|
// ── sender ────────────────────────────────────────────────────────
|
|
311
326
|
#onAck(friendId, st, data) {
|
|
312
327
|
if (data.length < 4)
|
|
313
328
|
return;
|
|
314
329
|
const ackedOffset = readU32be(data, 0);
|
|
330
|
+
const maxRecv = data.length >= 8 ? readU32be(data, 4) : ackedOffset;
|
|
331
|
+
// Gap = bytes the receiver has seen BEYOND the contiguous point that are
|
|
332
|
+
// still missing below maxRecv → loss (or reorder). A gap bigger than half
|
|
333
|
+
// the window means we're overrunning the path → back off (multiplicative
|
|
334
|
+
// decrease). Otherwise the path is keeping up → grow (slow-start / linear).
|
|
335
|
+
const gapChunks = Math.max(0, maxRecv - ackedOffset) / MAX_FILE_DATA_SIZE;
|
|
315
336
|
if (ackedOffset > st.acked) {
|
|
316
337
|
st.acked = Math.min(ackedOffset, st.size);
|
|
317
338
|
st.lastAckAdvanceMs = nowMs();
|
|
339
|
+
if (gapChunks > st.cwnd * 0.5) {
|
|
340
|
+
st.ssthresh = Math.max(MIN_CWND, st.cwnd >> 1);
|
|
341
|
+
st.cwnd = st.ssthresh;
|
|
342
|
+
}
|
|
343
|
+
else {
|
|
344
|
+
st.cwnd = st.cwnd < st.ssthresh
|
|
345
|
+
? Math.min(st.cwnd * 2, st.ssthresh)
|
|
346
|
+
: Math.min(st.cwnd + 1, MAX_CWND);
|
|
347
|
+
}
|
|
318
348
|
if (st.acked - st.lastProgressAcked >= 64 * 1024 || st.acked >= st.size) {
|
|
319
349
|
st.lastProgressAcked = st.acked;
|
|
320
350
|
this.emit("file-progress", { friendId, fileId: hex(st.fileId), received: st.acked, total: st.size, sending: true });
|
|
@@ -357,6 +387,10 @@ export class FileTransferManager {
|
|
|
357
387
|
this.emit("file-cancel", { friendId, fileId: hex(st.fileId), sending: true, reason: "timeout" });
|
|
358
388
|
return;
|
|
359
389
|
}
|
|
390
|
+
// Loss/stall detected → multiplicative decrease (back off), then resend
|
|
391
|
+
// from the ack point. This is the AIMD that paces us to the path.
|
|
392
|
+
st.ssthresh = Math.max(MIN_CWND, st.cwnd >> 1);
|
|
393
|
+
st.cwnd = st.ssthresh;
|
|
360
394
|
st.lastAckAdvanceMs = nowMs();
|
|
361
395
|
void this.#pumpFrom(friendId, st, st.acked);
|
|
362
396
|
}, Math.ceil(RETRANSMIT_MS / 2));
|
|
@@ -370,9 +404,9 @@ export class FileTransferManager {
|
|
|
370
404
|
st.pumping = false;
|
|
371
405
|
}
|
|
372
406
|
}
|
|
373
|
-
// Send chunks from `from` up to the window edge (acked +
|
|
407
|
+
// Send chunks from `from` up to the congestion-window edge (acked + cwnd).
|
|
374
408
|
async #pumpFrom(friendId, st, from) {
|
|
375
|
-
const windowEnd = Math.min(st.acked +
|
|
409
|
+
const windowEnd = Math.min(st.acked + Math.max(MIN_CWND, st.cwnd | 0) * MAX_FILE_DATA_SIZE, st.size);
|
|
376
410
|
let off = from;
|
|
377
411
|
while (off < windowEnd) {
|
|
378
412
|
if (!this.#map(this.#sending, friendId).has(st.fileNumber))
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decentnetwork/peer",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.53",
|
|
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",
|