/// <summary> /// Parses the SACK chunk fields. /// </summary> /// <param name="buffer">The buffer holding the serialised chunk.</param> /// <param name="posn">The position to start parsing at.</param> public static SctpSackChunk ParseChunk(byte[] buffer, int posn) { var sackChunk = new SctpSackChunk(); ushort chunkLen = sackChunk.ParseFirstWord(buffer, posn); ushort startPosn = (ushort)(posn + SCTP_CHUNK_HEADER_LENGTH); sackChunk.CumulativeTsnAck = NetConvert.ParseUInt32(buffer, startPosn); sackChunk.ARwnd = NetConvert.ParseUInt32(buffer, startPosn + 4); ushort numGapAckBlocks = NetConvert.ParseUInt16(buffer, startPosn + 8); ushort numDuplicateTSNs = NetConvert.ParseUInt16(buffer, startPosn + 10); int reportPosn = startPosn + FIXED_PARAMETERS_LENGTH; for (int i = 0; i < numGapAckBlocks; i++) { ushort start = NetConvert.ParseUInt16(buffer, reportPosn); ushort end = NetConvert.ParseUInt16(buffer, reportPosn + 2); sackChunk.GapAckBlocks.Add(new SctpTsnGapBlock { Start = start, End = end }); reportPosn += GAP_REPORT_LENGTH; } for (int j = 0; j < numDuplicateTSNs; j++) { sackChunk.DuplicateTSN.Add(NetConvert.ParseUInt32(buffer, reportPosn)); reportPosn += DUPLICATE_TSN_LENGTH; } return(sackChunk); }
/// <summary> /// Parses an SCTP chunk from a buffer. /// </summary> /// <param name="buffer">The buffer holding the serialised chunk.</param> /// <param name="posn">The position to start parsing at.</param> /// <returns>An SCTP chunk instance.</returns> public static SctpChunk Parse(byte[] buffer, int posn) { if (buffer.Length < posn + SCTP_CHUNK_HEADER_LENGTH) { throw new ApplicationException("Buffer did not contain the minimum of bytes for an SCTP chunk."); } byte chunkType = buffer[posn]; if (Enum.IsDefined(typeof(SctpChunkType), chunkType)) { switch ((SctpChunkType)chunkType) { case SctpChunkType.ABORT: return(SctpAbortChunk.ParseChunk(buffer, posn, true)); case SctpChunkType.DATA: return(SctpDataChunk.ParseChunk(buffer, posn)); case SctpChunkType.ERROR: return(SctpErrorChunk.ParseChunk(buffer, posn, false)); case SctpChunkType.SACK: return(SctpSackChunk.ParseChunk(buffer, posn)); case SctpChunkType.COOKIE_ACK: case SctpChunkType.COOKIE_ECHO: case SctpChunkType.HEARTBEAT: case SctpChunkType.HEARTBEAT_ACK: case SctpChunkType.SHUTDOWN_ACK: case SctpChunkType.SHUTDOWN_COMPLETE: return(ParseBaseChunk(buffer, posn)); case SctpChunkType.INIT: case SctpChunkType.INIT_ACK: return(SctpInitChunk.ParseChunk(buffer, posn)); case SctpChunkType.SHUTDOWN: return(SctpShutdownChunk.ParseChunk(buffer, posn)); default: logger.LogDebug($"TODO: Implement parsing logic for well known chunk type {(SctpChunkType)chunkType}."); return(ParseBaseChunk(buffer, posn)); } } // Shouldn't reach this point. The SCTP packet parsing logic checks if the chunk is // recognised before attempting to parse it. throw new ApplicationException($"SCTP chunk type of {chunkType} was not recognised."); }
/// <summary> /// Gets a SACK chunk that represents the current state of the receiver. /// </summary> /// <returns>A SACK chunk that can be sent to the remote peer to update the ACK TSN and /// request a retransmit of any missing DATA chunks.</returns> public SctpSackChunk GetSackChunk() { // Can't create a SACK until the initial DATA chunk has been received. if (_inOrderReceiveCount > 0) { SctpSackChunk sack = new SctpSackChunk(_lastInOrderTSN, _receiveWindow); sack.GapAckBlocks = GetForwardTSNGaps(); sack.DuplicateTSN = _duplicateTSN.Keys.ToList(); return(sack); } else { return(null); } }
/// <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(); } }