/// <summary> /// Updates the sender state for the gap reports received in a SACH chunk from the /// remote peer. /// </summary> /// <param name="sackGapBlocks">The gap reports from the remote peer.</param> /// <param name="maxTSNDistance">The maximum distance any valid TSN should be from the current /// ACK'ed TSN. If this distance gets exceeded by a gap report then it's likely something has been /// miscalculated.</param> private void ProcessGapReports(List <SctpTsnGapBlock> sackGapBlocks, uint maxTSNDistance) { uint lastAckTSN = _cumulativeAckTSN; foreach (var gapBlock in sackGapBlocks) { uint goodTSNStart = _cumulativeAckTSN + gapBlock.Start; if (SctpDataReceiver.GetDistance(lastAckTSN, goodTSNStart) > maxTSNDistance) { logger.LogWarning($"SCTP SACK gap report had a start TSN of {goodTSNStart} too distant from last good TSN {lastAckTSN}, ignoring rest of SACK."); break; } else if (!SctpDataReceiver.IsNewer(lastAckTSN, goodTSNStart)) { logger.LogWarning($"SCTP SACK gap report had a start TSN of {goodTSNStart} behind last good TSN {lastAckTSN}, ignoring rest of SACK."); break; } else { uint missingTSN = lastAckTSN + 1; logger.LogTrace($"SCTP SACK gap report start TSN {goodTSNStart} gap report end TSN {_cumulativeAckTSN + gapBlock.End} " + $"first missing TSN {missingTSN}."); while (missingTSN != goodTSNStart) { if (!_missingChunks.ContainsKey(missingTSN)) { if (!_unconfirmedChunks.ContainsKey(missingTSN)) { // What to do? Can't retransmit a chunk that's no longer available. // Hope it's a transient error from a duplicate or out of order SACK. // TODO: Maybe keep count of how many time this occurs and send an ABORT if it // gets to a certain threshold. logger.LogWarning($"SCTP SACK gap report reported missing TSN of {missingTSN} but no matching unconfirmed chunk available."); break; } else { logger.LogTrace($"SCTP SACK gap adding retransmit entry for TSN {missingTSN}."); _missingChunks.TryAdd(missingTSN, 0); } } missingTSN++; } } lastAckTSN = _cumulativeAckTSN + gapBlock.End; } }
/// <summary> /// Handler for SACK chunks received from the remote peer. /// </summary> /// <param name="sack">The SACK chunk from the remote peer.</param> public void GotSack(SctpSackChunk sack) { if (sack != null) { _inRetransmitMode = false; unchecked { uint maxTSNDistance = SctpDataReceiver.GetDistance(_cumulativeAckTSN, TSN); bool processGapReports = true; if (_unconfirmedChunks.TryGetValue(sack.CumulativeTsnAck, out var result)) { _lastAckedDataChunkSize = result.UserData.Length; } if (!_gotFirstSACK) { if (SctpDataReceiver.GetDistance(_initialTSN, sack.CumulativeTsnAck) < maxTSNDistance && SctpDataReceiver.IsNewerOrEqual(_initialTSN, sack.CumulativeTsnAck)) { logger.LogTrace($"SCTP first SACK remote peer TSN ACK {sack.CumulativeTsnAck} next sender TSN {TSN}, arwnd {sack.ARwnd} (gap reports {sack.GapAckBlocks.Count})."); _gotFirstSACK = true; _cumulativeAckTSN = _initialTSN; RemoveAckedUnconfirmedChunks(sack.CumulativeTsnAck); } } else { if (_cumulativeAckTSN != sack.CumulativeTsnAck) { if (SctpDataReceiver.GetDistance(_cumulativeAckTSN, sack.CumulativeTsnAck) > maxTSNDistance) { logger.LogWarning($"SCTP SACK TSN from remote peer of {sack.CumulativeTsnAck} was too distant from the expected {_cumulativeAckTSN}, ignoring."); processGapReports = false; } else if (!SctpDataReceiver.IsNewer(_cumulativeAckTSN, sack.CumulativeTsnAck)) { logger.LogWarning($"SCTP SACK TSN from remote peer of {sack.CumulativeTsnAck} was behind expected {_cumulativeAckTSN}, ignoring."); processGapReports = false; } else { logger.LogTrace($"SCTP SACK remote peer TSN ACK {sack.CumulativeTsnAck}, next sender TSN {TSN}, arwnd {sack.ARwnd} (gap reports {sack.GapAckBlocks.Count})."); RemoveAckedUnconfirmedChunks(sack.CumulativeTsnAck); } } else { logger.LogTrace($"SCTP SACK remote peer TSN ACK no change {_cumulativeAckTSN}, next sender TSN {TSN}, arwnd {sack.ARwnd} (gap reports {sack.GapAckBlocks.Count})."); RemoveAckedUnconfirmedChunks(sack.CumulativeTsnAck); } } // Check gap reports. Only process them if the cumulative ACK TSN was acceptable. if (processGapReports && sack.GapAckBlocks.Count > 0) { ProcessGapReports(sack.GapAckBlocks, maxTSNDistance); } } _receiverWindow = CalculateReceiverWindow(sack.ARwnd); _congestionWindow = CalculateCongestionWindow(_lastAckedDataChunkSize); // SACK's will normally allow more data to be sent. _senderMre.Set(); } }