public void TestRtcpPacket() { Console.WriteLine(System.Reflection.MethodBase.GetCurrentMethod().Name); //Write all Abstrractions to the console foreach (var abstraction in Media.Rtcp.RtcpPacket.GetImplementedAbstractions()) Console.WriteLine(string.Format(TestingFormat, "\tFound Abstraction", "Implemented By" + abstraction.Name)); //Write all Implementations to the console foreach (var implementation in Media.Rtcp.RtcpPacket.GetImplementations()) Console.WriteLine(string.Format(TestingFormat, "\tPayloadType " + implementation.Key, "Implemented By" + implementation.Value.Name)); //Create a RtpPacket instance Media.Rtcp.RtcpPacket p = new Media.Rtcp.RtcpPacket(new Media.Rtcp.RtcpHeader(0, 0, false, 0), Enumerable.Empty<byte>()); //Check the Padding bit after modification System.Diagnostics.Debug.Assert(p.SynchronizationSourceIdentifier == 0, "SynchronizationSourceIdentifier should equal 0"); //Set a values p.SynchronizationSourceIdentifier = 7; System.Diagnostics.Debug.Assert(p.SynchronizationSourceIdentifier == 7, "SynchronizationSourceIdentifier should equal 7"); //Cache a bitValue bool bitValue = false; //Test every possible bit packed value that can be valid in the first and second octet for (int ibitValue = 0; ibitValue < 2; ++ibitValue) { //Make a bitValue after the 0th iteration if (ibitValue > 0) bitValue = Convert.ToBoolean(ibitValue); //Complete tested the first and second octets with the current bitValue //Console.WriteLine(string.Format(TestingFormat, "\tbitValue", bitValue + "\r\n")); //Permute every possible value within the 2 bit Version for (int VersionCounter = 0; VersionCounter <= Media.Common.Binary.TwoBitMaxValue; ++VersionCounter) { //Set the version p.Version = VersionCounter; //Write the version information to the console. //Console.Write(string.Format(TestingFormat, "\tVersionCounter", VersionCounter)); //Console.Write(string.Format(TestingFormat, " Version", p.Version + "\r\n")); //Set the bit values in the first octet p.Padding = bitValue; //Check the version bits after modification System.Diagnostics.Debug.Assert(p.Version == VersionCounter, versionException.Message); //Check the Padding bit after modification System.Diagnostics.Debug.Assert(p.Padding == bitValue, paddingException.Message); //Permute every possible value in the 7 bit PayloadCounter for (int PayloadCounter = 0; PayloadCounter <= byte.MaxValue; ++PayloadCounter) { //Set the 7 bit value in the second octet. p.PayloadType = (byte)PayloadCounter; //Write the value of the PayloadCounter to the console and the packet value to the Console. //Console.Write(string.Format(TestingFormat, "\tPayloadCounter", PayloadCounter)); //Console.Write(string.Format(TestingFormat, " PayloadType", p.PayloadType + "\r\n")); //Check the PayloadType System.Diagnostics.Debug.Assert(p.PayloadType == PayloadCounter, payloadException.Message); //Check the Padding bit after setting the PayloadType System.Diagnostics.Debug.Assert(p.Padding == bitValue, paddingException.Message); //Permute every combination for a nybble for (int ReportBlockCounter = byte.MinValue; ReportBlockCounter <= Media.Common.Binary.FiveBitMaxValue; ++ReportBlockCounter) { ///////////////Set the CC nibble in the first Octet p.BlockCount = (byte)ReportBlockCounter; ///////////// //Identify the Contributing Source Counter and the Packet's value //Console.Write(string.Format(TestingFormat, "\tReportBlockCounter", ReportBlockCounter)); //Console.Write(string.Format(TestingFormat, " BlockCount", p.BlockCount + "\r\n")); //Check the BlockCount System.Diagnostics.Debug.Assert(p.BlockCount == ReportBlockCounter, reportBlockException.Message); //Ensure the Version after modification System.Diagnostics.Debug.Assert(p.Version == VersionCounter, versionException.Message); //Check the Padding after modification System.Diagnostics.Debug.Assert(p.Padding == bitValue, paddingException.Message); ///////////////Serialize the packet using (p = new Media.Rtcp.RtcpPacket(p.Prepare().ToArray(), 0)) { ///////////// //Ensure the version remains after modification System.Diagnostics.Debug.Assert(p.Version == VersionCounter, versionException.Message); //Ensure the Padding bit after modification System.Diagnostics.Debug.Assert(p.Padding == bitValue, paddingException.Message); //Check the BlockCount after modification System.Diagnostics.Debug.Assert(p.BlockCount == ReportBlockCounter, reportBlockException.Message); //Check for a valid header if (false == p.Header.IsValid(VersionCounter, PayloadCounter, bitValue) || //Check for validation per RFC3550 A.1 when the test permits false == bitValue && VersionCounter > 1 && PayloadCounter >= 200 && PayloadCounter <= 201 && false == Media.RFC3550.IsValidRtcpHeader(p.Header, VersionCounter)) throw inValidHeaderException; } //Perform checks with length in words set incorrectly } } } //Console.WriteLine(string.Format(TestingFormat, "\t*****Completed an iteration wih bitValue", bitValue + "*****")); } }
public void TestRtcpPacketExamples() { Console.WriteLine(System.Reflection.MethodBase.GetCurrentMethod().Name); byte[] output; //Keep a copy of these exceptions to throw in case some error occurs. Exception invalidLength = new Exception("Invalid Length"), invalidData = new Exception("Invalid Data in packet"), invalidPadding = new Exception("Invalid Padding"), incompleteFalse = new Exception("Packet IsComplete is false"); //Create a Media.RtcpPacket with only a header (results in 8 octets of 0x00 which make up the header) Media.Rtcp.RtcpPacket rtcpPacket = new Media.Rtcp.RtcpPacket(0, 0, 0, 0, 0, 0); //Prepare a sequence which contains the data in the packet including the header IEnumerable<byte> preparedPacket = rtcpPacket.Prepare(); //Check for an invlaid length if (rtcpPacket.Payload.Count > 0 || rtcpPacket.Header.LengthInWordsMinusOne != 0 && rtcpPacket.Length != Media.Rtcp.RtcpHeader.Length || preparedPacket.Count() != Media.Rtcp.RtcpHeader.Length) throw invalidLength; //Check for any data in the packet binary if (preparedPacket.Any(o => o != default(byte))) throw invalidData; //Set padding in the header rtcpPacket.Padding = true; //Check for some invalid valid if (rtcpPacket.PaddingOctets > 0) throw invalidPadding; //Ensure the packet is complete if (rtcpPacket.IsComplete == false) throw incompleteFalse; //Add nothing to the payload rtcpPacket.AddBytesToPayload(Media.RFC3550.CreatePadding(0), 0, 0); //Ensure the packet is complete if (rtcpPacket.IsComplete == false) throw incompleteFalse; //Check for some invalid value if (rtcpPacket.PaddingOctets > 0) throw invalidPadding; //Make a bunch of packets with padding for (int paddingAmount = 1, e = byte.MaxValue; paddingAmount <= e; ++paddingAmount) { //Write information for the test to the console Console.WriteLine(string.Format(TestingFormat, "Making Media.RtcpPacket with Padding", paddingAmount)); //Try to make a padded packet with the given amount rtcpPacket = new Media.Rtcp.RtcpPacket(0, 0, paddingAmount, 0, 0, 0); //A a 4 bytes which are not padding related rtcpPacket.AddBytesToPayload(Enumerable.Repeat(default(byte), 4)); //Check ReadPadding works after adding bytes to the payload if (rtcpPacket.PaddingOctets != paddingAmount) throw invalidPadding; //Ensure the packet is complete if (rtcpPacket.IsComplete == false) throw incompleteFalse; //Write information for the test to the console Console.WriteLine(string.Format(TestingFormat, "Packet Length", rtcpPacket.Length)); //Write information for the test to the console Console.WriteLine(string.Format(TestingFormat, "Packet Padding", rtcpPacket.PaddingOctets)); } //Create a new SendersReport with no blocks using (Media.Rtcp.RtcpReport testReport = new Media.Rtcp.SendersReport(2, 0, 7)) { //The Media.RtcpData property contains all data which in the Media.RtcpPacket without padding if (testReport.RtcpData.Count() != 20 && testReport.Length != 20) throw invalidLength; output = testReport.Prepare().ToArray();//should be exactly equal to example } //Example of a Senders Report byte[] example = new byte[] { 0x81,0xc8,0x00,0x0c,0xa3,0x36,0x84,0x36,0xd4,0xa6,0xaf,0x65,0x00,0x00,0x00,0x0, 0xcb,0xf9,0x44,0xd0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xa3,0x36,0x84,0x36, 0x00,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00 }; rtcpPacket = new Media.Rtcp.RtcpPacket(example, 0); if (rtcpPacket.Length != example.Length) throw new Exception("Invalid Length."); //Make a SendersReport to access the SendersInformation and ReportBlocks, do not dispose the packet when done with the report using (Media.Rtcp.SendersReport sr = new Media.Rtcp.SendersReport(rtcpPacket, false)) { //Check the invalid block count if (sr.BlockCount != 1) throw new Exception("Invalid Block Count!"); else Console.WriteLine(sr.BlockCount);//16, should be 1 if ((uint)sr.SynchronizationSourceIdentifier != (uint)2738258998) throw new Exception("Invalid Senders SSRC!"); else Console.WriteLine(sr.SynchronizationSourceIdentifier);//0xa3368436 //Ensure setting the value through a setter is correct sr.NtpTimestamp = sr.NtpTimestamp;//14697854519044210688 if ((ulong)sr.NtpTimestamp != 3567693669) throw new Exception("Invalid NtpTimestamp!"); else Console.WriteLine(sr.NtpTimestamp); //Timestamp if ((uint)sr.RtpTimestamp != 3422110928) throw new Exception("Invalid RtpTimestamp!"); else Console.WriteLine(sr.RtpTimestamp);//0 //Data in report (Should only be 1) foreach (Media.Rtcp.IReportBlock rb in sr) { //if ((uint)rb.BlockIdentifier != 2738258998) throw new Exception("Invalid BlockIdentifier"); //else if (rb is Media.Rtcp.ReportBlock) //{ Media.Rtcp.ReportBlock asReportBlock = (Media.Rtcp.ReportBlock)rb; if (rb.BlockIdentifier != asReportBlock.SendersSynchronizationSourceIdentifier) throw new Exception("Invalid SendersSynchronizationSourceIdentifier"); Console.WriteLine(asReportBlock.SendersSynchronizationSourceIdentifier);//0 Console.WriteLine(asReportBlock.FractionsLost);//0 Console.WriteLine(asReportBlock.CumulativePacketsLost);//0 Console.WriteLine(asReportBlock.ExtendedHighestSequenceNumberReceived);//0 Console.WriteLine(asReportBlock.InterarrivalJitterEstimate);//0 Console.WriteLine(asReportBlock.LastSendersReportTimestamp);//0 //} } //Check the length to be exactly the same as the example if (sr.Length != example.Length) throw new Exception("Invalid Length"); //Verify SendersReport byte for byte output = sr.Prepare().ToArray();//should be exactly equal to example for (int i = 0, e = example.Length; i < e; ++i) if (example[i] != output[i]) throw new Exception("Result Packet Does Not Match Example"); } if (rtcpPacket.Header.IsDisposed || rtcpPacket.IsDisposed) throw new Exception("Disposed the Media.RtcpPacket"); //Now the packet can be disposed rtcpPacket.Dispose(); rtcpPacket = null; rtcpPacket = new Media.Rtcp.SendersReport(2, 0, 7); example = rtcpPacket.Prepare().ToArray(); if (rtcpPacket.SynchronizationSourceIdentifier != 7) throw new Exception("Unexpected SynchronizationSourceIdentifier"); if (rtcpPacket.BlockCount != 0) throw new Exception("Unexpected BlockCount"); //Check the Length, 8 Byte Header, 20 Byte SendersInformation if (rtcpPacket.Length != Media.Rtcp.RtcpHeader.Length + Media.Rtcp.ReportBlock.ReportBlockSize) throw new Exception("Unexpected BlockCount"); //Iterate each IReportBlock in the RtcpReport representation of the rtcpPacket instance foreach (Media.Rtcp.IReportBlock rb in rtcpPacket as Media.Rtcp.RtcpReport) { Console.WriteLine(rb); throw new Exception("Unexpected BlockCount"); } //Next Sub Test //Create a GoodbyeReport with no SourceList, e.g. a BlockCount of 0. //There should be 8 bytes, 4 for the RtcpHeader and 4 for the SynchronizationSourceIdentifier //The LengthInWordsMinusOne should equal 1 (1 + 1 = 2, 2 * 4 = 8) using (var testReport = new Media.Rtcp.GoodbyeReport(2, 7)) { output = testReport.Prepare().ToArray(); if (output.Length != testReport.Length || testReport.Header.LengthInWordsMinusOne != 1 || testReport.Length != 8) throw new Exception("Invalid Length"); if (testReport.BlockCount != 0) throw reportBlockException; if (output[7] != 7 || testReport.SynchronizationSourceIdentifier != 7) throw new Exception("Invalid ssrc"); } //Add a Reason For Leaving //Should now have 4 words... Header, SSRC, Block, Reason using (var testReport = new Media.Rtcp.GoodbyeReport(2, 7, System.Text.Encoding.ASCII.GetBytes("v"))) { output = testReport.Prepare().ToArray(); //3 if (output.Length != testReport.Length || testReport.Header.LengthInWordsMinusOne != 2 || testReport.Length != 12) throw new Exception("Invalid Length"); if (testReport.BlockCount != 0) throw reportBlockException; if (output[7] != 7 || testReport.SynchronizationSourceIdentifier != 7) throw new Exception("Invalid ssrc"); if (false == testReport.HasReasonForLeaving) throw new Exception("Has no reason for leaving."); if (System.Text.Encoding.ASCII.GetString(testReport.ReasonForLeaving.ToArray()) != "v") throw new Exception("Does not have expected reason for leaving."); } //Next Sub Test ///// //Recievers Report and Source Description example = new byte[] { 0x81,0xc9,0x00,0x07, 0x69,0xf2,0x79,0x50, 0x61,0x37,0x94,0x50, 0xff,0xff,0xff,0xff, 0x00,0x01,0x00,0x52, 0x00,0x00,0x0e,0xbb, 0xce,0xd4,0xc8,0xf5, 0x00,0x00,0x84,0x28, 0x81,0xca,0x00,0x04, 0x69,0xf2,0x79,0x50, 0x01,0x06,0x4a,0x61, 0x79,0x2d,0x50,0x43, 0x00,0x00,0x00,0x00 }; int foundPackets = 0, foundSize = 0; foreach (Media.Rtcp.RtcpPacket packet in Media.Rtcp.RtcpPacket.GetPackets(example, 0, example.Length)) { ++foundPackets; foundSize += packet.Length; } if(foundPackets != 2) throw new Exception("Unexpected amount of packets found"); if (foundSize != example.Length) throw new Exception("Unexpected total length of packets found"); //Or manually for some reason rtcpPacket = new Media.Rtcp.RtcpPacket(example, 0); // The same as foundPackets[0] using (Media.Rtcp.ReceiversReport rr = new Media.Rtcp.ReceiversReport(rtcpPacket, false)) { Console.WriteLine(rr.SynchronizationSourceIdentifier);//1777498448 //Check the invalid block count if (rr.BlockCount != 1) throw new Exception("Invalid Block Count!"); else Console.WriteLine(rr.BlockCount);//16, should be 1 using (var enumerator = rr.GetEnumerator()) { while (enumerator.MoveNext()) { Console.WriteLine("Current IReportBlock Identifier: " + enumerator.Current.BlockIdentifier);//1631032400 //If the instance boxed in the Interface is a ReportBlock if (enumerator.Current is Media.Rtcp.ReportBlock) { //Unbox the Interface as it's ReportBlock Instance Media.Rtcp.ReportBlock asReportBlock = enumerator.Current as Media.Rtcp.ReportBlock; Console.WriteLine("Found a ReportBlock"); //Print the instance information Console.WriteLine("FractionsLost: " + asReportBlock.FractionsLost);//255/256 0xff Console.WriteLine("CumulativePacketsLost: " + asReportBlock.CumulativePacketsLost);//-1, 0xff,0xff,0xff Console.WriteLine("ExtendedHighestSequenceNumberReceived: " + asReportBlock.ExtendedHighestSequenceNumberReceived);//65618, 00, 01, 00, 52 Console.WriteLine("InterarrivalJitterEstimate: " + asReportBlock.InterarrivalJitterEstimate);//3771 Console.WriteLine("LastSendersReportTimestamp: " + asReportBlock.LastSendersReportTimestamp);//3470000128 } else //Not a ReportBlock { Console.WriteLine("Current IReportBlock TypeName: " + enumerator.Current.GetType().Name); Console.WriteLine("Current IReportBlock Data: " + BitConverter.ToString(enumerator.Current.BlockData.ToArray())); } } } //Verify RecieversReport byte for byte output = rr.Prepare().ToArray();//should be exactly equal to example's bytes when extension data is contained in the packet instance //Use to get the raw data in the packet including the header //.Take(rr.Length - rr.ExtensionDataOctets) //Or rr.ReportData which omits the header... //What other variations are relvent? Please submit examples, even invalid ones will be tolerated! //The bytes given here should reflect exactly the bytes in the example array because of how the data is formatted. //Yes the data is compound but there is an invalid LengthInWords in the packet which must be exactly copied when deserialized if (rr.HasExtensionData && false == output.SequenceEqual(example)) throw new Exception("Result Packet Does Not Match Example"); else output = rr.Prepare(true, true, false, true).ToArray(); for (int i = 0, e = output.Length; i < e; ++i) if (example[i] != output[i]) throw new Exception("Result Packet Does Not Match Example @" + i); } if (rtcpPacket.Header.IsDisposed || rtcpPacket.IsDisposed) throw new Exception("Disposed the Media.RtcpPacket"); //Now the packet can be disposed rtcpPacket.Dispose(); rtcpPacket = null; //Make another packet instance from the rest of the example data. rtcpPacket = new Media.Rtcp.RtcpPacket(example, output.Length); //Create a SourceDescriptionReport from the packet instance to access the SourceDescriptionChunks using (Media.Rtcp.SourceDescriptionReport sourceDescription = new Media.Rtcp.SourceDescriptionReport(rtcpPacket, false)) { if (false == sourceDescription.HasCName) throw new Exception("Unexpected HasCName"); if (sourceDescription.BlockCount != 1) throw new Exception("Unexpected BlockCount"); if (sourceDescription.Chunks.First().ChunkIdentifer != 1777498448) throw new Exception("Chunks.ChunkIdentifer"); if (false == sourceDescription.Chunks.First().HasItems) throw new Exception("Chunks.HasItems"); if (sourceDescription.Chunks.First().Items.First().ItemType != Media.Rtcp.SourceDescriptionReport.SourceDescriptionItem.SourceDescriptionItemType.CName) throw new Exception("Unexpected ItemType"); if (sourceDescription.Chunks.First().Items.First().ItemLength != 6) throw new Exception("Unexpected ItemLength"); foreach (Media.Rtcp.SourceDescriptionReport.SourceDescriptionChunk chunk in sourceDescription.GetChunkIterator()) { if (chunk.ChunkIdentifer != 1777498448) throw new Exception("Chunks.ChunkIdentifer"); Console.WriteLine(string.Format(TestingFormat, "Chunk Identifier", chunk.ChunkIdentifer)); //Use a SourceDescriptionItemList to access the items within the Chunk //This is performed auto magically when using the foreach pattern foreach (Media.Rtcp.SourceDescriptionReport.SourceDescriptionItem item in chunk /*.AsEnumerable<Rtcp.SourceDescriptionItem>()*/) { //if (item.ItemType != Media.Rtcp.SourceDescriptionReport.SourceDescriptionItem.SourceDescriptionItemType.CName) throw new Exception("Unexpected ItemType"); //if (item.ItemLength != 6) throw new Exception("Unexpected ItemLength"); Console.WriteLine(string.Format(TestingFormat, "Item Type", item.ItemType)); Console.WriteLine(string.Format(TestingFormat, "Item Length", item.ItemLength)); Console.WriteLine(string.Format(TestingFormat, "Item Data", BitConverter.ToString(item.ItemData.ToArray()))); } } //Verify SourceDescriptionReport byte for byte output = sourceDescription.Prepare().ToArray();//should be exactly equal to example for (int i = output.Length, e = sourceDescription.Length; i < e; ++i) if (example[i] != output[i]) throw new Exception("Result Packet Does Not Match Example"); } //ApplicationSpecific - qtsi example = new byte[] { 0x81, 0xcc, 0x00, 0x06, 0x4e, 0xc8, 0x79, 0x50, 0x71, 0x74, 0x73, 0x69, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x61, 0x74, 0x00, 0x04, 0x00, 0x00, 0x00, 0x14 }; rtcpPacket = new Media.Rtcp.RtcpPacket(example, 0); //Make a ApplicationSpecificReport instance Media.Rtcp.ApplicationSpecificReport app = new Media.Rtcp.ApplicationSpecificReport(rtcpPacket); //Check the name to be equal to qtsi if (false == app.Name.SequenceEqual(System.Text.Encoding.UTF8.GetBytes("qtsi"))) throw new Exception("Invalid App Packet Type"); //Check the length if (rtcpPacket.Length != example.Length) throw new Exception("Invalid Legnth"); //Verify ApplicationSpecificReport byte for byte output = rtcpPacket.Prepare().ToArray();//should be exactly equal to example for (int i = 0, e = example.Length; i < e; ++i) if (example[i] != output[i]) throw new Exception("Result Packet Does Not Match Example"); //Test making a packet with a known length in bytes Media.Rtcp.SourceDescriptionReport sd = new Media.Rtcp.SourceDescriptionReport(2); byte[] sdOut = sd.Prepare().ToArray(); //1 word when the ssrc is present but would be an invalid sdes because blockCount = 0 if (false == sd.IsComplete || sd.Length != Media.Rtcp.RtcpHeader.Length || sd.Header.LengthInWordsMinusOne != ushort.MaxValue) throw new Exception("Invalid Length"); //Create 9 bytes of data to add to the existing SourceDescriptionReport byte[] itemData = System.Text.Encoding.UTF8.GetBytes("FLABIA-PC"); int KnownId = 0x1AB7C080; //Point the rtcpPacket at the SourceDescription instance rtcpPacket = sd; //Create a Media.Rtcp.SourceDescriptionReport.SourceDescriptionChunk containing a Known Identifier //Which Contains a Media.Rtcp.SourceDescriptionReport.SourceDescriptionItem with the Type 'CName' containing the itemData //Add the Media.Rtcp.IReportBlock to the RtcpReport sd.Add((Media.Rtcp.IReportBlock)new Media.Rtcp.SourceDescriptionReport.SourceDescriptionChunk(KnownId, new Media.Rtcp.SourceDescriptionReport.SourceDescriptionItem(Media.Rtcp.SourceDescriptionReport.SourceDescriptionItem.SourceDescriptionItemType.CName, itemData.Length, itemData, 0))); // ItemType(End) = 1, ItemLength(9) = 1, ItemData(9) = 11 Bytes in the Item, ChunkIdentifier(0x1AB7C080) = 4, 15 total bytes //Add an unpadded item for a 19 byte packet. //sd.Add(new Media.Rtcp.SourceDescriptionReport.SourceDescriptionChunk(KnownId, // new Media.Rtcp.SourceDescriptionReport.SourceDescriptionItem(Media.Rtcp.SourceDescriptionReport.SourceDescriptionItem.SourceDescriptionItemType.CName, // itemData.Length, itemData, 0)), false); // ItemType(End) = 1, ItemLength(9) = 1, ItemData(9) = 11 Bytes in the Item, ChunkIdentifier(0x1AB7C080) = 4, 15 total bytes //Ensure the data is present where it is supposed to be, more data may be present to respect octet alignment if (false == sd.RtcpData.Skip(Media.Rtcp.SourceDescriptionReport.SourceDescriptionItem.ItemHeaderSize).Take(itemData.Length).SequenceEqual(itemData)) throw new Exception("Invalid ItemData"); if (false == sd.Chunks.First().HasItems) throw new Exception("Unexpected HasItems"); if (sd.Chunks.First().ChunkIdentifer != KnownId) throw new Exception("Unexpected Chunks.ChunkIdentifer"); if (sd.Chunks.First().Items.Count() != 2) throw new Exception("Unexpected Chunks.Items.Count"); if (sd.Chunks.First().Items.First().ItemType != Media.Rtcp.SourceDescriptionReport.SourceDescriptionItem.SourceDescriptionItemType.CName) throw new Exception("Unexpected Items.ItemType"); if (sd.Chunks.First().Items.First().ItemLength != 9) throw new Exception("Unexpected Chunks.Items.ItemLength"); if (false == sd.Chunks.First().Items.First().ItemData.SequenceEqual(itemData)) throw new Exception("Unexpected Chunks.Items.ItemData"); if (sd.Chunks.First().Items.Last().ItemType != Media.Rtcp.SourceDescriptionReport.SourceDescriptionItem.SourceDescriptionItemType.End) throw new Exception("Unexpected Items.ItemType"); if (sd.Chunks.First().Items.Last().ItemLength != 1) throw new Exception("Unexpected Chunks.Items.ItemLength"); if (false == sd.Chunks.First().Items.Last().ItemData.All(b => b == 0)) throw new Exception("Unexpected Chunks.Items.ItemData"); // // Header = 4 Bytes, 1 Word // There is a SSRC which occupies 1 Word //in a SourceDescription, The First Chunk is `Overlapped` in the header and the BlockIdentifier is shared with the SSRC //Ensure the data is present where it is supposed to be if (sd.SynchronizationSourceIdentifier != KnownId) throw new Exception("Invalid SynchronizationSourceIdentifier"); //asPacket now contains 11 octets in the payload. //asPacket now has 1 block (1 chunk of 15 bytes) //asPacket is 19 octets long, 11 octets in the payload and 8 octets in the header //asPacket would have a LengthInWordsMinusOne of 3 because 19 / 4 = 4 - 1 = 3 //But null octets are added (Per RFC3550 @ Page 45 [Paragraph 2] / http://tools.ietf.org/html/rfc3550#appendix-A.4) //19 + 1 = 20, 20 / 4 = 5, 5 - 1 = 4. if (false == rtcpPacket.IsComplete || rtcpPacket.Length != 20 || rtcpPacket.Header.LengthInWordsMinusOne != 4) throw new Exception("Invalid Length"); }
public void TestRtcpPacket() { Console.WriteLine(System.Reflection.MethodBase.GetCurrentMethod().Name); //Write all Abstrractions to the console foreach (var abstraction in Media.Rtcp.RtcpPacket.GetImplementedAbstractions()) { Console.WriteLine(string.Format(TestingFormat, "\tFound Abstraction", "Implemented By" + abstraction.Name)); } //Write all Implementations to the console foreach (var implementation in Media.Rtcp.RtcpPacket.GetImplementations()) { Console.WriteLine(string.Format(TestingFormat, "\tPayloadType " + implementation.Key, "Implemented By" + implementation.Value.Name)); } //Create a RtpPacket instance Media.Rtcp.RtcpPacket p = new Media.Rtcp.RtcpPacket(new Media.Rtcp.RtcpHeader(0, 0, false, 0), Enumerable.Empty <byte>()); //Check the Padding bit after modification System.Diagnostics.Debug.Assert(p.SynchronizationSourceIdentifier == 0, "SynchronizationSourceIdentifier should equal 0"); //Set a values p.SynchronizationSourceIdentifier = 7; System.Diagnostics.Debug.Assert(p.SynchronizationSourceIdentifier == 7, "SynchronizationSourceIdentifier should equal 7"); //Cache a bitValue bool bitValue = false; //Test every possible bit packed value that can be valid in the first and second octet for (int ibitValue = 0; ibitValue < 2; ++ibitValue) { //Make a bitValue after the 0th iteration if (ibitValue > 0) { bitValue = Convert.ToBoolean(ibitValue); } //Complete tested the first and second octets with the current bitValue //Console.WriteLine(string.Format(TestingFormat, "\tbitValue", bitValue + "\r\n")); //Permute every possible value within the 2 bit Version for (int VersionCounter = 0; VersionCounter <= Media.Common.Binary.TwoBitMaxValue; ++VersionCounter) { //Set the version p.Version = VersionCounter; //Write the version information to the console. //Console.Write(string.Format(TestingFormat, "\tVersionCounter", VersionCounter)); //Console.Write(string.Format(TestingFormat, " Version", p.Version + "\r\n")); //Set the bit values in the first octet p.Padding = bitValue; //Check the version bits after modification System.Diagnostics.Debug.Assert(p.Version == VersionCounter, versionException.Message); //Check the Padding bit after modification System.Diagnostics.Debug.Assert(p.Padding == bitValue, paddingException.Message); //Permute every possible value in the 7 bit PayloadCounter for (int PayloadCounter = 0; PayloadCounter <= byte.MaxValue; ++PayloadCounter) { //Set the 7 bit value in the second octet. p.PayloadType = (byte)PayloadCounter; //Write the value of the PayloadCounter to the console and the packet value to the Console. //Console.Write(string.Format(TestingFormat, "\tPayloadCounter", PayloadCounter)); //Console.Write(string.Format(TestingFormat, " PayloadType", p.PayloadType + "\r\n")); //Check the PayloadType System.Diagnostics.Debug.Assert(p.PayloadType == PayloadCounter, payloadException.Message); //Check the Padding bit after setting the PayloadType System.Diagnostics.Debug.Assert(p.Padding == bitValue, paddingException.Message); //Permute every combination for a nybble for (int ReportBlockCounter = byte.MinValue; ReportBlockCounter <= Media.Common.Binary.FiveBitMaxValue; ++ReportBlockCounter) { ///////////////Set the CC nibble in the first Octet p.BlockCount = (byte)ReportBlockCounter; ///////////// //Identify the Contributing Source Counter and the Packet's value //Console.Write(string.Format(TestingFormat, "\tReportBlockCounter", ReportBlockCounter)); //Console.Write(string.Format(TestingFormat, " BlockCount", p.BlockCount + "\r\n")); //Check the BlockCount System.Diagnostics.Debug.Assert(p.BlockCount == ReportBlockCounter, reportBlockException.Message); //Ensure the Version after modification System.Diagnostics.Debug.Assert(p.Version == VersionCounter, versionException.Message); //Check the Padding after modification System.Diagnostics.Debug.Assert(p.Padding == bitValue, paddingException.Message); ///////////////Serialize the packet using (p = new Media.Rtcp.RtcpPacket(p.Prepare().ToArray(), 0)) { ///////////// //Ensure the version remains after modification System.Diagnostics.Debug.Assert(p.Version == VersionCounter, versionException.Message); //Ensure the Padding bit after modification System.Diagnostics.Debug.Assert(p.Padding == bitValue, paddingException.Message); //Check the BlockCount after modification System.Diagnostics.Debug.Assert(p.BlockCount == ReportBlockCounter, reportBlockException.Message); //Check for a valid header if (false == p.Header.IsValid(VersionCounter, PayloadCounter, bitValue) || //Check for validation per RFC3550 A.1 when the test permits false == bitValue && VersionCounter > 1 && PayloadCounter >= 200 && PayloadCounter <= 201 && false == Media.RFC3550.IsValidRtcpHeader(p.Header, VersionCounter)) { throw inValidHeaderException; } } //Perform checks with length in words set incorrectly } } } //Console.WriteLine(string.Format(TestingFormat, "\t*****Completed an iteration wih bitValue", bitValue + "*****")); } }
public void TestRtcpPacketExamples() { Console.WriteLine(System.Reflection.MethodBase.GetCurrentMethod().Name); byte[] output; //Keep a copy of these exceptions to throw in case some error occurs. Exception invalidLength = new Exception("Invalid Length"), invalidData = new Exception("Invalid Data in packet"), invalidPadding = new Exception("Invalid Padding"), incompleteFalse = new Exception("Packet IsComplete is false"); //Create a Media.RtcpPacket with only a header (results in 8 octets of 0x00 which make up the header) Media.Rtcp.RtcpPacket rtcpPacket = new Media.Rtcp.RtcpPacket(0, 0, 0, 0, 0, 0); //Prepare a sequence which contains the data in the packet including the header IEnumerable <byte> preparedPacket = rtcpPacket.Prepare(); //Check for an invlaid length if (rtcpPacket.Payload.Count > 0 || rtcpPacket.Header.LengthInWordsMinusOne != 0 && rtcpPacket.Length != Media.Rtcp.RtcpHeader.Length || preparedPacket.Count() != Media.Rtcp.RtcpHeader.Length) { throw invalidLength; } //Check for any data in the packet binary if (preparedPacket.Any(o => o != default(byte))) { throw invalidData; } //Set padding in the header rtcpPacket.Padding = true; //Check for some invalid valid if (rtcpPacket.PaddingOctets > 0) { throw invalidPadding; } //Ensure the packet is complete if (rtcpPacket.IsComplete == false) { throw incompleteFalse; } //Add nothing to the payload rtcpPacket.AddBytesToPayload(Media.RFC3550.CreatePadding(0), 0, 0); //Ensure the packet is complete if (rtcpPacket.IsComplete == false) { throw incompleteFalse; } //Check for some invalid value if (rtcpPacket.PaddingOctets > 0) { throw invalidPadding; } //Make a bunch of packets with padding for (int paddingAmount = 1, e = byte.MaxValue; paddingAmount <= e; ++paddingAmount) { //Write information for the test to the console Console.WriteLine(string.Format(TestingFormat, "Making Media.RtcpPacket with Padding", paddingAmount)); //Try to make a padded packet with the given amount rtcpPacket = new Media.Rtcp.RtcpPacket(0, 0, paddingAmount, 0, 0, 0); //A a 4 bytes which are not padding related rtcpPacket.AddBytesToPayload(Enumerable.Repeat(default(byte), 4)); //Check ReadPadding works after adding bytes to the payload if (rtcpPacket.PaddingOctets != paddingAmount) { throw invalidPadding; } //Ensure the packet is complete if (rtcpPacket.IsComplete == false) { throw incompleteFalse; } //Write information for the test to the console Console.WriteLine(string.Format(TestingFormat, "Packet Length", rtcpPacket.Length)); //Write information for the test to the console Console.WriteLine(string.Format(TestingFormat, "Packet Padding", rtcpPacket.PaddingOctets)); } //Create a new SendersReport with no blocks using (Media.Rtcp.RtcpReport testReport = new Media.Rtcp.SendersReport(2, 0, 7)) { //The Media.RtcpData property contains all data which in the Media.RtcpPacket without padding if (testReport.RtcpData.Count() != 20 && testReport.Length != 20) { throw invalidLength; } output = testReport.Prepare().ToArray();//should be exactly equal to example } //Example of a Senders Report byte[] example = new byte[] { 0x81, 0xc8, 0x00, 0x0c, 0xa3, 0x36, 0x84, 0x36, 0xd4, 0xa6, 0xaf, 0x65, 0x00, 0x00, 0x00, 0x0, 0xcb, 0xf9, 0x44, 0xd0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa3, 0x36, 0x84, 0x36, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; rtcpPacket = new Media.Rtcp.RtcpPacket(example, 0); if (rtcpPacket.Length != example.Length) { throw new Exception("Invalid Length."); } //Make a SendersReport to access the SendersInformation and ReportBlocks, do not dispose the packet when done with the report using (Media.Rtcp.SendersReport sr = new Media.Rtcp.SendersReport(rtcpPacket, false)) { //Check the invalid block count if (sr.BlockCount != 1) { throw new Exception("Invalid Block Count!"); } else { Console.WriteLine(sr.BlockCount); //16, should be 1 } if ((uint)sr.SynchronizationSourceIdentifier != (uint)2738258998) { throw new Exception("Invalid Senders SSRC!"); } else { Console.WriteLine(sr.SynchronizationSourceIdentifier); //0xa3368436 } //Ensure setting the value through a setter is correct sr.NtpTimestamp = sr.NtpTimestamp;//14697854519044210688 if ((ulong)sr.NtpTimestamp != 3567693669) { throw new Exception("Invalid NtpTimestamp!"); } else { Console.WriteLine(sr.NtpTimestamp); } //Timestamp if ((uint)sr.RtpTimestamp != 3422110928) { throw new Exception("Invalid RtpTimestamp!"); } else { Console.WriteLine(sr.RtpTimestamp); //0 } //Data in report (Should only be 1) foreach (Media.Rtcp.IReportBlock rb in sr) { //if ((uint)rb.BlockIdentifier != 2738258998) throw new Exception("Invalid BlockIdentifier"); //else if (rb is Media.Rtcp.ReportBlock) //{ Media.Rtcp.ReportBlock asReportBlock = (Media.Rtcp.ReportBlock)rb; if (rb.BlockIdentifier != asReportBlock.SendersSynchronizationSourceIdentifier) { throw new Exception("Invalid SendersSynchronizationSourceIdentifier"); } Console.WriteLine(asReportBlock.SendersSynchronizationSourceIdentifier); //0 Console.WriteLine(asReportBlock.FractionsLost); //0 Console.WriteLine(asReportBlock.CumulativePacketsLost); //0 Console.WriteLine(asReportBlock.ExtendedHighestSequenceNumberReceived); //0 Console.WriteLine(asReportBlock.InterarrivalJitterEstimate); //0 Console.WriteLine(asReportBlock.LastSendersReportTimestamp); //0 //} } //Check the length to be exactly the same as the example if (sr.Length != example.Length) { throw new Exception("Invalid Length"); } //Verify SendersReport byte for byte output = sr.Prepare().ToArray();//should be exactly equal to example for (int i = 0, e = example.Length; i < e; ++i) { if (example[i] != output[i]) { throw new Exception("Result Packet Does Not Match Example"); } } } if (rtcpPacket.Header.IsDisposed || rtcpPacket.IsDisposed) { throw new Exception("Disposed the Media.RtcpPacket"); } //Now the packet can be disposed rtcpPacket.Dispose(); rtcpPacket = null; rtcpPacket = new Media.Rtcp.SendersReport(2, 0, 7); example = rtcpPacket.Prepare().ToArray(); if (rtcpPacket.SynchronizationSourceIdentifier != 7) { throw new Exception("Unexpected SynchronizationSourceIdentifier"); } if (rtcpPacket.BlockCount != 0) { throw new Exception("Unexpected BlockCount"); } //Check the Length, 8 Byte Header, 20 Byte SendersInformation if (rtcpPacket.Length != Media.Rtcp.RtcpHeader.Length + Media.Rtcp.ReportBlock.ReportBlockSize) { throw new Exception("Unexpected BlockCount"); } //Iterate each IReportBlock in the RtcpReport representation of the rtcpPacket instance foreach (Media.Rtcp.IReportBlock rb in rtcpPacket as Media.Rtcp.RtcpReport) { Console.WriteLine(rb); throw new Exception("Unexpected BlockCount"); } //Next Sub Test //Create a GoodbyeReport with no SourceList, e.g. a BlockCount of 0. //There should be 8 bytes, 4 for the RtcpHeader and 4 for the SynchronizationSourceIdentifier //The LengthInWordsMinusOne should equal 1 (1 + 1 = 2, 2 * 4 = 8) using (var testReport = new Media.Rtcp.GoodbyeReport(2, 7)) { output = testReport.Prepare().ToArray(); if (output.Length != testReport.Length || testReport.Header.LengthInWordsMinusOne != 1 || testReport.Length != 8) { throw new Exception("Invalid Length"); } if (testReport.BlockCount != 0) { throw reportBlockException; } if (output[7] != 7 || testReport.SynchronizationSourceIdentifier != 7) { throw new Exception("Invalid ssrc"); } } //Add a Reason For Leaving //Should now have 4 words... Header, SSRC, Block, Reason using (var testReport = new Media.Rtcp.GoodbyeReport(2, 7, System.Text.Encoding.ASCII.GetBytes("v"))) { output = testReport.Prepare().ToArray(); //3 if (output.Length != testReport.Length || testReport.Header.LengthInWordsMinusOne != 2 || testReport.Length != 12) { throw new Exception("Invalid Length"); } if (testReport.BlockCount != 0) { throw reportBlockException; } if (output[7] != 7 || testReport.SynchronizationSourceIdentifier != 7) { throw new Exception("Invalid ssrc"); } if (false == testReport.HasReasonForLeaving) { throw new Exception("Has no reason for leaving."); } if (System.Text.Encoding.ASCII.GetString(testReport.ReasonForLeaving.ToArray()) != "v") { throw new Exception("Does not have expected reason for leaving."); } } //Next Sub Test ///// //Recievers Report and Source Description example = new byte[] { 0x81, 0xc9, 0x00, 0x07, 0x69, 0xf2, 0x79, 0x50, 0x61, 0x37, 0x94, 0x50, 0xff, 0xff, 0xff, 0xff, 0x00, 0x01, 0x00, 0x52, 0x00, 0x00, 0x0e, 0xbb, 0xce, 0xd4, 0xc8, 0xf5, 0x00, 0x00, 0x84, 0x28, 0x81, 0xca, 0x00, 0x04, 0x69, 0xf2, 0x79, 0x50, 0x01, 0x06, 0x4a, 0x61, 0x79, 0x2d, 0x50, 0x43, 0x00, 0x00, 0x00, 0x00 }; int foundPackets = 0, foundSize = 0; foreach (Media.Rtcp.RtcpPacket packet in Media.Rtcp.RtcpPacket.GetPackets(example, 0, example.Length)) { ++foundPackets; foundSize += packet.Length; } if (foundPackets != 2) { throw new Exception("Unexpected amount of packets found"); } if (foundSize != example.Length) { throw new Exception("Unexpected total length of packets found"); } //Or manually for some reason rtcpPacket = new Media.Rtcp.RtcpPacket(example, 0); // The same as foundPackets[0] using (Media.Rtcp.ReceiversReport rr = new Media.Rtcp.ReceiversReport(rtcpPacket, false)) { Console.WriteLine(rr.SynchronizationSourceIdentifier);//1777498448 //Check the invalid block count if (rr.BlockCount != 1) { throw new Exception("Invalid Block Count!"); } else { Console.WriteLine(rr.BlockCount); //16, should be 1 } using (var enumerator = rr.GetEnumerator()) { while (enumerator.MoveNext()) { Console.WriteLine("Current IReportBlock Identifier: " + enumerator.Current.BlockIdentifier);//1631032400 //If the instance boxed in the Interface is a ReportBlock if (enumerator.Current is Media.Rtcp.ReportBlock) { //Unbox the Interface as it's ReportBlock Instance Media.Rtcp.ReportBlock asReportBlock = enumerator.Current as Media.Rtcp.ReportBlock; Console.WriteLine("Found a ReportBlock"); //Print the instance information Console.WriteLine("FractionsLost: " + asReportBlock.FractionsLost); //255/256 0xff Console.WriteLine("CumulativePacketsLost: " + asReportBlock.CumulativePacketsLost); //-1, 0xff,0xff,0xff Console.WriteLine("ExtendedHighestSequenceNumberReceived: " + asReportBlock.ExtendedHighestSequenceNumberReceived); //65618, 00, 01, 00, 52 Console.WriteLine("InterarrivalJitterEstimate: " + asReportBlock.InterarrivalJitterEstimate); //3771 Console.WriteLine("LastSendersReportTimestamp: " + asReportBlock.LastSendersReportTimestamp); //3470000128 } else //Not a ReportBlock { Console.WriteLine("Current IReportBlock TypeName: " + enumerator.Current.GetType().Name); Console.WriteLine("Current IReportBlock Data: " + BitConverter.ToString(enumerator.Current.BlockData.ToArray())); } } } //Verify RecieversReport byte for byte output = rr.Prepare().ToArray();//should be exactly equal to example's bytes when extension data is contained in the packet instance //Use to get the raw data in the packet including the header //.Take(rr.Length - rr.ExtensionDataOctets) //Or rr.ReportData which omits the header... //What other variations are relvent? Please submit examples, even invalid ones will be tolerated! //The bytes given here should reflect exactly the bytes in the example array because of how the data is formatted. //Yes the data is compound but there is an invalid LengthInWords in the packet which must be exactly copied when deserialized if (rr.HasExtensionData && false == output.SequenceEqual(example)) { throw new Exception("Result Packet Does Not Match Example"); } else { output = rr.Prepare(true, true, false, true).ToArray(); } for (int i = 0, e = output.Length; i < e; ++i) { if (example[i] != output[i]) { throw new Exception("Result Packet Does Not Match Example @" + i); } } } if (rtcpPacket.Header.IsDisposed || rtcpPacket.IsDisposed) { throw new Exception("Disposed the Media.RtcpPacket"); } //Now the packet can be disposed rtcpPacket.Dispose(); rtcpPacket = null; //Make another packet instance from the rest of the example data. rtcpPacket = new Media.Rtcp.RtcpPacket(example, output.Length); //Create a SourceDescriptionReport from the packet instance to access the SourceDescriptionChunks using (Media.Rtcp.SourceDescriptionReport sourceDescription = new Media.Rtcp.SourceDescriptionReport(rtcpPacket, false)) { if (false == sourceDescription.HasCName) { throw new Exception("Unexpected HasCName"); } if (sourceDescription.BlockCount != 1) { throw new Exception("Unexpected BlockCount"); } if (sourceDescription.Chunks.First().ChunkIdentifer != 1777498448) { throw new Exception("Chunks.ChunkIdentifer"); } if (false == sourceDescription.Chunks.First().HasItems) { throw new Exception("Chunks.HasItems"); } if (sourceDescription.Chunks.First().Items.First().ItemType != Media.Rtcp.SourceDescriptionReport.SourceDescriptionItem.SourceDescriptionItemType.CName) { throw new Exception("Unexpected ItemType"); } if (sourceDescription.Chunks.First().Items.First().ItemLength != 6) { throw new Exception("Unexpected ItemLength"); } foreach (Media.Rtcp.SourceDescriptionReport.SourceDescriptionChunk chunk in sourceDescription.GetChunkIterator()) { if (chunk.ChunkIdentifer != 1777498448) { throw new Exception("Chunks.ChunkIdentifer"); } Console.WriteLine(string.Format(TestingFormat, "Chunk Identifier", chunk.ChunkIdentifer)); //Use a SourceDescriptionItemList to access the items within the Chunk //This is performed auto magically when using the foreach pattern foreach (Media.Rtcp.SourceDescriptionReport.SourceDescriptionItem item in chunk /*.AsEnumerable<Rtcp.SourceDescriptionItem>()*/) { //if (item.ItemType != Media.Rtcp.SourceDescriptionReport.SourceDescriptionItem.SourceDescriptionItemType.CName) throw new Exception("Unexpected ItemType"); //if (item.ItemLength != 6) throw new Exception("Unexpected ItemLength"); Console.WriteLine(string.Format(TestingFormat, "Item Type", item.ItemType)); Console.WriteLine(string.Format(TestingFormat, "Item Length", item.ItemLength)); Console.WriteLine(string.Format(TestingFormat, "Item Data", BitConverter.ToString(item.ItemData.ToArray()))); } } //Verify SourceDescriptionReport byte for byte output = sourceDescription.Prepare().ToArray();//should be exactly equal to example for (int i = output.Length, e = sourceDescription.Length; i < e; ++i) { if (example[i] != output[i]) { throw new Exception("Result Packet Does Not Match Example"); } } } //ApplicationSpecific - qtsi example = new byte[] { 0x81, 0xcc, 0x00, 0x06, 0x4e, 0xc8, 0x79, 0x50, 0x71, 0x74, 0x73, 0x69, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x61, 0x74, 0x00, 0x04, 0x00, 0x00, 0x00, 0x14 }; rtcpPacket = new Media.Rtcp.RtcpPacket(example, 0); //Make a ApplicationSpecificReport instance Media.Rtcp.ApplicationSpecificReport app = new Media.Rtcp.ApplicationSpecificReport(rtcpPacket); //Check the name to be equal to qtsi if (false == app.Name.SequenceEqual(System.Text.Encoding.UTF8.GetBytes("qtsi"))) { throw new Exception("Invalid App Packet Type"); } //Check the length if (rtcpPacket.Length != example.Length) { throw new Exception("Invalid Legnth"); } //Verify ApplicationSpecificReport byte for byte output = rtcpPacket.Prepare().ToArray();//should be exactly equal to example for (int i = 0, e = example.Length; i < e; ++i) { if (example[i] != output[i]) { throw new Exception("Result Packet Does Not Match Example"); } } //Test making a packet with a known length in bytes Media.Rtcp.SourceDescriptionReport sd = new Media.Rtcp.SourceDescriptionReport(2); byte[] sdOut = sd.Prepare().ToArray(); //1 word when the ssrc is present but would be an invalid sdes because blockCount = 0 if (false == sd.IsComplete || sd.Length != Media.Rtcp.RtcpHeader.Length || sd.Header.LengthInWordsMinusOne != ushort.MaxValue) { throw new Exception("Invalid Length"); } //Create 9 bytes of data to add to the existing SourceDescriptionReport byte[] itemData = System.Text.Encoding.UTF8.GetBytes("FLABIA-PC"); int KnownId = 0x1AB7C080; //Point the rtcpPacket at the SourceDescription instance rtcpPacket = sd; //Create a Media.Rtcp.SourceDescriptionReport.SourceDescriptionChunk containing a Known Identifier //Which Contains a Media.Rtcp.SourceDescriptionReport.SourceDescriptionItem with the Type 'CName' containing the itemData //Add the Media.Rtcp.IReportBlock to the RtcpReport sd.Add((Media.Rtcp.IReportBlock) new Media.Rtcp.SourceDescriptionReport.SourceDescriptionChunk(KnownId, new Media.Rtcp.SourceDescriptionReport.SourceDescriptionItem(Media.Rtcp.SourceDescriptionReport.SourceDescriptionItem.SourceDescriptionItemType.CName, itemData.Length, itemData, 0))); // ItemType(End) = 1, ItemLength(9) = 1, ItemData(9) = 11 Bytes in the Item, ChunkIdentifier(0x1AB7C080) = 4, 15 total bytes //Add an unpadded item for a 19 byte packet. //sd.Add(new Media.Rtcp.SourceDescriptionReport.SourceDescriptionChunk(KnownId, // new Media.Rtcp.SourceDescriptionReport.SourceDescriptionItem(Media.Rtcp.SourceDescriptionReport.SourceDescriptionItem.SourceDescriptionItemType.CName, // itemData.Length, itemData, 0)), false); // ItemType(End) = 1, ItemLength(9) = 1, ItemData(9) = 11 Bytes in the Item, ChunkIdentifier(0x1AB7C080) = 4, 15 total bytes //Ensure the data is present where it is supposed to be, more data may be present to respect octet alignment if (false == sd.RtcpData.Skip(Media.Rtcp.SourceDescriptionReport.SourceDescriptionItem.ItemHeaderSize).Take(itemData.Length).SequenceEqual(itemData)) { throw new Exception("Invalid ItemData"); } if (false == sd.Chunks.First().HasItems) { throw new Exception("Unexpected HasItems"); } if (sd.Chunks.First().ChunkIdentifer != KnownId) { throw new Exception("Unexpected Chunks.ChunkIdentifer"); } if (sd.Chunks.First().Items.Count() != 2) { throw new Exception("Unexpected Chunks.Items.Count"); } if (sd.Chunks.First().Items.First().ItemType != Media.Rtcp.SourceDescriptionReport.SourceDescriptionItem.SourceDescriptionItemType.CName) { throw new Exception("Unexpected Items.ItemType"); } if (sd.Chunks.First().Items.First().ItemLength != 9) { throw new Exception("Unexpected Chunks.Items.ItemLength"); } if (false == sd.Chunks.First().Items.First().ItemData.SequenceEqual(itemData)) { throw new Exception("Unexpected Chunks.Items.ItemData"); } if (sd.Chunks.First().Items.Last().ItemType != Media.Rtcp.SourceDescriptionReport.SourceDescriptionItem.SourceDescriptionItemType.End) { throw new Exception("Unexpected Items.ItemType"); } if (sd.Chunks.First().Items.Last().ItemLength != 1) { throw new Exception("Unexpected Chunks.Items.ItemLength"); } if (false == sd.Chunks.First().Items.Last().ItemData.All(b => b == 0)) { throw new Exception("Unexpected Chunks.Items.ItemData"); } // // Header = 4 Bytes, 1 Word // There is a SSRC which occupies 1 Word //in a SourceDescription, The First Chunk is `Overlapped` in the header and the BlockIdentifier is shared with the SSRC //Ensure the data is present where it is supposed to be if (sd.SynchronizationSourceIdentifier != KnownId) { throw new Exception("Invalid SynchronizationSourceIdentifier"); } //asPacket now contains 11 octets in the payload. //asPacket now has 1 block (1 chunk of 15 bytes) //asPacket is 19 octets long, 11 octets in the payload and 8 octets in the header //asPacket would have a LengthInWordsMinusOne of 3 because 19 / 4 = 4 - 1 = 3 //But null octets are added (Per RFC3550 @ Page 45 [Paragraph 2] / http://tools.ietf.org/html/rfc3550#appendix-A.4) //19 + 1 = 20, 20 / 4 = 5, 5 - 1 = 4. if (false == rtcpPacket.IsComplete || rtcpPacket.Length != 20 || rtcpPacket.Header.LengthInWordsMinusOne != 4) { throw new Exception("Invalid Length"); } }
//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 }