Skip to content

Commit

Permalink
add RTP decoding in UdpPacket (dotpcap#143)
Browse files Browse the repository at this point in the history
Thanks to @jgaulon
  • Loading branch information
jgaulon committed Oct 1, 2021
1 parent 01affc1 commit 29d96fb
Show file tree
Hide file tree
Showing 4 changed files with 389 additions and 0 deletions.
33 changes: 33 additions & 0 deletions PacketDotNet/RtpFields.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
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 RtpFields
{
/// <summary>Length of the Base Header in bytes.</summary>
public static readonly int HeaderLength = 12;

/// <summary>Length of the CRSC in bytes.</summary>
public static readonly int CsrcIdLength = 4;

/// <summary>Length of the Profile-specific extension header ID in bytes.</summary>
public static readonly int ProfileSpecificExtensionHeaderLength = 2;

/// <summary>Length of the Extension Length field Length in bytes.</summary>
public static readonly int ExtensionLengthLength = 2;

// flag bit masks
public static readonly int VersionMask = 0xC0;
public static readonly int PaddingMask = 0x20;
public static readonly int ExtensionFlagMask = 0x10;
public static readonly int CsrcCountMask = 0xF;
public static readonly int MarkerMask = 0x80;
public static readonly int PayloadTypeMask = 0x7F;
}
}
256 changes: 256 additions & 0 deletions PacketDotNet/RtpPacket.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
/*
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 PacketDotNet.Utils;
using PacketDotNet.Utils.Converters;
#if DEBUG
using log4net;
using System.Reflection;
#endif

namespace PacketDotNet
{
/// <summary>
/// Real-time Transport Protocol (RTP)
/// See: https://en.wikipedia.org/wiki/Real-time_Transport_Protocol
/// See: https://wiki.wireshark.org/RTP
/// </summary>
public sealed class RtpPacket : 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 RtpPacket()
{
Log.Debug("");

// allocate memory for this packet
var length = RtpFields.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 RtpPacket(ByteArraySegment byteArraySegment, Packet parentPacket)
{
Log.Debug("");

// set the header field, header field values are retrieved from this byte array
Header = new ByteArraySegment(byteArraySegment) {Length = RtpFields.HeaderLength};

if (CsrcCount > 0)
Header.Length += CsrcCount * RtpFields.CsrcIdLength;

if (HasExtension)
Header.Length += RtpFields.ProfileSpecificExtensionHeaderLength + RtpFields.ExtensionLengthLength +
ExtensionHeaderLength;

ParentPacket = parentPacket;

// store the payload bytes
PayloadPacketOrData = new LazySlim<PacketOrByteArraySegment>(() =>
{
var result = new PacketOrByteArraySegment {ByteArraySegment = Header.NextSegment()};
return result;
});
}

/// <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] & RtpFields.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 => RtpFields.PaddingMask == (Header.Bytes[Header.Offset] & RtpFields.PaddingMask);
set
{
if (value)
{
Header.Bytes[Header.Offset] |= (byte) RtpFields.PaddingMask;
}
else
{
Header.Bytes[Header.Offset] &= (byte) ~RtpFields.PaddingMask;
}
}
}

/// <summary>
/// Gets or sets the extension (1 bit). If the extension bit is set, the fixed header MUST be followed by exactly one header extension
/// </summary>
public bool HasExtension
{
get => RtpFields.ExtensionFlagMask == (Header.Bytes[Header.Offset] & RtpFields.ExtensionFlagMask);
set
{
if (value)
{
Header.Bytes[Header.Offset] |= (byte) RtpFields.ExtensionFlagMask;
}
else
{
Header.Bytes[Header.Offset] &= (byte) ~RtpFields.ExtensionFlagMask;
}
}
}

/// <summary>
/// Gets or sets the CSRC count (4 bits). The CSRC count contains the number of CSRC identifiers that follow the fixed header.
/// </summary>
public int CsrcCount
{
get => (Header.Bytes[Header.Offset] & RtpFields.CsrcCountMask);
set
{
Header.Bytes[Header.Offset] |= (byte) (value & RtpFields.CsrcCountMask);
Header.Length = RtpFields.HeaderLength + CsrcCount * RtpFields.CsrcIdLength;
if (HasExtension)
Header.Length += RtpFields.ProfileSpecificExtensionHeaderLength + RtpFields.ExtensionLengthLength +
ExtensionHeaderLength;
}
}

/// <summary>
/// Gets or sets the Marker (1 bit). The interpretation of the marker is defined by a profile.
/// </summary>
public bool Marker
{
get => RtpFields.MarkerMask == (Header.Bytes[Header.Offset + 1] & RtpFields.MarkerMask);
set
{
if (value)
{
Header.Bytes[Header.Offset + 1] |= (byte) RtpFields.MarkerMask;
}
else
{
Header.Bytes[Header.Offset + 1] &= (byte) ~RtpFields.MarkerMask;
}
}
}

/// <summary>
/// Gets or sets the payload type (7 bits) This field identifies the format of the RTP payload and determines its interpretation by the application.
/// </summary>
public int PayloadType
{
get => (Header.Bytes[Header.Offset + 1] & RtpFields.PayloadTypeMask);
set => Header.Bytes[Header.Offset + 1] |= (byte) (value & RtpFields.PayloadTypeMask);
}

/// <summary>
/// Gets or sets the sequence number (16 bits). The sequence number increments by one for each RTP data packet sent, and may be used by the receiver
/// to detect packet loss and to restore packet sequence.
/// </summary>
public ushort SequenceNumber
{
get => EndianBitConverter.Big.ToUInt16(Header.Bytes, Header.Offset + 2);
set => EndianBitConverter.Big.CopyBytes(value, Header.Bytes, Header.Offset + 2);
}

/// <summary>
/// Gets or sets the timestamp (32 bits). The timestamp reflects the sampling instant of the first octet in the RTP data packet.
/// </summary>
public uint Timestamp
{
get => EndianBitConverter.Big.ToUInt32(Header.Bytes, Header.Offset + 4);
set => EndianBitConverter.Big.CopyBytes(value, Header.Bytes, Header.Offset + 4);
}

/// <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 + 8);
set => EndianBitConverter.Big.CopyBytes(value, Header.Bytes, Header.Offset + 8);
}

/// <summary>
/// Gets or sets the CSRC list, 0 to 15 items, 32 bits each
/// The CSRC list identifies the contributing sources for the payload contained in this packet.
/// </summary>
public uint[] CsrcIdentifiers
{
get
{
var ids = new uint[CsrcCount];
for (int i = 0; i < CsrcCount; i++)
{
ids[i] = EndianBitConverter.Big.ToUInt32(Header.Bytes, Header.Offset + 12 + i*4);
}
return ids;
}

set
{
CsrcCount = value.Length;
for (int i = 0; i < value.Length; i++)
{
EndianBitConverter.Big.CopyBytes(value[i], Header.Bytes, Header.Offset + 12 + i*4);
}
}
}

/// <summary>
/// Gets or sets the length of the extension
/// </summary>
public ushort ExtensionHeaderLength {
get => HasExtension ? EndianBitConverter.Big.ToUInt16(Header.Bytes, Header.Offset + 12 + CsrcCount * 4 + RtpFields.ProfileSpecificExtensionHeaderLength) : (ushort)0;
set
{
if (value > 0)
{
EndianBitConverter.Big.CopyBytes(value, Header.Bytes, Header.Offset + 12 + CsrcCount * 4 + RtpFields.ProfileSpecificExtensionHeaderLength);
Header.Length = RtpFields.HeaderLength + CsrcCount * RtpFields.CsrcIdLength;
Header.Length += RtpFields.ProfileSpecificExtensionHeaderLength + RtpFields.ExtensionLengthLength +
value;
}
else
{
HasExtension = false;
Header.Length = RtpFields.HeaderLength + CsrcCount * RtpFields.CsrcIdLength;
}
}
}
}
}
Binary file added Test/CaptureFiles/ipv6_rtp.pcap
Binary file not shown.
100 changes: 100 additions & 0 deletions Test/PacketType/RtpPacketTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*
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 RtpPacketTest
{
// RTP
[Test]
public void RtpParsing()
{
var dev = new CaptureFileReaderDevice(NUnitSetupClass.CaptureDirectory + "ipv6_rtp.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 == 6000)
{
return new PacketOrByteArraySegment
{
Packet = new RtpPacket(segment, packet)
};
}
return null;
};

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

Assert.IsNotNull(p);

var rtp = p.Extract<RtpPacket>();
Assert.IsNotNull(rtp);
Console.WriteLine(rtp.GetType());
Assert.AreEqual(2, rtp.Version);
Assert.IsFalse(rtp.HasPadding);
Assert.IsFalse(rtp.HasExtension);
Assert.AreEqual(0, rtp.CsrcCount);
Assert.IsTrue(rtp.Marker);
Assert.AreEqual(112, rtp.PayloadType);
Assert.AreEqual(0, rtp.SequenceNumber);
Assert.AreEqual(600, 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);
}

[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);
}
}
}

0 comments on commit 29d96fb

Please sign in to comment.