@@ -205,17 +205,19 @@ public void SetReceiverWindow(uint remoteARwnd)
205205 /// <param name="sack">The SACK chunk from the remote peer.</param>
206206 public void GotSack ( SctpChunkView sack )
207207 {
208- {
209- if ( _inRetransmitMode . TryTurnOff ( ) )
210- {
211- logger . LogDebug ( "SCTP sender exiting retransmit mode." ) ;
212- }
213-
214- unchecked
215- {
216- uint maxTSNDistance = SctpDataReceiver . GetDistance ( _cumulativeAckTSN , TSN ) ;
217- bool processGapReports = true ;
218- uint cumAckTSNBeforeSackProcessing = _cumulativeAckTSN ;
208+ {
209+ if ( _inRetransmitMode . TryTurnOff ( ) )
210+ {
211+ logger . LogDebug ( "SCTP sender exiting retransmit mode, cwnd {Cwnd} ssthresh {Ssthresh} outstanding {Outstanding}." ,
212+ _congestionWindow , _slowStartThreshold , _outstandingBytes ) ;
213+ }
214+
215+ unchecked
216+ {
217+ uint outstandingBytesBeforeSackProcessing = ( uint ) _outstandingBytes ;
218+ uint maxTSNDistance = SctpDataReceiver . GetDistance ( _cumulativeAckTSN , TSN ) ;
219+ bool processGapReports = true ;
220+ uint cumAckTSNBeforeSackProcessing = _cumulativeAckTSN ;
219221
220222 if ( _unconfirmedChunks . TryGetValue ( sack . CumulativeTsnAck , out var result ) )
221223 {
@@ -292,20 +294,26 @@ public void GotSack(SctpChunkView sack)
292294 ProcessGapReports ( sack . GapAckBlocks , maxTSNDistance , didIncrementCumAckTSN ) ;
293295 }
294296
295- // rfc4960 6.2.1 D iv
296- // If the Cumulative TSN Ack matches or exceeds the Fast Recovery exitpoint(Section 7.2.4), Fast Recovery is exited.
297- if ( SctpDataReceiver . IsNewerOrEqual ( _fastRecoveryExitPoint , _cumulativeAckTSN ) && _inFastRecoveryMode . TryTurnOff ( ) )
298- {
299- logger . LogTrace ( "SCTP sender exiting fast recovery at TSN {TSN}" , _fastRecoveryExitPoint ) ;
300- }
301- }
302-
303- var outstandingBytes = _outstandingBytes ;
304- _receiverWindow = CalculateReceiverWindow ( sack . ARwnd , outstandingBytes : ( uint ) outstandingBytes ) ;
305- _congestionWindow = CalculateCongestionWindow ( InterlockedEx . Read ( ref _lastAckedDataChunkSize ) , outstandingBytes : ( uint ) outstandingBytes ) ;
306-
307- // SACK's will normally allow more data to be sent.
308- _senderMre . Set ( ) ;
297+ // rfc4960 6.2.1 D iv
298+ // If the Cumulative TSN Ack matches or exceeds the Fast Recovery exitpoint(Section 7.2.4), Fast Recovery is exited.
299+ if ( SctpDataReceiver . IsNewerOrEqual ( _cumulativeAckTSN , _fastRecoveryExitPoint ) && _inFastRecoveryMode . TryTurnOff ( ) )
300+ {
301+ logger . LogTrace ( "SCTP sender exiting fast recovery at TSN {TSN} cwnd {Cwnd} ssthresh {Ssthresh} outstanding {Outstanding}." ,
302+ _fastRecoveryExitPoint , _congestionWindow , _slowStartThreshold , _outstandingBytes ) ;
303+ }
304+
305+ var outstandingBytesAfterSackProcessing = ( uint ) _outstandingBytes ;
306+ _receiverWindow = CalculateReceiverWindow ( sack . ARwnd , outstandingBytes : outstandingBytesAfterSackProcessing ) ;
307+ _congestionWindow = CalculateCongestionWindow ( InterlockedEx . Read ( ref _lastAckedDataChunkSize ) , outstandingBytes : outstandingBytesBeforeSackProcessing ) ;
308+
309+ logger . LogTrace (
310+ "SCTP sender SACK updated cwnd {Cwnd} rwnd {Rwnd} outstandingBefore {OutstandingBefore} outstandingAfter {OutstandingAfter} retransmitMode {Retransmit} fastRecoveryMode {FastRecovery}." ,
311+ _congestionWindow , _receiverWindow , outstandingBytesBeforeSackProcessing , outstandingBytesAfterSackProcessing ,
312+ _inRetransmitMode . IsOn ( ) , _inFastRecoveryMode . IsOn ( ) ) ;
313+ }
314+
315+ // SACK's will normally allow more data to be sent.
316+ _senderMre . Set ( ) ;
309317 }
310318 }
311319
@@ -511,7 +519,8 @@ private void ProcessGapReports(ReadOnlySpan<byte> sackGapBlocks, uint maxTSNDist
511519 var last = SctpTsnGapBlock . Read ( sackGapBlocks . Slice ( sackGapBlocks . Length - SctpSackChunk . GAP_REPORT_LENGTH ) ) ;
512520 _fastRecoveryExitPoint = _cumulativeAckTSN + last . End ;
513521
514- logger . LogDebug ( $ "SCTP sender entering fast recovery mode due to missing TSN { missingTSN } . Fast recovery exit point { _fastRecoveryExitPoint } .") ;
522+ logger . LogDebug ( "SCTP sender entering fast recovery mode due to missing TSN {MissingTSN}. ExitPoint {ExitPoint} cwnd {Cwnd} ssthresh {Ssthresh} outstanding {Outstanding}." ,
523+ missingTSN , _fastRecoveryExitPoint , _congestionWindow , _slowStartThreshold , _outstandingBytes ) ;
515524 // RFC4960 7.2.3
516525 _slowStartThreshold = ( uint ) Math . Max ( _congestionWindow / 2 , 4 * _defaultMTU ) ;
517526 _congestionWindow = _slowStartThreshold ;
@@ -574,14 +583,15 @@ private void DoSend(object state)
574583 {
575584 logger . LogDebug ( "SCTP association data send thread started for association {ID}." , _associationID ) ;
576585
577- while ( ! _closed . HasOccurred )
578- {
579- var outstandingBytes = ( uint ) _outstandingBytes ;
586+ while ( ! _closed . HasOccurred )
587+ {
588+ var outstandingBytes = ( uint ) _outstandingBytes ;
589+ var allowedWindow = Math . Min ( _congestionWindow , _receiverWindow ) ;
580590 // DateTime.Now calls have been a tiny bit expensive in the past so get a small saving by only
581591 // calling once per loop.
582592 var now = SctpDataChunk . Timestamp . Now ;
583593
584- int burstSize = ( _inRetransmitMode . IsOn ( ) || _inFastRecoveryMode . IsOn ( ) || _congestionWindow < outstandingBytes || _receiverWindow == 0 ) ? 1 : MAX_BURST ;
594+ int burstSize = ( _inRetransmitMode . IsOn ( ) || _inFastRecoveryMode . IsOn ( ) || allowedWindow <= outstandingBytes || _receiverWindow == 0 ) ? 1 : MAX_BURST ;
585595 int chunksSent = 0 ;
586596
587597 //logger.LogTrace($"SCTP sender burst size {burstSize}, in retransmit mode {_inRetransmitMode}, cwnd {_congestionWindow}, arwnd {_receiverWindow}.");
@@ -638,12 +648,13 @@ private void DoSend(object state)
638648 "flags {Flags:X2}, send count {Count}." ,
639649 chunk . TSN , chunk . UserDataLength , chunk . ChunkFlags , chunk . SendCount ) ;
640650
641- _sendDataChunk ( chunk ) ;
642- chunksSent ++ ;
643-
644- if ( _inRetransmitMode . TryTurnOn ( ) )
645- {
646- logger . LogDebug ( "SCTP sender entering retransmit mode." ) ;
651+ _sendDataChunk ( chunk ) ;
652+ chunksSent ++ ;
653+
654+ if ( _inRetransmitMode . TryTurnOn ( ) )
655+ {
656+ logger . LogDebug ( "SCTP sender entering retransmit mode, cwnd {Cwnd} ssthresh {Ssthresh} outstanding {Outstanding}." ,
657+ _congestionWindow , _slowStartThreshold , _outstandingBytes ) ;
647658
648659 // When the T3-rtx timer expires on an address, SCTP should perform slow start.
649660 // RFC4960 7.2.3
@@ -664,9 +675,9 @@ private void DoSend(object state)
664675 // rfc4960 6.1: At any given time, the sender MUST NOT transmit new data to a given transport address
665676 // if it has cwnd or more bytes of data outstanding to that transport address.
666677
667- // Send any new data chunks that have not yet been sent.
668- if ( chunksSent < burstSize && ! _sendQueue . IsEmpty && _congestionWindow > outstandingBytes )
669- {
678+ // Send any new data chunks that have not yet been sent.
679+ if ( chunksSent < burstSize && ! _sendQueue . IsEmpty && allowedWindow > outstandingBytes )
680+ {
670681 while ( chunksSent < burstSize && _sendQueue . TryDequeue ( out var dataChunk ) )
671682 {
672683 dataChunk . LastSentAt = SctpDataChunk . Timestamp . Now ;
@@ -708,14 +719,16 @@ private void DoSend(object state)
708719 /// <summary>
709720 /// Determines how many milliseconds the send thread should wait before the next send attempt.
710721 /// </summary>
711- private int GetSendWaitMilliseconds ( )
712- {
713- if ( ! _sendQueue . IsEmpty || ! _missingChunks . IsEmpty )
714- {
715- if ( _receiverWindow > 0 && _congestionWindow > ( uint ) _outstandingBytes )
716- {
717- return _burstPeriodMilliseconds ;
718- }
722+ private int GetSendWaitMilliseconds ( )
723+ {
724+ if ( ! _sendQueue . IsEmpty || ! _missingChunks . IsEmpty )
725+ {
726+ var allowedWindow = Math . Min ( _congestionWindow , _receiverWindow ) ;
727+
728+ if ( _receiverWindow > 0 && allowedWindow > ( uint ) _outstandingBytes )
729+ {
730+ return _burstPeriodMilliseconds ;
731+ }
719732 else
720733 {
721734 return _rtoMinimumMilliseconds ;
0 commit comments