Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add RTCP packet #154

Merged
merged 11 commits into from
Oct 14, 2021
Next Next commit
Split RtcpContainer and RtcpItem
  • Loading branch information
jgaulon committed Oct 11, 2021
commit 15afccf670f9b857c10319f4c42b25bf1fe99132
21 changes: 21 additions & 0 deletions PacketDotNet/RtcpFields.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
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
{
/// <summary>Length of the Base Header in bytes.</summary>
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;
}
}
174 changes: 174 additions & 0 deletions PacketDotNet/RtcpPacket.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
/*
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 <[email protected]>
* Copyright 2010 Chris Morgan <[email protected]>
*/

using System;
using PacketDotNet.Utils;
using PacketDotNet.Utils.Converters;
#if DEBUG
using log4net;
using System.Reflection;
#endif

namespace PacketDotNet
{
/// <summary>
/// RTP Control Protocol
/// See: https://en.wikipedia.org/wiki/RTP_Control_Protocol
/// See: https://wiki.wireshark.org/RTCP
/// </summary>
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

/// <summary>
/// Create from values
/// </summary>
public RtcpPacket()
{
Log.Debug("");

// allocate memory for this packet
var length = RtcpFields.HeaderLength;
var headerBytes = new byte[length];
Header = new ByteArraySegment(headerBytes, 0, length);
}

/// <summary>
/// Constructor
/// </summary>
/// <param name="byteArraySegment">
/// A <see cref="ByteArraySegment" />
/// </param>
/// <param name="parentPacket">
/// A <see cref="Packet" />
/// </param>
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);
RtpPayloadPacketOrData = new LazySlim<PacketOrByteArraySegment>(() => new PacketOrByteArraySegment
{
ByteArraySegment = new ByteArraySegment(rtcpPayload, 0, length)
});

var payload = Header.NextSegment();
if (payload.Length > 0)
{
// store the payload bytes
PayloadPacketOrData = new LazySlim<PacketOrByteArraySegment>(() => new PacketOrByteArraySegment
{
Packet = new RtcpPacket(payload, this)
});
}
}

ParentPacket = parentPacket;
}

/// <summary>Fetch ascii escape sequence of the color associated with this packet type.</summary>
public override string Color => AnsiEscapeSequences.BlueBackground;

/// <summary>
/// Gets or sets the RTP version (2 bits), Indicates the version of the protocol
/// </summary>
public int Version
{
get => (Header.Bytes[Header.Offset] & RtcpFields.VersionMask) >> 6;
set => Header.Bytes[Header.Offset] |= (byte) (value << 6);
}

/// <summary>
/// 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.
/// </summary>
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;
}
}
}

/// <summary>
/// Gets or sets (5 bits) The number of reception report blocks contained in this packet. A value of zero is valid.
/// </summary>
public int ReceptionReportCount
{
get => (Header.Bytes[Header.Offset] & RtcpFields.ReceptionReportCountMask);
set => Header.Bytes[Header.Offset] |= (byte) (value & RtcpFields.ReceptionReportCountMask);
}

/// <summary>
/// Gets or sets the Packet type: (8 bits) Contains a constant to identify RTCP packet type
/// </summary>
public ushort PacketType
{
get => Header.Bytes[Header.Offset + 1];
set => Header.Bytes[Header.Offset + 1] = (byte) (value);
}

/// <summary>
/// Gets or sets the Length (16 bits) Indicates the length of this RTCP packet (including the header itself) in 32-bit units minus one
/// </summary>
public ushort Length
{
get => EndianBitConverter.Big.ToUInt16(Header.Bytes, Header.Offset + 2);
set => EndianBitConverter.Big.CopyBytes(value, Header.Bytes, Header.Offset + 2);
}

/// <summary>
/// Gets or sets the SSRC (32 bits). The SSRC field identifies the synchronization source.
/// </summary>
public uint SsrcIdentifier
{
get => EndianBitConverter.Big.ToUInt32(Header.Bytes, Header.Offset + 4);
set => EndianBitConverter.Big.CopyBytes(value, Header.Bytes, Header.Offset + 4);
}

public LazySlim<PacketOrByteArraySegment> RtpPayloadPacketOrData = new LazySlim<PacketOrByteArraySegment>(null);

public bool IsValid()
{
if (Header.Length < RtcpFields.HeaderLength + (Length - 1) * 4)
return false;
if (Version != 2)
return false;
return true;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ namespace PacketDotNet
/// See: https://en.wikipedia.org/wiki/Real-time_Transport_Protocol
/// See: https://wiki.wireshark.org/RTP
/// </summary>
public sealed class RtpPacket : Packet
public sealed class RtpContainerPacket : Packet
{
#if DEBUG
private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
Expand All @@ -39,7 +39,7 @@ public sealed class RtpPacket : Packet
/// <summary>
/// Create from values
/// </summary>
public RtpPacket()
public RtpContainerPacket()
{
Log.Debug("");

Expand All @@ -58,7 +58,7 @@ public RtpPacket()
/// <param name="parentPacket">
/// A <see cref="Packet" />
/// </param>
public RtpPacket(ByteArraySegment byteArraySegment, Packet parentPacket)
public RtpContainerPacket(ByteArraySegment byteArraySegment, Packet parentPacket)
{
Log.Debug("");

Expand Down
Binary file added Test/CaptureFiles/ipv6_rtcp1.pcap
Binary file not shown.
Binary file added Test/CaptureFiles/ipv6_rtcp2.pcap
Binary file not shown.
151 changes: 151 additions & 0 deletions Test/PacketType/RtcpPacketTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
/*
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 RtcpPacket(segment, packet)
};
}

return null;
};

var p = Packet.ParsePacket(rawCapture.GetLinkLayers(), rawCapture.Data);

Assert.IsNotNull(p);

var rtcp = p.Extract<RtcpPacket>();
Assert.IsNotNull(rtcp);
Console.WriteLine(rtcp.GetType());
Assert.IsTrue(rtcp.IsValid());
Assert.AreEqual(2, rtcp.Version);
Assert.IsFalse(rtcp.HasPadding);
Assert.AreEqual(1, rtcp.ReceptionReportCount);
Assert.AreEqual(203, 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 RtcpPacket(segment, packet)
};
}

return null;
};

var p = Packet.ParsePacket(rawCapture.GetLinkLayers(), rawCapture.Data);

Assert.IsNotNull(p);

var rtcp = p.Extract<RtcpPacket>();
Assert.IsNotNull(rtcp);
Console.WriteLine(rtcp.GetType());
Assert.IsTrue(rtcp.IsValid());
Assert.AreEqual(2, rtcp.Version);
Assert.IsFalse(rtcp.HasPadding);
Assert.AreEqual(0, rtcp.ReceptionReportCount);
Assert.AreEqual(200, rtcp.PacketType);
Assert.AreEqual(6, rtcp.Length);
Assert.AreEqual(899629540, rtcp.SsrcIdentifier);
Assert.IsFalse(rtcp.HasPayloadData);
Assert.IsTrue(rtcp.HasPayloadPacket);

var nextRtcp = rtcp.PayloadPacket as RtcpPacket;
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(202, nextRtcp.PacketType);
Assert.AreEqual(6, nextRtcp.Length);
Assert.AreEqual(899629540, nextRtcp.SsrcIdentifier);
Assert.IsFalse(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 RtpContainerPacket
{
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);
}
}
}
Loading