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 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"); }
/// <summary> /// Creates a <see cref="ReceiversReport"/> from the given context. /// </summary> /// <param name="context">The context</param> /// <param name="empty">Indicates if the report should be empty</param> /// <returns>The report created</returns> public static ReceiversReport CreateReceiversReport(TransportContext context, bool empty) { ReceiversReport result = new ReceiversReport(context.Version, 0, context.SynchronizationSourceIdentifier); if (false == empty && context.RemoteSynchronizationSourceIdentifier != null && context.RemoteSynchronizationSourceIdentifier.Value != 0 && context.TotalRtpPacketsReceieved > 0) { #region Delay and Fraction //Should be performed in the Conference level, these values here will only //should allow a backoff to occur in reporting and possibly eventually to be turned off. int fraction = 0; uint extended_max = (uint)(context.RtpSeqCycles + context.RtpMaxSeq); int expected = (int)(extended_max - context.RtpBaseSeq + 1); int lost = (int)(expected - context.RtpPacketsReceived); int expected_interval = (int)(expected - context.RtpExpectedPrior); context.RtpExpectedPrior = (uint)expected; int received_interval = (int)(context.RtpPacketsReceived - context.RtpReceivedPrior); context.RtpReceivedPrior = (uint)context.RtpPacketsReceived; int lost_interval = expected_interval - received_interval; if (expected_interval == 0 || lost_interval <= 0) { fraction = 0; } else { fraction = (lost_interval << 8) / expected_interval; } #endregion //Create the ReportBlock based off the statistics of the last RtpPacket and last SendersReport result.Add(new Rtcp.ReportBlock((int)context.RemoteSynchronizationSourceIdentifier, (byte)fraction, lost, (int)context.SequenceNumber, (int)context.RtpJitter >> 4, (int)(context.SendersReport != null ? Media.Ntp.NetworkTimeProtocol.DateTimeToNptTimestamp32(context.SendersReport.NtpTime) : 0), (context.SendersReport != null ? ((DateTime.UtcNow - context.SendersReport.Created).Seconds / ushort.MaxValue) * 1000 : 0) )); } return result; }