/// <summary> /// Processes a data frame that is now ready and that is part of an SCTP stream. /// Stream frames must be delivered in order. /// </summary> /// <param name="frame">The data frame that became ready from the latest DATA chunk receive.</param> /// <returns>A sorted list of frames for the matching stream ID. Will be empty /// if the supplied frame is out of order for its stream.</returns> private List <SctpDataFrame> ProcessStreamFrame(SctpDataFrame frame) { // Relying on unsigned short wrapping. unchecked { // This is a stream chunk. Need to ensure in order delivery. var sortedFrames = new List <SctpDataFrame>(); if (!_streamLatestSeqNums.ContainsKey(frame.StreamID)) { // First frame for this stream. _streamLatestSeqNums.Add(frame.StreamID, frame.StreamSeqNum); sortedFrames.Add(frame); } else if ((ushort)(_streamLatestSeqNums[frame.StreamID] + 1) == frame.StreamSeqNum) { // Expected seqnum for stream. _streamLatestSeqNums[frame.StreamID] = frame.StreamSeqNum; sortedFrames.Add(frame); // There could also be out of order frames that can now be delivered. if (_streamOutOfOrderFrames.ContainsKey(frame.StreamID) && _streamOutOfOrderFrames[frame.StreamID].Count > 0) { var outOfOrder = _streamOutOfOrderFrames[frame.StreamID]; ushort nextSeqnum = (ushort)(_streamLatestSeqNums[frame.StreamID] + 1); while (outOfOrder.ContainsKey(nextSeqnum) && outOfOrder.TryGetValue(nextSeqnum, out var nextFrame)) { sortedFrames.Add(nextFrame); _streamLatestSeqNums[frame.StreamID] = nextSeqnum; outOfOrder.Remove(nextSeqnum); nextSeqnum++; } } } else { // Stream seqnum is out of order. if (!_streamOutOfOrderFrames.ContainsKey(frame.StreamID)) { _streamOutOfOrderFrames[frame.StreamID] = new Dictionary <ushort, SctpDataFrame>(); } if (_streamOutOfOrderFrames[frame.StreamID].Count > MAXIMUM_OUTOFORDER_FRAMES) { logger.LogWarning($"SCTP data receiver exceeded the maximum queue size for out of order frames for stream ID {frame.StreamID}."); // TODO https://tools.ietf.org/html/rfc4960#section-6.2 says to drop the chunk with the highest TSN that's ahead of the // ack'ed TSN. } else { _streamOutOfOrderFrames[frame.StreamID].Add(frame.StreamSeqNum, frame); } } return(sortedFrames); } }
/// <summary> /// Extracts a fragmented chunk from the receive dictionary and passes it to the ULP. /// </summary> /// <param name="fragments">The dictionary containing the chunk fragments.</param> /// <param name="beginTSN">The beginning TSN for the fragment.</param> /// <param name="endTSN">The end TSN for the fragment.</param> private SctpDataFrame GetFragmentedChunk(Dictionary <uint, SctpDataChunk> fragments, uint beginTSN, uint endTSN) { unchecked { byte[] full = new byte[MAX_FRAME_SIZE]; int posn = 0; var beginChunk = fragments[beginTSN]; var frame = new SctpDataFrame(beginChunk.Unordered, beginChunk.StreamID, beginChunk.StreamSeqNum, beginChunk.PPID, full); uint afterEndTSN = endTSN + 1; uint tsn = beginTSN; while (tsn != afterEndTSN) { var fragment = fragments[tsn].UserData; Buffer.BlockCopy(fragment, 0, full, posn, fragment.Length); posn += fragment.Length; fragments.Remove(tsn); tsn++; } frame.UserData = frame.UserData.Take(posn).ToArray(); return(frame); } }
/// <summary> /// Event handler for a DATA chunk being received. The chunk can be either a DCEP message or data channel data /// payload. /// </summary> /// <param name="dataFrame">The received data frame which could represent one or more chunks depending /// on fragmentation..</param> private void OnDataFrameReceived(SctpDataFrame dataFrame) { switch (dataFrame) { case var frame when frame.PPID == (uint)DataChannelPayloadProtocols.WebRTC_DCEP: switch (frame.UserData[0]) { case (byte)DataChannelMessageTypes.ACK: OnDataChannelOpened?.Invoke(frame.StreamID); break; case (byte)DataChannelMessageTypes.OPEN: var dcepOpen = DataChannelOpenMessage.Parse(frame.UserData, 0); logger.LogDebug($"DCEP OPEN channel type {dcepOpen.ChannelType}, priority {dcepOpen.Priority}, " + $"reliability {dcepOpen.Reliability}, label {dcepOpen.Label}, protocol {dcepOpen.Protocol}."); DataChannelTypes channelType = DataChannelTypes.DATA_CHANNEL_RELIABLE; if (Enum.IsDefined(typeof(DataChannelTypes), dcepOpen.ChannelType)) { channelType = (DataChannelTypes)dcepOpen.ChannelType; } else { logger.LogWarning($"DECP OPEN channel type of {dcepOpen.ChannelType} not recognised, defaulting to {channelType}."); } OnNewDataChannel?.Invoke( frame.StreamID, channelType, dcepOpen.Priority, dcepOpen.Reliability, dcepOpen.Label, dcepOpen.Protocol); break; default: logger.LogWarning($"DCEP message type {frame.UserData[0]} not recognised, ignoring."); break; } break; default: OnDataChannelData?.Invoke(dataFrame); break; } }
/// <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); } }