/// <summary> /// Parses the DATA 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 SctpDataChunk ParseChunk(byte[] buffer, int posn) { var dataChunk = new SctpDataChunk(); ushort chunkLen = dataChunk.ParseFirstWord(buffer, posn); if (chunkLen < FIXED_PARAMETERS_LENGTH) { throw new ApplicationException($"SCTP data chunk cannot be parsed as buffer too short for fixed parameter fields."); } dataChunk.Unordered = (dataChunk.ChunkFlags & 0x04) > 0; dataChunk.Begining = (dataChunk.ChunkFlags & 0x02) > 0; dataChunk.Ending = (dataChunk.ChunkFlags & 0x01) > 0; int startPosn = posn + SCTP_CHUNK_HEADER_LENGTH; dataChunk.TSN = NetConvert.ParseUInt32(buffer, startPosn); dataChunk.StreamID = NetConvert.ParseUInt16(buffer, startPosn + 4); dataChunk.StreamSeqNum = NetConvert.ParseUInt16(buffer, startPosn + 6); dataChunk.PPID = NetConvert.ParseUInt32(buffer, startPosn + 8); int userDataPosn = startPosn + FIXED_PARAMETERS_LENGTH; int userDataLen = chunkLen - SCTP_CHUNK_HEADER_LENGTH - FIXED_PARAMETERS_LENGTH; if (userDataLen > 0) { dataChunk.UserData = new byte[userDataLen]; Buffer.BlockCopy(buffer, userDataPosn, dataChunk.UserData, 0, dataChunk.UserData.Length); } return(dataChunk); }
/// <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> /// Sends a DATA chunk to the remote peer. /// </summary> /// <param name="streamID">The stream ID to sent the data on.</param> /// <param name="ppid">The payload protocol ID for the data.</param> /// <param name="message">The byte data to send.</param> public void SendData(ushort streamID, uint ppid, byte[] data) { lock (_sendQueue) { ushort seqnum = 0; if (_streamSeqnums.ContainsKey(streamID)) { unchecked { _streamSeqnums[streamID] = (ushort)(_streamSeqnums[streamID] + 1); seqnum = _streamSeqnums[streamID]; } } else { _streamSeqnums.Add(streamID, 0); } for (int index = 0; index *_defaultMTU < data.Length; index++) { int offset = (index == 0) ? 0 : (index * _defaultMTU); int payloadLength = (offset + _defaultMTU < data.Length) ? _defaultMTU : data.Length - offset; // Future TODO: Replace with slice when System.Memory is introduced as a dependency. byte[] payload = new byte[payloadLength]; Buffer.BlockCopy(data, offset, payload, 0, payloadLength); bool isBegining = index == 0; bool isEnd = ((offset + payloadLength) >= data.Length) ? true : false; SctpDataChunk dataChunk = new SctpDataChunk( false, isBegining, isEnd, TSN, streamID, seqnum, ppid, payload); _sendQueue.Enqueue(dataChunk); TSN = (TSN == UInt32.MaxValue) ? 0 : TSN + 1; } _senderMre.Set(); } }
/// <summary> /// Handler for processing new data chunks. /// </summary> /// <param name="dataChunk">The newly received data chunk.</param> /// <returns>If the received chunk resulted in a full chunk becoming available one /// or more new frames will be returned otherwise an empty frame is returned. Multiple /// frames may be returned if this chunk is part of a stream and was received out /// or order. For unordered chunks the list will always have a single entry.</returns> public List <SctpDataFrame> OnDataChunk(SctpDataChunk dataChunk) { var sortedFrames = new List <SctpDataFrame>(); var frame = SctpDataFrame.Empty; if (_inOrderReceiveCount == 0 && GetDistance(_initialTSN, dataChunk.TSN) > _windowSize) { logger.LogWarning($"SCTP data receiver received a data chunk with a {dataChunk.TSN} " + $"TSN when the initial TSN was {_initialTSN} and a " + $"window size of {_windowSize}, ignoring."); } else if (_inOrderReceiveCount > 0 && GetDistance(_lastInOrderTSN, dataChunk.TSN) > _windowSize) { logger.LogWarning($"SCTP data receiver received a data chunk with a {dataChunk.TSN} " + $"TSN when the expected TSN was {_lastInOrderTSN + 1} and a " + $"window size of {_windowSize}, ignoring."); } else if (_inOrderReceiveCount > 0 && !IsNewer(_lastInOrderTSN, dataChunk.TSN)) { logger.LogWarning($"SCTP data receiver received an old data chunk with {dataChunk.TSN} " + $"TSN when the expected TSN was {_lastInOrderTSN + 1}, ignoring."); } else if (!_forwardTSN.ContainsKey(dataChunk.TSN)) { logger.LogTrace($"SCTP receiver got data chunk with TSN {dataChunk.TSN}, " + $"last in order TSN {_lastInOrderTSN}, in order receive count {_inOrderReceiveCount}."); // Relying on unsigned integer wrapping. unchecked { if ((_inOrderReceiveCount > 0 && _lastInOrderTSN + 1 == dataChunk.TSN) || (_inOrderReceiveCount == 0 && dataChunk.TSN == _initialTSN)) { _inOrderReceiveCount++; _lastInOrderTSN = dataChunk.TSN; // See if the in order TSN can be bumped using any out of order chunks // already received. if (_inOrderReceiveCount > 0 && _forwardTSN.Count > 0) { while (_forwardTSN.ContainsKey(_lastInOrderTSN + 1)) { _lastInOrderTSN++; _inOrderReceiveCount++; _forwardTSN.Remove(_lastInOrderTSN); } } } else { _forwardTSN.Add(dataChunk.TSN, 1); } } // Now go about processing the data chunk. if (dataChunk.Begining && dataChunk.Ending) { // Single packet chunk. frame = new SctpDataFrame( dataChunk.Unordered, dataChunk.StreamID, dataChunk.StreamSeqNum, dataChunk.PPID, dataChunk.UserData); } else { // This is a data chunk fragment. _fragmentedChunks.Add(dataChunk.TSN, dataChunk); (var begin, var end) = GetChunkBeginAndEnd(_fragmentedChunks, dataChunk.TSN); if (begin != null && end != null) { frame = GetFragmentedChunk(_fragmentedChunks, begin.Value, end.Value); } } } else { logger.LogTrace($"SCTP duplicate TSN received for {dataChunk.TSN}."); if (!_duplicateTSN.ContainsKey(dataChunk.TSN)) { _duplicateTSN.Add(dataChunk.TSN, 1); } else { _duplicateTSN[dataChunk.TSN] = _duplicateTSN[dataChunk.TSN] + 1; } } if (!frame.IsEmpty() && !dataChunk.Unordered) { return(ProcessStreamFrame(frame)); } else { if (!frame.IsEmpty()) { sortedFrames.Add(frame); } return(sortedFrames); } }