/// <summary> /// Constructs a new PayloadSpecificFeedbackReport from the given <see cref="RtcpHeader"/> and payload. /// Changes to the header are immediately reflected in this instance. /// Changes to the payload are not immediately reflected in this instance. /// </summary> /// <param name="header">The header</param> /// <param name="payload">The payload</param> public PayloadSpecificFeedbackReport(RtcpHeader header, System.Collections.Generic.IEnumerable <byte> payload, bool shouldDispose = true) : base(header, payload, shouldDispose) { if (Header.PayloadType != PayloadType) { throw new System.ArgumentException("Header.PayloadType is not equal to the expected type of 206.", "reference"); } }
/// <summary> /// Constructs a new PayloadSpecificFeedbackReport from the given <see cref="RtcpHeader"/> and payload. /// Changes to the header and payload are immediately reflected in this instance. /// </summary> /// <param name="header"></param> /// <param name="payload"></param> public PayloadSpecificFeedbackReport(RtcpHeader header, Common.MemorySegment payload, bool shouldDipose = true) : base(header, payload, shouldDipose) { if (Header.PayloadType != PayloadType) { throw new System.ArgumentException("Header.PayloadType is not equal to the expected type of 206.", "reference"); } }
/// <summary> /// Constructs a new TransportLayerFeedbackReport from the given <see cref="RtcpHeader"/> and payload. /// Changes to the header are immediately reflected in this instance. /// Changes to the payload are not immediately reflected in this instance. /// </summary> /// <param name="header">The header</param> /// <param name="payload">The payload</param> public TransportLayerFeedbackReport(RtcpHeader header, IEnumerable <byte> payload, bool shouldDispose = true) : base(header, payload, shouldDispose) { if (Header.PayloadType != PayloadType) { throw new ArgumentException("Header.PayloadType is not equal to the expected type of 205.", "reference"); } }
//Should be extensions because it makes intellisense weird for all derived types. /// <summary> /// Throws an <see cref="ArgumentException"/> if the given header is null or disposed or does not have the given payloadType. /// </summary> /// <param name="payloadType"></param> /// <param name="header"></param> internal static void CheckHeaderAndPayloadType(ref int payloadType, RtcpHeader header) { if (Common.IDisposedExtensions.IsNullOrDisposed(header)) { throw new ArgumentException("header IsNullOrDisposed"); } int headerPayloadType = header.PayloadType; if (headerPayloadType != payloadType) { throw new ArgumentException(string.Format("Header.PayloadType is not equal to the expected type of {0}, found {1}.", payloadType, headerPayloadType), "header"); } }
internal static void CheckHeaderAndPayloadType(int payloadType, RtcpHeader header) { CheckHeaderAndPayloadType(ref payloadType, header); }
public RtcpReport(RtcpHeader header, Common.MemorySegment payload, bool shouldDispose = true) : base(header, payload, shouldDispose) { }
public RtcpReport(RtcpHeader header, IEnumerable <byte> payload, bool shouldDispose = true) : base(header, payload, shouldDispose) { }
/// <summary> /// Parses all RtcpPackets contained in the array using the given paramters. /// </summary> /// <param name="array">The array to parse packets from.</param> /// <param name="offset">The index to start parsing</param> /// <param name="count">The amount of bytes to use in parsing</param> /// <param name="version">The optional <see cref="RtcpPacket.Version"/>version of the packets</param> /// <param name="payloadType">The optional <see cref="RtcpPacket.PayloadType"/> of all packets</param> /// <param name="ssrc">The optional <see cref="RtcpPacket.SynchronizationSourceIdentifier"/> of all packets</param> /// <returns>A pointer to each packet found</returns> public static IEnumerable<RtcpPacket> GetPackets(byte[] array, int offset, int count, int version = 2, int? payloadType = null, int? ssrc = null) { //array.GetLowerBound(0) for VB, UpperBound(0) is then the index of the last element int lowerBound = 0, upperBound = array.Length; if (offset < lowerBound || offset > upperBound) throw new ArgumentOutOfRangeException("index", "Must refer to an accessible position in the given array"); if (count <= lowerBound) yield break; if (count > upperBound) throw new ArgumentOutOfRangeException("count", "Must refer to an accessible position in the given array"); //Would overflow the array if (count + offset > upperBound) throw new ArgumentOutOfRangeException("index", "Count must refer to an accessible position in the given array when deleniated by index"); //While a 32 bit value remains to be read in the vector while (offset + RtcpHeader.Length < upperBound && count >= RtcpHeader.Length) { //Get the header of the packet to verify if it is wanted or not using (var header = new RtcpHeader(new Common.MemorySegment(array, offset, count))) { //Determine how long the header was int payloadOffset = header.Size; //Determine the amount of bytes in the packet NOT INCLUDING the RtcpHeader (Which may be 0) int lengthInBytes = Binary.MachineWordsToBytes(header.LengthInWordsMinusOne + 1); //((lengthInWords + 1) * 4) - payloadOffset; //Create a packet using the existing header and the bytes left in the packet using (RtcpPacket newPacket = new RtcpPacket(header, new MemorySegment(array, offset + payloadOffset, Binary.Min(lengthInBytes - payloadOffset, count - payloadOffset)))) { //Move the offset the length in bytes of the size of the last packet (including the header). offset += newPacket.Length; //Reduce the count count -= newPacket.Length; //Check for the optional parameters if (payloadType.HasValue && payloadType.Value != header.PayloadType || // Check for the given payloadType if provided ssrc.HasValue && ssrc.Value != header.SendersSynchronizationSourceIdentifier) //Check for the given ssrc if provided { //Skip the packet continue; } //Yield the packet, disposed afterwards yield return newPacket; } } } //Done parsing yield break; }
/// <summary> /// Creates a new RtcpPacket with the given paramters. /// Throws a <see cref="OverflowException"/> if padding is less than 0 or greater than byte.MaxValue. /// </summary> /// <param name="version">Sets <see cref="Version"/></param> /// <param name="payloadType">Sets <see cref="PayloadType"/></param> /// <param name="padding">The amount of padding octets if greater than 0</param> /// <param name="ssrc">Sets <see cref="SendersSyncrhonizationSourceIdentifier"/></param> /// <param name="blockCount">Sets <see cref="BlockCount"/> </param> /// <param name="lengthInWords">Sets <see cref="RtcpHeader.LengthInWordsMinusOne"/></param> public RtcpPacket(int version, int payloadType, int padding, int ssrc, int blockCount, int lengthInWords, int blockSize, int extensionSize) { //If the padding is greater than allow throw an overflow exception if (padding < 0 || padding > byte.MaxValue) Binary.CreateOverflowException("padding", padding, byte.MinValue.ToString(), byte.MaxValue.ToString()); Header = new RtcpHeader(version, payloadType, padding > 0, blockCount, ssrc, lengthInWords); m_OwnsHeader = true; extensionSize = Binary.MachineWordsToBytes(Binary.BytesToMachineWords(extensionSize)); //Calulcate the size of the Payload Segment int payloadSize = (blockSize * blockCount) + extensionSize; //Octet alignment should always be respected when creating the payload, this will avoid a few uncecessary resizes. int nullOctetsRequired = (extensionSize & 0x03); payloadSize += nullOctetsRequired + padding; //Allocate an array of byte equal to the size required m_OwnedOctets = new byte[payloadSize]; //Segment the array to allow property access. Payload = new MemorySegment(m_OwnedOctets, 0, payloadSize); if (padding > 0) m_OwnedOctets[payloadSize - 1] = (byte)padding; }
/// <summary> /// Creates a RtcpPacket instance from the given parameters by copying data. /// </summary> /// <param name="buffer">The buffer which contains the binary RtcpPacket to decode</param> /// <param name="offset">The offset to start decoding</param> public RtcpPacket(byte[] buffer, int offset, int count, bool shouldDispose = true) { //The instance owns the header ShouldDispose = m_OwnsHeader = shouldDispose; //Create the header Header = new RtcpHeader(buffer, offset); //Determine the amount of bytes in the header and packet int headerLength = RtcpHeader.Length, packetLength = Header.LengthInWordsMinusOne; switch (packetLength) { case ushort.MinValue: { Payload = Common.MemorySegment.Empty; if(false == Padding) return; break; } default: { //Header has another word headerLength = Binary.BytesPerLong; //Packet length is given by the LengthInWordsMinusOne + 1 * 4 packetLength = ((ushort)((packetLength + 1) * 4)); //If there is no data in the payload don't consume it if (packetLength < headerLength) goto case ushort.MinValue; break; } } //ssrc doesn't technically coun't //int nonHeaderBytes = packetLength - headerLength; //Project the octets in the sequence taking the minimum of the octets present and the octets required as indicated by the header. //m_OwnedOctets = buffer.Skip(offset + headerLength).Take(nonHeaderBytes < 0 ? buffer.Length + nonHeaderBytes : nonHeaderBytes).ToArray(); //The Payload property must be assigned otherwise the properties will not function in the instance. //Payload = new Common.MemorySegment(m_OwnedOctets, shouldDispose); Payload = new Common.MemorySegment(buffer, offset + headerLength, count - headerLength, shouldDispose); m_OwnedOctets = Payload.Array; }
/// <summary> /// Creates a RtcpPacket instance from an existing RtcpHeader and payload. /// Check the IsValid property to see if the RtcpPacket is well formed. /// </summary> /// <param name="header">The existing RtpHeader (which is now owned by this instance)</param> /// <param name="payload">The data contained in the payload</param> public RtcpPacket(RtcpHeader header, MemorySegment payload, bool shouldDispose = true) { if (header == null) throw new ArgumentNullException("header"); //The instance owns the header ShouldDispose = m_OwnsHeader = shouldDispose; Header = header; Payload = payload; }
/// <summary> /// Creates a RtcpPacket instance by projecting the given sequence to an array which is subsequently owned by the instance. /// The length of the sequence projected is determined by <paramref name="header"/>. /// Throws a ArgumentNullException if header is null /// </summary> /// <param name="header">The header to utilize. When Dispose is called this header will be diposed.</param> /// <param name="octets">The octets to project</param> public RtcpPacket(RtcpHeader header, IEnumerable<byte> octets, bool ownsHeader = true) : this(header.Concat(octets).ToArray(), 0, true) { }
/// <summary> /// Used to handle Tcp framing /// </summary> /// <param name="buffer"></param> /// <param name="offset"></param> /// <param name="count"></param> /// <param name="socket"></param> /// <returns></returns> protected internal virtual int ProcessFrameData(byte[] buffer, int offset, int count, Socket socket) { if (count == 0) return 0; //If there is no buffer use our own buffer. if (buffer == null) buffer = m_Buffer.Array; //Determine which TransportContext will receive the data incoming TransportContext relevent = null; //The channel of the data byte frameChannel = 0; //Get the length of the given buffer (Should actually use m_Buffer.Count when using our own buffer) int bufferLength = buffer.Length, //The indicates length of the data frameLength = 0, //The amount of data remaining in the buffer remainingInBuffer = Media.Common.Extensions.Math.MathExtensions.Clamp(count, count, bufferLength - offset), //The amount of data received (which is already equal to what is remaining in the buffer) recievedTotal = remainingInBuffer; //Determine if Rtp or Rtcp is coming in or some other type (could be combined with expectRtcp and expectRtp == false) bool expectRtp = false, expectRtcp = false, incompatible = true, raisedEvent = false; //If anything remains on the socket the value will be calulcated. int remainingOnSocket = 0; //TODO handle receiving when no $ and Channel is presenent... e.g. RFC4571 //Would only be 2 then... //if (GetContextBySocket(socket).MediaDescription.MediaProtocol.StartsWith("TCP", StringComparison.OrdinalIgnoreCase)) //{ // //independent = true; //} int sessionRequired = InterleavedOverhead; //While not disposed and there is data remaining (within the buffer) while (false == IsDisposed && remainingInBuffer > 0 && offset >= m_Buffer.Offset) { //Assume not rtp or rtcp and that the data is compatible with the session expectRtp = expectRtcp = incompatible = false; //If a header can be read if (remainingInBuffer >= sessionRequired) { //Determine if an event was raised each time there was at least the required amount of data. raisedEvent = false; //Parse the frameLength from the given buffer, take changes to the offset through the function. frameLength = ReadApplicationLayerFraming(remainingInBuffer, out frameChannel, out relevent, ref offset, out raisedEvent, buffer); //If a frame was found (Including the null packet) if (frameLength >= 0) { //If there WAS a context if (relevent != null) { //Verify minimum and maximum packet sizes allowed by context. (taking into account the amount of bytes in the ALF) if (frameLength < relevent.MinimumPacketSize + sessionRequired || frameLength > relevent.MaximumPacketSize + sessionRequired) { //mark as incompatible incompatible = true; //ToDo //Make CreateLogString function Media.Common.ILoggingExtensions.Log(Logger, ToString() + "@ProcessFrameData - Irregular Packet of " + frameLength + " for Channel " + frameChannel + " remainingInBuffer=" + remainingInBuffer); //jump goto CheckPacketAttributes; } //TODO Independent framing... (e.g. no $)[ only 4 bytes not 6 ] //If all that remains is the frame header then receive more data. 6 comes from (InterleavedOverhead + CommonHeaderBits.Size) //We need more data to be able to verify the frame. if (remainingInBuffer <= 6) { //Remove the context relevent = null; goto CheckRemainingData; ////Only receive this many more bytes for now. //remainingOnSocket = X - remainingInBuffer; ////Receive the rest of the data indicated by frameLength. (Should probably only receive up to X more bytes then make another receive if needed) //goto GetRemainingData; } //Use CommonHeaderBits on the data after the Interleaved Frame Header using (var common = new Media.RFC3550.CommonHeaderBits(buffer[offset + sessionRequired], buffer[offset + sessionRequired + 1])) { //Check the version... incompatible = common.Version != relevent.Version; //If this is a valid context there must be at least a RtpHeader's worth of data in the buffer. //If this was a RtcpPacket with only 4 bytes it wouldn't have a ssrc and wouldn't be valid to be sent. if (false == incompatible && (frameChannel == relevent.DataChannel && remainingInBuffer <= Rtp.RtpHeader.Length + sessionRequired) || (frameChannel == relevent.ControlChannel && remainingInBuffer <= Rtcp.RtcpHeader.Length + sessionRequired)) { //Remove the context relevent = null; //Mark as incompatible incompatible = true; goto EndUsingHeader; ////Only receive this many more bytes for now. //remainingOnSocket = 16 - remainingInBuffer; ////Receive the rest of the data indicated by frameLength. (Should probably only receive up to 6 more bytes then make another receive if needed) //goto GetRemainingData; } //Perform a set of checks and set weather or not Rtp or Rtcp was expected. if (false == incompatible) { //Determine if the packet is Rtcp by looking at the found channel and the relvent control channel if (frameChannel == relevent.ControlChannel) { //Rtcp if (remainingInBuffer <= sessionRequired + Rtcp.RtcpHeader.Length) { //Remove the context relevent = null; goto CheckRemainingData; } //Store any rtcp length so we can verify its not 0 and then additionally ensure its value is not larger then the frameLength int rtcpLen; //use a rtcp header to extract the information in the packet using (Rtcp.RtcpHeader header = new RtcpHeader(buffer, offset + sessionRequired)) { //Get the length in 'words' (by adding one) //A length of 0 means 1 word //A length of 65535 means only the header (no ssrc [or payload]) ushort lengthInWordsPlusOne = (ushort)(header.LengthInWordsMinusOne + 1); //Convert to bytes rtcpLen = lengthInWordsPlusOne * 4; //Check that the supposed amount of contained words is greater than or equal to the frame length conveyed by the application layer framing //it must also be larger than the buffer incompatible = rtcpLen > frameLength && rtcpLen > bufferLength; //if rtcpLen >= ushort.MaxValue the packet may possibly span multiple segments unless a large buffer is used. if (false == incompatible && //It was not already ruled incomaptible lengthInWordsPlusOne > 0 && //If there is supposed to be SSRC in the packet header.Size > Rtcp.RtcpHeader.Length && //The header ACTUALLY contains enough bytes to have a SSRC false == relevent.InDiscovery)//The remote context knowns the identity of the remote stream { //Determine if Rtcp is expected //Perform another lookup and check compatibility expectRtcp = !(incompatible = (GetContextBySourceId(header.SendersSynchronizationSourceIdentifier)) == null); } } } //May be mixing channels... if (false == expectRtcp) { //Rtp if (remainingInBuffer <= sessionRequired + Rtp.RtpHeader.Length) { //Remove the context relevent = null; goto CheckRemainingData; } //the context by payload type is null is not discovering the identity check the SSRC. if (GetContextByPayloadType(common.RtpPayloadType) == null && false == relevent.InDiscovery) { using (Rtp.RtpHeader header = new RtpHeader(buffer, offset + InterleavedOverhead)) { //The context was obtained by the frameChannel //Use the SSRC to determine where it should be handled. //If there is no context the packet is incompatible expectRtp = !(incompatible = (GetContextBySourceId(header.SynchronizationSourceIdentifier)) == null); //(Could also check SequenceNumber to prevent duplicate packets from being processed.) ////Verify extensions (handled by ValidatePacket) //if (header.Extension) //{ //} } } else incompatible = false; } } EndUsingHeader: ; } } //Log state. //if (relevent == null) Media.Common.ILoggingExtensions.Log(Logger, InternalId + "-ProcessFrameData - No Context for Channel " + frameChannel + " frameLength=" + frameLength + " remainingInBuffer=" + remainingInBuffer); //else Media.Common.ILoggingExtensions.Log(Logger, InternalId + "ProcessFrameData " + frameChannel + " frameLength=" + frameLength + " remainingInBuffer=" + remainingInBuffer); CheckPacketAttributes: if (incompatible) { //If there was a context then incrment for failed receptions if (relevent != null) { if (expectRtp) ++relevent.m_FailedRtpReceptions; if (expectRtcp) ++relevent.m_FailedRtcpReceptions; } Media.Common.ILoggingExtensions.Log(Logger, InternalId + "ProcessFrameData - Incompatible Packet frameLength=" + frameLength + " for Channel " + frameChannel + " remainingInBuffer=" + remainingInBuffer); } //If frameLength was 0 or the frame was larger than we can store then interleave the header for handling if required //incompatible may not be true here. else if (frameLength == 0 || frameLength > bufferLength) { //Could check incompatible to determine if to should move further. //Just because there is no assoicated context on the client does not mean the packet is not useful elsewhere in Transport. //TODO It may be possible to let the event reiever known how much is available here. if (frameLength == 0) { Media.Common.ILoggingExtensions.Log(Logger, InternalId + "ProcessFrameData - Null Packet for Channel " + frameChannel + " remainingInBuffer=" + remainingInBuffer); } else //If there was a context then increment for failed receptions only for large packets { if (expectRtp) ++relevent.m_FailedRtpReceptions; if (expectRtcp) ++relevent.m_FailedRtcpReceptions; if (expectRtp || expectRtcp) Media.Common.ILoggingExtensions.Log(Logger, InternalId + "ProcessFrameData - Large Packet of " + frameLength + " for Channel " + frameChannel + " remainingInBuffer=" + remainingInBuffer); } } else goto CheckRemainingData; //The packet was incompatible or larger than the buffer //Determine how much we can move int toMove = Math.Min(remainingInBuffer, sessionRequired); //TODO It may be possible to let the event reiever known how much is available here. //Indicate what was received if not already done if (false == raisedEvent) OnInterleavedData(buffer, offset, toMove); //Move the offset offset += toMove; //Decrease by the length remainingInBuffer -= toMove; //Do another pass continue; }//else there was a frameLength of -1 this indicates there is not enough bytes for a header. } else//There is not enough data in the buffer as defined by sessionRequired. { //unset the frameLength read frameLength = -1; //unset the context read relevent = null; } //At this point there may be either less sessionRequired or not enough for a complete frame. CheckRemainingData: //See how many more bytes are required from the wire //If the frameLength is less than 0 AND there are less then are sessionRequired remaining in the buffer remainingOnSocket = frameLength < 0 && remainingInBuffer < sessionRequired ? sessionRequired - remainingInBuffer //Receive enough to complete the header : //Otherwise if the frameLength larger then what remains in the buffer allow for the buffer to be filled or nothing else remains. frameLength > remainingInBuffer ? frameLength - remainingInBuffer : 0; //GetRemainingData: //If there is anymore data remaining on the wire if (remainingOnSocket > 0) { //Align the buffer if anything remains on the socket. Array.Copy(buffer, offset, buffer, m_Buffer.Offset, remainingInBuffer); //Set the correct offset either way. offset = m_Buffer.Offset + remainingInBuffer; //Store the error if any SocketError error = SocketError.SocketError; //Get all the remaining data while (false == IsDisposed && remainingOnSocket > 0) { //Recieve from the wire the amount of bytes required (up to the length of the buffer) int recievedFromWire = socket == null ? 0 : Media.Common.Extensions.Socket.SocketExtensions.AlignedReceive(buffer, offset, remainingOnSocket, socket, out error); //Check for an error and then the allowed continue condition if (error != SocketError.Success && error != SocketError.TryAgain) break; //If nothing was recieved try again. if (recievedFromWire <= 0) continue; //Decrease what is remaining from the wire by what was received remainingOnSocket -= recievedFromWire; //Move the offset offset += recievedFromWire; //Increment received recievedTotal += recievedFromWire; //Incrment remaining in buffer for what was recieved. remainingInBuffer += recievedFromWire; } //If a socket error occured remove the context so no parsing occurs if (error != SocketError.Success) { OnInterleavedData(buffer, offset - remainingInBuffer, remainingInBuffer); return recievedTotal; } //Move back to where the frame started offset -= remainingInBuffer; } //If there was data unrelated to a frame if (raisedEvent) { if (relevent == null) { offset += frameLength; remainingInBuffer -= frameLength; } continue; } else if (false == IsDisposed && frameLength > 0) { //Parse the data in the buffer using (var memory = new Common.MemorySegment(buffer, offset + InterleavedOverhead, frameLength - InterleavedOverhead)) ParseAndCompleteData(memory, expectRtcp, expectRtp, memory.Count); //Decrease remaining in buffer remainingInBuffer -= frameLength; //Move the offset offset += frameLength; //Ensure large frames are completely received by receiving the rest of the frame now. (this only occurs for packets being skipped) if (frameLength > bufferLength) { //Remove the context relevent = null; //Determine how much remains remainingOnSocket = frameLength - bufferLength; //If there is anything left if (remainingOnSocket > 0) { //Set the new length of the frame based on the length of the buffer frameLength -= bufferLength; //Set what is remaining remainingInBuffer = 0; //Use all the buffer offset = m_Buffer.Offset; //go to receive it goto CheckRemainingData; } } } } //Handle any data which remains if not already if (false == raisedEvent && remainingInBuffer > 0) { OnInterleavedData(buffer, offset, remainingInBuffer); } //Return the number of bytes recieved return recievedTotal; }