/// <summary> /// Prepares the text in the stream which corresponds to the RtcpData of a Rtcp.RtcpPacket. /// </summary> /// <param name="format">The <see cref="FileFormat"/> to output.</param> /// <param name="packet">The <see cref="Rtcp.RtcpPacket"/> to describe</param> /// <returns>The text describes the packet if the <paramref name="format"/> is a text format, otherwise an empty string</returns> public static string ToTextualConvention(FileFormat format, Rtcp.RtcpPacket packet) { if (packet == null || packet.Payload.Count == 0 || format < FileFormat.Text || format == FileFormat.Short) return string.Empty; if(format == FileFormat.Unknown) return UnknownSpecifier; //Determine the format to use as well as the `Conventional Abbreviation` switch (packet.PayloadType) { case Rtcp.SourceDescriptionReport.PayloadType: //Use a SourceDescriptionReport to enumerate the SourceDescriptionChunk's and SourceDescriptionItem's contained in the packet. using (var sdes = new Rtcp.SourceDescriptionReport(packet, false)) //Don't dispose the packet when done. { return string.Format(RtcpExpressionFormat, "SDES", packet.SynchronizationSourceIdentifier, packet.Padding ? 1.ToString() : 0.ToString(), packet.BlockCount, packet.Header.LengthInWordsMinusOne, ToTextualConvention(sdes)); } case Rtcp.SendersReport.PayloadType: using (var sr = new Rtcp.SendersReport(packet, false)) { return string.Format(RtcpExpressionFormat, "SR", packet.SynchronizationSourceIdentifier, packet.Padding ? 1.ToString() : 0.ToString(), packet.BlockCount, packet.Header.LengthInWordsMinusOne, (ToTextualConvention(sr) + (char)Common.ASCII.Space + string.Format(RtcpSendersInformationFormat, //0 (DateTime.UtcNow - sr.NtpTime).TotalSeconds.ToString("0.000000"), //ts= //1 sr.NtpTimestamp, //ntp= //2 sr.SendersOctetCount, //osent= //3 sr.SendersPacketCount))); //psent= } case Rtcp.ReceiversReport.PayloadType: using (var rr = new Rtcp.ReceiversReport(packet, false)) { return string.Format(RtcpExpressionFormat, "RR", packet.SynchronizationSourceIdentifier, packet.Padding ? 1.ToString() : 0.ToString(), packet.BlockCount, packet.Header.LengthInWordsMinusOne, ToTextualConvention(rr)); } case Rtcp.GoodbyeReport.PayloadType: using (var bye = new Rtcp.GoodbyeReport(packet, false)) { return string.Format(RtcpExpressionFormat, "BYE", packet.SynchronizationSourceIdentifier, packet.Padding ? 1.ToString() : 0.ToString(), packet.BlockCount, packet.Header.LengthInWordsMinusOne, ToTextualConvention(bye)); } case Rtcp.ApplicationSpecificReport.PayloadType: return string.Format(RtcpExpressionFormat, "APP", packet.SynchronizationSourceIdentifier, packet.Padding ? 1.ToString() : 0.ToString(), packet.BlockCount, packet.Header.LengthInWordsMinusOne, string.Empty); default: //Unknown PayloadType use a Hex Representation of the PayloadType return string.Format(RtcpExpressionFormat, packet.PayloadType.ToString("X"), packet.SynchronizationSourceIdentifier, packet.Padding ? 1.ToString() : 0.ToString(), packet.BlockCount, packet.Header.LengthInWordsMinusOne, string.Empty); } }
/// <summary> /// O( ) /// </summary> public static void TestAConstructor_And_Reserialization() { //Permute every possible value in the 5 bit BlockCount for (byte ReportBlockCounter = byte.MinValue; ReportBlockCounter <= Media.Common.Binary.FiveBitMaxValue; ++ReportBlockCounter) { //Permute every possible value in the Padding field. for (byte PaddingCounter = byte.MinValue; PaddingCounter <= Media.Common.Binary.FiveBitMaxValue; ++PaddingCounter) { //Create a random id int RandomId = RFC3550.Random32(Utility.Random.Next()); //Create a SendersReport instance using the specified options. using (Media.Rtcp.ReceiversReport p = new Rtcp.ReceiversReport(0, PaddingCounter, ReportBlockCounter, RandomId)) { //Check IsComplete System.Diagnostics.Debug.Assert(p.IsComplete, "IsComplete must be true."); //Check Length System.Diagnostics.Debug.Assert(p.Length == Binary.BitsPerByte + Rtcp.ReportBlock.ReportBlockSize * ReportBlockCounter + PaddingCounter, "Unexpected Length"); //Check SynchronizationSourceIdentifier System.Diagnostics.Debug.Assert(p.SynchronizationSourceIdentifier == RandomId, "Unexpected SynchronizationSourceIdentifier"); //Check the BlockCount count System.Diagnostics.Debug.Assert(p.BlockCount == ReportBlockCounter, "Unexpected BlockCount"); //Check the PaddingOctets count System.Diagnostics.Debug.Assert(p.PaddingOctets == PaddingCounter, "Unexpected PaddingOctets"); //Check all data in the padding but not the padding octet itself. System.Diagnostics.Debug.Assert(p.PaddingData.Take(PaddingCounter - 1).All(b => b == 0), "Unexpected PaddingData"); //Verify all IReportBlock foreach (IReportBlock rb in p) { System.Diagnostics.Debug.Assert(rb.BlockIdentifier == 0, "Unexpected ChunkIdentifier"); System.Diagnostics.Debug.Assert(rb.BlockData.All(b => b == 0), "Unexpected BlockData"); System.Diagnostics.Debug.Assert(rb.Size == Media.Rtcp.ReportBlock.ReportBlockSize, "Unexpected Size"); } //Serialize and Deserialize and verify again using (Rtcp.ReceiversReport s = new Rtcp.ReceiversReport(new Rtcp.RtcpPacket(p.Prepare().ToArray(), 0), true)) { //Check SynchronizationSourceIdentifier System.Diagnostics.Debug.Assert(s.SynchronizationSourceIdentifier == p.SynchronizationSourceIdentifier, "Unexpected SynchronizationSourceIdentifier"); //Check the Payload.Count System.Diagnostics.Debug.Assert(s.Payload.Count == p.Payload.Count, "Unexpected Payload Count"); //Check the Length, System.Diagnostics.Debug.Assert(s.Length == p.Length, "Unexpected Length"); //Check the BlockCount count System.Diagnostics.Debug.Assert(s.BlockCount == p.BlockCount, "Unexpected BlockCount"); //Verify all IReportBlock foreach (IReportBlock rb in s) { System.Diagnostics.Debug.Assert(rb.BlockIdentifier == 0, "Unexpected ChunkIdentifier"); System.Diagnostics.Debug.Assert(rb.BlockData.All(b => b == 0), "Unexpected BlockData"); System.Diagnostics.Debug.Assert(rb.Size == Media.Rtcp.ReportBlock.ReportBlockSize, "Unexpected Size"); } //Check the RtcpData System.Diagnostics.Debug.Assert(p.RtcpData.SequenceEqual(s.RtcpData), "Unexpected RtcpData"); //Check the PaddingOctets System.Diagnostics.Debug.Assert(s.PaddingOctets == p.PaddingOctets, "Unexpected PaddingOctets"); //Check all data in the padding but not the padding octet itself. System.Diagnostics.Debug.Assert(s.PaddingData.SequenceEqual(p.PaddingData), "Unexpected PaddingData"); } } } } }
/// <summary> /// O( ) /// </summary> public static void TestAConstructor_And_Reserialization() { //Permute every possible value in the 5 bit BlockCount for (byte ReportBlockCounter = byte.MinValue; ReportBlockCounter <= Media.Common.Binary.FiveBitMaxValue; ++ReportBlockCounter) { //Permute every possible value in the Padding field. for (byte PaddingCounter = byte.MinValue; PaddingCounter <= Media.Common.Binary.FiveBitMaxValue; ++PaddingCounter) { //Create a random id int RandomId = RFC3550.Random32(Utility.Random.Next()); //Create a SendersReport instance using the specified options. using (Media.Rtcp.ReceiversReport p = new Rtcp.ReceiversReport(0, PaddingCounter, ReportBlockCounter, RandomId)) { //Check IsComplete System.Diagnostics.Debug.Assert(p.IsComplete, "IsComplete must be true."); //Check Length System.Diagnostics.Debug.Assert(p.Length == Binary.BitsPerByte + Rtcp.ReportBlock.ReportBlockSize * ReportBlockCounter + PaddingCounter, "Unexpected Length"); //Check SynchronizationSourceIdentifier System.Diagnostics.Debug.Assert(p.SynchronizationSourceIdentifier == RandomId, "Unexpected SynchronizationSourceIdentifier"); //Check the BlockCount count System.Diagnostics.Debug.Assert(p.BlockCount == ReportBlockCounter, "Unexpected BlockCount"); //Check the PaddingOctets count System.Diagnostics.Debug.Assert(p.PaddingOctets == PaddingCounter, "Unexpected PaddingOctets"); //Check all data in the padding but not the padding octet itself. System.Diagnostics.Debug.Assert(p.PaddingData.Take(PaddingCounter - 1).All(b => b == 0), "Unexpected PaddingData"); //Verify all IReportBlock foreach (Rtcp.IReportBlock rb in p) { System.Diagnostics.Debug.Assert(rb.BlockIdentifier == 0, "Unexpected ChunkIdentifier"); System.Diagnostics.Debug.Assert(rb.BlockData.All(b => b == 0), "Unexpected BlockData"); System.Diagnostics.Debug.Assert(rb.Size == Media.Rtcp.ReportBlock.ReportBlockSize, "Unexpected Size"); } //Serialize and Deserialize and verify again using (Rtcp.ReceiversReport s = new Rtcp.ReceiversReport(new Rtcp.RtcpPacket(p.Prepare().ToArray(), 0), true)) { //Check SynchronizationSourceIdentifier System.Diagnostics.Debug.Assert(s.SynchronizationSourceIdentifier == p.SynchronizationSourceIdentifier, "Unexpected SynchronizationSourceIdentifier"); //Check the Payload.Count System.Diagnostics.Debug.Assert(s.Payload.Count == p.Payload.Count, "Unexpected Payload Count"); //Check the Length, System.Diagnostics.Debug.Assert(s.Length == p.Length, "Unexpected Length"); //Check the BlockCount count System.Diagnostics.Debug.Assert(s.BlockCount == p.BlockCount, "Unexpected BlockCount"); //Verify all IReportBlock foreach (Rtcp.IReportBlock rb in s) { System.Diagnostics.Debug.Assert(rb.BlockIdentifier == 0, "Unexpected ChunkIdentifier"); System.Diagnostics.Debug.Assert(rb.BlockData.All(b => b == 0), "Unexpected BlockData"); System.Diagnostics.Debug.Assert(rb.Size == Media.Rtcp.ReportBlock.ReportBlockSize, "Unexpected Size"); } //Check the RtcpData System.Diagnostics.Debug.Assert(p.RtcpData.SequenceEqual(s.RtcpData), "Unexpected RtcpData"); //Check the PaddingOctets System.Diagnostics.Debug.Assert(s.PaddingOctets == p.PaddingOctets, "Unexpected PaddingOctets"); //Check all data in the padding but not the padding octet itself. System.Diagnostics.Debug.Assert(s.PaddingData.SequenceEqual(p.PaddingData), "Unexpected PaddingData"); } } } } }
//6.3.2 Initialization.... //I will do no such thing, I will no have no table when no table is required such as be the case when no expectance is put on the identity of the recipient. //All such packets should be considered equal unless specifically negioated by means provided by an alternate mechanism such as SDP or the RTP-Info header and is beyond the scope of the RtpClient implementation [based on my interpretation that is.] //I could go on and on about this but I think we all get the point //6.3.3 Rtp or Rtcp protected internal virtual void HandleIncomingRtcpPacket(object rtpClient, RtcpPacket packet) { //Determine if the packet can be handled if (IsDisposed || false == RtcpEnabled || false == HandleIncomingRtcpPackets || packet == null || packet.IsDisposed) return; //Raise an event for the rtcp packet received OnRtcpPacketReceieved(packet); //Get a context for the packet by the identity of the receiver TransportContext transportContext = null; //Cache the ssrc of the packet's sender. int partyId = packet.SynchronizationSourceIdentifier; //See if there is a context for the remote party. (Allows 0) transportContext = GetContextBySourceId(partyId); //Only if the packet was not addressed to a unique party with the id of 0 if (partyId != 0 && transportContext == null || transportContext.InDiscovery) //The remote party has not yet been identified. { //Cache the payloadType and blockCount int payloadType = packet.PayloadType, blockCount = packet.BlockCount; //Check the type if (payloadType == Rtcp.ReceiversReport.PayloadType && //The packet is a RecieversReport blockCount > 0)//There is at least 1 block { //Create a wrapper around the packet to access the ReportBlocks using (var rr = new Rtcp.ReceiversReport(packet, false)) { //Iterate each contained ReportBlock foreach (Rtcp.IReportBlock reportBlock in rr) { int blockId = reportBlock.BlockIdentifier; //Attempt to obtain a context by the identifier in the report block transportContext = GetContextBySourceId(blockId); //If there was a context and the remote party has not yet been identified. if (transportContext != null && transportContext.InDiscovery) { //Identify the remote party by this id. transportContext.RemoteSynchronizationSourceIdentifier = partyId; Media.Common.ILoggingExtensions.Log(Logger, ToString() + "@HandleIncomingRtcpPacket Set RemoteSynchronizationSourceIdentifier @ " + transportContext.SynchronizationSourceIdentifier + " to=" + transportContext.RemoteSynchronizationSourceIdentifier + "RR blockId=" + blockId); //Stop looking for a context. break; } } } } else if (payloadType == Rtcp.GoodbyeReport.PayloadType && blockCount > 0) //The GoodbyeReport report from a remote party { //Create a wrapper around the packet to access the source list using (var gb = new Rtcp.GoodbyeReport(packet, false)) { using (var sl = gb.GetSourceList()) { //Iterate each party leaving foreach (int party in sl) { //Attempt to obtain a context by the identifier in the report block transportContext = GetContextBySourceId(party); //If there was a context if (transportContext != null && false == transportContext.IsDisposed) { //Send report now if possible. bool reportsSent = SendReports(transportContext); Media.Common.ILoggingExtensions.Log(Logger, ToString() + "@HandleIncomingRtcpPacket Recieved Goodbye @ " + transportContext.SynchronizationSourceIdentifier + " from=" + partyId + " reportSent=" + reportsSent); //Stop looking for a context. break; } } } } } else if (payloadType == Rtcp.SendersReport.PayloadType) //The senders report from a remote party { //If there is a context if (transportContext != null) { //The context is valid and still discovering a remote identity if (transportContext.IsValid && transportContext.InDiscovery) { //Assign it transportContext.RemoteSynchronizationSourceIdentifier = partyId; Media.Common.ILoggingExtensions.Log(Logger, ToString() + "@HandleIncomingRtcpPacket Set RemoteSynchronizationSourceIdentifier @ " + transportContext.SynchronizationSourceIdentifier + " to=" + transportContext.RemoteSynchronizationSourceIdentifier + " SR=" + partyId); } //If the context has been identified by an identity other than the remote party of the packet else if (transportContext.RemoteSynchronizationSourceIdentifier != partyId) { //Attempt to obtain a context by the identity used previously transportContext = GetContextBySourceId(partyId); //If ther is no longer a context or the context cannot handle the packet if (transportContext == null || transportContext.IsDisposed) { goto NoContext; } //If the context needs a remote identity and is still not yet valid if (transportContext.InDiscovery && false == transportContext.IsValid) { //Assign it transportContext.RemoteSynchronizationSourceIdentifier = partyId; Media.Common.ILoggingExtensions.Log(Logger, ToString() + "@HandleIncomingRtcpPacket Set RemoteSynchronizationSourceIdentifier @ " + transportContext.SynchronizationSourceIdentifier + " to=" + transportContext.RemoteSynchronizationSourceIdentifier + " SR=" + partyId); } } }//Validate by using the blocks of the report if possible else if (blockCount > 0) { //Create a wrapper around the packet to access the ReportBlocks using (var sr = new Rtcp.SendersReport(packet, false)) { //Iterate each contained ReportBlock foreach (Rtcp.IReportBlock reportBlock in sr) { int blockId = reportBlock.BlockIdentifier; //Attempt to obtain a context by the identifier in the report block var context = GetContextBySourceId(reportBlock.BlockIdentifier); //If there was a context if (context != null) { //if the context found identifies the context assumed if (context.SynchronizationSourceIdentifier == transportContext.SynchronizationSourceIdentifier) { //Identify the remote party by this id. transportContext.RemoteSynchronizationSourceIdentifier = packet.SynchronizationSourceIdentifier; Media.Common.ILoggingExtensions.Log(Logger, ToString() + "@HandleIncomingRtcpPacket Set RemoteSynchronizationSourceIdentifier @ " + transportContext.SynchronizationSourceIdentifier + " to=" + transportContext.RemoteSynchronizationSourceIdentifier + "SR blockId=" + blockId); //Remove any reference context = null; //Stop looking for a context. break; } } } //might not have checked anything if the list was 'incomplete'.. } } } } //Handle Goodbyes with a positive blockcount but no sourcelist...? NoContext: //If no transportContext could be found if (transportContext == null) { //Attempt to see if this was a rtp packet by using the RtpPayloadType int rtpPayloadType = packet.Header.First16Bits.RtpPayloadType; if (rtpPayloadType == 13 || GetContextByPayloadType(rtpPayloadType) != null) { Media.Common.ILoggingExtensions.Log(Logger, InternalId + "HandleIncomingRtcpPacket - Incoming RtcpPacket actually was Rtp. Ssrc= " + partyId + " Type=" + rtpPayloadType + " Len=" + packet.Length); //Raise an event for the 'RtpPacket' received. //Todo Use the existing reference / memory of the RtcpPacket) OnRtpPacketReceieved(new RtpPacket(packet.Prepare().ToArray(), 0)); //Don't do anything else return; } //Could attempt to find the context in which this packet is trying to communicate with if we had a RemoteEndPoint indicating where the packet was received from... //Cannot find a context because there may be more then one context which has not yet been identified //Could attempt to check that there is only 1 context and then if not yet valid assign the identity... //if(TransportContexts.Count == 1) ... Media.Common.ILoggingExtensions.Log(Logger, InternalId + "HandleIncomingRtcpPacket - No Context for packet " + partyId + "@" + packet.PayloadType); //Don't do anything else. return; } //There is a transportContext //If there is a collision in the unique identifiers if (transportContext.SynchronizationSourceIdentifier == partyId) { //Handle it. HandleCollision(transportContext); } //Make a copy of the packet now and only refer to this copy RtcpPacket localPacket = packet; #region Unused [Packet Completion] //Complete the RtcpPacket if required. //while (!localPacket.IsComplete) //{ // //Complete the packet. // int received = localPacket.CompleteFrom(transportContext.RtcpSocket, localPacket.Payload); //} #endregion //Last Rtcp packet was received right now now. transportContext.m_LastRtcpIn = packet.Created; //The context is active. transportContext.m_InactiveTime = Media.Common.Extensions.TimeSpan.TimeSpanExtensions.InfiniteTimeSpan; //Don't worry about overflow unchecked { //Increment packets received for the valid context. ++transportContext.RtcpPacketsReceived; //Keep track of the the bytes sent in the context transportContext.RtcpBytesRecieved += localPacket.Length; //Set the time when the first rtcp packet was recieved if (transportContext.m_FirstPacketReceived == DateTime.MinValue) transportContext.m_FirstPacketReceived = packet.Created; } #region Unused [Handle if packet was Goodbye] //bool goodBye = packet.PayloadType == Rtcp.GoodbyeReport.PayloadType; ////If the context is valid, AND the remote identify has a value and the packet identity is not the same then reset the state and account for the new identity //if (transportContext.IsValid && transportContext.RemoteSynchronizationSourceIdentifier.HasValue && localPacket.SynchronizationSourceIdentifier != transportContext.RemoteSynchronizationSourceIdentifier) //{ // //Tell the source we are no longer listening to the old identity // //SendGoodbye(transportContext); // //Reset state for the counters // //transportContext.ResetState(); // //Assign the new remote ID (EVENT?) // transportContext.RemoteSynchronizationSourceIdentifier = localPacket.SynchronizationSourceIdentifier; // //Send reports if we can unless this is a Goodbye // /*if (!goodBye) */SendReports(transportContext); //} //if (goodBye && packet.BlockCount > 0) transportContext.m_SendInterval = Media.Common.Extensions.TimeSpan.TimeSpanExtensions.InfiniteTimeSpan; //Then never send reports again? #endregion }