/// <summary> /// Constructs a new GoodbyeReport from the given values. /// </summary> /// <param name="version">The version of the report</param> /// <param name="padding"></param> /// <param name="ssrc">The id of the senders of the report</param> /// <param name="sourcesLeaving">The SourceList which describes the sources who are leaving</param> /// <param name="reasonForLeaving">An optional reason for leaving(only the first 255 octets will be used)</param> internal GoodbyeReport(int version, int padding, int ssrc, Media.RFC3550.SourceList sourcesLeaving, byte[] reasonForLeaving) : this(version, padding, ssrc, sourcesLeaving.Count + 1, //BlockCount, + 1 because the ssrc is present. 0, // 0 is extensionSize which is assigned by the size of reasonForLeaving in the constructor via ref. sourcesLeaving.Count *RFC3550.SourceList.ItemSize, //bytesInSourceList reasonForLeaving) { sourcesLeaving.TryCopyTo(m_OwnedOctets, Payload.Offset); }
/// <summary> /// Prepares a text description which is compatible with 'rtpsend' among other 'rtp tools'. /// </summary> /// <param name="format">The <see cref="FileFormat"/> to output.</param> /// <param name="packet">The <see cref="Rtp.RtpPacket"/> to describe</param> /// <param name="time">Timeoffset from when recoding began, should be 0 for the first packet.</param> /// <param name="source">The <see cref="System.Net.IPEndPoint"/> from which the packet was received.</param> /// <returns>The text which describes the packet if the <paramref name="format"/> is a text format, otherwise an empty string</returns> public static string ToTextualConvention(FileFormat format, Rtp.RtpPacket packet, TimeSpan time, System.Net.IPEndPoint source) { //StringBuilder? //If the format is Short (Vat or Rtp Data in tablular form) if (format == FileFormat.Short) { //Then return that format return string.Format(RtpSend.ShortFormat, //0 time.TotalSeconds.ToString("0.000000"), //1 (packet.Marker ? (-packet.SequenceNumber) : packet.SequenceNumber).ToString(), //2 source.Port.ToString()); } StringBuilder builder = new StringBuilder(64); //All Rtp are described with this format builder.Append(string.Format(RtpSend.Format, //0 time.TotalSeconds.ToString("0.000000"), //1 string.Format(RtpSend.RtpPacketFormat, //0 packet.Length, //Packet size in bytes including header //1 source.Address.ToString() + ':' + source.Port.ToString(), //2 packet.Version.ToString(), //3 packet.Padding ? "1" : "0", //4 packet.Extension ? "1" : "0", //5 packet.ContributingSourceCount.ToString(), //6 packet.Marker ? "1" : "0", //7 packet.PayloadType, //8 (Format description) string.Format(RtpSend.PayloadDescriptionFormat, packet.PayloadType, RtpSendExtensions.PayloadDescription(packet)), //9 packet.SequenceNumber, //10 packet.SynchronizationSourceIdentifier, //11 packet.Timestamp ))); //Write the textual representation of the items in the sourceList if (packet.ContributingSourceCount > 0) { //Note //http://www.cs.columbia.edu/irt/software/rtptools/ChangeLog.html //States the format is not hex when in the format csrc[n] //However - //http://www.cs.columbia.edu/irt/software/rtptools/#rtpsend //States that the format is : //cc=<CSRC count> //csrc=<CSRC> //This is basically the same thing and a parsing semantic. using (Media.RFC3550.SourceList sl = new Media.RFC3550.SourceList(packet)) { if (sl == null) { builder.Append("#Incomplete Source List Not Included"); builder.Append((char)Common.ASCII.LineFeed); } else { //csrc= while (sl.MoveNext()) { builder.Append(string.Format(RtpSend.HexFormat, "csrc", HexSpecifier, sl.CurrentSource.ToString("X"), (char)Common.ASCII.LineFeed)); } } } } //Write the textual representation of the Extension if (packet.Extension) { using (var rtpExtension = packet.GetExtension()) { if (rtpExtension == null) { builder.Append("#Incomplete Extension Not Included"); builder.Append((char)Common.ASCII.LineFeed); } else { builder.Append(string.Format(RtpSend.HexFormat, "ext_type", HexSpecifier + rtpExtension.Flags.ToString("X"), (char)Common.ASCII.LineFeed)); builder.Append(string.Format(RtpSend.NonQuotedFormat, "ext_len", rtpExtension.LengthInWords)); builder.Append((char)Common.ASCII.LineFeed); builder.Append(string.Format(RtpSend.HexFormat, "ext_data", BitConverter.ToString(rtpExtension.Data.ToArray()).Replace("-", string.Empty), (char)Common.ASCII.LineFeed)); } } } //If the format is hex then add the payload dump if (format == FileFormat.Hex) { var data = packet.PayloadData; if (data.Any()) builder.Append(string.Format(RtpSend.HexFormat, "data", BitConverter.ToString(data.ToArray()).Replace("-", string.Empty), (char)Common.ASCII.LineFeed)); else builder.Append(string.Format(HexFormat, "data", NullSpecifier, (char)Common.ASCII.LineFeed)); } //Return the result return builder.ToString(); }
/// <summary> /// O( ) /// </summary> public static void TestAConstructor_And_Reserialization() { //Permute every possible value in the 5 bit BlockCount except the last, it is possible to have 32 with there are 31 entries in the SourceList, this logic should be tested seperately. for (byte SourceCounter = byte.MinValue; SourceCounter <= Media.Common.Binary.FiveBitMaxValue - 1; ++SourceCounter) { //Permute every possible value in the Padding field. for (byte PaddingCounter = byte.MinValue; PaddingCounter <= Media.Common.Binary.FiveBitMaxValue; ++PaddingCounter) { //Enumerate every possible reason length within reason. for (byte ReasonLength = byte.MinValue; ReasonLength <= Media.Common.Binary.FiveBitMaxValue; ++ReasonLength) { //Create the RandomId and ReasonForLeaving int RandomId = RFC3550.Random32(Utility.Random.Next()); IEnumerable <byte> ReasonForLeaving = Array.ConvertAll(Enumerable.Range(1, (int)ReasonLength).ToArray(), Convert.ToByte); //Create a GoodbyeReport instance using the specified options. using (Media.Rtcp.GoodbyeReport p = new Rtcp.GoodbyeReport(0, PaddingCounter, RandomId, new RFC3550.SourceList(SourceCounter), ReasonForLeaving.ToArray())) { //Check IsComplete System.Diagnostics.Debug.Assert(p.IsComplete, "IsComplete must be true."); //Check SynchronizationSourceIdentifier System.Diagnostics.Debug.Assert(p.SynchronizationSourceIdentifier == RandomId, "Unexpected SynchronizationSourceIdentifier"); //Calculate the length of the ReasonForLeaving, should always be padded to 32 bits for octet alignment. int expectedReasonLength = ReasonLength > 0 ? Binary.BytesToMachineWords(ReasonLength + 1) * Binary.BytesPerInteger : 0; //Check HasReasonForLeaving System.Diagnostics.Debug.Assert(expectedReasonLength > 0 == p.HasReasonForLeaving, "Unexpected HasReasonForLeaving"); //The ssrc is always present in these tests. int expectedBlockCount = SourceCounter + 1; //Check BlockCount System.Diagnostics.Debug.Assert(p.BlockCount == expectedBlockCount, "Unexpected BlockCount"); //Check the SourceList int expectedSourceListSize = expectedBlockCount * RFC3550.SourceList.ItemSize; //The first entry is in the header.... if (expectedSourceListSize > 0) { //Use the SourceList using (Media.RFC3550.SourceList sourceList = p.GetSourceList()) { System.Diagnostics.Debug.Assert(sourceList.IsComplete == true, "SourceList.IsComplete"); System.Diagnostics.Debug.Assert(expectedSourceListSize == sourceList.Size, "Unexpected SourceList Size"); System.Diagnostics.Debug.Assert(expectedBlockCount == sourceList.Count, "Unexpected SourceList Count"); System.Diagnostics.Debug.Assert(RandomId == sourceList.CurrentSource, "Unexpected Source in SourceList"); System.Diagnostics.Debug.Assert((uint)RandomId == sourceList.First(), "Unexpected Source in SourceList"); System.Diagnostics.Debug.Assert(sourceList.Skip(1).All(s => s == uint.MinValue), "Unexpected Source in SourceList"); System.Diagnostics.Debug.Assert(sourceList.ToArray().SequenceEqual(Enumerable.Concat <uint>(LinqExtensions.Yield((uint)RandomId), Enumerable.Repeat(uint.MinValue, SourceCounter))), "Unexpected Source in SourceList"); } } //The amount of bytes expected in the payload does not contain the first entry of the list. int expectedInPayload = expectedSourceListSize - RFC3550.SourceList.ItemSize; //Check the Payload.Count System.Diagnostics.Debug.Assert(p.Payload.Count == expectedInPayload + PaddingCounter + expectedReasonLength, "Unexpected Payload Count"); //Check the Length, System.Diagnostics.Debug.Assert(p.Length == p.Header.Size + expectedInPayload + PaddingCounter + expectedReasonLength, "Unexpected Length"); //Check the reaosn for leaving System.Diagnostics.Debug.Assert(p.ReasonForLeavingData.SequenceEqual(ReasonForLeaving), "Unexpected ReasonForLeaving data"); //Check the PaddingOctets count System.Diagnostics.Debug.Assert(p.PaddingOctets == PaddingCounter, "Unexpected PaddingOctets"); //Check all data in the padding but not the padding octet itself. System.Diagnostics.Debug.Assert(p.PaddingData.Take(PaddingCounter - 1).All(b => b == 0), "Unexpected PaddingData"); //Add remaining amount of reports to test the Add method //Enumerate the RtcpReport version of the instance //Serialize and Deserialize and verify again using (Rtcp.GoodbyeReport s = new Rtcp.GoodbyeReport(new Rtcp.RtcpPacket(p.Prepare().ToArray(), 0), true)) { //Check the Payload.Count System.Diagnostics.Debug.Assert(s.Payload.Count == p.Payload.Count, "Unexpected Payload Count"); //Check the Length, System.Diagnostics.Debug.Assert(s.Length == p.Length, "Unexpected Length"); //Check the BlockCount count System.Diagnostics.Debug.Assert(s.BlockCount == p.BlockCount, "Unexpected BlockCount"); //Check the reaosn for leaving System.Diagnostics.Debug.Assert(s.ReasonForLeavingData.SequenceEqual(ReasonForLeaving) && s.ReasonForLeavingData.Count() == ReasonLength, "Unexpected ReasonForLeaving data"); //Check the PaddingOctets count System.Diagnostics.Debug.Assert(s.PaddingOctets == p.PaddingOctets, "Unexpected PaddingOctets"); //Check all data in the padding but not the padding octet itself. System.Diagnostics.Debug.Assert(s.PaddingData.SequenceEqual(p.PaddingData), "Unexpected PaddingData"); } } } } } }
public GoodbyeReport(int version, int ssrc, Media.RFC3550.SourceList sourcesLeaving, byte[] reasonForLeaving) : this(version, 0, ssrc, sourcesLeaving == null ? Media.RFC3550.SourceList.Empty : sourcesLeaving, reasonForLeaving) { }
/// <summary> /// Constructs a new GoodbyeReport from the given values. /// </summary> /// <param name="version">The version of the report</param> /// <param name="padding"></param> /// <param name="ssrc">The id of the senders of the report</param> /// <param name="sourcesLeaving">The SourceList which describes the sources who are leaving</param> /// <param name="reasonForLeaving">An optional reason for leaving(only the first 255 octets will be used)</param> public GoodbyeReport(int version, int padding, int ssrc, Media.RFC3550.SourceList sourcesLeaving, byte[] reasonForLeaving) : base(version, PayloadType, padding, ssrc, sourcesLeaving != null ? sourcesLeaving.Count : 0, //BlockCount Media.RFC3550.SourceList.ItemSize, //Size in bytes of each block Media.Common.Extensions.Array.ArrayExtensions.IsNullOrEmpty(reasonForLeaving) ? 0 : 1 + reasonForLeaving.Length) //Extension size in bytes { //The working offset in the payload int offset = Payload.Offset; //Copy the source list (IMHO it should be before the reason...) if (sourcesLeaving != null) { sourcesLeaving.TryCopyTo(Payload.Array, offset); offset += sourcesLeaving.Size; } #region Babble /* If I won't have a participant list then I sure as shit won't make thing as they shouldn't be here just for [Wireshark, et al] * 6.6 BYE: Goodbye RTCP Packet * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |V=2|P| SC | PT=BYE=203 | length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | SSRC/CSRC | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | : ... : (THIS IS WHERE THE SOURCE LIST GOES FYI) +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ | (opt) | length | reason for leaving ... +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | * When SC = 0 the packet is useless. * When SC >= 1 the SourceList appears BEFORE the (opt) Length, The id there may be different from SSRC/CSRC * Length is optional and a 0 value should not HAVE to be present as including a 0 value forces you to inlcude 3 more 0's to octet align the payload */ #endregion //If a reason was given there will be extension data if (HasExtensionData) { int extensionLength = Media.Common.Extensions.Array.ArrayExtensions.IsNullOrEmpty(reasonForLeaving) ? 0 : reasonForLeaving.Length; if (extensionLength > 0) { //Ensure it will fit if (extensionLength > byte.MaxValue) { throw new InvalidOperationException("Only 255 octets can occupy the ReasonForLeaving in a GoodbyeReport."); } //The length before the string Payload.Array[offset++] = (byte)extensionLength; //Copy it to the payload reasonForLeaving.CopyTo(Payload.Array, offset); } } }