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="SendersReport"/> from the given context. /// Note, If empty is false and no previous <see cref="SendersReport"/> was sent then the report will be empty anyway. /// </summary> /// <param name="context"></param> /// <param name="empty">Specifies if the report should have any report blocks if possible</param> /// <returns>The report created</returns> /// TODO, Allow an alternate ssrc public static SendersReport CreateSendersReport(TransportContext context, bool empty, bool rfc = true) { //Create a SendersReport SendersReport result = new SendersReport(context.Version, 0, context.SynchronizationSourceIdentifier); //Use the values from the TransportChannel (Use .NtpTimestamp = 0 to Disable NTP)[Should allow for this to be disabled] result.NtpTimestamp = context.NtpTimestamp; //Note that in most cases this timestamp will not be equal to the RTP timestamp in any adjacent data packet. Rather, it MUST be calculated from the corresponding NTP timestamp using the relationship between the RTP timestamp counter and real time as maintained by periodically checking the wallclock time at a sampling instant. result.RtpTimestamp = context.RtpTimestamp; //Counters result.SendersOctetCount = (int)(rfc ? context.RfcRtpBytesSent : context.RtpBytesSent); result.SendersPacketCount = (int)context.RtpPacketsSent; //Ensure there is a remote party //If source blocks are included include them and calculate their statistics if (false == empty && context.RemoteSynchronizationSourceIdentifier != null && context.RemoteSynchronizationSourceIdentifier.Value != 0 && context.TotalPacketsSent > 0) { #region Delay and Fraction //Should be a single function and when backoff reached 0 RtcpDisabled should be true.... //Currently if rtcp becomes `disabled` then Goodbye will not be handled and the source will eat cpu will timing out... //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. //// RFC 3550 A.3 Determining Number of Packets Expected and Lost. 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, //The middle 32 bits out of 64 in the NTP timestamp (as explained in Section 4) received as part of the most recent RTCP sender report (SR) packet from source SSRC_n. If no SR has been received yet, the field is set to zero. (int)((context.NtpTimestamp >> 16) << 32), //The delay, expressed in units of 1/65536 seconds, between receiving the last SR packet from source SSRC_n and sending this reception report block. If no SR packet has been received yet from SSRC_n, the DLSR field is set to zero. context.LastRtcpReportSent > TimeSpan.MinValue ? (int)context.LastRtcpReportSent.TotalSeconds / ushort.MaxValue : 0)); } return result; }