diff --git a/PacketDotNet/RtcpContainerPacket.cs b/PacketDotNet/RtcpContainerPacket.cs new file mode 100644 index 00000000..99e58f5b --- /dev/null +++ b/PacketDotNet/RtcpContainerPacket.cs @@ -0,0 +1,109 @@ +/* +This file is part of PacketDotNet. + +This Source Code Form is subject to the terms of the Mozilla Public +License, v. 2.0. If a copy of the MPL was not distributed with this +file, You can obtain one at https://mozilla.org/MPL/2.0/. +*/ +/* + * Copyright 2010 Evan Plaice + * Copyright 2010 Chris Morgan + */ + +using System; +using System.Collections.Generic; +using PacketDotNet.Utils; +#if DEBUG +using log4net; +using System.Reflection; +#endif + +namespace PacketDotNet +{ + /// + /// RTP Control Protocol + /// See: https://en.wikipedia.org/wiki/RTP_Control_Protocol + /// See: https://wiki.wireshark.org/RTCP + /// + public sealed class RtcpContainerPacket : Packet + { +#if DEBUG + private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); +#else +// NOTE: No need to warn about lack of use, the compiler won't +// put any calls to 'log' here but we need 'log' to exist to compile +#pragma warning disable 0169, 0649 + private static readonly ILogInactive Log; +#pragma warning restore 0169, 0649 +#endif + private LazySlim> _packets; + + /// + /// Create from values + /// + public RtcpContainerPacket() + { + Log.Debug(""); + + // allocate memory for this packet + var length = RtcpFields.HeaderLength; + var headerBytes = new byte[length]; + Header = new ByteArraySegment(headerBytes, 0, length); + + _packets = new LazySlim>(() => new List()); + } + + /// + /// Constructor + /// + /// + /// A + /// + /// + /// A + /// + public RtcpContainerPacket(ByteArraySegment byteArraySegment, Packet parentPacket) + { + Log.Debug(""); + + _packets = new LazySlim>(() => + { + var list = new List(); + ByteArraySegment segment = byteArraySegment; + do + { + var item = new RtcpPacket(segment, this); + list.Add(item); + segment = item.HeaderDataSegment; + } while (segment.Length > 0); + + return list; + }); + + ParentPacket = parentPacket; + } + + /// Fetch ascii escape sequence of the color associated with this packet type. + public override string Color => AnsiEscapeSequences.BlueBackground; + + /// + /// Gets the Rtcp packets + /// + public List Packets + { + get => _packets.Value; + set + { + _packets = new LazySlim>(() => value); + + var offset = 0; + foreach (var packet in value) + { + var packetBytes = packet.Bytes; + Array.Copy(packetBytes, 0, Header.Bytes, Header.Offset + offset, packetBytes.Length); + offset += packetBytes.Length; + } + } + } + } +} diff --git a/PacketDotNet/RtcpFields.cs b/PacketDotNet/RtcpFields.cs new file mode 100644 index 00000000..fb3c396b --- /dev/null +++ b/PacketDotNet/RtcpFields.cs @@ -0,0 +1,27 @@ +/* +This file is part of PacketDotNet. + +This Source Code Form is subject to the terms of the Mozilla Public +License, v. 2.0. If a copy of the MPL was not distributed with this +file, You can obtain one at https://mozilla.org/MPL/2.0/. +*/ + +namespace PacketDotNet +{ + public struct RtcpFields + { + /// Length of the Base Header in bytes. + public static readonly int HeaderLength = 8; + + // flag bit masks + public static readonly int VersionMask = 0xC0; + public static readonly int PaddingMask = 0x20; + public static readonly int ReceptionReportCountMask = 0x1F; + + public static readonly int SenderReportType = 200; + public static readonly int ReceiverReportType = 201; + public static readonly int SourceDescriptionType = 202; + public static readonly int GoodbyeType = 203; + public static readonly int ApplicationDefinedType = 204; + } +} diff --git a/PacketDotNet/RtcpPacket.cs b/PacketDotNet/RtcpPacket.cs new file mode 100644 index 00000000..19c66c47 --- /dev/null +++ b/PacketDotNet/RtcpPacket.cs @@ -0,0 +1,161 @@ +/* +This file is part of PacketDotNet. + +This Source Code Form is subject to the terms of the Mozilla Public +License, v. 2.0. If a copy of the MPL was not distributed with this +file, You can obtain one at https://mozilla.org/MPL/2.0/. +*/ +/* + * Copyright 2010 Evan Plaice + * Copyright 2010 Chris Morgan + */ + +using System; +using PacketDotNet.Utils; +using PacketDotNet.Utils.Converters; +#if DEBUG +using log4net; +using System.Reflection; +#endif + +namespace PacketDotNet +{ + /// + /// RTP Control Protocol + /// See: https://en.wikipedia.org/wiki/RTP_Control_Protocol + /// See: https://wiki.wireshark.org/RTCP + /// + public sealed class RtcpPacket : Packet + { +#if DEBUG + private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); +#else +// NOTE: No need to warn about lack of use, the compiler won't +// put any calls to 'log' here but we need 'log' to exist to compile +#pragma warning disable 0169, 0649 + private static readonly ILogInactive Log; +#pragma warning restore 0169, 0649 +#endif + + /// + /// Create from values + /// + public RtcpPacket() + { + Log.Debug(""); + + // allocate memory for this packet + var length = RtcpFields.HeaderLength; + var headerBytes = new byte[length]; + Header = new ByteArraySegment(headerBytes, 0, length); + } + + /// + /// Constructor + /// + /// + /// A + /// + /// + /// A + /// + public RtcpPacket(ByteArraySegment byteArraySegment, Packet parentPacket) + { + Log.Debug(""); + + // set the header field, header field values are retrieved from this byte array + Header = new ByteArraySegment(byteArraySegment) {Length = RtcpFields.HeaderLength}; + + if (Length > 1) + { + Header.Length += (Length - 1) * 4; + + var length = Length - 1; + var rtcpPayload = new byte[length*4]; + Buffer.BlockCopy(Header.Bytes, Header.Offset + RtcpFields.HeaderLength, rtcpPayload, 0, length*4); + PayloadPacketOrData = new LazySlim(() => new PacketOrByteArraySegment + { + ByteArraySegment = new ByteArraySegment(rtcpPayload, 0, length) + }); + } + + ParentPacket = parentPacket; + } + + /// + /// Gets or sets the RTP version (2 bits), Indicates the version of the protocol + /// + public int Version + { + get => (Header.Bytes[Header.Offset] & RtcpFields.VersionMask) >> 6; + set => Header.Bytes[Header.Offset] |= (byte) (value << 6); + } + + /// + /// Gets or sets the padding (1 bit). If the padding bit is set, the packet contains one or more + /// additional padding octets at the end which are not part of the payload. + /// + public bool HasPadding + { + get => RtcpFields.PaddingMask == (Header.Bytes[Header.Offset] & RtcpFields.PaddingMask); + set + { + if (value) + { + Header.Bytes[Header.Offset] |= (byte) RtcpFields.PaddingMask; + } + else + { + Header.Bytes[Header.Offset] &= (byte) ~RtcpFields.PaddingMask; + } + } + } + + /// + /// Gets or sets (5 bits) The number of reception report blocks contained in this packet. A value of zero is valid. + /// + public int ReceptionReportCount + { + get => (Header.Bytes[Header.Offset] & RtcpFields.ReceptionReportCountMask); + set => Header.Bytes[Header.Offset] |= (byte) (value & RtcpFields.ReceptionReportCountMask); + } + + /// + /// Gets or sets the Packet type: (8 bits) Contains a constant to identify RTCP packet type + /// + public ushort PacketType + { + get => Header.Bytes[Header.Offset + 1]; + set => Header.Bytes[Header.Offset + 1] = (byte) (value); + } + + /// + /// Gets or sets the Length (16 bits) Indicates the length of this RTCP packet (including the header itself) in 32-bit units minus one + /// + public ushort Length + { + get => EndianBitConverter.Big.ToUInt16(Header.Bytes, Header.Offset + 2); + set => EndianBitConverter.Big.CopyBytes(value, Header.Bytes, Header.Offset + 2); + } + + /// + /// Gets or sets the SSRC (32 bits). The SSRC field identifies the synchronization source. + /// + public uint SsrcIdentifier + { + get => EndianBitConverter.Big.ToUInt32(Header.Bytes, Header.Offset + 4); + set => EndianBitConverter.Big.CopyBytes(value, Header.Bytes, Header.Offset + 4); + } + + public bool IsValid() + { + if (Header.Length < RtcpFields.HeaderLength + (Length - 1) * 4) + return false; + + if (Version != 2) + return false; + + return true; + } + } +} diff --git a/Test/CaptureFiles/ipv6_rtcp1.pcap b/Test/CaptureFiles/ipv6_rtcp1.pcap new file mode 100644 index 00000000..9b10257f Binary files /dev/null and b/Test/CaptureFiles/ipv6_rtcp1.pcap differ diff --git a/Test/CaptureFiles/ipv6_rtcp2.pcap b/Test/CaptureFiles/ipv6_rtcp2.pcap new file mode 100644 index 00000000..f2a25e85 Binary files /dev/null and b/Test/CaptureFiles/ipv6_rtcp2.pcap differ diff --git a/Test/PacketType/RtcpPacketTest.cs b/Test/PacketType/RtcpPacketTest.cs new file mode 100644 index 00000000..5f5bde46 --- /dev/null +++ b/Test/PacketType/RtcpPacketTest.cs @@ -0,0 +1,161 @@ +/* +This file is part of PacketDotNet. + +This Source Code Form is subject to the terms of the Mozilla Public +License, v. 2.0. If a copy of the MPL was not distributed with this +file, You can obtain one at https://mozilla.org/MPL/2.0/. +*/ + +using System; +using NUnit.Framework; +using PacketDotNet; +using SharpPcap; +using SharpPcap.LibPcap; + +namespace Test.PacketType +{ + [TestFixture] + public class RtcpPacketTest + { + // RTCP + [Test] + public void RtcpGoodByeParsing() + { + var dev = new CaptureFileReaderDevice(NUnitSetupClass.CaptureDirectory + "ipv6_rtcp1.pcap"); + dev.Open(); + PacketCapture c; + dev.GetNextPacket(out c); + var rawCapture = c.GetPacket(); + dev.Close(); + + TransportPacket.CustomPayloadDecoder = (segment, packet) => + { + if (packet is UdpPacket && packet.DestinationPort == 6001) + { + return new PacketOrByteArraySegment + { + Packet = new RtcpContainerPacket(segment, packet) + }; + } + + return null; + }; + + var p = Packet.ParsePacket(rawCapture.GetLinkLayers(), rawCapture.Data); + + Assert.IsNotNull(p); + + var rtcpContainer = p.Extract(); + Assert.IsNotNull(rtcpContainer); + Console.WriteLine(rtcpContainer.GetType()); + Assert.IsFalse(rtcpContainer.HasPayloadData); + Assert.IsFalse(rtcpContainer.HasPayloadPacket); + + Assert.AreEqual(1,rtcpContainer.Packets.Count); + var rtcp = rtcpContainer.Packets[0]; + Assert.IsTrue(rtcp.IsValid()); + Assert.AreEqual(2, rtcp.Version); + Assert.IsFalse(rtcp.HasPadding); + Assert.AreEqual(1, rtcp.ReceptionReportCount); + Assert.AreEqual(RtcpFields.GoodbyeType, rtcp.PacketType); + Assert.AreEqual(1, rtcp.Length); + Assert.AreEqual(1199516466, rtcp.SsrcIdentifier); + Assert.IsFalse(rtcp.HasPayloadData); + Assert.IsFalse(rtcp.HasPayloadPacket); + } + + // RTCP + [Test] + public void RtcpReportsParsing() + { + var dev = new CaptureFileReaderDevice(NUnitSetupClass.CaptureDirectory + "ipv6_rtcp2.pcap"); + dev.Open(); + PacketCapture c; + dev.GetNextPacket(out c); + var rawCapture = c.GetPacket(); + dev.Close(); + + TransportPacket.CustomPayloadDecoder = (segment, packet) => + { + if (packet is UdpPacket && packet.DestinationPort == 6001) + { + return new PacketOrByteArraySegment + { + Packet = new RtcpContainerPacket(segment, packet) + }; + } + + return null; + }; + + var p = Packet.ParsePacket(rawCapture.GetLinkLayers(), rawCapture.Data); + + Assert.IsNotNull(p); + + var rtcpContainer = p.Extract(); + Assert.IsNotNull(rtcpContainer); + Console.WriteLine(rtcpContainer.GetType()); + Assert.IsFalse(rtcpContainer.HasPayloadData); + Assert.IsFalse(rtcpContainer.HasPayloadPacket); + + Assert.AreEqual(2,rtcpContainer.Packets.Count); + var rtcp = rtcpContainer.Packets[0]; + Assert.IsTrue(rtcp.IsValid()); + Assert.AreEqual(2, rtcp.Version); + Assert.IsFalse(rtcp.HasPadding); + Assert.AreEqual(0, rtcp.ReceptionReportCount); + Assert.AreEqual(RtcpFields.SenderReportType, rtcp.PacketType); + Assert.AreEqual(6, rtcp.Length); + Assert.AreEqual(899629540, rtcp.SsrcIdentifier); + Assert.IsTrue(rtcp.HasPayloadData); + Assert.IsFalse(rtcp.HasPayloadPacket); + + var nextRtcp = rtcpContainer.Packets[1]; + Assert.IsNotNull(nextRtcp); + Console.WriteLine(nextRtcp.GetType()); + Assert.IsTrue(nextRtcp.IsValid()); + Assert.AreEqual(2, nextRtcp.Version); + Assert.IsFalse(nextRtcp.HasPadding); + Assert.AreEqual(1, nextRtcp.ReceptionReportCount); + Assert.AreEqual(RtcpFields.SourceDescriptionType, nextRtcp.PacketType); + Assert.AreEqual(6, nextRtcp.Length); + Assert.AreEqual(899629540, nextRtcp.SsrcIdentifier); + Assert.IsTrue(nextRtcp.HasPayloadData); + Assert.IsFalse(nextRtcp.HasPayloadPacket); + } + + [Test] + public void ConstructRtpPacketFromValues() + { + byte[] data = new byte[] { 0xef, 0x00, 0x00, 0x00, 0xef, 0x00, 0x00, 0x00, 0x6f, 0xef, 0xbb, 0xbf }; + + var rtp = new RtpPacket + { + PayloadData = data, + Version = 2, + HasPadding = false, + HasExtension = false, + CsrcCount = 3, + Marker = true, + PayloadType = 112, + SequenceNumber = 1234, + Timestamp = 1200, + SsrcIdentifier = 899629540 + }; + Assert.IsNotNull(rtp); + Assert.AreEqual(2, rtp.Version); + Assert.IsFalse(rtp.HasPadding); + Assert.IsFalse(rtp.HasExtension); + Assert.AreEqual(3, rtp.CsrcCount); + Assert.IsTrue(rtp.Marker); + Assert.AreEqual(112, rtp.PayloadType); + Assert.AreEqual(1234, rtp.SequenceNumber); + Assert.AreEqual(1200, rtp.Timestamp); + Assert.AreEqual(899629540, rtp.SsrcIdentifier); + Assert.AreEqual(0, rtp.ExtensionHeaderLength); + Assert.IsTrue(rtp.HasPayloadData); + Assert.IsNotNull(rtp.PayloadData); + Assert.AreEqual(12, rtp.PayloadData.Length); + } + } +}