werift 0.22.8 → 0.23.0
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/lib/common/src/crc.d.ts +4 -0
- package/lib/common/src/crc.js +124 -0
- package/lib/common/src/crc.js.map +1 -0
- package/lib/common/src/index.d.ts +1 -0
- package/lib/common/src/index.js +1 -0
- package/lib/common/src/index.js.map +1 -1
- package/lib/dtls/src/flight/server/flight6.js +12 -2
- package/lib/dtls/src/flight/server/flight6.js.map +1 -1
- package/lib/dtls/src/server.js +6 -1
- package/lib/dtls/src/server.js.map +1 -1
- package/lib/dtls/src/socket.d.ts +1 -0
- package/lib/dtls/src/socket.js +3 -0
- package/lib/dtls/src/socket.js.map +1 -1
- package/lib/ice/src/stun/message.js +2 -10
- package/lib/ice/src/stun/message.js.map +1 -1
- package/lib/index.mjs +705 -113
- package/lib/nonstandard/index.mjs +308 -44
- package/lib/rtp/src/index.d.ts +1 -0
- package/lib/rtp/src/index.js +1 -0
- package/lib/rtp/src/index.js.map +1 -1
- package/lib/rtp/src/srtp/cipher/ctr.d.ts +3 -3
- package/lib/rtp/src/srtp/cipher/ctr.js +25 -14
- package/lib/rtp/src/srtp/cipher/ctr.js.map +1 -1
- package/lib/rtp/src/srtp/cipher/gcm.d.ts +3 -3
- package/lib/rtp/src/srtp/cipher/gcm.js +57 -20
- package/lib/rtp/src/srtp/cipher/gcm.js.map +1 -1
- package/lib/rtp/src/srtp/cipher/index.d.ts +1 -1
- package/lib/rtp/src/srtp/cipher/index.js +1 -1
- package/lib/rtp/src/srtp/cipher/index.js.map +1 -1
- package/lib/rtp/src/srtp/context/srtp.d.ts +2 -1
- package/lib/rtp/src/srtp/context/srtp.js +22 -5
- package/lib/rtp/src/srtp/context/srtp.js.map +1 -1
- package/lib/rtp/src/srtp/error.d.ts +3 -0
- package/lib/rtp/src/srtp/error.js +11 -0
- package/lib/rtp/src/srtp/error.js.map +1 -0
- package/lib/rtp/src/srtp/packet.d.ts +5 -0
- package/lib/rtp/src/srtp/packet.js +48 -0
- package/lib/rtp/src/srtp/packet.js.map +1 -0
- package/lib/sctp/src/chunk.js +3 -6
- package/lib/sctp/src/chunk.js.map +1 -1
- package/lib/sctp/src/sctp.d.ts +19 -0
- package/lib/sctp/src/sctp.js +259 -23
- package/lib/sctp/src/sctp.js.map +1 -1
- package/lib/webrtc/src/dataChannel.d.ts +1 -0
- package/lib/webrtc/src/dataChannel.js +5 -2
- package/lib/webrtc/src/dataChannel.js.map +1 -1
- package/lib/webrtc/src/peerConnection.d.ts +4 -1
- package/lib/webrtc/src/peerConnection.js +26 -5
- package/lib/webrtc/src/peerConnection.js.map +1 -1
- package/lib/webrtc/src/sctpManager.d.ts +1 -1
- package/lib/webrtc/src/sctpManager.js +3 -2
- package/lib/webrtc/src/sctpManager.js.map +1 -1
- package/lib/webrtc/src/sdpManager.d.ts +1 -1
- package/lib/webrtc/src/sdpManager.js +3 -4
- package/lib/webrtc/src/sdpManager.js.map +1 -1
- package/lib/webrtc/src/secureTransportManager.js +1 -1
- package/lib/webrtc/src/secureTransportManager.js.map +1 -1
- package/lib/webrtc/src/transceiverManager.js +3 -2
- package/lib/webrtc/src/transceiverManager.js.map +1 -1
- package/lib/webrtc/src/transport/dtls.d.ts +1 -0
- package/lib/webrtc/src/transport/dtls.js +117 -12
- package/lib/webrtc/src/transport/dtls.js.map +1 -1
- package/lib/webrtc/src/transport/sctp.d.ts +9 -3
- package/lib/webrtc/src/transport/sctp.js +35 -9
- package/lib/webrtc/src/transport/sctp.js.map +1 -1
- package/lib/webrtc/src/utils.d.ts +2 -0
- package/lib/webrtc/src/utils.js +20 -0
- package/lib/webrtc/src/utils.js.map +1 -1
- package/package.json +1 -3
package/lib/sctp/src/sctp.d.ts
CHANGED
|
@@ -21,6 +21,8 @@ export declare class SCTP {
|
|
|
21
21
|
started: boolean;
|
|
22
22
|
state: SCTPConnectionState;
|
|
23
23
|
isServer: boolean;
|
|
24
|
+
private isStopping;
|
|
25
|
+
private isClosed;
|
|
24
26
|
private hmacKey;
|
|
25
27
|
private localPartialReliability;
|
|
26
28
|
private localPort;
|
|
@@ -30,6 +32,8 @@ export declare class SCTP {
|
|
|
30
32
|
private remotePort?;
|
|
31
33
|
private remoteVerificationTag;
|
|
32
34
|
private advertisedRwnd;
|
|
35
|
+
private peerAdvertisedRwnd;
|
|
36
|
+
private get peerRwnd();
|
|
33
37
|
private inboundStreams;
|
|
34
38
|
_inboundStreamsCount: number;
|
|
35
39
|
_inboundStreamsMax: number;
|
|
@@ -37,6 +41,9 @@ export declare class SCTP {
|
|
|
37
41
|
private sackDuplicates;
|
|
38
42
|
private sackMisOrdered;
|
|
39
43
|
private sackNeeded;
|
|
44
|
+
private sackPacketCount;
|
|
45
|
+
private sackHasNewDataInPacket;
|
|
46
|
+
private sackImmediate;
|
|
40
47
|
private sackTimeout;
|
|
41
48
|
private cwnd;
|
|
42
49
|
private fastRecoveryExit?;
|
|
@@ -52,6 +59,8 @@ export declare class SCTP {
|
|
|
52
59
|
private advancedPeerAckTsn;
|
|
53
60
|
private partialBytesAcked;
|
|
54
61
|
private sentQueue;
|
|
62
|
+
private transmitting;
|
|
63
|
+
private transmitRequested;
|
|
55
64
|
/**初期TSNと同じ値に初期化される単調に増加する数です. これは、新しいre-configuration requestパラメーターを送信するたびに1ずつ増加します */
|
|
56
65
|
reconfigRequestSeq: number;
|
|
57
66
|
/**このフィールドは、incoming要求のre-configuration requestシーケンス番号を保持します. 他の場合では、次に予想されるre-configuration requestシーケンス番号から1を引いた値が保持されます */
|
|
@@ -74,12 +83,16 @@ export declare class SCTP {
|
|
|
74
83
|
/**Re-configuration Timer */
|
|
75
84
|
private timerReconfigHandle?;
|
|
76
85
|
private timerReconfigFailures;
|
|
86
|
+
private timerHeartbeatHandle?;
|
|
87
|
+
private heartbeatInterval;
|
|
77
88
|
private ssthresh?;
|
|
78
89
|
constructor(transport: Transport, port?: number);
|
|
90
|
+
private get isStopped();
|
|
79
91
|
get maxChannels(): number | undefined;
|
|
80
92
|
static client(transport: Transport, port?: number): SCTP;
|
|
81
93
|
static server(transport: Transport, port?: number): SCTP;
|
|
82
94
|
private handleData;
|
|
95
|
+
private scheduleSack;
|
|
83
96
|
private sendSack;
|
|
84
97
|
private receiveChunk;
|
|
85
98
|
private getExtensions;
|
|
@@ -97,6 +110,7 @@ export declare class SCTP {
|
|
|
97
110
|
ordered?: boolean;
|
|
98
111
|
}) => Promise<void>;
|
|
99
112
|
private transmit;
|
|
113
|
+
private transmitOnce;
|
|
100
114
|
transmitReconfigRequest(): Promise<void>;
|
|
101
115
|
sendReconfigParam(param: StreamParam): Promise<void>;
|
|
102
116
|
private sendResetRequest;
|
|
@@ -119,6 +133,11 @@ export declare class SCTP {
|
|
|
119
133
|
private timerReconfigHandleStart;
|
|
120
134
|
private timerReconfigHandleExpired;
|
|
121
135
|
private timerReconfigCancel;
|
|
136
|
+
private heartbeatStart;
|
|
137
|
+
private heartbeatRestart;
|
|
138
|
+
private heartbeatCancel;
|
|
139
|
+
private timerHeartbeatExpired;
|
|
140
|
+
setHeartbeatInterval(interval: number): void;
|
|
122
141
|
private updateAdvancedPeerAckPoint;
|
|
123
142
|
private maybeAbandon;
|
|
124
143
|
static getCapabilities(): RTCSctpCapabilities;
|
package/lib/sctp/src/sctp.js
CHANGED
|
@@ -23,10 +23,16 @@ const SCTP_MAX_ASSOCIATION_RETRANS = 10;
|
|
|
23
23
|
const SCTP_MAX_INIT_RETRANS = 8;
|
|
24
24
|
const SCTP_RTO_ALPHA = 1 / 8;
|
|
25
25
|
const SCTP_RTO_BETA = 1 / 4;
|
|
26
|
+
// RFC 9260 RTO.Initial/RTO.Min/RTO.Max defaults and bounds are in seconds.
|
|
26
27
|
const SCTP_RTO_INITIAL = 3;
|
|
27
28
|
const SCTP_RTO_MIN = 1;
|
|
28
29
|
const SCTP_RTO_MAX = 60;
|
|
29
30
|
const SCTP_TSN_MODULO = 2 ** 32;
|
|
31
|
+
// RFC 9260 delayed ACK guidance: send SACK within 200ms at the latest.
|
|
32
|
+
// Use a small default delay to keep request/response latency low while still delaying ACKs.
|
|
33
|
+
const SCTP_SACK_DELAY_MS = 200;
|
|
34
|
+
// RFC 9260 HB.interval recommended default is 30 seconds.
|
|
35
|
+
const SCTP_HEARTBEAT_INTERVAL = 30;
|
|
30
36
|
const RECONFIG_MAX_STREAMS = 135;
|
|
31
37
|
// # parameters
|
|
32
38
|
const SCTP_STATE_COOKIE = 0x0007;
|
|
@@ -39,6 +45,10 @@ const SCTPConnectionStates = [
|
|
|
39
45
|
"connecting",
|
|
40
46
|
];
|
|
41
47
|
class SCTP {
|
|
48
|
+
get peerRwnd() {
|
|
49
|
+
// RFC 9260 §6.2.1(D-ii): available receiver window = a_rwnd - outstanding DATA.
|
|
50
|
+
return Math.max(0, this.peerAdvertisedRwnd - this.flightSize);
|
|
51
|
+
}
|
|
42
52
|
constructor(transport, port = 5000) {
|
|
43
53
|
Object.defineProperty(this, "transport", {
|
|
44
54
|
enumerable: true,
|
|
@@ -107,6 +117,18 @@ class SCTP {
|
|
|
107
117
|
writable: true,
|
|
108
118
|
value: true
|
|
109
119
|
});
|
|
120
|
+
Object.defineProperty(this, "isStopping", {
|
|
121
|
+
enumerable: true,
|
|
122
|
+
configurable: true,
|
|
123
|
+
writable: true,
|
|
124
|
+
value: false
|
|
125
|
+
});
|
|
126
|
+
Object.defineProperty(this, "isClosed", {
|
|
127
|
+
enumerable: true,
|
|
128
|
+
configurable: true,
|
|
129
|
+
writable: true,
|
|
130
|
+
value: false
|
|
131
|
+
});
|
|
110
132
|
Object.defineProperty(this, "hmacKey", {
|
|
111
133
|
enumerable: true,
|
|
112
134
|
configurable: true,
|
|
@@ -162,6 +184,12 @@ class SCTP {
|
|
|
162
184
|
writable: true,
|
|
163
185
|
value: 1024 * 1024
|
|
164
186
|
}); // Receiver Window
|
|
187
|
+
Object.defineProperty(this, "peerAdvertisedRwnd", {
|
|
188
|
+
enumerable: true,
|
|
189
|
+
configurable: true,
|
|
190
|
+
writable: true,
|
|
191
|
+
value: this.advertisedRwnd
|
|
192
|
+
});
|
|
165
193
|
Object.defineProperty(this, "inboundStreams", {
|
|
166
194
|
enumerable: true,
|
|
167
195
|
configurable: true,
|
|
@@ -204,6 +232,24 @@ class SCTP {
|
|
|
204
232
|
writable: true,
|
|
205
233
|
value: false
|
|
206
234
|
});
|
|
235
|
+
Object.defineProperty(this, "sackPacketCount", {
|
|
236
|
+
enumerable: true,
|
|
237
|
+
configurable: true,
|
|
238
|
+
writable: true,
|
|
239
|
+
value: 0
|
|
240
|
+
});
|
|
241
|
+
Object.defineProperty(this, "sackHasNewDataInPacket", {
|
|
242
|
+
enumerable: true,
|
|
243
|
+
configurable: true,
|
|
244
|
+
writable: true,
|
|
245
|
+
value: false
|
|
246
|
+
});
|
|
247
|
+
Object.defineProperty(this, "sackImmediate", {
|
|
248
|
+
enumerable: true,
|
|
249
|
+
configurable: true,
|
|
250
|
+
writable: true,
|
|
251
|
+
value: false
|
|
252
|
+
});
|
|
207
253
|
Object.defineProperty(this, "sackTimeout", {
|
|
208
254
|
enumerable: true,
|
|
209
255
|
configurable: true,
|
|
@@ -290,6 +336,18 @@ class SCTP {
|
|
|
290
336
|
writable: true,
|
|
291
337
|
value: []
|
|
292
338
|
});
|
|
339
|
+
Object.defineProperty(this, "transmitting", {
|
|
340
|
+
enumerable: true,
|
|
341
|
+
configurable: true,
|
|
342
|
+
writable: true,
|
|
343
|
+
value: false
|
|
344
|
+
});
|
|
345
|
+
Object.defineProperty(this, "transmitRequested", {
|
|
346
|
+
enumerable: true,
|
|
347
|
+
configurable: true,
|
|
348
|
+
writable: true,
|
|
349
|
+
value: false
|
|
350
|
+
});
|
|
293
351
|
// # reconfiguration
|
|
294
352
|
/**初期TSNと同じ値に初期化される単調に増加する数です. これは、新しいre-configuration requestパラメーターを送信するたびに1ずつ増加します */
|
|
295
353
|
Object.defineProperty(this, "reconfigRequestSeq", {
|
|
@@ -395,6 +453,18 @@ class SCTP {
|
|
|
395
453
|
writable: true,
|
|
396
454
|
value: 0
|
|
397
455
|
});
|
|
456
|
+
Object.defineProperty(this, "timerHeartbeatHandle", {
|
|
457
|
+
enumerable: true,
|
|
458
|
+
configurable: true,
|
|
459
|
+
writable: true,
|
|
460
|
+
value: void 0
|
|
461
|
+
});
|
|
462
|
+
Object.defineProperty(this, "heartbeatInterval", {
|
|
463
|
+
enumerable: true,
|
|
464
|
+
configurable: true,
|
|
465
|
+
writable: true,
|
|
466
|
+
value: SCTP_HEARTBEAT_INTERVAL
|
|
467
|
+
});
|
|
398
468
|
// etc
|
|
399
469
|
Object.defineProperty(this, "ssthresh", {
|
|
400
470
|
enumerable: true,
|
|
@@ -441,17 +511,9 @@ class SCTP {
|
|
|
441
511
|
if (ordered) {
|
|
442
512
|
this.outboundStreamSeq[streamId] = (0, common_1.uint16Add)(streamSeqNum, 1);
|
|
443
513
|
}
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
else {
|
|
448
|
-
if (this.outboundQueue.length) {
|
|
449
|
-
await this.flush.asPromise();
|
|
450
|
-
}
|
|
451
|
-
else {
|
|
452
|
-
// unreachable?
|
|
453
|
-
await new Promise((r) => setImmediate(r));
|
|
454
|
-
}
|
|
514
|
+
await this.transmit();
|
|
515
|
+
while (this.outboundQueue.length) {
|
|
516
|
+
await this.flush.asPromise();
|
|
455
517
|
}
|
|
456
518
|
}
|
|
457
519
|
});
|
|
@@ -460,6 +522,8 @@ class SCTP {
|
|
|
460
522
|
configurable: true,
|
|
461
523
|
writable: true,
|
|
462
524
|
value: () => {
|
|
525
|
+
if (this.isStopped)
|
|
526
|
+
return;
|
|
463
527
|
this.timer1Failures++;
|
|
464
528
|
this.timer1Handle = undefined;
|
|
465
529
|
if (this.timer1Failures > SCTP_MAX_INIT_RETRANS) {
|
|
@@ -467,10 +531,14 @@ class SCTP {
|
|
|
467
531
|
}
|
|
468
532
|
else {
|
|
469
533
|
setImmediate(() => {
|
|
534
|
+
if (this.isStopped)
|
|
535
|
+
return;
|
|
470
536
|
this.sendChunk(this.timer1Chunk).catch((err) => {
|
|
471
537
|
log("send timer1 chunk failed", err.message);
|
|
472
538
|
});
|
|
473
539
|
});
|
|
540
|
+
if (this.isStopped)
|
|
541
|
+
return;
|
|
474
542
|
this.timer1Handle = setTimeout(this.timer1Expired, this.rto * 1000);
|
|
475
543
|
}
|
|
476
544
|
}
|
|
@@ -480,6 +548,8 @@ class SCTP {
|
|
|
480
548
|
configurable: true,
|
|
481
549
|
writable: true,
|
|
482
550
|
value: () => {
|
|
551
|
+
if (this.isStopped)
|
|
552
|
+
return;
|
|
483
553
|
this.timer2Failures++;
|
|
484
554
|
this.timer2Handle = undefined;
|
|
485
555
|
if (this.timer2Failures > SCTP_MAX_ASSOCIATION_RETRANS) {
|
|
@@ -487,10 +557,14 @@ class SCTP {
|
|
|
487
557
|
}
|
|
488
558
|
else {
|
|
489
559
|
setImmediate(() => {
|
|
560
|
+
if (this.isStopped)
|
|
561
|
+
return;
|
|
490
562
|
this.sendChunk(this.timer2Chunk).catch((err) => {
|
|
491
563
|
log("send timer2Chunk failed", err.message);
|
|
492
564
|
});
|
|
493
565
|
});
|
|
566
|
+
if (this.isStopped)
|
|
567
|
+
return;
|
|
494
568
|
this.timer2Handle = setTimeout(this.timer2Expired, this.rto * 1000);
|
|
495
569
|
}
|
|
496
570
|
}
|
|
@@ -500,6 +574,8 @@ class SCTP {
|
|
|
500
574
|
configurable: true,
|
|
501
575
|
writable: true,
|
|
502
576
|
value: () => {
|
|
577
|
+
if (this.isStopped)
|
|
578
|
+
return;
|
|
503
579
|
this.timer3Handle = undefined;
|
|
504
580
|
// # mark retransmit or abandoned chunks
|
|
505
581
|
this.sentQueue.forEach((chunk) => {
|
|
@@ -522,6 +598,8 @@ class SCTP {
|
|
|
522
598
|
configurable: true,
|
|
523
599
|
writable: true,
|
|
524
600
|
value: async () => {
|
|
601
|
+
if (this.isStopped)
|
|
602
|
+
return;
|
|
525
603
|
this.timerReconfigFailures++;
|
|
526
604
|
// back off
|
|
527
605
|
this.rto = Math.ceil(this.rto * 1.5);
|
|
@@ -533,15 +611,41 @@ class SCTP {
|
|
|
533
611
|
else if (this.reconfigRequest) {
|
|
534
612
|
log("timerReconfigHandleExpired", this.timerReconfigFailures, this.rto);
|
|
535
613
|
await this.sendReconfigParam(this.reconfigRequest);
|
|
614
|
+
if (this.isStopped)
|
|
615
|
+
return;
|
|
536
616
|
this.timerReconfigHandle = setTimeout(this.timerReconfigHandleExpired, this.rto * 1000);
|
|
537
617
|
}
|
|
538
618
|
}
|
|
539
619
|
});
|
|
620
|
+
Object.defineProperty(this, "timerHeartbeatExpired", {
|
|
621
|
+
enumerable: true,
|
|
622
|
+
configurable: true,
|
|
623
|
+
writable: true,
|
|
624
|
+
value: async () => {
|
|
625
|
+
this.timerHeartbeatHandle = undefined;
|
|
626
|
+
if (this.associationState !== const_1.SCTP_STATE.ESTABLISHED)
|
|
627
|
+
return;
|
|
628
|
+
if (this.flightSize === 0 && this.outboundQueue.length === 0) {
|
|
629
|
+
const heartbeat = new chunk_1.HeartbeatChunk();
|
|
630
|
+
heartbeat.params.push([
|
|
631
|
+
1,
|
|
632
|
+
Buffer.from(jspack_1.jspack.Pack("!L", [Math.floor(Date.now() / 1000)])),
|
|
633
|
+
]);
|
|
634
|
+
await this.sendChunk(heartbeat).catch((err) => {
|
|
635
|
+
log("send heartbeat failed", err.message);
|
|
636
|
+
});
|
|
637
|
+
}
|
|
638
|
+
this.heartbeatStart();
|
|
639
|
+
}
|
|
640
|
+
});
|
|
540
641
|
this.localPort = this.port;
|
|
541
642
|
this.transport.onData = (buf) => {
|
|
542
643
|
this.handleData(buf);
|
|
543
644
|
};
|
|
544
645
|
}
|
|
646
|
+
get isStopped() {
|
|
647
|
+
return this.isStopping || this.isClosed;
|
|
648
|
+
}
|
|
545
649
|
get maxChannels() {
|
|
546
650
|
if (this._inboundStreamsCount > 0) {
|
|
547
651
|
return Math.min(this._inboundStreamsCount, this._outboundStreamsCount);
|
|
@@ -560,6 +664,8 @@ class SCTP {
|
|
|
560
664
|
}
|
|
561
665
|
// call from dtls transport
|
|
562
666
|
async handleData(data) {
|
|
667
|
+
if (this.isStopped)
|
|
668
|
+
return;
|
|
563
669
|
let expectedTag;
|
|
564
670
|
const [, , verificationTag, chunks] = (0, chunk_1.parsePacket)(data);
|
|
565
671
|
const initChunk = chunks.filter((v) => v.type === chunk_1.InitChunk.type).length;
|
|
@@ -575,18 +681,47 @@ class SCTP {
|
|
|
575
681
|
if (verificationTag !== expectedTag) {
|
|
576
682
|
return;
|
|
577
683
|
}
|
|
684
|
+
this.sackHasNewDataInPacket = false;
|
|
578
685
|
for (const chunk of chunks) {
|
|
579
686
|
await this.receiveChunk(chunk);
|
|
580
687
|
}
|
|
581
688
|
if (this.sackNeeded) {
|
|
689
|
+
// RFC 9260 delayed ACK guidance is per packet ("every second packet").
|
|
690
|
+
if (this.sackHasNewDataInPacket) {
|
|
691
|
+
this.sackPacketCount++;
|
|
692
|
+
}
|
|
693
|
+
if (this.sackPacketCount >= 2) {
|
|
694
|
+
this.sackImmediate = true;
|
|
695
|
+
}
|
|
696
|
+
await this.scheduleSack();
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
async scheduleSack() {
|
|
700
|
+
if (this.isStopped)
|
|
701
|
+
return;
|
|
702
|
+
if (!this.sackNeeded)
|
|
703
|
+
return;
|
|
704
|
+
if (this.sackImmediate) {
|
|
705
|
+
if (this.sackTimeout) {
|
|
706
|
+
clearTimeout(this.sackTimeout);
|
|
707
|
+
this.sackTimeout = undefined;
|
|
708
|
+
}
|
|
582
709
|
await this.sendSack();
|
|
710
|
+
return;
|
|
583
711
|
}
|
|
712
|
+
if (this.sackTimeout)
|
|
713
|
+
return;
|
|
714
|
+
// RFC 9260 delayed ACK path: defer SACK, but never beyond 200ms.
|
|
715
|
+
this.sackTimeout = setTimeout(() => {
|
|
716
|
+
this.sackTimeout = undefined;
|
|
717
|
+
this.sendSack().catch((err) => {
|
|
718
|
+
log("send delayed sack failed", err.message);
|
|
719
|
+
});
|
|
720
|
+
}, SCTP_SACK_DELAY_MS);
|
|
584
721
|
}
|
|
585
722
|
async sendSack() {
|
|
586
|
-
if (this.
|
|
723
|
+
if (this.isStopped)
|
|
587
724
|
return;
|
|
588
|
-
await new Promise((r) => (this.sackTimeout = setImmediate(r)));
|
|
589
|
-
this.sackTimeout = undefined;
|
|
590
725
|
if (!this.sackNeeded)
|
|
591
726
|
return;
|
|
592
727
|
const gaps = [];
|
|
@@ -611,6 +746,8 @@ class SCTP {
|
|
|
611
746
|
});
|
|
612
747
|
this.sackDuplicates = [];
|
|
613
748
|
this.sackNeeded = false;
|
|
749
|
+
this.sackPacketCount = 0;
|
|
750
|
+
this.sackImmediate = false;
|
|
614
751
|
}
|
|
615
752
|
async receiveChunk(chunk) {
|
|
616
753
|
switch (chunk.type) {
|
|
@@ -629,6 +766,7 @@ class SCTP {
|
|
|
629
766
|
this.reconfigResponseSeq = tsnMinusOne(init.initialTsn);
|
|
630
767
|
this.remoteVerificationTag = init.initiateTag;
|
|
631
768
|
this.ssthresh = init.advertisedRwnd;
|
|
769
|
+
this.peerAdvertisedRwnd = init.advertisedRwnd;
|
|
632
770
|
this.getExtensions(init.params);
|
|
633
771
|
this._inboundStreamsCount = Math.min(init.outboundStreams, this._inboundStreamsMax);
|
|
634
772
|
this._outboundStreamsCount = Math.min(this._outboundStreamsCount, init.inboundStreams);
|
|
@@ -662,6 +800,7 @@ class SCTP {
|
|
|
662
800
|
this.reconfigResponseSeq = tsnMinusOne(initAck.initialTsn);
|
|
663
801
|
this.remoteVerificationTag = initAck.initiateTag;
|
|
664
802
|
this.ssthresh = initAck.advertisedRwnd;
|
|
803
|
+
this.peerAdvertisedRwnd = initAck.advertisedRwnd;
|
|
665
804
|
this.getExtensions(initAck.params);
|
|
666
805
|
this._inboundStreamsCount = Math.min(initAck.outboundStreams, this._inboundStreamsMax);
|
|
667
806
|
this._outboundStreamsCount = Math.min(this._outboundStreamsCount, initAck.inboundStreams);
|
|
@@ -862,8 +1001,21 @@ class SCTP {
|
|
|
862
1001
|
}
|
|
863
1002
|
receiveDataChunk(chunk) {
|
|
864
1003
|
this.sackNeeded = true;
|
|
865
|
-
if (this.markReceived(chunk.tsn))
|
|
1004
|
+
if (this.markReceived(chunk.tsn)) {
|
|
1005
|
+
this.sackImmediate = true;
|
|
866
1006
|
return;
|
|
1007
|
+
}
|
|
1008
|
+
this.sackHasNewDataInPacket = true;
|
|
1009
|
+
// RFC 9260 delayed ACK is optional; prefer immediate DATA ACK to keep ACK-clock responsive.
|
|
1010
|
+
this.sackImmediate = true;
|
|
1011
|
+
if ((chunk.flags & SCTP_DATA_LAST_FRAG) === 0) {
|
|
1012
|
+
// Keep fragmented-message feedback prompt to avoid long tail-loss recovery.
|
|
1013
|
+
this.sackImmediate = true;
|
|
1014
|
+
}
|
|
1015
|
+
if (this.sackMisOrdered.size > 0) {
|
|
1016
|
+
// RFC 9260 permits immediate SACK on gap/loss indicators.
|
|
1017
|
+
this.sackImmediate = true;
|
|
1018
|
+
}
|
|
867
1019
|
const inboundStream = this.getInboundStream(chunk.streamId);
|
|
868
1020
|
inboundStream.addChunk(chunk);
|
|
869
1021
|
this.advertisedRwnd -= chunk.userData.length;
|
|
@@ -972,12 +1124,15 @@ class SCTP {
|
|
|
972
1124
|
else if (done > 0) {
|
|
973
1125
|
this.timer3Restart();
|
|
974
1126
|
}
|
|
1127
|
+
// RFC 9260 §6.2.1(D-ii): keep latest peer a_rwnd; available window is derived via flightSize.
|
|
1128
|
+
this.peerAdvertisedRwnd = chunk.advertisedRwnd;
|
|
975
1129
|
this.updateAdvancedPeerAckPoint();
|
|
976
1130
|
await this.onSackReceived();
|
|
977
1131
|
await this.transmit();
|
|
978
1132
|
}
|
|
979
1133
|
receiveForwardTsnChunk(chunk) {
|
|
980
1134
|
this.sackNeeded = true;
|
|
1135
|
+
this.sackImmediate = true;
|
|
981
1136
|
if ((0, common_1.uint32Gte)(this.lastReceivedTsn, chunk.cumulativeTsn)) {
|
|
982
1137
|
return;
|
|
983
1138
|
}
|
|
@@ -1053,6 +1208,26 @@ class SCTP {
|
|
|
1053
1208
|
return false;
|
|
1054
1209
|
}
|
|
1055
1210
|
async transmit() {
|
|
1211
|
+
if (this.isStopped)
|
|
1212
|
+
return;
|
|
1213
|
+
if (this.transmitting) {
|
|
1214
|
+
this.transmitRequested = true;
|
|
1215
|
+
return;
|
|
1216
|
+
}
|
|
1217
|
+
this.transmitting = true;
|
|
1218
|
+
try {
|
|
1219
|
+
do {
|
|
1220
|
+
this.transmitRequested = false;
|
|
1221
|
+
await this.transmitOnce();
|
|
1222
|
+
} while (this.transmitRequested);
|
|
1223
|
+
}
|
|
1224
|
+
finally {
|
|
1225
|
+
this.transmitting = false;
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
async transmitOnce() {
|
|
1229
|
+
if (this.isStopped)
|
|
1230
|
+
return;
|
|
1056
1231
|
// """
|
|
1057
1232
|
// Transmit outbound data.
|
|
1058
1233
|
// """
|
|
@@ -1077,7 +1252,7 @@ class SCTP {
|
|
|
1077
1252
|
this.fastRecoveryTransmit = false;
|
|
1078
1253
|
}
|
|
1079
1254
|
else if (this.flightSize >= cwnd) {
|
|
1080
|
-
|
|
1255
|
+
break;
|
|
1081
1256
|
}
|
|
1082
1257
|
this.flightSizeIncrease(dataChunk);
|
|
1083
1258
|
dataChunk.misses = 0;
|
|
@@ -1092,11 +1267,17 @@ class SCTP {
|
|
|
1092
1267
|
}
|
|
1093
1268
|
retransmitEarliest = false;
|
|
1094
1269
|
}
|
|
1095
|
-
//
|
|
1096
|
-
while (this.outboundQueue.length > 0
|
|
1270
|
+
// Drain queued outbound DATA; retransmission pacing is controlled by RTO/T3.
|
|
1271
|
+
while (this.outboundQueue.length > 0 &&
|
|
1272
|
+
this.flightSize < cwnd &&
|
|
1273
|
+
this.peerRwnd > 0) {
|
|
1097
1274
|
const chunk = this.outboundQueue.shift();
|
|
1098
1275
|
if (!chunk)
|
|
1099
|
-
|
|
1276
|
+
break;
|
|
1277
|
+
if (chunk.bookSize > this.peerRwnd && this.flightSize > 0) {
|
|
1278
|
+
this.outboundQueue.unshift(chunk);
|
|
1279
|
+
break;
|
|
1280
|
+
}
|
|
1100
1281
|
this.sentQueue.push(chunk);
|
|
1101
1282
|
this.flightSizeIncrease(chunk);
|
|
1102
1283
|
// # update counters
|
|
@@ -1109,8 +1290,10 @@ class SCTP {
|
|
|
1109
1290
|
this.timer3Start();
|
|
1110
1291
|
}
|
|
1111
1292
|
}
|
|
1112
|
-
|
|
1113
|
-
|
|
1293
|
+
if (!this.outboundQueue.length) {
|
|
1294
|
+
// Resetting the queue to empty array mitigates this.
|
|
1295
|
+
this.outboundQueue = [];
|
|
1296
|
+
}
|
|
1114
1297
|
this.flush.execute();
|
|
1115
1298
|
}
|
|
1116
1299
|
async transmitReconfigRequest() {
|
|
@@ -1185,14 +1368,19 @@ class SCTP {
|
|
|
1185
1368
|
}
|
|
1186
1369
|
/**t3 is wait for data sack */
|
|
1187
1370
|
timer3Start() {
|
|
1371
|
+
if (this.isStopped)
|
|
1372
|
+
return;
|
|
1188
1373
|
if (this.timer3Handle)
|
|
1189
1374
|
throw new Error();
|
|
1375
|
+
// RFC 9260 T3-rtx runs on current RTO; rto is stored in seconds.
|
|
1190
1376
|
this.timer3Handle = setTimeout(this.timer3Expired, this.rto * 1000);
|
|
1191
1377
|
}
|
|
1192
1378
|
timer3Restart() {
|
|
1379
|
+
if (this.isStopped)
|
|
1380
|
+
return;
|
|
1193
1381
|
this.timer3Cancel();
|
|
1194
|
-
//
|
|
1195
|
-
this.timer3Handle = setTimeout(this.timer3Expired, this.rto);
|
|
1382
|
+
// RFC 9260 requires restarting T3-rtx with current RTO (seconds -> JS ms).
|
|
1383
|
+
this.timer3Handle = setTimeout(this.timer3Expired, this.rto * 1000);
|
|
1196
1384
|
}
|
|
1197
1385
|
timer3Cancel() {
|
|
1198
1386
|
if (this.timer3Handle) {
|
|
@@ -1202,6 +1390,8 @@ class SCTP {
|
|
|
1202
1390
|
}
|
|
1203
1391
|
/**Re-configuration Timer */
|
|
1204
1392
|
timerReconfigHandleStart() {
|
|
1393
|
+
if (this.isStopped)
|
|
1394
|
+
return;
|
|
1205
1395
|
if (this.timerReconfigHandle)
|
|
1206
1396
|
return;
|
|
1207
1397
|
log("timerReconfigHandleStart", { rto: this.rto });
|
|
@@ -1217,6 +1407,31 @@ class SCTP {
|
|
|
1217
1407
|
this.timerReconfigFailures = 0;
|
|
1218
1408
|
}
|
|
1219
1409
|
}
|
|
1410
|
+
heartbeatStart() {
|
|
1411
|
+
if (this.timerHeartbeatHandle ||
|
|
1412
|
+
this.associationState !== const_1.SCTP_STATE.ESTABLISHED) {
|
|
1413
|
+
return;
|
|
1414
|
+
}
|
|
1415
|
+
// RFC 9260 heartbeat timer is based on RTO + HB.interval for idle paths.
|
|
1416
|
+
this.timerHeartbeatHandle = setTimeout(this.timerHeartbeatExpired, (this.rto + this.heartbeatInterval) * 1000);
|
|
1417
|
+
}
|
|
1418
|
+
heartbeatRestart() {
|
|
1419
|
+
this.heartbeatCancel();
|
|
1420
|
+
this.heartbeatStart();
|
|
1421
|
+
}
|
|
1422
|
+
heartbeatCancel() {
|
|
1423
|
+
if (this.timerHeartbeatHandle) {
|
|
1424
|
+
clearTimeout(this.timerHeartbeatHandle);
|
|
1425
|
+
this.timerHeartbeatHandle = undefined;
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
setHeartbeatInterval(interval) {
|
|
1429
|
+
if (interval <= 0) {
|
|
1430
|
+
throw new Error("heartbeat interval must be > 0");
|
|
1431
|
+
}
|
|
1432
|
+
this.heartbeatInterval = interval;
|
|
1433
|
+
this.heartbeatRestart();
|
|
1434
|
+
}
|
|
1220
1435
|
updateAdvancedPeerAckPoint() {
|
|
1221
1436
|
if ((0, common_1.uint32Gt)(this.lastSackedTsn, this.advancedPeerAckTsn)) {
|
|
1222
1437
|
this.advancedPeerAckTsn = this.lastSackedTsn;
|
|
@@ -1330,13 +1545,22 @@ class SCTP {
|
|
|
1330
1545
|
this.associationState = state;
|
|
1331
1546
|
}
|
|
1332
1547
|
if (state === const_1.SCTP_STATE.ESTABLISHED) {
|
|
1548
|
+
this.isStopping = false;
|
|
1549
|
+
this.isClosed = false;
|
|
1333
1550
|
this.setConnectionState("connected");
|
|
1551
|
+
this.heartbeatStart();
|
|
1334
1552
|
}
|
|
1335
1553
|
else if (state === const_1.SCTP_STATE.CLOSED) {
|
|
1554
|
+
this.isClosed = true;
|
|
1336
1555
|
this.timer1Cancel();
|
|
1337
1556
|
this.timer2Cancel();
|
|
1338
1557
|
this.timer3Cancel();
|
|
1339
1558
|
this.timerReconfigCancel();
|
|
1559
|
+
this.heartbeatCancel();
|
|
1560
|
+
if (this.sackTimeout) {
|
|
1561
|
+
clearTimeout(this.sackTimeout);
|
|
1562
|
+
this.sackTimeout = undefined;
|
|
1563
|
+
}
|
|
1340
1564
|
this.setConnectionState("closed");
|
|
1341
1565
|
this.removeAllListeners();
|
|
1342
1566
|
}
|
|
@@ -1347,6 +1571,16 @@ class SCTP {
|
|
|
1347
1571
|
this.stateChanged[state].execute();
|
|
1348
1572
|
}
|
|
1349
1573
|
async stop() {
|
|
1574
|
+
if (this.isStopped) {
|
|
1575
|
+
this.setState(const_1.SCTP_STATE.CLOSED);
|
|
1576
|
+
return;
|
|
1577
|
+
}
|
|
1578
|
+
this.isStopping = true;
|
|
1579
|
+
this.transport.onData = undefined;
|
|
1580
|
+
if (this.sackTimeout) {
|
|
1581
|
+
clearTimeout(this.sackTimeout);
|
|
1582
|
+
this.sackTimeout = undefined;
|
|
1583
|
+
}
|
|
1350
1584
|
if (this.associationState !== const_1.SCTP_STATE.CLOSED) {
|
|
1351
1585
|
await this.abort();
|
|
1352
1586
|
}
|
|
@@ -1355,6 +1589,8 @@ class SCTP {
|
|
|
1355
1589
|
clearTimeout(this.timer2Handle);
|
|
1356
1590
|
clearTimeout(this.timer3Handle);
|
|
1357
1591
|
clearTimeout(this.timerReconfigHandle);
|
|
1592
|
+
clearTimeout(this.timerHeartbeatHandle);
|
|
1593
|
+
clearTimeout(this.sackTimeout);
|
|
1358
1594
|
}
|
|
1359
1595
|
async abort() {
|
|
1360
1596
|
const abort = new chunk_1.AbortChunk();
|