//Test AddReports And Enumerator And Reserialization public static void Test_RFC3550_Page40_Figure2() { //Create a random id int RandomId = RFC3550.Random32(Utility.Random.Next()); //Create a SendersReport instance using the specified options. using (Media.Rtcp.SendersReport p = new Rtcp.SendersReport(2, 0, 0, RandomId)) { //Let LSR = DateTime ExpectedDateTime = new DateTime(1995, 11, 10, 11, 33, 25, 125, DateTimeKind.Utc); //n = LSR // //sec = 0xb44d_b705 p.NtpMSW = -1269975291; //unchecked((int)3024992005); //frac = 0x2000_0000 p.NtpLSW = 536870912; //Ensure was written and read correctly if (p.NtpDateTime != ExpectedDateTime) { throw new Exception("NtpDateTime does not equal ExpectedDate: " + p.NtpDateTime); } //The middle 32 bits out of 64 in the NTP timestamp (as explained in Section 4) received as part of the most recent RTCP sender report (SR) packet from source SSRC_n. If no SR has been received yet, the field is set to zero. if ((ulong)((p.NtpTimestamp) >> 16) << 32 != 0xB705200000000000) { throw new Exception(); } //In Seconds, DSLR = 0x0005:4000 (5.250s) const double ExpectedDelay = 5.250; //Let A = DateTime A = new DateTime(1995, 11, 10, 11, 33, 36, 500, DateTimeKind.Utc); //.ToString("s.ffff") //The delay, expressed in units of 1/65536 seconds, between receiving the last SR packet from source SSRC_n and sending this reception report block. If no SR packet has been received yet from SSRC_n, the DLSR field is set to zero. TimeSpan delay = A.Subtract(TimeSpan.FromSeconds(ExpectedDelay)).Subtract(ExpectedDateTime); //46864.500 - 5.250 - 46853.125 = 6.125 if (delay.TotalSeconds != 6.125) { throw new Exception("Unexpected delay of: " + delay.ToString()); } } }
/// <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.SendersReport p = new Rtcp.SendersReport(0, PaddingCounter, ReportBlockCounter, RandomId)) { //Check SendersInformation System.Diagnostics.Debug.Assert(p.SendersInformation.Count() == Rtcp.SendersReport.SendersInformationSize && p.SendersInformation.All(s=> s== 0), "Unexpected SendersInformation"); //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 + Rtcp.SendersReport.SendersInformationSize + 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.SendersReport s = new Rtcp.SendersReport(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"); //Check SendersInformation System.Diagnostics.Debug.Assert(s.SendersInformation.Count() == Rtcp.SendersReport.SendersInformationSize && s.SendersInformation.All(o => o == 0), "Unexpected SendersInformation"); } } } } }
/// <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.SendersReport p = new Rtcp.SendersReport(0, PaddingCounter, ReportBlockCounter, RandomId)) { //Check SendersInformation System.Diagnostics.Debug.Assert(p.SendersInformation.Count() == Rtcp.SendersReport.SendersInformationSize, "Unexpected SendersInformation Count"); System.Diagnostics.Debug.Assert(p.SendersInformation.All(s => s == 0), "Unexpected SendersInformation Data"); //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 + Rtcp.SendersReport.SendersInformationSize + 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.SendersReport s = new Rtcp.SendersReport(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"); //Check SendersInformation System.Diagnostics.Debug.Assert(s.SendersInformation.Count() == Rtcp.SendersReport.SendersInformationSize && s.SendersInformation.All(o => o == 0), "Unexpected SendersInformation"); } } } } }
//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 }