diff --git a/.editorconfig b/.editorconfig
index da9ca7bc73..73bf192fdf 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -24,4 +24,22 @@ csharp_preserve_single_line_statements = true
csharp_preserve_single_line_blocks = true
# Don't allow single line if/else blocks without braces.
-csharp_prefer_braces = true:error
\ No newline at end of file
+csharp_prefer_braces = true:error
+
+# IDE0007: Use implicit type
+dotnet_diagnostic.IDE0007.severity = suggestion
+
+# IDE0019: Use pattern matching
+dotnet_diagnostic.IDE0019.severity = suggestion
+
+# IDE0030: Use coalesce expression
+dotnet_diagnostic.IDE0030.severity = suggestion
+
+# IDE0031: Use null propagation
+dotnet_diagnostic.IDE0031.severity = suggestion
+
+# IDE0078: Use pattern matching
+dotnet_diagnostic.IDE0078.severity = suggestion
+
+# IDE0083: Use pattern matching
+dotnet_diagnostic.IDE0083.severity = suggestion
diff --git a/src/SIPSorcery.csproj b/src/SIPSorcery.csproj
index b65e4315d0..78bf6aeac2 100755
--- a/src/SIPSorcery.csproj
+++ b/src/SIPSorcery.csproj
@@ -39,8 +39,9 @@
netstandard2.0;netstandard2.1;netcoreapp3.1;net462;net5.0;net6.0;net8.0
12.0
true
- $(NoWarn);SYSLIB0050
+ $(NoWarn);SYSLIB0050;CS1591;CS1573;CS1587
True
+ $(WarningsNotAsErrors);CS0809;CS0618;CS8632
true
$(NoWarn);CS1591;CS1573;CS1587
@@ -93,6 +94,7 @@
true
snupkg
true
+ true
diff --git a/src/app/Media/Codecs/AudioEncoder.cs b/src/app/Media/Codecs/AudioEncoder.cs
index 49489992cc..e776a9bbd0 100755
--- a/src/app/Media/Codecs/AudioEncoder.cs
+++ b/src/app/Media/Codecs/AudioEncoder.cs
@@ -18,6 +18,7 @@
using System.Collections.Generic;
using System.Linq;
using SIPSorceryMedia.Abstractions;
+using SIPSorcery.Sys;
using Concentus.Enums;
namespace SIPSorcery.Media
@@ -121,16 +122,13 @@ public byte[] EncodeAudio(short[] pcm, AudioFormat format)
}
else if (format.Codec == AudioCodecsEnum.L16)
{
- // When netstandard2.1 can be used.
- //return MemoryMarshal.Cast(pcm)
-
// Put on the wire in network byte order (big endian).
- return pcm.SelectMany(x => new byte[] { (byte)(x >> 8), (byte)(x) }).ToArray();
+ return MemoryOperations.ToBigEndianBytes(pcm);
}
else if (format.Codec == AudioCodecsEnum.PCM_S16LE)
{
// Put on the wire as little endian.
- return pcm.SelectMany(x => new byte[] { (byte)(x), (byte)(x >> 8) }).ToArray();
+ return MemoryOperations.ToLittleEndianBytes(pcm);
}
else if (format.Codec == AudioCodecsEnum.OPUS)
{
@@ -148,7 +146,7 @@ public byte[] EncodeAudio(short[] pcm, AudioFormat format)
byte[] encodedSample = new byte[pcm.Length];
int encodedLength = _opusEncoder.Encode(pcmFloat, pcmFloat.Length / format.ChannelCount, encodedSample, encodedSample.Length);
- return encodedSample.Take(encodedLength).ToArray();
+ return encodedSample.AsSpan(0, encodedLength).ToArray();
}
else
{
@@ -174,7 +172,7 @@ public short[] DecodeAudio(byte[] encodedSample, AudioFormat format)
short[] decodedPcm = new short[encodedSample.Length * 2];
int decodedSampleCount = _g722Decoder.Decode(_g722DecoderState, decodedPcm, encodedSample, encodedSample.Length);
- return decodedPcm.Take(decodedSampleCount).ToArray();
+ return decodedPcm.AsSpan(0, decodedSampleCount).ToArray();
}
if (format.Codec == AudioCodecsEnum.G729)
{
diff --git a/src/app/Media/Codecs/G729Encoder.cs b/src/app/Media/Codecs/G729Encoder.cs
index 20167d1eb1..20dc902fd4 100644
--- a/src/app/Media/Codecs/G729Encoder.cs
+++ b/src/app/Media/Codecs/G729Encoder.cs
@@ -55,7 +55,7 @@ public class G729Encoder : Ld8k
* Initialization of the coder.
*/
- private byte[] _leftover = new byte[0];
+ private byte[] _leftover = Array.Empty();
/**
* Init the Ld8k Coder
diff --git a/src/core/SIP/Channels/SIPClientWebSocketChannel.cs b/src/core/SIP/Channels/SIPClientWebSocketChannel.cs
index 423842a24f..41e111183e 100644
--- a/src/core/SIP/Channels/SIPClientWebSocketChannel.cs
+++ b/src/core/SIP/Channels/SIPClientWebSocketChannel.cs
@@ -376,7 +376,7 @@ private void MonitorReceiveTasks()
if (receiveTask.IsCompleted)
{
logger.LogDebug("Client web socket connection to {ServerUri} received {BytesReceived} bytes.", conn.ServerUri, receiveTask.Result.Count);
- //SIPMessageReceived(this, conn.LocalEndPoint, conn.RemoteEndPoint, conn.ReceiveBuffer.Take(receiveTask.Result.Count).ToArray()).Wait();
+ //SIPMessageReceived(this, conn.LocalEndPoint, conn.RemoteEndPoint, conn.ReceiveBuffer.AsSpan(0, receiveTask.Result.Count).ToArray()).Wait();
ExtractSIPMessages(this, conn, conn.ReceiveBuffer, receiveTask.Result.Count);
conn.ReceiveTask = conn.Client.ReceiveAsync(conn.ReceiveBuffer, m_cts.Token);
}
diff --git a/src/core/SIP/Channels/SIPWebSocketChannel.cs b/src/core/SIP/Channels/SIPWebSocketChannel.cs
index 4bf8842a31..33f1d77c56 100644
--- a/src/core/SIP/Channels/SIPWebSocketChannel.cs
+++ b/src/core/SIP/Channels/SIPWebSocketChannel.cs
@@ -122,7 +122,7 @@ protected override void OnError(ErrorEventArgs e)
public void Send(byte[] buffer, int offset, int length)
{
- base.Send(buffer.Skip(offset).Take(length).ToArray());
+ base.Send(buffer.AsSpan(offset, length).ToArray());
}
}
diff --git a/src/net/DtlsSrtp/DtlsSrtpTransport.cs b/src/net/DtlsSrtp/DtlsSrtpTransport.cs
index 22d24a07b6..44216fef70 100755
--- a/src/net/DtlsSrtp/DtlsSrtpTransport.cs
+++ b/src/net/DtlsSrtp/DtlsSrtpTransport.cs
@@ -18,6 +18,7 @@
using System;
using System.Collections.Concurrent;
+using System.ComponentModel;
using Microsoft.Extensions.Logging;
using Org.BouncyCastle.Tls;
using SIPSorcery.Sys;
@@ -482,11 +483,15 @@ public int GetSendLimit()
return this._sendLimit;
}
- public void WriteToRecvStream(byte[] buf)
+ [Obsolete("Use WriteToRecvStream(ReadOnlySpan) instead.", false)]
+ [EditorBrowsable(EditorBrowsableState.Advanced)]
+ public void WriteToRecvStream(byte[] buf) => WriteToRecvStream(buf.AsSpan());
+
+ public void WriteToRecvStream(ReadOnlySpan buf)
{
if (!_isClosed)
{
- _chunks.Add(buf);
+ _chunks.Add(buf.ToArray());
}
}
diff --git a/src/net/HEP/HepPacket.cs b/src/net/HEP/HepPacket.cs
index f2e2a89903..6eaca2f74d 100755
--- a/src/net/HEP/HepPacket.cs
+++ b/src/net/HEP/HepPacket.cs
@@ -316,7 +316,7 @@ public static byte[] GetBytes(SIPEndPoint srcEndPoint, SIPEndPoint dstEndPoint,
Buffer.BlockCopy(BitConverter.GetBytes((ushort)offset), 0, packetBuffer, 4, 2);
}
- return packetBuffer.Take(offset).ToArray();
+ return packetBuffer.AsSpan(0, offset).ToArray();
}
}
}
diff --git a/src/net/ICE/RtpIceChannel.cs b/src/net/ICE/RtpIceChannel.cs
index 6baad2535f..0913720aad 100755
--- a/src/net/ICE/RtpIceChannel.cs
+++ b/src/net/ICE/RtpIceChannel.cs
@@ -904,7 +904,7 @@ public void AddRemoteCandidate(RTCIceCandidate candidate)
{
OnIceCandidateError?.Invoke(candidate, $"Remote ICE candidate had an invalid port {candidate.port}.");
}
- else if(IPAddress.TryParse(candidate.address, out var addrIPv6) &&
+ else if (IPAddress.TryParse(candidate.address, out var addrIPv6) &&
addrIPv6.AddressFamily == AddressFamily.InterNetworkV6 &&
!Socket.OSSupportsIPv6 &&
NetServices.HasActiveIPv6Address())
@@ -1123,7 +1123,7 @@ private void CheckIceServers(Object state)
{
if (_activeIceServer == null || _activeIceServer.Error != SocketError.Success)
{
- if (_iceServerResolver.IceServers.Count(x => x.Value.Error == SocketError.Success) == 0)
+ if (!_iceServerResolver.IceServers.Any(x => x.Value.Error == SocketError.Success))
{
logger.LogDebug("RTP ICE Channel all ICE server connection checks failed, stopping ICE servers timer.");
_processIceServersTimer.Dispose();
@@ -1136,7 +1136,7 @@ private void CheckIceServers(Object state)
.OrderByDescending(x => x.Value._uri.Scheme) // TURN serves take priority.
.FirstOrDefault();
- if (!entry.Equals(default(KeyValuePair)))
+ if (entry.Key is not null && entry.Value is not null)
{
_activeIceServer = entry.Value;
}
@@ -2168,7 +2168,7 @@ private ChecklistEntry GetChecklistEntryForStunResponse(byte[] transactionID)
/// If found a matching state object or null if not.
private IceServer GetIceServerForTransactionID(byte[] transactionID)
{
- if (_iceServerResolver.IceServers.Count() == 0)
+ if (_iceServerResolver.IceServers.Count == 0)
{
return null;
}
@@ -2180,7 +2180,7 @@ private IceServer GetIceServerForTransactionID(byte[] transactionID)
.Where(x => x.Value.IsTransactionIDMatch(txID))
.SingleOrDefault();
- if (!entry.Equals(default(KeyValuePair)))
+ if (entry.Key is not null && entry.Value is not null)
{
return entry.Value;
}
@@ -2576,7 +2576,7 @@ private async Task ResolveMdnsName(RTCIceCandidate candidate)
{
if (MdnsResolve != null)
{
- logger.LogWarning("RTP ICE channel has both "+ nameof(MdnsGetAddresses) + " and " + nameof(MdnsGetAddresses) + " set. Only " + nameof(MdnsGetAddresses) + " will be used.");
+ logger.LogWarning("RTP ICE channel has both " + nameof(MdnsGetAddresses) + " and " + nameof(MdnsGetAddresses) + " set. Only " + nameof(MdnsGetAddresses) + " will be used.");
}
return await MdnsGetAddresses(candidate.address).ConfigureAwait(false);
}
diff --git a/src/net/RTCP/RTCPBye.cs b/src/net/RTCP/RTCPBye.cs
index 56158e640c..f1e9ab51d3 100644
--- a/src/net/RTCP/RTCPBye.cs
+++ b/src/net/RTCP/RTCPBye.cs
@@ -1,4 +1,4 @@
-//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
// Filename: RTCPBye.cs
//
// Description: RTCP Goodbye packet as defined in RFC3550.
@@ -27,6 +27,9 @@
//-----------------------------------------------------------------------------
using System;
+using System.Buffers.Binary;
+using System.ComponentModel;
+using System.Diagnostics;
using System.Text;
using SIPSorcery.Sys;
@@ -73,7 +76,17 @@ public RTCPBye(uint ssrc, string reason)
/// Create a new RTCP Goodbye packet from a serialised byte array.
///
/// The byte array holding the Goodbye packet.
- public RTCPBye(byte[] packet)
+ [Obsolete("Use RTCPBye(ReadOnlySpan packet) instead.", false)]
+ [EditorBrowsable(EditorBrowsableState.Advanced)]
+ public RTCPBye(byte[] packet) : this(new ReadOnlySpan(packet))
+ {
+ }
+
+ ///
+ /// Create a new RTCP Goodbye packet from a serialised byte array.
+ ///
+ /// The byte array holding the Goodbye packet.
+ public RTCPBye(ReadOnlySpan packet)
{
if (packet.Length < MIN_PACKET_SIZE)
{
@@ -82,14 +95,7 @@ public RTCPBye(byte[] packet)
Header = new RTCPHeader(packet);
- if (BitConverter.IsLittleEndian)
- {
- SSRC = NetConvert.DoReverseEndian(BitConverter.ToUInt32(packet, 4));
- }
- else
- {
- SSRC = BitConverter.ToUInt32(packet, 4);
- }
+ SSRC = BinaryPrimitives.ReadUInt32BigEndian(packet.Slice(4));
if (packet.Length > MIN_PACKET_SIZE)
{
@@ -97,41 +103,52 @@ public RTCPBye(byte[] packet)
if (packet.Length - MIN_PACKET_SIZE - 1 >= reasonLength)
{
- Reason = Encoding.UTF8.GetString(packet, 9, reasonLength);
+ Reason = Encoding.UTF8.GetString(packet.Slice(9, reasonLength));
}
}
}
+ public int GetPacketSize() => RTCPHeader.HEADER_BYTES_LENGTH + GetPaddedLength(((Reason is not null) ? Encoding.UTF8.GetByteCount(Reason) : 0));
+
///
/// Gets the raw bytes for the Goodbye packet.
///
/// A byte array.
public byte[] GetBytes()
{
- byte[] reasonBytes = (Reason != null) ? Encoding.UTF8.GetBytes(Reason) : null;
- int reasonLength = (reasonBytes != null) ? reasonBytes.Length : 0;
- byte[] buffer = new byte[RTCPHeader.HEADER_BYTES_LENGTH + GetPaddedLength(reasonLength)];
- Header.SetLength((ushort)(buffer.Length / 4 - 1));
+ var buffer = new byte[GetPacketSize()];
- Buffer.BlockCopy(Header.GetBytes(), 0, buffer, 0, RTCPHeader.HEADER_BYTES_LENGTH);
- int payloadIndex = RTCPHeader.HEADER_BYTES_LENGTH;
+ WriteBytesCore(buffer);
- if (BitConverter.IsLittleEndian)
- {
- Buffer.BlockCopy(BitConverter.GetBytes(NetConvert.DoReverseEndian(SSRC)), 0, buffer, payloadIndex, 4);
- }
- else
+ return buffer;
+ }
+
+ public int WriteBytes(Span buffer)
+ {
+ var size = GetPacketSize();
+
+ if (buffer.Length < size)
{
- Buffer.BlockCopy(BitConverter.GetBytes(SSRC), 0, buffer, payloadIndex, 4);
+ throw new ArgumentOutOfRangeException($"The buffer should have at least {size} bytes and had only {buffer.Length}.");
}
- if (reasonLength > 0)
+ WriteBytesCore(buffer.Slice(0, size));
+
+ return size;
+ }
+
+ private void WriteBytesCore(Span buffer)
+ {
+ Header.SetLength((ushort)(buffer.Length / 4 - 1));
+ _ = Header.WriteBytes(buffer);
+
+ BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(RTCPHeader.HEADER_BYTES_LENGTH), SSRC);
+
+ if (Reason is not null)
{
- buffer[payloadIndex + 4] = (byte)reasonLength;
- Buffer.BlockCopy(reasonBytes, 0, buffer, payloadIndex + 5, reasonBytes.Length);
+ buffer[RTCPHeader.HEADER_BYTES_LENGTH + 4] =
+ (byte)Encoding.UTF8.GetBytes(Reason.AsSpan(), buffer.Slice(RTCPHeader.HEADER_BYTES_LENGTH + 5));
}
-
- return buffer;
}
///
diff --git a/src/net/RTCP/RTCPCompoundPacket.cs b/src/net/RTCP/RTCPCompoundPacket.cs
index 780cd9fa0f..019c50665e 100644
--- a/src/net/RTCP/RTCPCompoundPacket.cs
+++ b/src/net/RTCP/RTCPCompoundPacket.cs
@@ -16,6 +16,7 @@
using System;
using System.Collections.Generic;
+using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Text;
@@ -62,9 +63,19 @@ public RTCPCompoundPacket(RTCPReceiverReport receiverReport, RTCPSDesReport sdes
/// Creates a new RTCP compound packet from a serialised buffer.
///
/// The serialised RTCP compound packet to parse.
- public RTCPCompoundPacket(byte[] packet)
+ [Obsolete("Use RTCPCompoundPacket(ReadOnlySpan packet) instead.", false)]
+ [EditorBrowsable(EditorBrowsableState.Advanced)]
+ public RTCPCompoundPacket(byte[] packet) : this(new ReadOnlySpan(packet))
{
- int offset = 0;
+ }
+
+ ///
+ /// Creates a new RTCP compound packet from a serialised buffer.
+ ///
+ /// The serialised RTCP compound packet to parse.
+ public RTCPCompoundPacket(ReadOnlySpan packet)
+ {
+ var offset = 0;
while (offset < packet.Length)
{
if (packet.Length - offset < RTCPHeader.HEADER_BYTES_LENGTH)
@@ -74,43 +85,44 @@ public RTCPCompoundPacket(byte[] packet)
}
else
{
- var buffer = packet.Skip(offset).ToArray();
+ var buffer = packet.Slice(offset);
// The payload type field is the second byte in the RTCP header.
- byte packetTypeID = buffer[1];
+ var packetTypeID = buffer[1];
switch (packetTypeID)
{
case (byte)RTCPReportTypesEnum.SR:
SenderReport = new RTCPSenderReport(buffer);
- int srLength = (SenderReport != null) ? SenderReport.GetBytes().Length : Int32.MaxValue;
+ var srLength = SenderReport.GetPacketSize();
offset += srLength;
break;
case (byte)RTCPReportTypesEnum.RR:
ReceiverReport = new RTCPReceiverReport(buffer);
- int rrLength = (ReceiverReport != null) ? ReceiverReport.GetBytes().Length : Int32.MaxValue;
+ var rrLength = ReceiverReport.GetPacketSize();
offset += rrLength;
break;
case (byte)RTCPReportTypesEnum.SDES:
SDesReport = new RTCPSDesReport(buffer);
- int sdesLength = (SDesReport != null) ? SDesReport.GetBytes().Length : Int32.MaxValue;
+ var sdesLength = SDesReport.GetPacketSize();
offset += sdesLength;
break;
case (byte)RTCPReportTypesEnum.BYE:
Bye = new RTCPBye(buffer);
- int byeLength = (Bye != null) ? Bye.GetBytes().Length : Int32.MaxValue;
+ var byeLength = Bye.GetPacketSize();
offset += byeLength;
break;
case (byte)RTCPReportTypesEnum.RTPFB:
var typ = RTCPHeader.ParseFeedbackType(buffer);
- switch (typ) {
+ switch (typ)
+ {
case RTCPFeedbackTypesEnum.TWCC:
TWCCFeedback = new RTCPTWCCFeedback(buffer);
- int twccFeedbackLength = (TWCCFeedback.Header.Length + 1) * 4;
+ var twccFeedbackLength = (TWCCFeedback.Header.Length + 1) * 4;
offset += twccFeedbackLength;
break;
default:
Feedback = new RTCPFeedback(buffer);
- int rtpfbFeedbackLength = Feedback.GetBytes().Length;
+ var rtpfbFeedbackLength = Feedback.GetPacketSize();
offset += rtpfbFeedbackLength;
break;
}
@@ -118,7 +130,7 @@ public RTCPCompoundPacket(byte[] packet)
case (byte)RTCPReportTypesEnum.PSFB:
// TODO: Interpret Payload specific feedback reports.
Feedback = new RTCPFeedback(buffer);
- int psfbFeedbackLength = (Feedback != null) ? Feedback.GetBytes().Length : Int32.MaxValue;
+ var psfbFeedbackLength = Feedback.GetPacketSize();
offset += psfbFeedbackLength;
//var psfbHeader = new RTCPHeader(buffer);
//offset += psfbHeader.Length * 4 + 4;
@@ -132,31 +144,82 @@ public RTCPCompoundPacket(byte[] packet)
}
}
+ // TODO: optimize this
+ public int GetPacketSize() =>
+ (SenderReport?.GetPacketSize()).GetValueOrDefault() +
+ (ReceiverReport?.GetPacketSize()).GetValueOrDefault() +
+ SDesReport.GetPacketSize() +
+ (Bye?.GetPacketSize()).GetValueOrDefault();
+
///
/// Serialises a compound RTCP packet to a byte array ready for transmission.
///
/// A byte array representing a serialised compound RTCP packet.
public byte[] GetBytes()
{
- if (SenderReport == null && ReceiverReport == null)
+ if (SenderReport is null && ReceiverReport is null)
{
- throw new ApplicationException("An RTCP compound packet must have either a Sender or Receiver report set.");
+ throw new InvalidOperationException("An RTCP compound packet must have either a Sender or Receiver report set.");
}
- else if (SDesReport == null)
+ else if (SDesReport is null)
{
- throw new ApplicationException("An RTCP compound packet must have an SDES report set.");
+ throw new InvalidOperationException("An RTCP compound packet must have an SDES report set.");
}
- List compoundBuffer = new List();
- compoundBuffer.AddRange((SenderReport != null) ? SenderReport.GetBytes() : ReceiverReport.GetBytes());
- compoundBuffer.AddRange(SDesReport.GetBytes());
+ var size = GetPacketSize();
- if (Bye != null)
+ var buffer = new byte[size];
+
+ WriteBytesCore(buffer);
+
+ return buffer;
+ }
+
+ public int WriteBytes(Span buffer)
+ {
+ if (SenderReport is null && ReceiverReport is null)
+ {
+ throw new InvalidOperationException("An RTCP compound packet must have either a Sender or Receiver report set.");
+ }
+ else if (SDesReport is null)
+ {
+ throw new InvalidOperationException("An RTCP compound packet must have an SDES report set.");
+ }
+
+ var size = GetPacketSize();
+
+ if (buffer.Length < size)
{
- compoundBuffer.AddRange(Bye.GetBytes());
+ throw new ArgumentOutOfRangeException($"The buffer should have at least {size} bytes and had only {buffer.Length}.");
}
- return compoundBuffer.ToArray();
+ WriteBytesCore(buffer.Slice(0, size));
+
+ return size;
+ }
+
+ private void WriteBytesCore(Span buffer)
+ {
+ if (SenderReport is not null)
+ {
+ var bytesWritten = SenderReport.WriteBytes(buffer);
+ buffer = buffer.Slice(bytesWritten);
+ }
+ else
+ {
+ var bytesWritten = ReceiverReport.WriteBytes(buffer);
+ buffer = buffer.Slice(bytesWritten);
+ }
+
+ {
+ var bytesWritten = SDesReport.WriteBytes(buffer);
+ buffer = buffer.Slice(bytesWritten);
+ }
+
+ if (Bye != null)
+ {
+ var bytesWritten = Bye.WriteBytes(buffer);
+ }
}
public string GetDebugSummary()
@@ -202,13 +265,21 @@ public string GetDebugSummary()
return sb.ToString().TrimEnd('\n');
}
+ ///
+ /// Creates a new RTCP compound packet from a serialised buffer.
+ ///
+ ///
+ ///
+ ///
+ /// The amount read from the packet
+ [Obsolete("Use TryParse(ReadOnlySpan, RTCPCompoundPacket, int) instead.", false)]
+ [EditorBrowsable(EditorBrowsableState.Advanced)]
public static bool TryParse(
- ReadOnlySpan packet,
- out RTCPCompoundPacket rtcpCompoundPacket,
+ byte[] packet,
+ RTCPCompoundPacket rtcpCompoundPacket,
out int consumed)
{
- rtcpCompoundPacket = new RTCPCompoundPacket();
- return TryParse(packet.ToArray(), rtcpCompoundPacket, out consumed);
+ return TryParse(packet.AsSpan(), rtcpCompoundPacket, out consumed);
}
///
@@ -219,7 +290,7 @@ public static bool TryParse(
///
/// The amount read from the packet
public static bool TryParse(
- byte[] packet,
+ ReadOnlySpan packet,
RTCPCompoundPacket rtcpCompoundPacket,
out int consumed)
{
@@ -227,74 +298,92 @@ public static bool TryParse(
{
rtcpCompoundPacket = new RTCPCompoundPacket();
}
- int offset = 0;
+
+ var offset = 0;
+
while (offset < packet.Length)
{
if (packet.Length - offset < RTCPHeader.HEADER_BYTES_LENGTH)
{
- // Not enough bytes left for a RTCP header.
break;
}
else
{
- var buffer = packet.Skip(offset).ToArray();
+ var buffer = packet.Slice(offset);
+ var packetTypeID = buffer[1];
- // The payload type field is the second byte in the RTCP header.
- byte packetTypeID = buffer[1];
switch (packetTypeID)
{
case (byte)RTCPReportTypesEnum.SR:
- rtcpCompoundPacket.SenderReport = new RTCPSenderReport(buffer);
- int srLength = (rtcpCompoundPacket.SenderReport != null) ? rtcpCompoundPacket.SenderReport.GetBytes().Length : Int32.MaxValue;
- offset += srLength;
- break;
+ {
+ var report = new RTCPSenderReport(buffer);
+ rtcpCompoundPacket.SenderReport = report;
+ var length = report?.GetPacketSize() ?? int.MaxValue;
+ offset += length;
+ break;
+ }
case (byte)RTCPReportTypesEnum.RR:
- rtcpCompoundPacket.ReceiverReport = new RTCPReceiverReport(buffer);
- int rrLength = (rtcpCompoundPacket.ReceiverReport != null) ? rtcpCompoundPacket.ReceiverReport.GetBytes().Length : Int32.MaxValue;
- offset += rrLength;
- break;
+ {
+ var report = new RTCPReceiverReport(buffer);
+ rtcpCompoundPacket.ReceiverReport = report;
+ var length = report?.GetPacketSize() ?? int.MaxValue;
+ offset += length;
+ break;
+ }
case (byte)RTCPReportTypesEnum.SDES:
- rtcpCompoundPacket.SDesReport = new RTCPSDesReport(buffer);
- int sdesLength = (rtcpCompoundPacket.SDesReport != null) ? rtcpCompoundPacket.SDesReport.GetBytes().Length : Int32.MaxValue;
- offset += sdesLength;
- break;
+ {
+ var report = new RTCPSDesReport(buffer);
+ rtcpCompoundPacket.SDesReport = report;
+ var length = report?.GetPacketSize() ?? int.MaxValue;
+ offset += length;
+ break;
+ }
case (byte)RTCPReportTypesEnum.BYE:
- rtcpCompoundPacket.Bye = new RTCPBye(buffer);
- int byeLength = (rtcpCompoundPacket.Bye != null) ? rtcpCompoundPacket.Bye.GetBytes().Length : Int32.MaxValue;
- offset += byeLength;
- break;
+ {
+ var report = new RTCPBye(buffer);
+ rtcpCompoundPacket.Bye = report;
+ var length = report?.GetPacketSize() ?? int.MaxValue;
+ offset += length;
+ break;
+ }
case (byte)RTCPReportTypesEnum.RTPFB:
- var typ = RTCPHeader.ParseFeedbackType(buffer);
- switch (typ)
{
- default:
- {
- rtcpCompoundPacket.Feedback = new RTCPFeedback(buffer);
- int rtpfbFeedbackLength = (rtcpCompoundPacket.Feedback != null) ? rtcpCompoundPacket.Feedback.GetBytes().Length : Int32.MaxValue;
- offset += rtpfbFeedbackLength;
- }
- break;
- case RTCPFeedbackTypesEnum.TWCC:
- {
- rtcpCompoundPacket.TWCCFeedback = new RTCPTWCCFeedback(buffer);
- int twccFeedbackLength = (rtcpCompoundPacket.TWCCFeedback != null) ? rtcpCompoundPacket.TWCCFeedback.GetBytes().Length : Int32.MaxValue;
- offset += twccFeedbackLength;
- }
- break;
+ var typ = RTCPHeader.ParseFeedbackType(buffer);
+ switch (typ)
+ {
+ default:
+ {
+ var feedback = new RTCPFeedback(buffer);
+ rtcpCompoundPacket.Feedback = feedback;
+ var length = feedback?.GetPacketSize() ?? int.MaxValue;
+ offset += length;
+ break;
+ }
+ case RTCPFeedbackTypesEnum.TWCC:
+ {
+ var feedback = new RTCPTWCCFeedback(buffer);
+ rtcpCompoundPacket.TWCCFeedback = feedback;
+ var length = feedback?.GetPacketSize() ?? int.MaxValue;
+ offset += length;
+ break;
+ }
+ }
+ break;
}
- break;
case (byte)RTCPReportTypesEnum.PSFB:
- // TODO: Interpret Payload specific feedback reports.
- rtcpCompoundPacket.Feedback = new RTCPFeedback(buffer);
- int psfbFeedbackLength = (rtcpCompoundPacket.Feedback != null) ? rtcpCompoundPacket.Feedback.GetBytes().Length : Int32.MaxValue;
- offset += psfbFeedbackLength;
- //var psfbHeader = new RTCPHeader(buffer);
- //offset += psfbHeader.Length * 4 + 4;
- break;
+ {
+ var feedback = new RTCPFeedback(buffer);
+ rtcpCompoundPacket.Feedback = feedback;
+ var length = feedback?.GetPacketSize() ?? int.MaxValue;
+ offset += length;
+ break;
+ }
default:
- offset = Int32.MaxValue;
- logger.LogWarning("RTCPCompoundPacket did not recognise packet type ID {PacketTypeID}. {Packet}", packetTypeID, packet.HexStr());
- break;
+ {
+ offset = int.MaxValue;
+ logger.LogWarning("RTCPCompoundPacket did not recognise packet type ID {PacketTypeID}. {Packet}", packetTypeID, packet.HexStr());
+ break;
+ }
}
}
}
diff --git a/src/net/RTCP/RTCPFeedback.cs b/src/net/RTCP/RTCPFeedback.cs
index c5e462cfe1..d39bd6081d 100644
--- a/src/net/RTCP/RTCPFeedback.cs
+++ b/src/net/RTCP/RTCPFeedback.cs
@@ -27,6 +27,9 @@
//-----------------------------------------------------------------------------
using System;
+using System.Buffers.Binary;
+using System.ComponentModel;
+using System.Text;
using Microsoft.Extensions.Logging;
using SIPSorcery.Sys;
@@ -146,21 +149,23 @@ public RTCPFeedback(uint senderSsrc, uint mediaSsrc, PSFBFeedbackTypesEnum feedb
/// Create a new RTCP Report from a serialised byte array.
///
/// The byte array holding the serialised feedback report.
- public RTCPFeedback(byte[] packet)
+ [Obsolete("Use RTCPFeedback(ReadOnlySpan packet) instead.", false)]
+ [EditorBrowsable(EditorBrowsableState.Advanced)]
+ public RTCPFeedback(byte[] packet) : this(new ReadOnlySpan(packet))
+ {
+ }
+
+ ///
+ /// Create a new RTCP Report from a serialised byte array.
+ ///
+ /// The byte array holding the serialised feedback report.
+ public RTCPFeedback(ReadOnlySpan packet)
{
Header = new RTCPHeader(packet);
- int payloadIndex = RTCPHeader.HEADER_BYTES_LENGTH;
- if (BitConverter.IsLittleEndian)
- {
- SenderSSRC = NetConvert.DoReverseEndian(BitConverter.ToUInt32(packet, payloadIndex));
- MediaSSRC = NetConvert.DoReverseEndian(BitConverter.ToUInt32(packet, payloadIndex + 4));
- }
- else
- {
- SenderSSRC = BitConverter.ToUInt32(packet, payloadIndex);
- MediaSSRC = BitConverter.ToUInt32(packet, payloadIndex + 4);
- }
+ var payloadIndex = RTCPHeader.HEADER_BYTES_LENGTH;
+ SenderSSRC = BinaryPrimitives.ReadUInt32BigEndian(packet.Slice(payloadIndex));
+ MediaSSRC = BinaryPrimitives.ReadUInt32BigEndian(packet.Slice(payloadIndex + 4));
switch (Header)
{
@@ -170,16 +175,8 @@ public RTCPFeedback(byte[] packet)
break;
case var x when x.PacketType == RTCPReportTypesEnum.RTPFB:
SENDER_PAYLOAD_SIZE = 12;
- if (BitConverter.IsLittleEndian)
- {
- PID = NetConvert.DoReverseEndian(BitConverter.ToUInt16(packet, payloadIndex + 8));
- BLP = NetConvert.DoReverseEndian(BitConverter.ToUInt16(packet, payloadIndex + 10));
- }
- else
- {
- PID = BitConverter.ToUInt16(packet, payloadIndex + 8);
- BLP = BitConverter.ToUInt16(packet, payloadIndex + 10);
- }
+ PID = BinaryPrimitives.ReadUInt16BigEndian(packet.Slice(payloadIndex + 8));
+ BLP = BinaryPrimitives.ReadUInt16BigEndian(packet.Slice(payloadIndex + 10));
break;
case var x when x.PacketType == RTCPReportTypesEnum.PSFB && x.PayloadFeedbackMessageType == PSFBFeedbackTypesEnum.PLI:
@@ -202,10 +199,10 @@ public RTCPFeedback(byte[] packet)
SENDER_PAYLOAD_SIZE = 8 + 12; // 8 bytes from (SenderSSRC + MediaSSRC) + extra 12 bytes from REMB Definition
var currentCounter = payloadIndex + 8;
- UniqueID = System.Text.ASCIIEncoding.ASCII.GetString(packet, currentCounter, 4);
+ UniqueID = Encoding.ASCII.GetString(packet.Slice(currentCounter, 4));
currentCounter += 4;
- if (string.Equals(UniqueID,"REMB", StringComparison.CurrentCultureIgnoreCase))
+ if (string.Equals(UniqueID, "REMB", StringComparison.CurrentCultureIgnoreCase))
{
// Read first 8 bits
NumSsrcs = packet[currentCounter];
@@ -215,26 +212,12 @@ public RTCPFeedback(byte[] packet)
BitrateExp = (byte)(packet[currentCounter] >> 2);
// Now read next 18 bits
- var remaininMantissaBytes = new byte[] { (byte)0, (byte)(packet[currentCounter] & 3), packet[currentCounter + 1], packet[currentCounter + 2] };
- if (BitConverter.IsLittleEndian)
- {
- BitrateMantissa = NetConvert.DoReverseEndian(BitConverter.ToUInt32(remaininMantissaBytes, 0));
- }
- else
- {
- BitrateMantissa = BitConverter.ToUInt32(remaininMantissaBytes, 0);
- }
-
- currentCounter += 3;
-
- if (BitConverter.IsLittleEndian)
- {
- FeedbackSSRC = NetConvert.DoReverseEndian(BitConverter.ToUInt32(packet, currentCounter));
- }
- else
- {
- FeedbackSSRC = BitConverter.ToUInt32(packet, currentCounter);
- }
+ BitrateMantissa =
+ ((((uint)packet[currentCounter++]) & 0b11U) << 16) |
+ (((uint)packet[currentCounter++]) << 8) |
+ ((uint)packet[currentCounter++]);
+
+ FeedbackSSRC = BinaryPrimitives.ReadUInt32BigEndian(packet.Slice(currentCounter));
}
break;
@@ -244,53 +227,59 @@ public RTCPFeedback(byte[] 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| FMT | PT | length |
- //+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- //| SSRC of packet sender |
- //+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- //| SSRC of media source |
- //+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- //: Feedback Control Information(FCI) :
- //: :
+ public int GetPacketSize() => RTCPHeader.HEADER_BYTES_LENGTH + SENDER_PAYLOAD_SIZE;
+
+ //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| FMT | PT | length |
+ //+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ //| SSRC of packet sender |
+ //+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ //| SSRC of media source |
+ //+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ //: Feedback Control Information(FCI) :
+ //: :
public byte[] GetBytes()
{
- byte[] buffer = new byte[RTCPHeader.HEADER_BYTES_LENGTH + SENDER_PAYLOAD_SIZE];
- Header.SetLength((ushort)(buffer.Length / 4 - 1));
+ var buffer = new byte[GetPacketSize()];
- Buffer.BlockCopy(Header.GetBytes(), 0, buffer, 0, RTCPHeader.HEADER_BYTES_LENGTH);
- int payloadIndex = RTCPHeader.HEADER_BYTES_LENGTH;
+ WriteBytesCore(buffer);
- // All feedback packets require the Sender and Media SSRC's to be set.
- if (BitConverter.IsLittleEndian)
- {
- Buffer.BlockCopy(BitConverter.GetBytes(NetConvert.DoReverseEndian(SenderSSRC)), 0, buffer, payloadIndex, 4);
- Buffer.BlockCopy(BitConverter.GetBytes(NetConvert.DoReverseEndian(MediaSSRC)), 0, buffer, payloadIndex + 4, 4);
- }
- else
+ return buffer;
+ }
+
+ public int WriteBytes(Span buffer)
+ {
+ var size = GetPacketSize();
+
+ if (buffer.Length < size)
{
- Buffer.BlockCopy(BitConverter.GetBytes(SenderSSRC), 0, buffer, payloadIndex, 4);
- Buffer.BlockCopy(BitConverter.GetBytes(MediaSSRC), 0, buffer, payloadIndex + 4, 4);
+ throw new ArgumentOutOfRangeException($"The buffer should have at least {size} bytes and had only {buffer.Length}.");
}
+ WriteBytesCore(buffer.Slice(0, size));
+
+ return size;
+ }
+
+ private void WriteBytesCore(Span buffer)
+ {
+ Header.SetLength((ushort)(buffer.Length / 4 - 1));
+ _ = Header.WriteBytes(buffer);
+
+ // All feedback packets require the Sender and Media SSRC's to be set.
+ BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(RTCPHeader.HEADER_BYTES_LENGTH), SenderSSRC);
+ BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(RTCPHeader.HEADER_BYTES_LENGTH + 4), MediaSSRC);
+
switch (Header)
{
case var x when x.PacketType == RTCPReportTypesEnum.RTPFB && x.FeedbackMessageType == RTCPFeedbackTypesEnum.RTCP_SR_REQ:
// PLI feedback reports do no have any additional parameters.
break;
case var x when x.PacketType == RTCPReportTypesEnum.RTPFB:
- if (BitConverter.IsLittleEndian)
- {
- Buffer.BlockCopy(BitConverter.GetBytes(NetConvert.DoReverseEndian(PID)), 0, buffer, payloadIndex + 8, 2);
- Buffer.BlockCopy(BitConverter.GetBytes(NetConvert.DoReverseEndian(BLP)), 0, buffer, payloadIndex + 10, 2);
- }
- else
- {
- Buffer.BlockCopy(BitConverter.GetBytes(PID), 0, buffer, payloadIndex + 8, 2);
- Buffer.BlockCopy(BitConverter.GetBytes(BLP), 0, buffer, payloadIndex + 10, 2);
- }
+ BinaryPrimitives.WriteUInt16BigEndian(buffer.Slice(RTCPHeader.HEADER_BYTES_LENGTH + 8), PID);
+ BinaryPrimitives.WriteUInt16BigEndian(buffer.Slice(RTCPHeader.HEADER_BYTES_LENGTH + 10), BLP);
break;
case var x when x.PacketType == RTCPReportTypesEnum.PSFB && x.PayloadFeedbackMessageType == PSFBFeedbackTypesEnum.PLI:
@@ -308,51 +297,30 @@ public byte[] GetBytes()
//| Num SSRC | BR Exp | BR Mantissa |
//+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//| SSRC feedback |
- var currentCounter = payloadIndex + 8;
//Fix UniqueID Size
- if(string.IsNullOrEmpty(UniqueID))
+ if (string.IsNullOrEmpty(UniqueID))
{
- UniqueID = System.Text.ASCIIEncoding.ASCII.GetString(new byte[4]);
+ UniqueID = new string('\0', 4);
}
while (UniqueID.Length < 4)
{
UniqueID += (char)0;
}
- Buffer.BlockCopy(System.Text.ASCIIEncoding.ASCII.GetBytes(UniqueID), 0, buffer, currentCounter, 4);
- currentCounter += 4;
+ Encoding.ASCII.GetBytes(UniqueID.AsSpan(), buffer.Slice(RTCPHeader.HEADER_BYTES_LENGTH + 8));
- if (string.Equals(UniqueID, "REMB", StringComparison.CurrentCultureIgnoreCase))
+ if (string.Equals(UniqueID, "REMB", StringComparison.OrdinalIgnoreCase))
{
// Copy NumSsrcs
- buffer[currentCounter] = NumSsrcs;
- currentCounter++;
-
-
- byte[] remaininMantissaBytes;
- if (BitConverter.IsLittleEndian)
- {
- remaininMantissaBytes = BitConverter.GetBytes(NetConvert.DoReverseEndian(BitrateMantissa));
- }
- else
- {
- remaininMantissaBytes = BitConverter.GetBytes(BitrateMantissa);
- }
- buffer[currentCounter] = (byte)((BitrateExp << 2) | (remaininMantissaBytes[1] & 3));
- buffer[currentCounter + 1] = remaininMantissaBytes[2];
- buffer[currentCounter + 2] = remaininMantissaBytes[3];
-
- currentCounter += 3;
-
- if (BitConverter.IsLittleEndian)
- {
- Buffer.BlockCopy(BitConverter.GetBytes(NetConvert.DoReverseEndian(FeedbackSSRC)), 0, buffer, currentCounter, 4);
- }
- else
- {
- Buffer.BlockCopy(BitConverter.GetBytes(FeedbackSSRC), 0, buffer, currentCounter, 4);
- }
+ buffer[RTCPHeader.HEADER_BYTES_LENGTH + 12] = NumSsrcs;
+
+ var temp = BitrateMantissa;
+ buffer[RTCPHeader.HEADER_BYTES_LENGTH + 15] = (byte)temp;
+ buffer[RTCPHeader.HEADER_BYTES_LENGTH + 14] = (byte)(temp >>= 8);
+ buffer[RTCPHeader.HEADER_BYTES_LENGTH + 13] = (byte)((BitrateExp << 2) | (byte)((temp >>= 8) & 0b11));
+
+ BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(RTCPHeader.HEADER_BYTES_LENGTH + 16), FeedbackSSRC);
}
break;
@@ -362,7 +330,6 @@ public byte[] GetBytes()
//throw new NotImplementedException($"Serialisation for feedback report {Header.PacketType} and message type "
//+ $"{Header.FeedbackMessageType} not yet implemented.");
}
- return buffer;
}
}
}
diff --git a/src/net/RTCP/RTCPHeader.cs b/src/net/RTCP/RTCPHeader.cs
index 1c57ec3042..759c2bc56e 100644
--- a/src/net/RTCP/RTCPHeader.cs
+++ b/src/net/RTCP/RTCPHeader.cs
@@ -33,6 +33,8 @@
//-----------------------------------------------------------------------------
using System;
+using System.Buffers.Binary;
+using System.ComponentModel;
using SIPSorcery.Sys;
namespace SIPSorcery.Net
@@ -128,18 +130,20 @@ public bool IsFeedbackReport()
}
}
+ [Obsolete("Use ParseFeedbackType(ReadOnlySpan packet) instead.", false)]
+ [EditorBrowsable(EditorBrowsableState.Advanced)]
public static RTCPFeedbackTypesEnum ParseFeedbackType(byte[] packet)
+ => ParseFeedbackType(new ReadOnlySpan(packet));
+
+ public static RTCPFeedbackTypesEnum ParseFeedbackType(ReadOnlySpan packet)
{
if (packet.Length < HEADER_BYTES_LENGTH)
{
throw new ApplicationException("The packet did not contain the minimum number of bytes for an RTCP header packet.");
}
- UInt16 firstWord = BitConverter.ToUInt16(packet, 0);
- if (BitConverter.IsLittleEndian)
- {
- firstWord = NetConvert.DoReverseEndian(firstWord);
- }
+ var firstWord = BinaryPrimitives.ReadUInt16BigEndian(packet);
+
return (RTCPFeedbackTypesEnum)((firstWord >> 8) & 0x1f);
}
@@ -147,24 +151,21 @@ public static RTCPFeedbackTypesEnum ParseFeedbackType(byte[] packet)
/// Extract and load the RTCP header from an RTCP packet.
///
///
- public RTCPHeader(byte[] packet)
+ [Obsolete("Use RTCPHeader(ReadOnlySpan packet) instead.", false)]
+ [EditorBrowsable(EditorBrowsableState.Advanced)]
+ public RTCPHeader(byte[] packet) : this(new ReadOnlySpan(packet))
+ {
+ }
+
+ public RTCPHeader(ReadOnlySpan packet)
{
if (packet.Length < HEADER_BYTES_LENGTH)
{
throw new ApplicationException("The packet did not contain the minimum number of bytes for an RTCP header packet.");
}
- UInt16 firstWord = BitConverter.ToUInt16(packet, 0);
-
- if (BitConverter.IsLittleEndian)
- {
- firstWord = NetConvert.DoReverseEndian(firstWord);
- Length = NetConvert.DoReverseEndian(BitConverter.ToUInt16(packet, 2));
- }
- else
- {
- Length = BitConverter.ToUInt16(packet, 2);
- }
+ var firstWord = BinaryPrimitives.ReadUInt16BigEndian(packet);
+ Length = BinaryPrimitives.ReadUInt16BigEndian(packet.Slice(2));
Version = Convert.ToInt32(firstWord >> 14);
PaddingFlag = Convert.ToInt32((firstWord >> 13) & 0x1);
@@ -209,11 +210,34 @@ public void SetLength(ushort length)
Length = length;
}
+ public int GetPacketSize() => 4;
+
public byte[] GetBytes()
{
- byte[] header = new byte[4];
+ var buffer = new byte[GetPacketSize()];
+
+ WriteBytesCore(buffer);
+
+ return buffer;
+ }
+
+ public int WriteBytes(Span buffer)
+ {
+ var size = GetPacketSize();
+
+ if (buffer.Length < size)
+ {
+ throw new ArgumentOutOfRangeException($"The buffer should have at least {size} bytes and had only {buffer.Length}.");
+ }
+
+ WriteBytesCore(buffer.Slice(0, size));
+
+ return size;
+ }
- UInt32 firstWord = ((uint)Version << 30) + ((uint)PaddingFlag << 29) + ((uint)PacketType << 16) + Length;
+ private void WriteBytesCore(Span buffer)
+ {
+ var firstWord = ((uint)Version << 30) + ((uint)PaddingFlag << 29) + ((uint)PacketType << 16) + Length;
if (IsFeedbackReport())
{
@@ -231,16 +255,7 @@ public byte[] GetBytes()
firstWord += (uint)ReceptionReportCount << 24;
}
- if (BitConverter.IsLittleEndian)
- {
- Buffer.BlockCopy(BitConverter.GetBytes(NetConvert.DoReverseEndian(firstWord)), 0, header, 0, 4);
- }
- else
- {
- Buffer.BlockCopy(BitConverter.GetBytes(firstWord), 0, header, 0, 4);
- }
-
- return header;
+ BinaryPrimitives.WriteUInt32BigEndian(buffer, firstWord);
}
}
}
diff --git a/src/net/RTCP/RTCPReceiverReport.cs b/src/net/RTCP/RTCPReceiverReport.cs
index c71891ac82..6912c073aa 100644
--- a/src/net/RTCP/RTCPReceiverReport.cs
+++ b/src/net/RTCP/RTCPReceiverReport.cs
@@ -45,7 +45,9 @@
//-----------------------------------------------------------------------------
using System;
+using System.Buffers.Binary;
using System.Collections.Generic;
+using System.ComponentModel;
using System.Linq;
using SIPSorcery.Sys;
@@ -76,7 +78,17 @@ public RTCPReceiverReport(uint ssrc, List receptionReport
/// Create a new RTCP Receiver Report from a serialised byte array.
///
/// The byte array holding the serialised receiver report.
- public RTCPReceiverReport(byte[] packet)
+ [Obsolete("Use RTCPReceiverReport(ReadOnlySpan packet) instead.", false)]
+ [EditorBrowsable(EditorBrowsableState.Advanced)]
+ public RTCPReceiverReport(byte[] packet) : this(new ReadOnlySpan(packet))
+ {
+ }
+
+ ///
+ /// Create a new RTCP Receiver Report from a serialised byte array.
+ ///
+ /// The byte array holding the serialised receiver report.
+ public RTCPReceiverReport(ReadOnlySpan packet)
{
if (packet.Length < MIN_PACKET_SIZE)
{
@@ -86,19 +98,12 @@ public RTCPReceiverReport(byte[] packet)
Header = new RTCPHeader(packet);
ReceptionReports = new List();
- if (BitConverter.IsLittleEndian)
- {
- SSRC = NetConvert.DoReverseEndian(BitConverter.ToUInt32(packet, 4));
- }
- else
- {
- SSRC = BitConverter.ToUInt32(packet, 4);
- }
+ SSRC = BinaryPrimitives.ReadUInt32BigEndian(packet.Slice(4, 4));
- int rrIndex = 8;
- for (int i = 0; i < Header.ReceptionReportCount; i++)
+ var rrIndex = 8;
+ for (var i = 0; i < Header.ReceptionReportCount; i++)
{
- var pkt = packet.Skip(rrIndex + i * ReceptionReportSample.PAYLOAD_SIZE).ToArray();
+ var pkt = packet.Slice(rrIndex + i * ReceptionReportSample.PAYLOAD_SIZE);
if (pkt.Length >= ReceptionReportSample.PAYLOAD_SIZE)
{
var rr = new ReceptionReportSample(pkt);
@@ -107,37 +112,51 @@ public RTCPReceiverReport(byte[] packet)
}
}
+ public int GetPacketSize() => RTCPHeader.HEADER_BYTES_LENGTH + 4 + (ReceptionReports?.Count).GetValueOrDefault() * ReceptionReportSample.PAYLOAD_SIZE;
+
///
/// Gets the serialised bytes for this Receiver Report.
///
/// A byte array.
public byte[] GetBytes()
{
- int rrCount = (ReceptionReports != null) ? ReceptionReports.Count : 0;
- byte[] buffer = new byte[RTCPHeader.HEADER_BYTES_LENGTH + 4 + rrCount * ReceptionReportSample.PAYLOAD_SIZE];
- Header.SetLength((ushort)(buffer.Length / 4 - 1));
+ var buffer = new byte[GetPacketSize()];
- Buffer.BlockCopy(Header.GetBytes(), 0, buffer, 0, RTCPHeader.HEADER_BYTES_LENGTH);
- int payloadIndex = RTCPHeader.HEADER_BYTES_LENGTH;
+ WriteBytesCore(buffer);
- if (BitConverter.IsLittleEndian)
- {
- Buffer.BlockCopy(BitConverter.GetBytes(NetConvert.DoReverseEndian(SSRC)), 0, buffer, payloadIndex, 4);
- }
- else
+ return buffer;
+ }
+
+ public int WriteBytes(Span buffer)
+ {
+ var size = GetPacketSize();
+
+ if (buffer.Length < size)
{
- Buffer.BlockCopy(BitConverter.GetBytes(SSRC), 0, buffer, payloadIndex, 4);
+ throw new ArgumentOutOfRangeException($"The buffer should have at least {size} bytes and had only {buffer.Length}.");
}
- int bufferIndex = payloadIndex + 4;
- for (int i = 0; i < rrCount; i++)
+ WriteBytesCore(buffer.Slice(0, size));
+
+ return size;
+ }
+
+ private void WriteBytesCore(Span buffer)
+ {
+ Header.SetLength((ushort)(buffer.Length / 4 - 1));
+ _ = Header.WriteBytes(buffer);
+
+ BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(RTCPHeader.HEADER_BYTES_LENGTH), SSRC);
+
+ if (ReceptionReports is { Count: > 0 } receptionReports)
{
- var receptionReportBytes = ReceptionReports[i].GetBytes();
- Buffer.BlockCopy(receptionReportBytes, 0, buffer, bufferIndex, ReceptionReportSample.PAYLOAD_SIZE);
- bufferIndex += ReceptionReportSample.PAYLOAD_SIZE;
+ buffer = buffer.Slice(RTCPHeader.HEADER_BYTES_LENGTH + 4);
+ for (var i = 0; i < receptionReports.Count; i++)
+ {
+ _ = receptionReports[i].WriteBytes(buffer);
+ buffer = buffer.Slice(ReceptionReportSample.PAYLOAD_SIZE);
+ }
}
-
- return buffer;
}
}
}
diff --git a/src/net/RTCP/RTCPSdesReport.cs b/src/net/RTCP/RTCPSdesReport.cs
index c90a7f6f18..e40892ced9 100644
--- a/src/net/RTCP/RTCPSdesReport.cs
+++ b/src/net/RTCP/RTCPSdesReport.cs
@@ -1,4 +1,4 @@
-//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
// Filename: RTCPSDesReport.cs
//
// Description: RTCP Source Description (SDES) report as defined in RFC3550.
@@ -51,6 +51,8 @@
//-----------------------------------------------------------------------------
using System;
+using System.Buffers.Binary;
+using System.ComponentModel;
using System.Text;
using SIPSorcery.Sys;
@@ -100,7 +102,17 @@ public RTCPSDesReport(uint ssrc, string cname)
/// Create a new RTCP SDES item from a serialised byte array.
///
/// The byte array holding the SDES report.
- public RTCPSDesReport(byte[] packet)
+ [Obsolete("Use RTCPSDesReport(ReadOnlySpan packet) instead.", false)]
+ [EditorBrowsable(EditorBrowsableState.Advanced)]
+ public RTCPSDesReport(byte[] packet) : this(new ReadOnlySpan(packet))
+ {
+ }
+
+ ///
+ /// Create a new RTCP SDES item from a serialised byte array.
+ ///
+ /// The byte array holding the SDES report.
+ public RTCPSDesReport(ReadOnlySpan packet)
{
// if (packet.Length < MIN_PACKET_SIZE)
// {
@@ -117,16 +129,9 @@ public RTCPSDesReport(byte[] packet)
return;
}
- if (packet.Length >= RTCPHeader.HEADER_BYTES_LENGTH+4)
+ if (packet.Length >= RTCPHeader.HEADER_BYTES_LENGTH + 4)
{
- if (BitConverter.IsLittleEndian)
- {
- SSRC = NetConvert.DoReverseEndian(BitConverter.ToUInt32(packet, RTCPHeader.HEADER_BYTES_LENGTH));
- }
- else
- {
- SSRC = BitConverter.ToUInt32(packet, RTCPHeader.HEADER_BYTES_LENGTH);
- }
+ SSRC = BinaryPrimitives.ReadUInt32BigEndian(packet.Slice(RTCPHeader.HEADER_BYTES_LENGTH, 4));
}
if (packet.Length >= MIN_PACKET_SIZE)
@@ -137,10 +142,12 @@ public RTCPSDesReport(byte[] packet)
CNAME = string.Empty;
return;
}
- CNAME = Encoding.UTF8.GetString(packet, 10, cnameLength);
+ CNAME = Encoding.UTF8.GetString(packet.Slice(10, cnameLength));
}
}
+ public int GetPacketSize() => RTCPHeader.HEADER_BYTES_LENGTH + GetPaddedLength(Encoding.UTF8.GetByteCount(CNAME));
+
///
/// Gets the raw bytes for the SDES item. This packet is ready to be included
/// directly in an RTCP packet.
@@ -148,27 +155,37 @@ public RTCPSDesReport(byte[] packet)
/// A byte array containing the serialised SDES item.
public byte[] GetBytes()
{
- byte[] cnameBytes = CNAME == null ? Array.Empty() : Encoding.UTF8.GetBytes(CNAME);
- byte[] buffer = new byte[RTCPHeader.HEADER_BYTES_LENGTH + GetPaddedLength(cnameBytes.Length)]; // Array automatically initialised with 0x00 values.
- Header.SetLength((ushort)(buffer.Length / 4 - 1));
+ var buffer = new byte[GetPacketSize()]; // Array automatically initialised with 0x00 values.
- Buffer.BlockCopy(Header.GetBytes(), 0, buffer, 0, RTCPHeader.HEADER_BYTES_LENGTH);
- int payloadIndex = RTCPHeader.HEADER_BYTES_LENGTH;
+ WriteBytesCore(buffer);
- if (BitConverter.IsLittleEndian)
- {
- Buffer.BlockCopy(BitConverter.GetBytes(NetConvert.DoReverseEndian(SSRC)), 0, buffer, payloadIndex, 4);
- }
- else
+ return buffer;
+ }
+
+ public int WriteBytes(Span buffer)
+ {
+ var size = GetPacketSize();
+
+ if (buffer.Length < size)
{
- Buffer.BlockCopy(BitConverter.GetBytes(SSRC), 0, buffer, payloadIndex, 4);
+ throw new ArgumentOutOfRangeException($"The buffer should have at least {size} bytes and had only {buffer.Length}.");
}
- buffer[payloadIndex + 4] = CNAME_ID;
- buffer[payloadIndex + 5] = (byte)cnameBytes.Length;
- Buffer.BlockCopy(cnameBytes, 0, buffer, payloadIndex + 6, cnameBytes.Length);
+ WriteBytesCore(buffer.Slice(0, size));
- return buffer;
+ return size;
+ }
+
+ private void WriteBytesCore(Span buffer)
+ {
+ Header.SetLength((ushort)(buffer.Length / 4 - 1));
+ _ = Header.WriteBytes(buffer);
+
+ BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(RTCPHeader.HEADER_BYTES_LENGTH), SSRC);
+
+ buffer[RTCPHeader.HEADER_BYTES_LENGTH + 4] = CNAME_ID;
+ buffer[RTCPHeader.HEADER_BYTES_LENGTH + 5] =
+ (byte)Encoding.UTF8.GetBytes(CNAME.AsSpan(), buffer.Slice(RTCPHeader.HEADER_BYTES_LENGTH + 6));
}
///
diff --git a/src/net/RTCP/RTCPSenderReport.cs b/src/net/RTCP/RTCPSenderReport.cs
index 16203261c4..dfdc2fb0fc 100644
--- a/src/net/RTCP/RTCPSenderReport.cs
+++ b/src/net/RTCP/RTCPSenderReport.cs
@@ -45,7 +45,9 @@
//-----------------------------------------------------------------------------
using System;
+using System.Buffers.Binary;
using System.Collections.Generic;
+using System.ComponentModel;
using System.Linq;
using SIPSorcery.Sys;
@@ -91,7 +93,17 @@ public RTCPSenderReport(uint ssrc, ulong ntpTimestamp, uint rtpTimestamp, uint p
/// Create a new RTCP Sender Report from a serialised byte array.
///
/// The byte array holding the serialised sender report.
- public RTCPSenderReport(byte[] packet)
+ [Obsolete("Use RTCPSenderReport(ReadOnlySpan packet) instead.", false)]
+ [EditorBrowsable(EditorBrowsableState.Advanced)]
+ public RTCPSenderReport(byte[] packet) : this(new ReadOnlySpan(packet))
+ {
+ }
+
+ ///
+ /// Create a new RTCP Sender Report from a serialised byte array.
+ ///
+ /// The byte array holding the serialised sender report.
+ public RTCPSenderReport(ReadOnlySpan packet)
{
if (packet.Length < MIN_PACKET_SIZE)
{
@@ -101,71 +113,69 @@ public RTCPSenderReport(byte[] packet)
Header = new RTCPHeader(packet);
ReceptionReports = new List();
- if (BitConverter.IsLittleEndian)
- {
- SSRC = NetConvert.DoReverseEndian(BitConverter.ToUInt32(packet, 4));
- NtpTimestamp = NetConvert.DoReverseEndian(BitConverter.ToUInt64(packet, 8));
- RtpTimestamp = NetConvert.DoReverseEndian(BitConverter.ToUInt32(packet, 16));
- PacketCount = NetConvert.DoReverseEndian(BitConverter.ToUInt32(packet, 20));
- OctetCount = NetConvert.DoReverseEndian(BitConverter.ToUInt32(packet, 24));
- }
- else
- {
- SSRC = BitConverter.ToUInt32(packet, 4);
- NtpTimestamp = BitConverter.ToUInt64(packet, 8);
- RtpTimestamp = BitConverter.ToUInt32(packet, 16);
- PacketCount = BitConverter.ToUInt32(packet, 20);
- OctetCount = BitConverter.ToUInt32(packet, 24);
- }
+ SSRC = BinaryPrimitives.ReadUInt32BigEndian(packet.Slice(RTCPHeader.HEADER_BYTES_LENGTH, 4));
+ NtpTimestamp = BinaryPrimitives.ReadUInt64BigEndian(packet.Slice(RTCPHeader.HEADER_BYTES_LENGTH + 4, 8));
+ RtpTimestamp = BinaryPrimitives.ReadUInt32BigEndian(packet.Slice(RTCPHeader.HEADER_BYTES_LENGTH + 12, 4));
+ PacketCount = BinaryPrimitives.ReadUInt32BigEndian(packet.Slice(RTCPHeader.HEADER_BYTES_LENGTH + 16, 4));
+ OctetCount = BinaryPrimitives.ReadUInt32BigEndian(packet.Slice(RTCPHeader.HEADER_BYTES_LENGTH + 20, 4));
- int rrIndex = 28;
- for (int i = 0; i < Header.ReceptionReportCount; i++)
+ var rrIndex = 28;
+ for (var i = 0; i < Header.ReceptionReportCount; i++)
{
- var pkt = packet.Skip(rrIndex + i * ReceptionReportSample.PAYLOAD_SIZE).ToArray();
+ var pkt = packet.Slice(rrIndex + i * ReceptionReportSample.PAYLOAD_SIZE);
if (pkt.Length >= ReceptionReportSample.PAYLOAD_SIZE)
{
var rr = new ReceptionReportSample(pkt);
ReceptionReports.Add(rr);
}
-
}
}
+ public int GetPacketSize() => RTCPHeader.HEADER_BYTES_LENGTH + 4 + SENDER_PAYLOAD_SIZE + (ReceptionReports?.Count).GetValueOrDefault() * ReceptionReportSample.PAYLOAD_SIZE;
+
public byte[] GetBytes()
{
- int rrCount = (ReceptionReports != null) ? ReceptionReports.Count : 0;
- byte[] buffer = new byte[RTCPHeader.HEADER_BYTES_LENGTH + 4 + SENDER_PAYLOAD_SIZE + rrCount * ReceptionReportSample.PAYLOAD_SIZE];
- Header.SetLength((ushort)(buffer.Length / 4 - 1));
+ var buffer = new byte[GetPacketSize()];
- Buffer.BlockCopy(Header.GetBytes(), 0, buffer, 0, RTCPHeader.HEADER_BYTES_LENGTH);
- int payloadIndex = RTCPHeader.HEADER_BYTES_LENGTH;
+ WriteBytesCore(buffer);
- if (BitConverter.IsLittleEndian)
- {
- Buffer.BlockCopy(BitConverter.GetBytes(NetConvert.DoReverseEndian(SSRC)), 0, buffer, payloadIndex, 4);
- Buffer.BlockCopy(BitConverter.GetBytes(NetConvert.DoReverseEndian(NtpTimestamp)), 0, buffer, payloadIndex + 4, 8);
- Buffer.BlockCopy(BitConverter.GetBytes(NetConvert.DoReverseEndian(RtpTimestamp)), 0, buffer, payloadIndex + 12, 4);
- Buffer.BlockCopy(BitConverter.GetBytes(NetConvert.DoReverseEndian(PacketCount)), 0, buffer, payloadIndex + 16, 4);
- Buffer.BlockCopy(BitConverter.GetBytes(NetConvert.DoReverseEndian(OctetCount)), 0, buffer, payloadIndex + 20, 4);
- }
- else
+ return buffer;
+ }
+
+ public int WriteBytes(Span buffer)
+ {
+ var size = GetPacketSize();
+
+ if (buffer.Length < size)
{
- Buffer.BlockCopy(BitConverter.GetBytes(SSRC), 0, buffer, payloadIndex, 4);
- Buffer.BlockCopy(BitConverter.GetBytes(NtpTimestamp), 0, buffer, payloadIndex + 4, 8);
- Buffer.BlockCopy(BitConverter.GetBytes(RtpTimestamp), 0, buffer, payloadIndex + 12, 4);
- Buffer.BlockCopy(BitConverter.GetBytes(PacketCount), 0, buffer, payloadIndex + 16, 4);
- Buffer.BlockCopy(BitConverter.GetBytes(OctetCount), 0, buffer, payloadIndex + 20, 4);
+ throw new ArgumentOutOfRangeException($"The buffer should have at least {size} bytes and had only {buffer.Length}.");
}
- int bufferIndex = payloadIndex + 24;
- for (int i = 0; i < rrCount; i++)
+ WriteBytesCore(buffer.Slice(0, size));
+
+ return size;
+ }
+
+ private void WriteBytesCore(Span buffer)
+ {
+ Header.SetLength((ushort)(buffer.Length / 4 - 1));
+ _ = Header.WriteBytes(buffer);
+
+ BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(RTCPHeader.HEADER_BYTES_LENGTH), SSRC);
+ BinaryPrimitives.WriteUInt64BigEndian(buffer.Slice(RTCPHeader.HEADER_BYTES_LENGTH + 4), NtpTimestamp);
+ BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(RTCPHeader.HEADER_BYTES_LENGTH + 12), RtpTimestamp);
+ BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(RTCPHeader.HEADER_BYTES_LENGTH + 16), PacketCount);
+ BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(RTCPHeader.HEADER_BYTES_LENGTH + 20), OctetCount);
+
+ if (ReceptionReports is { Count: > 0 } receptionReports)
{
- var receptionReportBytes = ReceptionReports[i].GetBytes();
- Buffer.BlockCopy(receptionReportBytes, 0, buffer, bufferIndex, ReceptionReportSample.PAYLOAD_SIZE);
- bufferIndex += ReceptionReportSample.PAYLOAD_SIZE;
+ buffer = buffer.Slice(RTCPHeader.HEADER_BYTES_LENGTH + 24);
+ for (var i = 0; i < receptionReports.Count; i++)
+ {
+ _ = receptionReports[i].WriteBytes(buffer);
+ buffer = buffer.Slice(ReceptionReportSample.PAYLOAD_SIZE);
+ }
}
-
- return buffer;
}
}
}
diff --git a/src/net/RTCP/RTCPTWCCFeedback.cs b/src/net/RTCP/RTCPTWCCFeedback.cs
index f9225b84c6..cd6b169566 100644
--- a/src/net/RTCP/RTCPTWCCFeedback.cs
+++ b/src/net/RTCP/RTCPTWCCFeedback.cs
@@ -35,7 +35,10 @@
* 2025-02-20 Initial creation.
*/
using System;
+using System.Buffers.Binary;
using System.Collections.Generic;
+using System.ComponentModel;
+using System.Diagnostics;
using System.Linq;
using SIPSorcery.Sys;
@@ -155,31 +158,44 @@ public class RTCPTWCCFeedback
///
/// Parses a TWCC feedback packet from the given byte array.
///
- public RTCPTWCCFeedback(byte[] packet)
+ [Obsolete("Use RTCPSDesReport(ReadOnlySpan packet) instead.", false)]
+ [EditorBrowsable(EditorBrowsableState.Advanced)]
+ public RTCPTWCCFeedback(byte[] packet) : this(new ReadOnlySpan(packet))
+ {
+ }
+
+ ///
+ /// Constructs a TWCC feedback message from the raw RTCP packet.
+ ///
+ /// The complete RTCP TWCC feedback packet.
+ ///
+ /// Parses a TWCC feedback packet from the given byte array.
+ ///
+ public RTCPTWCCFeedback(ReadOnlySpan packet)
{
ValidatePacket(packet);
// Parse the RTCP header.
Header = new RTCPHeader(packet);
- int offset = RTCPHeader.HEADER_BYTES_LENGTH;
+ packet = packet.Slice(RTCPHeader.HEADER_BYTES_LENGTH);
// Parse sender and media SSRCs
- SenderSSRC = ReadUInt32(packet, ref offset);
- MediaSSRC = ReadUInt32(packet, ref offset);
+ SenderSSRC = BinaryOperations.ReadUInt32BigEndian(ref packet);
+ MediaSSRC = BinaryOperations.ReadUInt32BigEndian(ref packet);
// Parse Base Sequence Number and Packet Status Count
- BaseSequenceNumber = ReadUInt16(packet, ref offset);
- PacketStatusCount = ReadUInt16(packet, ref offset);
+ BaseSequenceNumber = BinaryOperations.ReadUInt16BigEndian(ref packet);
+ PacketStatusCount = BinaryOperations.ReadUInt16BigEndian(ref packet);
// Parse Reference Time and Feedback Packet Count
- ReferenceTime = ParseReferenceTime(packet, ref offset, out byte fbCount);
+ ReferenceTime = ParseReferenceTime(ref packet, out byte fbCount);
FeedbackPacketCount = fbCount;
// Parse status chunks
- var statusSymbols = ParseStatusChunks(packet, ref offset);
+ var statusSymbols = ParseStatusChunks(ref packet);
// Parse delta values with validation
- var (deltaValues, lastOffset) = ParseDeltaValues(packet, offset, statusSymbols);
+ var deltaValues = ParseDeltaValues(ref packet, statusSymbols);
// Build final packet status list
BuildPacketStatusList(statusSymbols, deltaValues);
@@ -220,6 +236,8 @@ private void ParseRunLengthChunk(ushort chunk, List status
remainingStatuses -= runLength;
}
+ [Obsolete("Use ValidatePacket(ReadOnlySpan packet) instead.", false)]
+ [EditorBrowsable(EditorBrowsableState.Advanced)]
private void ValidatePacket(byte[] packet)
{
if (packet == null)
@@ -227,40 +245,46 @@ private void ValidatePacket(byte[] packet)
throw new ArgumentNullException(nameof(packet));
}
+ ValidatePacket(new ReadOnlySpan(packet));
+ }
+
+ private void ValidatePacket(ReadOnlySpan packet)
+ {
if (packet.Length < (RTCPHeader.HEADER_BYTES_LENGTH + 12))
{
throw new ArgumentException("Packet too short to be a valid TWCC feedback message.");
}
}
- private uint ParseReferenceTime(byte[] packet, ref int offset, out byte fbCount)
+ private uint ParseReferenceTime(ref ReadOnlySpan packet, out byte fbCount)
{
- if (offset + 4 > packet.Length)
+ if (packet.Length < 4)
{
throw new ArgumentException("Packet truncated at reference time.");
}
- byte b1 = packet[offset++];
- byte b2 = packet[offset++];
- byte b3 = packet[offset++];
- fbCount = packet[offset++];
+ var b1 = packet[0];
+ var b2 = packet[1];
+ var b3 = packet[2];
+ fbCount = packet[3];
+ packet = packet.Slice(4);
return (uint)((b1 << 16) | (b2 << 8) | b3);
}
- private List ParseStatusChunks(byte[] packet, ref int offset)
+ private List ParseStatusChunks(ref ReadOnlySpan packet)
{
var statusSymbols = new List();
int remainingStatuses = PacketStatusCount;
while (remainingStatuses > 0)
{
- if (offset + 2 > packet.Length)
+ if (packet.Length < 2)
{
throw new ArgumentException($"Packet truncated during status chunk parsing. Expected {remainingStatuses} more statuses.");
}
- ushort chunk = ReadUInt16(packet, ref offset);
- int chunkType = chunk >> 14;
+ var chunk = BinaryOperations.ReadUInt16BigEndian(ref packet);
+ var chunkType = chunk >> 14;
switch (chunkType)
{
@@ -344,6 +368,44 @@ private void ParseOneBitStatusVector(ushort chunk, List st
return (deltaValues, offset);
}
+ private List ParseDeltaValues(ref ReadOnlySpan packet, List statusSymbols)
+ {
+ var deltaValues = new List();
+ int expectedDeltaCount = statusSymbols.Count(s => s is TWCCPacketStatusType.ReceivedSmallDelta or TWCCPacketStatusType.ReceivedLargeDelta);
+
+ foreach (var status in statusSymbols)
+ {
+ if (status is TWCCPacketStatusType.NotReceived or TWCCPacketStatusType.Reserved)
+ {
+ deltaValues.Add(0);
+ continue;
+ }
+
+ // Check if we have enough data for the delta
+ var deltaSize = status == TWCCPacketStatusType.ReceivedSmallDelta ? 1 : 2;
+ if (deltaSize > packet.Length)
+ {
+ // Instead of throwing, we'll add a special value to indicate truncation
+ deltaValues.Add(int.MinValue);
+ break;
+ }
+
+ if (status == TWCCPacketStatusType.ReceivedSmallDelta)
+ {
+ deltaValues.Add((sbyte)packet[0] * DeltaScale);
+ packet = packet.Slice(1);
+ }
+ else // ReceivedLargeDelta
+ {
+ var rawDelta = (short)((packet[0] << 8) | packet[1]);
+ deltaValues.Add(rawDelta * DeltaScale);
+ packet = packet.Slice(2);
+ }
+ }
+
+ return deltaValues;
+ }
+
private void BuildPacketStatusList(List statusSymbols, List deltaValues)
{
PacketStatuses = new List();
@@ -364,27 +426,17 @@ private void BuildPacketStatusList(List statusSymbols, Lis
}
}
- ///
- /// Serializes this TWCC feedback message to a byte array.
- /// Note: The serialization logic rebuilds the packet status chunks from the PacketStatuses list.
- /// This implements the run-length chunk when possible and defaults to two-bit
- /// status vector chunks if a run-length encoding isn’t efficient.
- ///
- /// The serialized RTCP TWCC feedback packet.
- public byte[] GetBytes()
+ public int GetPacketSize()
{
- // Build a list of TWCCPacketStatusType from PacketStatuses.
- List symbols = PacketStatuses.Select(ps => ps.Status).ToList();
+ var length = RTCPHeader.HEADER_BYTES_LENGTH + 4 + 4 + 2 + 2 + 4;
- // Reconstruct packet status chunks.
- List chunks = new List();
- int i = 0;
- while (i < symbols.Count)
+ var packageStatusesCount = PacketStatuses.Count;
+ for (var i = 0; i < packageStatusesCount;)
{
// Try to use run-length chunk: count how many consecutive statuses are identical.
- int runLength = 1;
- TWCCPacketStatusType current = symbols[i];
- while (i + runLength < symbols.Count && symbols[i + runLength] == current && runLength < 0x0FFF)
+ var runLength = 1;
+ var current = PacketStatuses[i].Status;
+ while (i + runLength < packageStatusesCount && PacketStatuses[i + runLength].Status == current && runLength < 0x0FFF)
{
runLength++;
}
@@ -413,22 +465,22 @@ public byte[] GetBytes()
break;
}
- ushort chunk = (ushort)(statusBits << 12);
+ var chunk = (ushort)(statusBits << 12);
chunk |= (ushort)(runLength & 0x0FFF);
- chunks.Add(chunk);
+ length += 2;
i += runLength;
}
else
{
// Otherwise, pack into a two-bit status vector chunk.
- int count = Math.Min(7, symbols.Count - i);
+ var count = Math.Min(7, packageStatusesCount - i);
ushort chunk = 0x8000; // Set top bits to 10 for vector chunk
- for (int j = 0; j < count; j++)
+ for (var j = 0; j < count; j++)
{
// Convert status to correct bit pattern
ushort statusBits;
- switch (symbols[i + j])
+ switch (PacketStatuses[i + j].Status)
{
case TWCCPacketStatusType.NotReceived:
statusBits = 0;
@@ -446,77 +498,178 @@ public byte[] GetBytes()
chunk |= (ushort)(statusBits << (12 - 2 * j));
}
- chunks.Add(chunk);
+ length += 2;
i += count;
}
}
- // Build the delta values array.
- List deltaBytes = new List();
foreach (var ps in PacketStatuses)
+ {
+ length += GetDeltaLength(ps);
+ }
+
+ var check = GetBytes().Length;
+
+ Debug.Assert(check == length);
+
+ return check;
+
+ static int GetDeltaLength(TWCCPacketStatus ps)
{
if (ps.Status == TWCCPacketStatusType.ReceivedSmallDelta)
{
- // Delta was stored already scaled; convert back to raw units.
- sbyte delta = (sbyte)(ps.Delta.HasValue ? ps.Delta.Value / DeltaScale : 0);
- deltaBytes.Add((byte)delta);
+ return 1;
}
- else if (ps.Status == TWCCPacketStatusType.ReceivedLargeDelta)
+
+ if (ps.Status == TWCCPacketStatusType.ReceivedLargeDelta)
{
- if (!ps.Delta.HasValue)
+ return 2;
+ }
+
+ return 0;
+ }
+ }
+
+ ///
+ /// Serializes this TWCC feedback message to a byte array.
+ /// Note: The serialization logic rebuilds the packet status chunks from the PacketStatuses list.
+ /// This implements the run-length chunk when possible and defaults to two-bit
+ /// status vector chunks if a run-length encoding isn’t efficient.
+ ///
+ /// The serialized RTCP TWCC feedback packet.
+ public byte[] GetBytes()
{
- ps.Delta = 0;
- //throw new ApplicationException("Missing delta for a large delta packet.");
+ var buffer = new byte[GetPacketSize()];
+
+ WriteBytesCore(buffer);
+
+ return buffer;
}
- short delta = (short)(ps.Delta.Value / DeltaScale);
- byte high = (byte)(delta >> 8);
- byte low = (byte)(delta & 0xFF);
- deltaBytes.Add(high);
- deltaBytes.Add(low);
+
+ public int WriteBytes(Span buffer)
+ {
+ var size = GetPacketSize();
+
+ if (buffer.Length < size)
+ {
+ throw new ArgumentOutOfRangeException($"The buffer should have at least {size} bytes and had only {buffer.Length}.");
}
- // For not received or reserved, no delta bytes are added.
- }
- // Calculate fixed part length.
- int fixedPart = RTCPHeader.HEADER_BYTES_LENGTH + 4 + 4 + 2 + 2 + 4; // header, two SSRCs, Base Seq, Status Count, RefTime+FbkCnt
- int chunksPart = chunks.Count * 2;
- int deltasPart = deltaBytes.Count;
- int totalLength = fixedPart + chunksPart + deltasPart;
- byte[] buffer = new byte[totalLength];
+ WriteBytesCore(buffer.Slice(0, size));
- // Write header (we update length later).
- Buffer.BlockCopy(Header.GetBytes(), 0, buffer, 0, RTCPHeader.HEADER_BYTES_LENGTH);
- int offset = RTCPHeader.HEADER_BYTES_LENGTH;
+ return size;
+ }
+
+ private void WriteBytesCore(Span buffer)
+ {
+ // Update the header length (in 32-bit words minus one).
+ Header.SetLength((ushort)(buffer.Length / 4 - 1));
+ _ = Header.WriteBytes(buffer);
+ buffer = buffer.Slice(RTCPHeader.HEADER_BYTES_LENGTH);
// Write Sender and Media SSRC.
- WriteUInt32(buffer, ref offset, SenderSSRC);
- WriteUInt32(buffer, ref offset, MediaSSRC);
+ BinaryOperations.WriteUInt32BigEndian(ref buffer, SenderSSRC);
+ BinaryOperations.WriteUInt32BigEndian(ref buffer, MediaSSRC);
// Write Base Sequence Number and Packet Status Count.
- WriteUInt16(buffer, ref offset, BaseSequenceNumber);
- WriteUInt16(buffer, ref offset, PacketStatusCount);
+ BinaryOperations.WriteUInt16BigEndian(ref buffer, BaseSequenceNumber);
+ BinaryOperations.WriteUInt16BigEndian(ref buffer, PacketStatusCount);
// Build the 32-bit word for ReferenceTime and FeedbackPacketCount.
- uint refTimeAndCount = (ReferenceTime << 8) | FeedbackPacketCount;
- WriteUInt32(buffer, ref offset, refTimeAndCount);
+ BinaryOperations.WriteUInt32BigEndian(ref buffer, (ReferenceTime << 8) | FeedbackPacketCount);
- // Write packet status chunks.
- foreach (ushort chunk in chunks)
+ for (var i = 0; i < PacketStatuses.Count;)
+ {
+ // Try to use run-length chunk: count how many consecutive statuses are identical.
+ var runLength = 1;
+ var current = PacketStatuses[i].Status;
+ while (i + runLength < PacketStatuses.Count && PacketStatuses[i + runLength].Status == current && runLength < 0x0FFF)
{
- WriteUInt16(buffer, ref offset, chunk);
+ runLength++;
+ }
+ if (runLength >= 2)
+ {
+ // Build run-length chunk.
+ // Currently:
+ // ushort chunk = (ushort)(((int)current & 0x3) << 12);
+
+ // Need to modify to use correct status bit mapping:
+ ushort statusBits;
+ switch (current)
+ {
+ case TWCCPacketStatusType.NotReceived:
+ statusBits = 0; // 00
+ break;
+ case TWCCPacketStatusType.ReceivedSmallDelta:
+ statusBits = 1; // 01 for small delta
+ // Note: status 10 (2) also means small delta
+ break;
+ case TWCCPacketStatusType.ReceivedLargeDelta:
+ statusBits = 3; // 11 for large delta
+ break;
+ default:
+ statusBits = 0;
+ break;
+ }
+
+ var chunk = (ushort)(statusBits << 12);
+ chunk |= (ushort)(runLength & 0x0FFF);
+ BinaryOperations.WriteUInt16BigEndian(ref buffer, chunk);
+ i += runLength;
}
+ else
+ {
+ // Otherwise, pack into a two-bit status vector chunk.
+ var count = Math.Min(7, PacketStatuses.Count - i);
+ ushort chunk = 0x8000; // Set top bits to 10 for vector chunk
- // Write delta values.
- foreach (byte b in deltaBytes)
+ for (var j = 0; j < count; j++)
+ {
+ // Convert status to correct bit pattern
+ ushort statusBits;
+ switch (PacketStatuses[i + j].Status)
{
- buffer[offset++] = b;
+ case TWCCPacketStatusType.NotReceived:
+ statusBits = 0;
+ break;
+ case TWCCPacketStatusType.ReceivedSmallDelta:
+ statusBits = 1;
+ break;
+ case TWCCPacketStatusType.ReceivedLargeDelta:
+ statusBits = 3;
+ break;
+ default:
+ statusBits = 0;
+ break;
}
- // Update the header length (in 32-bit words minus one).
- Header.SetLength((ushort)(totalLength / 4 - 1));
- Buffer.BlockCopy(Header.GetBytes(), 0, buffer, 0, RTCPHeader.HEADER_BYTES_LENGTH);
+ chunk |= (ushort)(statusBits << (12 - 2 * j));
+ }
+ BinaryOperations.WriteUInt16BigEndian(ref buffer, chunk);
+ i += count;
+ }
+ }
- return buffer;
+ foreach (var ps in PacketStatuses)
+ {
+ if (ps.Status == TWCCPacketStatusType.ReceivedSmallDelta)
+ {
+ // Delta was stored already scaled; convert back to raw units.
+ var delta = (sbyte)(ps.Delta.GetValueOrDefault() / DeltaScale);
+ buffer[0] = (byte)delta;
+ buffer = buffer.Slice(1);
+ }
+ else if (ps.Status == TWCCPacketStatusType.ReceivedLargeDelta)
+ {
+ var delta = (short)(ps.Delta.GetValueOrDefault() / DeltaScale);
+ var high = (byte)(delta >> 8);
+ var low = (byte)(delta & 0xFF);
+ buffer[0] = high;
+ buffer[1] = low;
+ buffer = buffer.Slice(2);
+ }
+ // For not received or reserved, no delta bytes are added.
+ }
}
public override string ToString()
@@ -528,51 +681,5 @@ public override string ToString()
$"StatusCount={PacketStatusCount}, RefTime={ReferenceTime} (1/64 sec), " +
$"FbkPktCount={FeedbackPacketCount}, PacketStatuses=[{packetStatusInfo}]";
}
-
- #region Helper Methods
-
- private uint ReadUInt32(byte[] buffer, ref int offset)
- {
- uint value = BitConverter.ToUInt32(buffer, offset);
- if (BitConverter.IsLittleEndian)
- {
- value = NetConvert.DoReverseEndian(value);
- }
- offset += 4;
- return value;
- }
-
- private ushort ReadUInt16(byte[] buffer, ref int offset)
- {
- ushort value = BitConverter.ToUInt16(buffer, offset);
- if (BitConverter.IsLittleEndian)
- {
- value = NetConvert.DoReverseEndian(value);
- }
- offset += 2;
- return value;
- }
-
- private void WriteUInt32(byte[] buffer, ref int offset, uint value)
- {
- if (BitConverter.IsLittleEndian)
- {
- value = NetConvert.DoReverseEndian(value);
- }
- Buffer.BlockCopy(BitConverter.GetBytes(value), 0, buffer, offset, 4);
- offset += 4;
- }
-
- private void WriteUInt16(byte[] buffer, ref int offset, ushort value)
- {
- if (BitConverter.IsLittleEndian)
- {
- value = NetConvert.DoReverseEndian(value);
- }
- Buffer.BlockCopy(BitConverter.GetBytes(value), 0, buffer, offset, 2);
- offset += 2;
- }
-
- #endregion
}
}
diff --git a/src/net/RTCP/ReceptionReport.cs b/src/net/RTCP/ReceptionReport.cs
index 0fbef37793..fef2517640 100644
--- a/src/net/RTCP/ReceptionReport.cs
+++ b/src/net/RTCP/ReceptionReport.cs
@@ -1,4 +1,4 @@
-//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
// Filename: ReceptionReport.cs
//
// Description: One or more reception report blocks are included in each
@@ -34,6 +34,8 @@
using System;
using SIPSorcery.Sys;
+using System.Buffers.Binary;
+using System.ComponentModel;
namespace SIPSorcery.Net
{
@@ -111,60 +113,68 @@ public ReceptionReportSample(
DelaySinceLastSenderReport = delaySinceLastSR;
}
- public ReceptionReportSample(byte[] packet)
+ [Obsolete("Use ReceptionReportSample(ReadOnlySpan packet) instead.", false)]
+ [EditorBrowsable(EditorBrowsableState.Advanced)]
+ public ReceptionReportSample(byte[] packet) : this(new ReadOnlySpan(packet))
{
- if (BitConverter.IsLittleEndian)
- {
- SSRC = NetConvert.DoReverseEndian(BitConverter.ToUInt32(packet, 0));
- FractionLost = packet[4];
- PacketsLost = NetConvert.DoReverseEndian(BitConverter.ToInt32(new byte[] { 0x00, packet[5], packet[6], packet[7] }, 0));
- ExtendedHighestSequenceNumber = NetConvert.DoReverseEndian(BitConverter.ToUInt32(packet, 8));
- Jitter = NetConvert.DoReverseEndian(BitConverter.ToUInt32(packet, 12));
- LastSenderReportTimestamp = NetConvert.DoReverseEndian(BitConverter.ToUInt32(packet, 16));
- DelaySinceLastSenderReport = NetConvert.DoReverseEndian(BitConverter.ToUInt32(packet, 20));
- }
- else
+ }
+
+ public ReceptionReportSample(ReadOnlySpan packet)
+ {
+ SSRC = BinaryPrimitives.ReadUInt32BigEndian(packet.Slice(0, 4));
+ FractionLost = packet[4];
+ PacketsLost = ReadInt24BigEndian(packet.Slice(5, 3));
+ ExtendedHighestSequenceNumber = BinaryPrimitives.ReadUInt32BigEndian(packet.Slice(8, 4));
+ Jitter = BinaryPrimitives.ReadUInt32BigEndian(packet.Slice(12, 4));
+ LastSenderReportTimestamp = BinaryPrimitives.ReadUInt32BigEndian(packet.Slice(16, 4));
+ DelaySinceLastSenderReport = BinaryPrimitives.ReadUInt32BigEndian(packet.Slice(20, 4));
+
+ static int ReadInt24BigEndian(ReadOnlySpan packet)
{
- SSRC = BitConverter.ToUInt32(packet, 4);
- FractionLost = packet[4];
- PacketsLost = BitConverter.ToInt32(new byte[] { 0x00, packet[5], packet[6], packet[7] }, 0);
- ExtendedHighestSequenceNumber = BitConverter.ToUInt32(packet, 8);
- Jitter = BitConverter.ToUInt32(packet, 12);
- LastSenderReportTimestamp = BitConverter.ToUInt32(packet, 16);
- DelaySinceLastSenderReport = BitConverter.ToUInt32(packet, 20);
+ Span buffer = stackalloc byte[4];
+ packet.CopyTo(buffer.Slice(BitConverter.IsLittleEndian ? 1 : 0, 3));
+ return BinaryPrimitives.ReadInt32BigEndian(buffer);
}
}
+ public int GetPacketSize() => 24;
+
///
/// Serialises the reception report block to a byte array.
///
/// A byte array.
public byte[] GetBytes()
{
- byte[] payload = new byte[24];
+ var buffer = new byte[GetPacketSize()];
+
+ WriteBytesCore(buffer);
+
+ return buffer;
+ }
+
+ public int WriteBytes(Span buffer)
+ {
+ var size = GetPacketSize();
- if (BitConverter.IsLittleEndian)
+ if (buffer.Length < size)
{
- Buffer.BlockCopy(BitConverter.GetBytes(NetConvert.DoReverseEndian(SSRC)), 0, payload, 0, 4);
- payload[4] = FractionLost;
- Buffer.BlockCopy(BitConverter.GetBytes(NetConvert.DoReverseEndian(PacketsLost)), 1, payload, 5, 3);
- Buffer.BlockCopy(BitConverter.GetBytes(NetConvert.DoReverseEndian(ExtendedHighestSequenceNumber)), 0, payload, 8, 4);
- Buffer.BlockCopy(BitConverter.GetBytes(NetConvert.DoReverseEndian(Jitter)), 0, payload, 12, 4);
- Buffer.BlockCopy(BitConverter.GetBytes(NetConvert.DoReverseEndian(LastSenderReportTimestamp)), 0, payload, 16, 4);
- Buffer.BlockCopy(BitConverter.GetBytes(NetConvert.DoReverseEndian(DelaySinceLastSenderReport)), 0, payload, 20, 4);
+ throw new ArgumentOutOfRangeException(nameof(buffer), $"The buffer should have at least {size} bytes and had only {buffer.Length}.");
}
- else
- {
- Buffer.BlockCopy(BitConverter.GetBytes(SSRC), 0, payload, 0, 4);
- payload[4] = FractionLost;
- Buffer.BlockCopy(BitConverter.GetBytes(PacketsLost), 1, payload, 5, 3);
- Buffer.BlockCopy(BitConverter.GetBytes(ExtendedHighestSequenceNumber), 0, payload, 8, 4);
- Buffer.BlockCopy(BitConverter.GetBytes(Jitter), 0, payload, 12, 4);
- Buffer.BlockCopy(BitConverter.GetBytes(LastSenderReportTimestamp), 0, payload, 16, 4);
- Buffer.BlockCopy(BitConverter.GetBytes(DelaySinceLastSenderReport), 0, payload, 20, 4);
+
+ WriteBytesCore(buffer.Slice(0, size));
+
+ return size;
}
- return payload;
+ private void WriteBytesCore(Span buffer)
+ {
+ BinaryPrimitives.WriteUInt32BigEndian(buffer, SSRC);
+ BinaryPrimitives.WriteInt32BigEndian(buffer.Slice(4), PacketsLost); // only 24 bits
+ buffer[4] = FractionLost; // overwrite first 8 bits of PacketsLost
+ BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(8), ExtendedHighestSequenceNumber);
+ BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(12), Jitter);
+ BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(16), LastSenderReportTimestamp);
+ BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(20), DelaySinceLastSenderReport);
}
}
diff --git a/src/net/RTP/MediaStream.cs b/src/net/RTP/MediaStream.cs
index 8a62cf6d3c..62f631b111 100755
--- a/src/net/RTP/MediaStream.cs
+++ b/src/net/RTP/MediaStream.cs
@@ -280,7 +280,7 @@ public bool IsSecurityContextReady()
if (res == 0)
{
- return (true, buffer.Take(outBufLen).ToArray());
+ return (true, buffer.AsSpan(0, outBufLen).ToArray());
}
else
{
@@ -467,7 +467,7 @@ 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
}
else
{
- rtpChannel.Send(RTPChannelSocketsEnum.RTP, DestinationEndPoint, rtpBuffer.Take(outBufLen).ToArray());
+ rtpChannel.Send(RTPChannelSocketsEnum.RTP, DestinationEndPoint, rtpBuffer.AsSpan(0, outBufLen).ToArray());
}
}
@@ -615,7 +615,7 @@ private bool SendRtcpReport(byte[] reportBuffer)
}
else
{
- rtpChannel.Send(sendOnSocket, ControlDestinationEndPoint, sendBuffer.Take(outBufLen).ToArray());
+ rtpChannel.Send(sendOnSocket, ControlDestinationEndPoint, sendBuffer.AsSpan(0, outBufLen).ToArray());
}
}
}
diff --git a/src/net/RTP/Packetisation/H264Packetiser.cs b/src/net/RTP/Packetisation/H264Packetiser.cs
index 2f9014cdbf..336a06319a 100644
--- a/src/net/RTP/Packetisation/H264Packetiser.cs
+++ b/src/net/RTP/Packetisation/H264Packetiser.cs
@@ -30,6 +30,7 @@
// BSD 3-Clause "New" or "Revised" License, see included LICENSE.md file.
//-----------------------------------------------------------------------------
+using System;
using System.Collections.Generic;
using System.Linq;
@@ -74,7 +75,7 @@ public static IEnumerable ParseNals(byte[] accessUnit)
int nalSize = endPosn - currPosn;
bool isLast = currPosn + nalSize == accessUnit.Length;
- yield return new H264Nal(accessUnit.Skip(currPosn).Take(nalSize).ToArray(), isLast);
+ yield return new H264Nal(accessUnit.AsSpan(currPosn, nalSize).ToArray(), isLast);
}
currPosn = nalStart;
@@ -87,7 +88,7 @@ public static IEnumerable ParseNals(byte[] accessUnit)
if (currPosn < accessUnit.Length)
{
- yield return new H264Nal(accessUnit.Skip(currPosn).ToArray(), true);
+ yield return new H264Nal(accessUnit.AsSpan(currPosn).ToArray(), true);
}
}
diff --git a/src/net/RTP/Packetisation/H265Packetiser.cs b/src/net/RTP/Packetisation/H265Packetiser.cs
index a124f31f28..a8effc6cc9 100644
--- a/src/net/RTP/Packetisation/H265Packetiser.cs
+++ b/src/net/RTP/Packetisation/H265Packetiser.cs
@@ -16,6 +16,7 @@
// License:
// BSD 3-Clause "New" or "Revised" License, see included LICENSE.md file.
//-----------------------------------------------------------------------------
+using System;
using System.Collections.Generic;
using System.Linq;
@@ -60,7 +61,7 @@ public static IEnumerable ParseNals(byte[] accessUnit)
int nalSize = endPosn - currPosn;
bool isLast = currPosn + nalSize == accessUnit.Length;
- yield return new H265Nal(accessUnit.Skip(currPosn).Take(nalSize).ToArray(), isLast);
+ yield return new H265Nal(accessUnit.AsSpan(currPosn, nalSize).ToArray(), isLast);
}
currPosn = nalStart;
@@ -74,7 +75,7 @@ public static IEnumerable ParseNals(byte[] accessUnit)
if (currPosn < accessUnit.Length)
{
- yield return new H265Nal(accessUnit.Skip(currPosn).ToArray(), true);
+ yield return new H265Nal(accessUnit.AsSpan(currPosn).ToArray(), true);
}
}
diff --git a/src/net/RTP/Packetisation/MJPEGDepacketiser.cs b/src/net/RTP/Packetisation/MJPEGDepacketiser.cs
index 556bd1b3d2..6756ccab2b 100644
--- a/src/net/RTP/Packetisation/MJPEGDepacketiser.cs
+++ b/src/net/RTP/Packetisation/MJPEGDepacketiser.cs
@@ -151,10 +151,10 @@ public class MJPEGDepacketiser
private bool _hasExternalQuantizationTable;
- private byte[] _jpegHeaderBytes = new byte[0];
+ private byte[] _jpegHeaderBytes = Array.Empty();
private ArraySegment _jpegHeaderBytesSegment;
- private byte[] _quantizationTables = new byte[0];
+ private byte[] _quantizationTables = Array.Empty();
private int _quantizationTablesLength;
diff --git a/src/net/RTP/Packetisation/MJPEGPacketiser.cs b/src/net/RTP/Packetisation/MJPEGPacketiser.cs
index aae8ea303c..ad3d78b7aa 100644
--- a/src/net/RTP/Packetisation/MJPEGPacketiser.cs
+++ b/src/net/RTP/Packetisation/MJPEGPacketiser.cs
@@ -220,7 +220,7 @@ public static byte[] GetMJPEGRTPHeader(MJPEG customData, int offset)
qTableHeader.SetValue(customData.MjpegHeaderQTable.lengthHigh, 2);
qTableHeader.SetValue(customData.MjpegHeaderQTable.lengthLow, 3);
- var qtables = new byte[0];
+ var qtables = Array.Empty();
foreach (var qTable in customData.QTables)
{
qtables = qtables.Concat(qTable).ToArray();
@@ -259,7 +259,7 @@ public static MJPEGData GetFrameData(byte[] jpegFrame, out MJPEG customData)
{
if (currentMarker.StartPosition > 0)
{
- currentMarker.MarkerBytes = jpegFrame.Skip(currentMarker.StartPosition).Take(index + 1).ToArray();
+ currentMarker.MarkerBytes = jpegFrame.AsSpan(currentMarker.StartPosition, index + 1).ToArray();
markers.Add(currentMarker);
currentMarker = new Marker();
}
@@ -276,7 +276,7 @@ public static MJPEGData GetFrameData(byte[] jpegFrame, out MJPEG customData)
}
if (currentMarker.StartPosition > 0)
{
- currentMarker.MarkerBytes = jpegFrame.Skip(currentMarker.StartPosition).Take(index).ToArray();
+ currentMarker.MarkerBytes = jpegFrame.AsSpan(currentMarker.StartPosition, index).ToArray();
markers.Add(currentMarker);
}
@@ -317,7 +317,7 @@ private static void ProcessJpegDri(Marker marker, MJPEG mjpeg)
private static MJPEGData ProcessJpegSos(Marker marker, MJPEG mjpeg)
{
var hdrLength = (marker.MarkerBytes[0] << 8) | marker.MarkerBytes[1];
- var bytes = marker.MarkerBytes.Skip(hdrLength).ToArray();
+ var bytes = marker.MarkerBytes.AsSpan(hdrLength).ToArray();
return new MJPEGData() { Data = bytes };
}
@@ -335,7 +335,7 @@ private static void ProcessJpegDqt(Marker marker, MJPEG mjpeg)
var qCount = (hdrLength - QTableHeaderLength) / QTableBlockLength8Bit;
for (var i = 0; i < qCount; i++)
{
- var bytes = marker.MarkerBytes.Skip(QTableHeaderLength + i * QTableBlockLength8Bit + QTableParamsLength).Take(QTableLength8Bit).ToArray();
+ var bytes = marker.MarkerBytes.AsSpan(QTableHeaderLength + i * QTableBlockLength8Bit + QTableParamsLength, QTableLength8Bit).ToArray();
mjpeg.QTables.Add(bytes);
mjpeg.MjpegHeaderQTable.SetLength(mjpeg.MjpegHeaderQTable.GetLength() + bytes.Length);
}
diff --git a/src/net/RTP/Packetisation/RtpVideoFramer.cs b/src/net/RTP/Packetisation/RtpVideoFramer.cs
index ab547e068e..1050163e61 100644
--- a/src/net/RTP/Packetisation/RtpVideoFramer.cs
+++ b/src/net/RTP/Packetisation/RtpVideoFramer.cs
@@ -89,7 +89,7 @@ public byte[] GotRtpPacket(RTPPacket rtpPacket)
if (rtpPacket.Header.MarkerBit > 0)
{
- var frame = _currVideoFrame.Take(_currVideoFramePosn).ToArray();
+ var frame = _currVideoFrame.AsSpan(0, _currVideoFramePosn).ToArray();
_currVideoFramePosn = 0;
diff --git a/src/net/RTP/RTPHeader.cs b/src/net/RTP/RTPHeader.cs
index a71ab7c946..bce8d341bc 100644
--- a/src/net/RTP/RTPHeader.cs
+++ b/src/net/RTP/RTPHeader.cs
@@ -142,45 +142,50 @@ public byte[] GetHeader(UInt16 sequenceNumber, uint timestamp, uint syncSource)
return GetBytes();
}
+ public int GetPacketSize() => Length;
+
public byte[] GetBytes()
{
- byte[] header = new byte[Length];
+ var buffer = new byte[Length];
- UInt16 firstWord = Convert.ToUInt16(Version * 16384 + PaddingFlag * 8192 + HeaderExtensionFlag * 4096 + CSRCCount * 256 + MarkerBit * 128 + PayloadType);
+ WriteBytesCore(buffer);
- if (BitConverter.IsLittleEndian)
+ return buffer;
+ }
+
+ public int WriteBytes(Span buffer)
{
- Buffer.BlockCopy(BitConverter.GetBytes(NetConvert.DoReverseEndian(firstWord)), 0, header, 0, 2);
- Buffer.BlockCopy(BitConverter.GetBytes(NetConvert.DoReverseEndian(SequenceNumber)), 0, header, 2, 2);
- Buffer.BlockCopy(BitConverter.GetBytes(NetConvert.DoReverseEndian(Timestamp)), 0, header, 4, 4);
- Buffer.BlockCopy(BitConverter.GetBytes(NetConvert.DoReverseEndian(SyncSource)), 0, header, 8, 4);
+ var size = GetPacketSize();
- if (HeaderExtensionFlag == 1)
+ if (buffer.Length < size)
{
- Buffer.BlockCopy(BitConverter.GetBytes(NetConvert.DoReverseEndian(ExtensionProfile)), 0, header, 12 + 4 * CSRCCount, 2);
- Buffer.BlockCopy(BitConverter.GetBytes(NetConvert.DoReverseEndian(ExtensionLength)), 0, header, 14 + 4 * CSRCCount, 2);
+ throw new ArgumentOutOfRangeException($"The buffer should have at least {size} bytes and had only {buffer.Length}.");
}
+
+ WriteBytesCore(buffer.Slice(0, size));
+
+ return size;
}
- else
+
+ private void WriteBytesCore(Span buffer)
{
- Buffer.BlockCopy(BitConverter.GetBytes(firstWord), 0, header, 0, 2);
- Buffer.BlockCopy(BitConverter.GetBytes(SequenceNumber), 0, header, 2, 2);
- Buffer.BlockCopy(BitConverter.GetBytes(Timestamp), 0, header, 4, 4);
- Buffer.BlockCopy(BitConverter.GetBytes(SyncSource), 0, header, 8, 4);
+ var firstWord = Convert.ToUInt16(Version * 16384 + PaddingFlag * 8192 + HeaderExtensionFlag * 4096 + CSRCCount * 256 + MarkerBit * 128 + PayloadType);
+
+ BinaryPrimitives.WriteUInt16BigEndian(buffer, firstWord);
+ BinaryPrimitives.WriteUInt16BigEndian(buffer.Slice(2), SequenceNumber);
+ BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(4), Timestamp);
+ BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(8), SyncSource);
if (HeaderExtensionFlag == 1)
{
- Buffer.BlockCopy(BitConverter.GetBytes(ExtensionProfile), 0, header, 12 + 4 * CSRCCount, 2);
- Buffer.BlockCopy(BitConverter.GetBytes(ExtensionLength), 0, header, 14 + 4 * CSRCCount, 2);
- }
+ BinaryPrimitives.WriteUInt16BigEndian(buffer.Slice(12 + 4 * CSRCCount), ExtensionProfile);
+ BinaryPrimitives.WriteUInt16BigEndian(buffer.Slice(14 + 4 * CSRCCount), ExtensionLength);
}
- if (ExtensionLength > 0 && ExtensionPayload != null)
+ if (ExtensionLength > 0 && ExtensionPayload is { Length: > 0 })
{
- Buffer.BlockCopy(ExtensionPayload, 0, header, 16 + 4 * CSRCCount, ExtensionLength * 4);
+ ExtensionPayload.CopyTo(buffer.Slice(16 + 4 * CSRCCount));
}
-
- return header;
}
private RTPHeaderExtensionData GetExtensionAtPosition(ref int position, int id, int len, RTPHeaderExtensionType type, out bool invalid)
@@ -196,7 +201,7 @@ private RTPHeaderExtensionData GetExtensionAtPosition(ref int position, int id,
invalid = true;
return null;
}
- ext = new RTPHeaderExtensionData(id, ExtensionPayload.Skip(position).Take(len).ToArray(), type);
+ ext = new RTPHeaderExtensionData(id, ExtensionPayload.AsSpan(position, len).ToArray(), type);
position += len;
}
else
diff --git a/src/net/RTP/RTPHeaderExtensions/AbsSendTimeExtension.cs b/src/net/RTP/RTPHeaderExtensions/AbsSendTimeExtension.cs
index c3ef965a17..130c8d7002 100644
--- a/src/net/RTP/RTPHeaderExtensions/AbsSendTimeExtension.cs
+++ b/src/net/RTP/RTPHeaderExtensions/AbsSendTimeExtension.cs
@@ -1,5 +1,5 @@
using System;
-using SIPSorcery.Net;
+using System.Buffers.Binary;
namespace SIPSorcery.Net
{
@@ -56,16 +56,14 @@ public override Object Unmarshal(RTPHeader header, byte[] data)
// DateTimeOffset.UnixEpoch only available in newer target frameworks
private static readonly DateTimeOffset UnixEpoch = new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero);
- private ulong? GetUlong(byte[] data)
+ private ulong? GetUlong(ReadOnlySpan data)
{
if ( (data.Length != ExtensionSize) || ((sizeof(ulong) - 1) > data.Length) )
{
return null;
}
- return BitConverter.IsLittleEndian ?
- SIPSorcery.Sys.NetConvert.DoReverseEndian(BitConverter.ToUInt64(data, 0)) :
- BitConverter.ToUInt64(data, 0);
+ return BinaryPrimitives.ReadUInt16BigEndian(data);
}
}
}
diff --git a/src/net/RTP/RTPPacket.cs b/src/net/RTP/RTPPacket.cs
index 4da11bf740..b3adf1e888 100644
--- a/src/net/RTP/RTPPacket.cs
+++ b/src/net/RTP/RTPPacket.cs
@@ -41,17 +41,39 @@ public RTPPacket(byte[] packet)
Array.Copy(packet, Header.Length, Payload, 0, Payload.Length);
}
+ public int GetPacketSize() => Header.GetPacketSize() + Payload.Length;
+
public byte[] GetBytes()
{
- byte[] header = Header.GetBytes();
- byte[] packet = new byte[header.Length + Payload.Length];
+ var packet = new byte[GetPacketSize()];
+
+ var buffer = packet.AsSpan();
- Array.Copy(header, packet, header.Length);
- Array.Copy(Payload, 0, packet, header.Length, Payload.Length);
+ WriteBytesCore(buffer);
return packet;
}
+ public int WriteBytes(Span buffer)
+ {
+ var size = GetPacketSize();
+
+ if (buffer.Length < size)
+ {
+ throw new ArgumentOutOfRangeException($"The buffer should have at least {size} bytes and had only {buffer.Length}.");
+ }
+
+ WriteBytesCore(buffer.Slice(0, size));
+
+ return size;
+ }
+
+ private void WriteBytesCore(Span buffer)
+ {
+ var bytesWritten = Header.WriteBytes(buffer);
+ Payload.CopyTo(buffer.Slice(bytesWritten));
+ }
+
private byte[] GetNullPayload(int numBytes)
{
byte[] payload = new byte[numBytes];
diff --git a/src/net/RTP/RTPSession.cs b/src/net/RTP/RTPSession.cs
index 8f74db0021..08d645e318 100755
--- a/src/net/RTP/RTPSession.cs
+++ b/src/net/RTP/RTPSession.cs
@@ -2098,8 +2098,9 @@ private SDP GetSessionDescription(List mediaStreamList, IPAddress c
{
if (rtpSessionConfig.IsMediaMultiplexed)
{
- rtpPort = m_primaryStream.GetRTPChannel().RTPDynamicNATEndPoint != null ?
- m_primaryStream.GetRTPChannel().RTPDynamicNATEndPoint.Port : m_primaryStream.GetRTPChannel().RTPPort;
+ var rtpChannel = m_primaryStream.GetRTPChannel();
+ rtpPort = rtpChannel.RTPDynamicNATEndPoint != null ?
+ rtpChannel.RTPDynamicNATEndPoint.Port : rtpChannel.RTPPort;
}
else
{
@@ -2511,7 +2512,7 @@ private void OnReceiveRTCPPacket(int localPort, IPEndPoint remoteEndPoint, byte[
}
else
{
- buffer = buffer.Take(outBufLen).ToArray();
+ buffer = buffer.AsSpan(0, outBufLen).ToArray();
}
}
diff --git a/src/net/RTP/VideoStream.cs b/src/net/RTP/VideoStream.cs
index e8d5f3b2a5..87dd3cfce4 100644
--- a/src/net/RTP/VideoStream.cs
+++ b/src/net/RTP/VideoStream.cs
@@ -148,7 +148,7 @@ private void SendH26XNal(uint duration, int payloadTypeID, byte[] nal, bool isLa
//logger.LogDebug($"Send NAL {nal.Length}, is last {isLastNal}, timestamp {videoTrack.Timestamp}.");
//logger.LogDebug($"nri {nalNri:X2}, type {nalType:X2}.");
var naluHeaderSize = is265 ? 2 : 1;
- byte[] naluHeader = is265 ? nal.Take(2).ToArray() : nal.Take(1).ToArray();
+ byte[] naluHeader = is265 ? nal.AsSpan(0, 2).ToArray() : nal.AsSpan(0, 1).ToArray();
if (nal.Length <= RTPSession.RTP_MAX_PAYLOAD)
{
@@ -169,7 +169,7 @@ private void SendH26XNal(uint duration, int payloadTypeID, byte[] nal, bool isLa
}
else
{
- nal = nal.Skip(naluHeaderSize).ToArray();
+ nal = nal.AsSpan(naluHeaderSize).ToArray();
//logger.LogTrace("Fragmenting");
// Send as Fragmentation Unit A (FU-A):
@@ -288,14 +288,14 @@ public void SendMJPEGFrame(uint durationRtpUnits, int payloadID, byte[] sample)
{
var dataSize = RTPSession.RTP_MAX_PAYLOAD - rtpHeader.Length;
var isLast = dataSize >= restBytes.Length;
- var data = isLast ? restBytes : restBytes.Take(dataSize).ToArray();
+ var data = isLast ? restBytes : restBytes.AsSpan(0, dataSize).ToArray();
var markerBit = isLast ? 0 : 1;
var payload = rtpHeader.Concat(data).ToArray();
SendRtpRaw(payload, LocalTrack.Timestamp, markerBit, payloadID, true);
offset += RTPSession.RTP_MAX_PAYLOAD;
rtpHeader = MJPEGPacketiser.GetMJPEGRTPHeader(customData, offset);
- restBytes = restBytes.Skip(data.Length).ToArray();
+ restBytes = restBytes.AsSpan(data.Length).ToArray();
}
}
}
diff --git a/src/net/SCTP/Chunks/SctpChunk.cs b/src/net/SCTP/Chunks/SctpChunk.cs
index 49fd46db1e..9caef33813 100644
--- a/src/net/SCTP/Chunks/SctpChunk.cs
+++ b/src/net/SCTP/Chunks/SctpChunk.cs
@@ -18,9 +18,11 @@
//-----------------------------------------------------------------------------
using System;
-using System.Linq;
+using System.Buffers.Binary;
using System.Collections;
using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
using Microsoft.Extensions.Logging;
using SIPSorcery.Sys;
@@ -185,18 +187,27 @@ public virtual ushort GetChunkLength(bool padded)
/// The buffer holding the serialised chunk.
/// The position in the buffer that indicates the start of the chunk.
/// The chunk length value.
+ [Obsolete("Use ParseFirstWord(ReadOnlySpan) instead.", false)]
+ [EditorBrowsable(EditorBrowsableState.Advanced)]
public ushort ParseFirstWord(byte[] buffer, int posn)
+ => ParseFirstWord(buffer.AsSpan(posn));
+
+ ///
+ /// The first 32 bits of all chunks represent the same 3 fields. This method
+ /// parses those fields and sets them on the current instance.
+ ///
+ /// The buffer holding the serialised chunk.
+ /// The chunk length value.
+ public ushort ParseFirstWord(ReadOnlySpan buffer)
{
- ChunkType = buffer[posn];
- ChunkFlags = buffer[posn + 1];
- ushort chunkLength = NetConvert.ParseUInt16(buffer, posn + 2);
+ ChunkType = buffer[0];
+ ChunkFlags = buffer[1];
+ var chunkLength = BinaryPrimitives.ReadUInt16BigEndian(buffer.Slice(2));
- if (chunkLength > 0 && buffer.Length < posn + chunkLength)
+ if (chunkLength > 0 && buffer.Length < chunkLength)
{
// The buffer was not big enough to supply the specified chunk length.
- int bytesRequired = chunkLength;
- int bytesAvailable = buffer.Length - posn;
- throw new ApplicationException($"The SCTP chunk buffer was too short. Required {bytesRequired} bytes but only {bytesAvailable} available.");
+ throw new ApplicationException($"The SCTP chunk buffer was too short. Required {chunkLength} bytes but only {buffer.Length} available.");
}
return chunkLength;
@@ -211,9 +222,22 @@ public ushort ParseFirstWord(byte[] buffer, int posn)
/// The padded length of this chunk.
protected void WriteChunkHeader(byte[] buffer, int posn)
{
- buffer[posn] = ChunkType;
- buffer[posn + 1] = ChunkFlags;
- NetConvert.ToBuffer(GetChunkLength(false), buffer, posn + 2);
+ WriteChunkHeader(buffer.AsSpan(posn));
+ }
+
+ ///
+ /// Writes the chunk header to the buffer. All chunks use the same three
+ /// header fields.
+ ///
+ /// The buffer to write the chunk header to.
+ /// The number of bytes, including padding, written to the buffer.
+ protected int WriteChunkHeader(Span buffer)
+ {
+ buffer[0] = ChunkType;
+ buffer[1] = ChunkFlags;
+ var chunkLength = GetChunkLength(false);
+ BinaryPrimitives.WriteUInt16BigEndian(buffer.Slice(2), chunkLength);
+ return chunkLength + 2;
}
///
@@ -227,16 +251,44 @@ protected void WriteChunkHeader(byte[] buffer, int posn)
/// The number of bytes, including padding, written to the buffer.
public virtual ushort WriteTo(byte[] buffer, int posn)
{
- WriteChunkHeader(buffer, posn);
+ WriteToCore(buffer.AsSpan(posn));
- if (ChunkValue?.Length > 0)
- {
- Buffer.BlockCopy(ChunkValue, 0, buffer, posn + SCTP_CHUNK_HEADER_LENGTH, ChunkValue.Length);
- }
+ return GetChunkLength(true);
+ }
+
+ ///
+ /// Serialises the chunk to a pre-allocated buffer. This method gets overridden
+ /// by specialised SCTP chunks that have their own parameters and need to be serialised
+ /// differently.
+ ///
+ /// The buffer to write the serialised chunk bytes to. It
+ /// must have the required space already allocated.
+ /// The number of bytes, including padding, written to the buffer.
+ public virtual int WriteTo(Span buffer)
+ {
+ WriteToCore(buffer);
return GetChunkLength(true);
}
+ ///
+ /// Serialises the chunk to a pre-allocated buffer. This method gets overridden
+ /// by specialised SCTP chunks that have their own parameters and need to be serialised
+ /// differently.
+ ///
+ /// The buffer to write the serialised chunk bytes to. It
+ /// must have the required space already allocated.
+ /// The number of bytes, including padding, written to the buffer.
+ private void WriteToCore(Span buffer)
+ {
+ var bytesWritten = WriteChunkHeader(buffer);
+
+ if (ChunkValue is { Length: > 0 } chunkValue)
+ {
+ chunkValue.CopyTo(buffer.Slice(SCTP_CHUNK_HEADER_LENGTH));
+ }
+ }
+
///
/// Handler for processing an unrecognised chunk parameter.
///
@@ -276,14 +328,26 @@ public bool GotUnrecognisedParameter(SctpTlvChunkParameter chunkParameter)
/// The buffer holding the serialised chunk.
/// The position to start parsing at.
/// An SCTP chunk instance.
+ [Obsolete("Use ParseBaseChunk(ReadOnlySpan) instead.", false)]
+ [EditorBrowsable(EditorBrowsableState.Advanced)]
public static SctpChunk ParseBaseChunk(byte[] buffer, int posn)
+ => ParseBaseChunk(buffer.AsSpan(posn));
+
+ ///
+ /// Parses a simple chunk and does not attempt to process any chunk value.
+ /// This method is suitable when:
+ /// - the chunk type consists only of the 4 byte header and has
+ /// no fixed or variable parameters set.
+ ///
+ /// The buffer holding the serialised chunk.
+ /// An SCTP chunk instance.
+ public static SctpChunk ParseBaseChunk(ReadOnlySpan buffer)
{
var chunk = new SctpChunk();
- ushort chunkLength = chunk.ParseFirstWord(buffer, posn);
+ var chunkLength = chunk.ParseFirstWord(buffer);
if (chunkLength > SCTP_CHUNK_HEADER_LENGTH)
{
- chunk.ChunkValue = new byte[chunkLength - SCTP_CHUNK_HEADER_LENGTH];
- Buffer.BlockCopy(buffer, posn + SCTP_CHUNK_HEADER_LENGTH, chunk.ChunkValue, 0, chunk.ChunkValue.Length);
+ chunk.ChunkValue = buffer.Slice(SCTP_CHUNK_HEADER_LENGTH, chunkLength - SCTP_CHUNK_HEADER_LENGTH).ToArray();
}
return chunk;
@@ -294,23 +358,9 @@ public static SctpChunk ParseBaseChunk(byte[] buffer, int posn)
/// parses any variable length parameters from a chunk's value.
///
/// The buffer holding the serialised chunk.
- /// The position in the buffer to start parsing variable length
- /// parameters from.
- /// The length of the TLV chunk parameters in the buffer.
/// A list of chunk parameters. Can be empty.
- public static IEnumerable GetParameters(byte[] buffer, int posn, int length)
- {
- int paramPosn = posn;
-
- while (paramPosn < posn + length)
- {
- var chunkParam = SctpTlvChunkParameter.ParseTlvParameter(buffer, paramPosn);
-
- yield return chunkParam;
-
- paramPosn += chunkParam.GetParameterLength(true);
- }
- }
+ public static ParametersEnumerator GetParameters(ReadOnlySpan buffer)
+ => new ParametersEnumerator(buffer);
///
/// Parses an SCTP chunk from a buffer.
@@ -318,48 +368,58 @@ public static IEnumerable GetParameters(byte[] buffer, in
/// The buffer holding the serialised chunk.
/// The position to start parsing at.
/// An SCTP chunk instance.
+ [Obsolete("Use Parse(ReadOnlySpan) instead.", false)]
+ [EditorBrowsable(EditorBrowsableState.Advanced)]
public static SctpChunk Parse(byte[] buffer, int posn)
+ => Parse(buffer.AsSpan(posn));
+
+ ///
+ /// Parses an SCTP chunk from a buffer.
+ ///
+ /// The buffer holding the serialised chunk.
+ /// An SCTP chunk instance.
+ public static SctpChunk Parse(ReadOnlySpan buffer)
{
- if (buffer.Length < posn + SCTP_CHUNK_HEADER_LENGTH)
+ if (buffer.Length < SCTP_CHUNK_HEADER_LENGTH)
{
throw new ApplicationException("Buffer did not contain the minimum of bytes for an SCTP chunk.");
}
- byte chunkType = buffer[posn];
+ var chunkType = buffer[0];
- if (Enum.IsDefined(typeof(SctpChunkType), chunkType))
+ switch ((SctpChunkType)chunkType)
{
- switch ((SctpChunkType)chunkType)
- {
- case SctpChunkType.ABORT:
- return SctpAbortChunk.ParseChunk(buffer, posn, true);
- case SctpChunkType.DATA:
- return SctpDataChunk.ParseChunk(buffer, posn);
- case SctpChunkType.ERROR:
- return SctpErrorChunk.ParseChunk(buffer, posn, false);
- case SctpChunkType.SACK:
- return SctpSackChunk.ParseChunk(buffer, posn);
- case SctpChunkType.COOKIE_ACK:
- case SctpChunkType.COOKIE_ECHO:
- case SctpChunkType.HEARTBEAT:
- case SctpChunkType.HEARTBEAT_ACK:
- case SctpChunkType.SHUTDOWN_ACK:
- case SctpChunkType.SHUTDOWN_COMPLETE:
- return ParseBaseChunk(buffer, posn);
- case SctpChunkType.INIT:
- case SctpChunkType.INIT_ACK:
- return SctpInitChunk.ParseChunk(buffer, posn);
- case SctpChunkType.SHUTDOWN:
- return SctpShutdownChunk.ParseChunk(buffer, posn);
- default:
- logger.LogDebug("TODO: Implement parsing logic for well known chunk type {ChunkType}.", (SctpChunkType)chunkType);
- return ParseBaseChunk(buffer, posn);
- }
+ case SctpChunkType.ABORT:
+ return SctpAbortChunk.ParseChunk(buffer, true);
+ case SctpChunkType.DATA:
+ return SctpDataChunk.ParseChunk(buffer);
+ case SctpChunkType.ERROR:
+ return SctpErrorChunk.ParseChunk(buffer, false);
+ case SctpChunkType.SACK:
+ return SctpSackChunk.ParseChunk(buffer);
+ case SctpChunkType.COOKIE_ACK:
+ case SctpChunkType.COOKIE_ECHO:
+ case SctpChunkType.HEARTBEAT:
+ case SctpChunkType.HEARTBEAT_ACK:
+ case SctpChunkType.SHUTDOWN_ACK:
+ case SctpChunkType.SHUTDOWN_COMPLETE:
+ return ParseBaseChunk(buffer);
+ case SctpChunkType.INIT:
+ case SctpChunkType.INIT_ACK:
+ return SctpInitChunk.ParseChunk(buffer);
+ case SctpChunkType.SHUTDOWN:
+ return SctpShutdownChunk.ParseChunk(buffer);
+ default:
+ if (!Enum.IsDefined(typeof(SctpChunkType), chunkType))
+ {
+ // Shouldn't reach this point. The SCTP packet parsing logic checks if the chunk is
+ // recognised before attempting to parse it.
+ throw new ApplicationException($"SCTP chunk type of {chunkType} was not recognised.");
+ }
+
+ logger.LogDebug("TODO: Implement parsing logic for well known chunk type {ChunkType}.", (SctpChunkType)chunkType);
+ return ParseBaseChunk(buffer);
}
-
- // Shouldn't reach this point. The SCTP packet parsing logic checks if the chunk is
- // recognised before attempting to parse it.
- throw new ApplicationException($"SCTP chunk type of {chunkType} was not recognised.");
}
///
@@ -369,9 +429,20 @@ public static SctpChunk Parse(byte[] buffer, int posn)
/// The start position of the serialised chunk.
/// If true the length field will be padded to a 4 byte boundary.
/// The padded length of the serialised chunk.
+ [Obsolete("Use Parse(ReadOnlySpan) instead.", false)]
+ [EditorBrowsable(EditorBrowsableState.Advanced)]
public static uint GetChunkLengthFromHeader(byte[] buffer, int posn, bool padded)
+ => GetChunkLengthFromHeader(buffer.AsSpan(posn), padded);
+
+ ///
+ /// Extracts the padded length field from a serialised chunk buffer.
+ ///
+ /// The buffer holding the serialised chunk.
+ /// If true the length field will be padded to a 4 byte boundary.
+ /// The padded length of the serialised chunk.
+ public static uint GetChunkLengthFromHeader(ReadOnlySpan buffer, bool padded)
{
- ushort len = NetConvert.ParseUInt16(buffer, posn + 2);
+ var len = BinaryPrimitives.ReadUInt16BigEndian(buffer.Slice(2));
return (padded) ? SctpPadding.PadTo4ByteBoundary(len) : len;
}
@@ -389,11 +460,60 @@ public static SctpUnrecognisedChunkActions GetUnrecognisedChunkAction(ushort chu
/// The buffer containing the chunk.
/// The position in the buffer that the unrecognised chunk starts.
/// A new buffer containing a copy of the chunk.
+ [Obsolete("Use Parse(ReadOnlySpan) instead.", false)]
+ [EditorBrowsable(EditorBrowsableState.Advanced)]
public static byte[] CopyUnrecognisedChunk(byte[] buffer, int posn)
+ => CopyUnrecognisedChunk(buffer.AsSpan(posn));
+
+ ///
+ /// Copies an unrecognised chunk to a byte buffer and returns it. This method is
+ /// used to assist in reporting unrecognised chunk types.
+ ///
+ /// The buffer containing the chunk.
+ /// A new buffer containing a copy of the chunk.
+ public static byte[] CopyUnrecognisedChunk(ReadOnlySpan buffer)
+ {
+ var length = (int)SctpChunk.GetChunkLengthFromHeader(buffer, true);
+ return buffer.Slice(0, length).ToArray();
+ }
+
+ public ref struct ParametersEnumerator
{
- byte[] unrecognised = new byte[SctpChunk.GetChunkLengthFromHeader(buffer, posn, true)];
- Buffer.BlockCopy(buffer, posn, unrecognised, 0, unrecognised.Length);
- return unrecognised;
+ private ReadOnlySpan _buffer;
+ private SctpTlvChunkParameter _current;
+
+ public ParametersEnumerator(ReadOnlySpan buffer)
+ {
+ _buffer = buffer;
+ _current = default!;
+ }
+
+ public SctpTlvChunkParameter Current => _current;
+
+ public ParametersEnumerator GetEnumerator() => this;
+
+ public bool MoveNext()
+ {
+ if (!_buffer.IsEmpty)
+ {
+ _current = SctpTlvChunkParameter.ParseTlvParameter(_buffer);
+ var chunkParameterLength = _current.GetParameterLength(true);
+
+ if (chunkParameterLength >= _buffer.Length)
+ {
+ _buffer = default;
+ }
+ else
+ {
+ _buffer = _buffer.Slice(chunkParameterLength);
+ }
+
+ return true;
+ }
+
+ _current = null;
+ return false;
+ }
}
}
}
diff --git a/src/net/SCTP/Chunks/SctpDataChunk.cs b/src/net/SCTP/Chunks/SctpDataChunk.cs
index 2b2a15a489..7346695595 100644
--- a/src/net/SCTP/Chunks/SctpDataChunk.cs
+++ b/src/net/SCTP/Chunks/SctpDataChunk.cs
@@ -18,6 +18,8 @@
//-----------------------------------------------------------------------------
using System;
+using System.Buffers.Binary;
+using System.ComponentModel;
using SIPSorcery.Sys;
namespace SIPSorcery.Net
@@ -156,24 +158,38 @@ public override ushort GetChunkLength(bool padded)
/// The number of bytes, including padding, written to the buffer.
public override ushort WriteTo(byte[] buffer, int posn)
{
- WriteChunkHeader(buffer, posn);
+ WriteToCore(buffer.AsSpan(posn));
- // Write fixed parameters.
- int startPosn = posn + SCTP_CHUNK_HEADER_LENGTH;
+ return GetChunkLength(true);
+ }
+
+ ///
+ /// Serialises a DATA chunk to a pre-allocated buffer.
+ ///
+ /// The buffer to write the serialised chunk bytes to. It
+ /// must have the required space already allocated.
+ /// The number of bytes, including padding, written to the buffer.
+ public override int WriteTo(Span buffer)
+ {
+ WriteToCore(buffer);
+
+ return GetChunkLength(true);
+ }
- NetConvert.ToBuffer(TSN, buffer, startPosn);
- NetConvert.ToBuffer(StreamID, buffer, startPosn + 4);
- NetConvert.ToBuffer(StreamSeqNum, buffer, startPosn + 6);
- NetConvert.ToBuffer(PPID, buffer, startPosn + 8);
+ private void WriteToCore(Span buffer)
+ {
+ WriteChunkHeader(buffer);
- int userDataPosn = startPosn + FIXED_PARAMETERS_LENGTH;
+ // Write fixed parameters.
+ BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(SCTP_CHUNK_HEADER_LENGTH), TSN);
+ BinaryPrimitives.WriteUInt16BigEndian(buffer.Slice(SCTP_CHUNK_HEADER_LENGTH + 4), StreamID);
+ BinaryPrimitives.WriteUInt16BigEndian(buffer.Slice(SCTP_CHUNK_HEADER_LENGTH + 6), StreamSeqNum);
+ BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(SCTP_CHUNK_HEADER_LENGTH + 8), PPID);
- if (UserData != null)
+ if (UserData is { Length: > 0 } userData)
{
- Buffer.BlockCopy(UserData, 0, buffer, userDataPosn, UserData.Length);
+ userData.CopyTo(buffer.Slice(SCTP_CHUNK_HEADER_LENGTH + FIXED_PARAMETERS_LENGTH));
}
-
- return GetChunkLength(true);
}
public bool IsEmpty()
@@ -186,10 +202,19 @@ public bool IsEmpty()
///
/// The buffer holding the serialised chunk.
/// The position to start parsing at.
+ [Obsolete("Use Parse(ParseChunk) instead.", false)]
+ [EditorBrowsable(EditorBrowsableState.Advanced)]
public static SctpDataChunk ParseChunk(byte[] buffer, int posn)
+ => ParseChunk(buffer.AsSpan(posn));
+
+ ///
+ /// Parses the DATA chunk fields
+ ///
+ /// The buffer holding the serialised chunk.
+ public static SctpDataChunk ParseChunk(ReadOnlySpan buffer)
{
var dataChunk = new SctpDataChunk();
- ushort chunkLen = dataChunk.ParseFirstWord(buffer, posn);
+ var chunkLen = dataChunk.ParseFirstWord(buffer);
if (chunkLen < FIXED_PARAMETERS_LENGTH)
{
@@ -200,20 +225,16 @@ public static SctpDataChunk ParseChunk(byte[] buffer, int posn)
dataChunk.Begining = (dataChunk.ChunkFlags & 0x02) > 0;
dataChunk.Ending = (dataChunk.ChunkFlags & 0x01) > 0;
- int startPosn = posn + SCTP_CHUNK_HEADER_LENGTH;
-
- dataChunk.TSN = NetConvert.ParseUInt32(buffer, startPosn);
- dataChunk.StreamID = NetConvert.ParseUInt16(buffer, startPosn + 4);
- dataChunk.StreamSeqNum = NetConvert.ParseUInt16(buffer, startPosn + 6);
- dataChunk.PPID = NetConvert.ParseUInt32(buffer, startPosn + 8);
+ dataChunk.TSN = BinaryPrimitives.ReadUInt32BigEndian(buffer.Slice(SCTP_CHUNK_HEADER_LENGTH));
+ dataChunk.StreamID = BinaryPrimitives.ReadUInt16BigEndian(buffer.Slice(SCTP_CHUNK_HEADER_LENGTH + 4));
+ dataChunk.StreamSeqNum = BinaryPrimitives.ReadUInt16BigEndian(buffer.Slice(SCTP_CHUNK_HEADER_LENGTH + 6));
+ dataChunk.PPID = BinaryPrimitives.ReadUInt32BigEndian(buffer.Slice(SCTP_CHUNK_HEADER_LENGTH + 8));
- int userDataPosn = startPosn + FIXED_PARAMETERS_LENGTH;
- int userDataLen = chunkLen - SCTP_CHUNK_HEADER_LENGTH - FIXED_PARAMETERS_LENGTH;
+ var userDataLen = chunkLen - SCTP_CHUNK_HEADER_LENGTH - FIXED_PARAMETERS_LENGTH;
if (userDataLen > 0)
{
- dataChunk.UserData = new byte[userDataLen];
- Buffer.BlockCopy(buffer, userDataPosn, dataChunk.UserData, 0, dataChunk.UserData.Length);
+ dataChunk.UserData = buffer.Slice(SCTP_CHUNK_HEADER_LENGTH + FIXED_PARAMETERS_LENGTH, userDataLen).ToArray();
}
return dataChunk;
diff --git a/src/net/SCTP/Chunks/SctpErrorCauses.cs b/src/net/SCTP/Chunks/SctpErrorCauses.cs
index 8159d1984d..d14a142a31 100644
--- a/src/net/SCTP/Chunks/SctpErrorCauses.cs
+++ b/src/net/SCTP/Chunks/SctpErrorCauses.cs
@@ -19,6 +19,7 @@
//-----------------------------------------------------------------------------
using System;
+using System.Buffers.Binary;
using System.Collections.Generic;
using System.Text;
using SIPSorcery.Sys;
@@ -50,6 +51,7 @@ public interface ISctpErrorCause
SctpErrorCauseCode CauseCode { get; }
ushort GetErrorCauseLength(bool padded);
int WriteTo(byte[] buffer, int posn);
+ int WriteTo(Span buffer);
}
///
@@ -89,8 +91,13 @@ public SctpCauseOnlyError(SctpErrorCauseCode causeCode)
public int WriteTo(byte[] buffer, int posn)
{
- NetConvert.ToBuffer((ushort)CauseCode, buffer, posn);
- NetConvert.ToBuffer(ERROR_CAUSE_LENGTH, buffer, posn + 2);
+ return WriteTo(buffer.AsSpan(posn));
+ }
+
+ public int WriteTo(Span buffer)
+ {
+ BinaryPrimitives.WriteUInt16BigEndian(buffer, (ushort)CauseCode);
+ BinaryPrimitives.WriteUInt16BigEndian(buffer.Slice(2), ERROR_CAUSE_LENGTH);
return ERROR_CAUSE_LENGTH;
}
}
@@ -117,9 +124,14 @@ public struct SctpErrorInvalidStreamIdentifier : ISctpErrorCause
public int WriteTo(byte[] buffer, int posn)
{
- NetConvert.ToBuffer((ushort)CauseCode, buffer, posn);
- NetConvert.ToBuffer(ERROR_CAUSE_LENGTH, buffer, posn + 2);
- NetConvert.ToBuffer(StreamID, buffer, posn + 4);
+ return WriteTo(buffer.AsSpan(posn));
+ }
+
+ public int WriteTo(Span buffer)
+ {
+ BinaryPrimitives.WriteUInt16BigEndian(buffer, (ushort)CauseCode);
+ BinaryPrimitives.WriteUInt16BigEndian(buffer.Slice(2), ERROR_CAUSE_LENGTH);
+ BinaryPrimitives.WriteUInt16BigEndian(buffer.Slice(4), StreamID);
return ERROR_CAUSE_LENGTH;
}
}
@@ -144,19 +156,27 @@ public ushort GetErrorCauseLength(bool padded)
}
public int WriteTo(byte[] buffer, int posn)
+ {
+ return WriteTo(buffer.AsSpan(posn));
+ }
+
+ public int WriteTo(Span buffer)
{
var len = GetErrorCauseLength(true);
- NetConvert.ToBuffer((ushort)CauseCode, buffer, posn);
- NetConvert.ToBuffer(len, buffer, posn + 2);
- if (MissingParameters != null)
+
+ BinaryPrimitives.WriteUInt16BigEndian(buffer, (ushort)CauseCode);
+ BinaryPrimitives.WriteUInt16BigEndian(buffer.Slice(2), len);
+
+ if (MissingParameters is { Count: > 0 } missingParameters)
{
- int valPosn = posn + 4;
- foreach (var missing in MissingParameters)
+ buffer = buffer.Slice(4);
+ foreach (var missing in missingParameters)
{
- NetConvert.ToBuffer(missing, buffer, valPosn);
- valPosn += 2;
+ BinaryPrimitives.WriteUInt16BigEndian(buffer, missing);
+ buffer = buffer.Slice(2);
}
}
+
return len;
}
}
@@ -182,9 +202,14 @@ public struct SctpErrorStaleCookieError : ISctpErrorCause
public int WriteTo(byte[] buffer, int posn)
{
- NetConvert.ToBuffer((ushort)CauseCode, buffer, posn);
- NetConvert.ToBuffer(ERROR_CAUSE_LENGTH, buffer, posn + 2);
- NetConvert.ToBuffer(MeasureOfStaleness, buffer, posn + 4);
+ return WriteTo(buffer.AsSpan(posn));
+ }
+
+ public int WriteTo(Span buffer)
+ {
+ BinaryPrimitives.WriteUInt16BigEndian(buffer, (ushort)CauseCode);
+ BinaryPrimitives.WriteUInt16BigEndian(buffer.Slice(2), ERROR_CAUSE_LENGTH);
+ BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(4), MeasureOfStaleness);
return ERROR_CAUSE_LENGTH;
}
}
@@ -215,14 +240,22 @@ public ushort GetErrorCauseLength(bool padded)
}
public int WriteTo(byte[] buffer, int posn)
+ {
+ return WriteTo(buffer.AsSpan(posn));
+ }
+
+ public int WriteTo(Span buffer)
{
var len = GetErrorCauseLength(true);
- NetConvert.ToBuffer((ushort)CauseCode, buffer, posn);
- NetConvert.ToBuffer(len, buffer, posn + 2);
- if (UnresolvableAddress != null)
+
+ BinaryPrimitives.WriteUInt16BigEndian(buffer, (ushort)CauseCode);
+ BinaryPrimitives.WriteUInt16BigEndian(buffer.Slice(2), len);
+
+ if (UnresolvableAddress is { Length: > 0 } uresolvableAddress)
{
- Buffer.BlockCopy(UnresolvableAddress, 0, buffer, posn + 4, UnresolvableAddress.Length);
+ uresolvableAddress.CopyTo(buffer.Slice(4));
}
+
return len;
}
}
@@ -252,14 +285,22 @@ public ushort GetErrorCauseLength(bool padded)
}
public int WriteTo(byte[] buffer, int posn)
+ {
+ return WriteTo(buffer.AsSpan(posn));
+ }
+
+ public int WriteTo(Span buffer)
{
var len = GetErrorCauseLength(true);
- NetConvert.ToBuffer((ushort)CauseCode, buffer, posn);
- NetConvert.ToBuffer(len, buffer, posn + 2);
- if (UnrecognizedChunk != null)
+
+ BinaryPrimitives.WriteUInt16BigEndian(buffer, (ushort)CauseCode);
+ BinaryPrimitives.WriteUInt16BigEndian(buffer.Slice(2), len);
+
+ if (UnrecognizedChunk is { Length: > 0 } unrecognizedChunk)
{
- Buffer.BlockCopy(UnrecognizedChunk, 0, buffer, posn + 4, UnrecognizedChunk.Length);
+ unrecognizedChunk.CopyTo(buffer.Slice(4));
}
+
return len;
}
}
@@ -293,14 +334,22 @@ public ushort GetErrorCauseLength(bool padded)
}
public int WriteTo(byte[] buffer, int posn)
+ {
+ return WriteTo(buffer.AsSpan(posn));
+ }
+
+ public int WriteTo(Span buffer_)
{
var len = GetErrorCauseLength(true);
- NetConvert.ToBuffer((ushort)CauseCode, buffer, posn);
- NetConvert.ToBuffer(len, buffer, posn + 2);
- if (UnrecognizedParameters != null)
+
+ BinaryPrimitives.WriteUInt16BigEndian(buffer_, (ushort)CauseCode);
+ BinaryPrimitives.WriteUInt16BigEndian(buffer_.Slice(2), len);
+
+ if (UnrecognizedParameters is { Length: > 0 } unrecognizedParameters)
{
- Buffer.BlockCopy(UnrecognizedParameters, 0, buffer, posn + 4, UnrecognizedParameters.Length);
+ unrecognizedParameters.CopyTo(buffer_.Slice(4));
}
+
return len;
}
}
@@ -328,9 +377,14 @@ public struct SctpErrorNoUserData : ISctpErrorCause
public int WriteTo(byte[] buffer, int posn)
{
- NetConvert.ToBuffer((ushort)CauseCode, buffer, posn);
- NetConvert.ToBuffer(ERROR_CAUSE_LENGTH, buffer, posn + 2);
- NetConvert.ToBuffer(TSN, buffer, posn + 4);
+ return WriteTo(buffer.AsSpan(posn));
+ }
+
+ public int WriteTo(Span buffer)
+ {
+ BinaryPrimitives.WriteUInt16BigEndian(buffer, (ushort)CauseCode);
+ BinaryPrimitives.WriteUInt16BigEndian(buffer.Slice(2), ERROR_CAUSE_LENGTH);
+ BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(4), TSN);
return ERROR_CAUSE_LENGTH;
}
}
@@ -361,14 +415,22 @@ public ushort GetErrorCauseLength(bool padded)
}
public int WriteTo(byte[] buffer, int posn)
+ {
+ return WriteTo(buffer.AsSpan(posn));
+ }
+
+ public int WriteTo(Span buffer)
{
var len = GetErrorCauseLength(true);
- NetConvert.ToBuffer((ushort)CauseCode, buffer, posn);
- NetConvert.ToBuffer(len, buffer, posn + 2);
- if (NewAddressTLVs != null)
+
+ BinaryPrimitives.WriteUInt16BigEndian(buffer, (ushort)CauseCode);
+ BinaryPrimitives.WriteUInt16BigEndian(buffer.Slice(2), len);
+
+ if (NewAddressTLVs is { Length: > 0 } newAddressTLVs)
{
- Buffer.BlockCopy(NewAddressTLVs, 0, buffer, posn + 4, NewAddressTLVs.Length);
+ newAddressTLVs.CopyTo(buffer.Slice(4));
}
+
return len;
}
}
@@ -396,15 +458,22 @@ public ushort GetErrorCauseLength(bool padded)
}
public int WriteTo(byte[] buffer, int posn)
+ {
+ return WriteTo(buffer.AsSpan(posn));
+ }
+
+ public int WriteTo(Span buffer_)
{
var len = GetErrorCauseLength(true);
- NetConvert.ToBuffer((ushort)CauseCode, buffer, posn);
- NetConvert.ToBuffer(len, buffer, posn + 2);
+
+ BinaryPrimitives.WriteUInt16BigEndian(buffer_, (ushort)CauseCode);
+ BinaryPrimitives.WriteUInt16BigEndian(buffer_.Slice(2), len);
+
if (!string.IsNullOrEmpty(AbortReason))
{
- var reasonBuffer = Encoding.UTF8.GetBytes(AbortReason);
- Buffer.BlockCopy(reasonBuffer, 0, buffer, posn + 4, reasonBuffer.Length);
+ Encoding.UTF8.GetBytes(AbortReason.AsSpan(), buffer_.Slice(4));
}
+
return len;
}
}
@@ -433,15 +502,22 @@ public ushort GetErrorCauseLength(bool padded)
}
public int WriteTo(byte[] buffer, int posn)
+ {
+ return WriteTo(buffer.AsSpan(posn));
+ }
+
+ public int WriteTo(Span buffer)
{
var len = GetErrorCauseLength(true);
- NetConvert.ToBuffer((ushort)CauseCode, buffer, posn);
- NetConvert.ToBuffer(len, buffer, posn + 2);
+
+ BinaryPrimitives.WriteUInt16BigEndian(buffer, (ushort)CauseCode);
+ BinaryPrimitives.WriteUInt16BigEndian(buffer.Slice(2), len);
+
if (!string.IsNullOrEmpty(AdditionalInformation))
{
- var reasonBuffer = Encoding.UTF8.GetBytes(AdditionalInformation);
- Buffer.BlockCopy(reasonBuffer, 0, buffer, posn + 4, reasonBuffer.Length);
+ Encoding.UTF8.GetBytes(AdditionalInformation.AsSpan(), buffer.Slice(4));
}
+
return len;
}
}
diff --git a/src/net/SCTP/Chunks/SctpErrorChunk.cs b/src/net/SCTP/Chunks/SctpErrorChunk.cs
index da8c689db0..b59679db2b 100644
--- a/src/net/SCTP/Chunks/SctpErrorChunk.cs
+++ b/src/net/SCTP/Chunks/SctpErrorChunk.cs
@@ -18,7 +18,10 @@
//-----------------------------------------------------------------------------
using System;
+using System.Buffers.Binary;
using System.Collections.Generic;
+using System.ComponentModel;
+using System.Diagnostics;
using System.Linq;
using System.Text;
using Microsoft.Extensions.Logging;
@@ -47,7 +50,7 @@ public class SctpErrorChunk : SctpChunk
protected SctpErrorChunk(SctpChunkType chunkType, bool verificationTagBit)
: base(chunkType)
{
- if(verificationTagBit)
+ if (verificationTagBit)
{
ChunkFlags = ABORT_CHUNK_TBIT_FLAG;
}
@@ -90,9 +93,9 @@ public void AddErrorCause(ISctpErrorCause errorCause)
public override ushort GetChunkLength(bool padded)
{
ushort len = SCTP_CHUNK_HEADER_LENGTH;
- if(ErrorCauses != null && ErrorCauses.Count > 0)
+ if (ErrorCauses != null && ErrorCauses.Count > 0)
{
- foreach(var cause in ErrorCauses)
+ foreach (var cause in ErrorCauses)
{
len += cause.GetErrorCauseLength(padded);
}
@@ -109,16 +112,37 @@ public override ushort GetChunkLength(bool padded)
/// The number of bytes, including padding, written to the buffer.
public override ushort WriteTo(byte[] buffer, int posn)
{
- WriteChunkHeader(buffer, posn);
- if (ErrorCauses != null && ErrorCauses.Count > 0)
+ WriteToCore(buffer.AsSpan(posn));
+
+ return GetChunkLength(true);
+ }
+
+ ///
+ /// Serialises the ERROR chunk to a pre-allocated buffer.
+ ///
+ /// The buffer to write the serialised chunk bytes to. It
+ /// must have the required space already allocated.
+ /// The number of bytes, including padding, written to the buffer.
+ public override int WriteTo(Span buffer)
+ {
+ WriteToCore(buffer);
+
+ return GetChunkLength(true);
+ }
+
+ private void WriteToCore(Span buffer)
+ {
+ WriteChunkHeader(buffer);
+
+ if (ErrorCauses is { Count: > 0 } errorCauses)
{
- int causePosn = posn + 4;
- foreach (var cause in ErrorCauses)
+ buffer = buffer.Slice(4);
+ foreach (var cause in errorCauses)
{
- causePosn += cause.WriteTo(buffer, causePosn);
+ var bytesWritten = cause.WriteTo(buffer);
+ buffer = buffer.Slice(bytesWritten);
}
}
- return GetChunkLength(true);
}
///
@@ -126,43 +150,56 @@ public override ushort WriteTo(byte[] buffer, int posn)
///
/// The buffer holding the serialised chunk.
/// The position to start parsing at.
+ [Obsolete("Use Parse(ParseChunk) instead.", false)]
+ [EditorBrowsable(EditorBrowsableState.Advanced)]
public static SctpErrorChunk ParseChunk(byte[] buffer, int posn, bool isAbort)
+ => ParseChunk(buffer.AsSpan(posn), isAbort);
+
+ ///
+ /// Parses the ERROR chunk fields.
+ ///
+ /// The buffer holding the serialised chunk.
+ public static SctpErrorChunk ParseChunk(ReadOnlySpan buffer, bool isAbort)
{
var errorChunk = (isAbort) ? new SctpAbortChunk(false) : new SctpErrorChunk();
- ushort chunkLen = errorChunk.ParseFirstWord(buffer, posn);
+ var chunkLen = errorChunk.ParseFirstWord(buffer);
- int paramPosn = posn + SCTP_CHUNK_HEADER_LENGTH;
- int paramsBufferLength = chunkLen - SCTP_CHUNK_HEADER_LENGTH;
+ var paramPosn = SCTP_CHUNK_HEADER_LENGTH;
+ var paramsBufferLength = chunkLen - SCTP_CHUNK_HEADER_LENGTH;
if (paramPosn < paramsBufferLength)
{
- bool stopProcessing = false;
+ var stopProcessing = false;
- foreach (var varParam in GetParameters(buffer, paramPosn, paramsBufferLength))
+ var paramsBuffer = buffer.Slice(paramPosn, paramsBufferLength);
+ foreach (var varParam in GetParameters(paramsBuffer))
+ //while (GetNexParameterParameter(ref paramsBuffer, out var varParam))
{
+ Debug.Assert(varParam is not null);
+
switch (varParam.ParameterType)
{
case (ushort)SctpErrorCauseCode.InvalidStreamIdentifier:
- ushort streamID = (ushort)((varParam.ParameterValue != null) ?
- NetConvert.ParseUInt16(varParam.ParameterValue, 0) : 0);
+ var streamID = (ushort)((varParam.ParameterValue != null) ?
+ BinaryPrimitives.ReadUInt16BigEndian(varParam.ParameterValue) : 0);
var invalidStreamID = new SctpErrorInvalidStreamIdentifier { StreamID = streamID };
errorChunk.AddErrorCause(invalidStreamID);
break;
case (ushort)SctpErrorCauseCode.MissingMandatoryParameter:
- List missingIDs = new List();
+ var missingIDs = new List();
if (varParam.ParameterValue != null)
{
- for (int i = 0; i < varParam.ParameterValue.Length; i += 2)
+ for (var i = 0; i < varParam.ParameterValue.Length; i += 2)
{
- missingIDs.Add(NetConvert.ParseUInt16(varParam.ParameterValue, i));
+ missingIDs.Add(BinaryPrimitives.ReadUInt16BigEndian(varParam.ParameterValue.AsSpan(i)));
}
}
var missingMandatory = new SctpErrorMissingMandatoryParameter { MissingParameters = missingIDs };
errorChunk.AddErrorCause(missingMandatory);
break;
case (ushort)SctpErrorCauseCode.StaleCookieError:
- uint staleness = (uint)((varParam.ParameterValue != null) ?
- NetConvert.ParseUInt32(varParam.ParameterValue, 0) : 0);
+ var staleness = (uint)((varParam.ParameterValue != null) ?
+ BinaryPrimitives.ReadUInt32BigEndian(varParam.ParameterValue) : 0);
var staleCookie = new SctpErrorStaleCookieError { MeasureOfStaleness = staleness };
errorChunk.AddErrorCause(staleCookie);
break;
@@ -186,7 +223,7 @@ public static SctpErrorChunk ParseChunk(byte[] buffer, int posn, bool isAbort)
break;
case (ushort)SctpErrorCauseCode.NoUserData:
uint tsn = (uint)((varParam.ParameterValue != null) ?
- NetConvert.ParseUInt32(varParam.ParameterValue, 0) : 0);
+ BinaryPrimitives.ReadUInt32BigEndian(varParam.ParameterValue) : 0);
var noData = new SctpErrorNoUserData { TSN = tsn };
errorChunk.AddErrorCause(noData);
break;
@@ -199,13 +236,13 @@ public static SctpErrorChunk ParseChunk(byte[] buffer, int posn, bool isAbort)
errorChunk.AddErrorCause(restartAddress);
break;
case (ushort)SctpErrorCauseCode.UserInitiatedAbort:
- string reason = (varParam.ParameterValue != null) ?
+ var reason = (varParam.ParameterValue != null) ?
Encoding.UTF8.GetString(varParam.ParameterValue) : null;
var userAbort = new SctpErrorUserInitiatedAbort { AbortReason = reason };
errorChunk.AddErrorCause(userAbort);
break;
case (ushort)SctpErrorCauseCode.ProtocolViolation:
- string info = (varParam.ParameterValue != null) ?
+ var info = (varParam.ParameterValue != null) ?
Encoding.UTF8.GetString(varParam.ParameterValue) : null;
var protocolViolation = new SctpErrorProtocolViolation { AdditionalInformation = info };
errorChunk.AddErrorCause(protocolViolation);
diff --git a/src/net/SCTP/Chunks/SctpInitChunk.cs b/src/net/SCTP/Chunks/SctpInitChunk.cs
index 2df18b763e..0cea6a80a9 100644
--- a/src/net/SCTP/Chunks/SctpInitChunk.cs
+++ b/src/net/SCTP/Chunks/SctpInitChunk.cs
@@ -18,7 +18,10 @@
//-----------------------------------------------------------------------------
using System;
+using System.Buffers.Binary;
using System.Collections.Generic;
+using System.ComponentModel;
+using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Net.Sockets;
@@ -239,11 +242,11 @@ private List GetVariableParameters()
if (SupportedAddressTypes.Count > 0)
{
- byte[] paramVal = new byte[SupportedAddressTypes.Count * 2];
- int paramValPosn = 0;
+ var paramVal = new byte[SupportedAddressTypes.Count * 2];
+ var paramValPosn = 0;
foreach (var supAddr in SupportedAddressTypes)
{
- NetConvert.ToBuffer((ushort)supAddr, paramVal, paramValPosn);
+ BinaryPrimitives.WriteUInt16BigEndian(paramVal.AsSpan(paramValPosn), (ushort)supAddr);
paramValPosn += 2;
}
varParams.Add(
@@ -288,30 +291,46 @@ public override ushort GetChunkLength(bool padded)
/// The number of bytes, including padding, written to the buffer.
public override ushort WriteTo(byte[] buffer, int posn)
{
- WriteChunkHeader(buffer, posn);
+ WriteToCore(buffer.AsSpan(posn));
- // Write fixed parameters.
- int startPosn = posn + SCTP_CHUNK_HEADER_LENGTH;
+ return GetChunkLength(true);
+ }
+
+ ///
+ /// Serialises an INIT or INIT ACK chunk to a pre-allocated buffer.
+ ///
+ /// The buffer to write the serialised chunk bytes to. It
+ /// must have the required space already allocated.
+ /// The number of bytes, including padding, written to the buffer.
+ public override int WriteTo(Span buffer)
+ {
+ WriteToCore(buffer);
+
+ return GetChunkLength(true);
+ }
+
+ private void WriteToCore(Span buffer)
+ {
+ var bytesWritten = WriteChunkHeader(buffer);
- NetConvert.ToBuffer(InitiateTag, buffer, startPosn);
- NetConvert.ToBuffer(ARwnd, buffer, startPosn + 4);
- NetConvert.ToBuffer(NumberOutboundStreams, buffer, startPosn + 8);
- NetConvert.ToBuffer(NumberInboundStreams, buffer, startPosn + 10);
- NetConvert.ToBuffer(InitialTSN, buffer, startPosn + 12);
+ // Write fixed parameters.
- var varParameters = GetVariableParameters();
+ BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(SCTP_CHUNK_HEADER_LENGTH), InitiateTag);
+ BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(SCTP_CHUNK_HEADER_LENGTH + 4), ARwnd);
+ BinaryPrimitives.WriteUInt16BigEndian(buffer.Slice(SCTP_CHUNK_HEADER_LENGTH + 8), NumberOutboundStreams);
+ BinaryPrimitives.WriteUInt16BigEndian(buffer.Slice(SCTP_CHUNK_HEADER_LENGTH + 10), NumberInboundStreams);
+ BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(SCTP_CHUNK_HEADER_LENGTH + 12), InitialTSN);
// Write optional parameters.
- if (varParameters.Count > 0)
+ if (GetVariableParameters() is { Count: > 0 } varParameters)
{
- int paramPosn = startPosn + FIXED_PARAMETERS_LENGTH;
+ buffer = buffer.Slice(SCTP_CHUNK_HEADER_LENGTH + FIXED_PARAMETERS_LENGTH);
foreach (var optParam in varParameters)
{
- paramPosn += optParam.WriteTo(buffer, paramPosn);
+ var bytesWriten = optParam.WriteTo(buffer);
+ buffer = buffer.Slice(bytesWriten);
}
}
-
- return GetChunkLength(true);
}
///
@@ -319,28 +338,39 @@ public override ushort WriteTo(byte[] buffer, int posn)
///
/// The buffer holding the serialised chunk.
/// The position to start parsing at.
+ [Obsolete("Use ParseChunk(ReadOnlySpan) instead.", false)]
+ [EditorBrowsable(EditorBrowsableState.Advanced)]
public static SctpInitChunk ParseChunk(byte[] buffer, int posn)
+ => ParseChunk(buffer.AsSpan(posn));
+
+ ///
+ /// Parses the INIT or INIT ACK chunk fields
+ ///
+ /// The buffer holding the serialised chunk.
+ public static SctpInitChunk ParseChunk(ReadOnlySpan buffer)
{
var initChunk = new SctpInitChunk();
- ushort chunkLen = initChunk.ParseFirstWord(buffer, posn);
-
- int startPosn = posn + SCTP_CHUNK_HEADER_LENGTH;
+ var chunkLen = initChunk.ParseFirstWord(buffer);
- initChunk.InitiateTag = NetConvert.ParseUInt32(buffer, startPosn);
- initChunk.ARwnd = NetConvert.ParseUInt32(buffer, startPosn + 4);
- initChunk.NumberOutboundStreams = NetConvert.ParseUInt16(buffer, startPosn + 8);
- initChunk.NumberInboundStreams = NetConvert.ParseUInt16(buffer, startPosn + 10);
- initChunk.InitialTSN = NetConvert.ParseUInt32(buffer, startPosn + 12);
+ initChunk.InitiateTag = BinaryPrimitives.ReadUInt32BigEndian(buffer.Slice(SCTP_CHUNK_HEADER_LENGTH));
+ initChunk.ARwnd = BinaryPrimitives.ReadUInt32BigEndian(buffer.Slice(SCTP_CHUNK_HEADER_LENGTH + 4));
+ initChunk.NumberOutboundStreams = BinaryPrimitives.ReadUInt16BigEndian(buffer.Slice(SCTP_CHUNK_HEADER_LENGTH + 8));
+ initChunk.NumberInboundStreams = BinaryPrimitives.ReadUInt16BigEndian(buffer.Slice(SCTP_CHUNK_HEADER_LENGTH + 10));
+ initChunk.InitialTSN = BinaryPrimitives.ReadUInt32BigEndian(buffer.Slice(SCTP_CHUNK_HEADER_LENGTH + 12));
- int paramPosn = startPosn + FIXED_PARAMETERS_LENGTH;
- int paramsBufferLength = chunkLen - SCTP_CHUNK_HEADER_LENGTH - FIXED_PARAMETERS_LENGTH;
+ var paramPosn = SCTP_CHUNK_HEADER_LENGTH + FIXED_PARAMETERS_LENGTH;
+ var paramsBufferLength = chunkLen - SCTP_CHUNK_HEADER_LENGTH - FIXED_PARAMETERS_LENGTH;
if (paramPosn < paramsBufferLength)
{
- bool stopProcessing = false;
+ var stopProcessing = false;
- foreach (var varParam in GetParameters(buffer, paramPosn, paramsBufferLength))
+ var paramsBuffer = buffer.Slice(paramPosn, paramsBufferLength);
+ foreach (var varParam in GetParameters(paramsBuffer))
+ //while (GetNexParameterParameter(ref paramsBuffer, out var varParam))
{
+ Debug.Assert(varParam is not null);
+
switch (varParam.ParameterType)
{
case (ushort)SctpInitChunkParameterType.IPv4Address:
@@ -350,7 +380,7 @@ public static SctpInitChunk ParseChunk(byte[] buffer, int posn)
break;
case (ushort)SctpInitChunkParameterType.CookiePreservative:
- initChunk.CookiePreservative = NetConvert.ParseUInt32(varParam.ParameterValue, 0);
+ initChunk.CookiePreservative = BinaryPrimitives.ReadUInt32BigEndian(varParam.ParameterValue.AsSpan());
break;
case (ushort)SctpInitChunkParameterType.HostNameAddress:
@@ -360,7 +390,7 @@ public static SctpInitChunk ParseChunk(byte[] buffer, int posn)
case (ushort)SctpInitChunkParameterType.SupportedAddressTypes:
for (int valPosn = 0; valPosn < varParam.ParameterValue.Length; valPosn += 2)
{
- switch (NetConvert.ParseUInt16(varParam.ParameterValue, valPosn))
+ switch (BinaryPrimitives.ReadUInt16BigEndian(varParam.ParameterValue.AsSpan(valPosn)))
{
case (ushort)SctpInitChunkParameterType.IPv4Address:
initChunk.SupportedAddressTypes.Add(SctpInitChunkParameterType.IPv4Address);
diff --git a/src/net/SCTP/Chunks/SctpSackChunk.cs b/src/net/SCTP/Chunks/SctpSackChunk.cs
index 313170db68..42ca7692b0 100644
--- a/src/net/SCTP/Chunks/SctpSackChunk.cs
+++ b/src/net/SCTP/Chunks/SctpSackChunk.cs
@@ -17,7 +17,10 @@
// BSD 3-Clause "New" or "Revised" License, see included LICENSE.md file.
//-----------------------------------------------------------------------------
+using System;
+using System.Buffers.Binary;
using System.Collections.Generic;
+using System.ComponentModel;
using SIPSorcery.Sys;
namespace SIPSorcery.Net
@@ -79,7 +82,7 @@ public SctpSackChunk(uint cumulativeTsnAck, uint arwnd) : base(SctpChunkType.SAC
/// The length of the chunk.
public override ushort GetChunkLength(bool padded)
{
- var len = (ushort)(SCTP_CHUNK_HEADER_LENGTH +
+ var len = (ushort)(SCTP_CHUNK_HEADER_LENGTH +
FIXED_PARAMETERS_LENGTH +
GapAckBlocks.Count * GAP_REPORT_LENGTH +
DuplicateTSN.Count * DUPLICATE_TSN_LENGTH);
@@ -97,31 +100,47 @@ public override ushort GetChunkLength(bool padded)
/// The number of bytes, including padding, written to the buffer.
public override ushort WriteTo(byte[] buffer, int posn)
{
- WriteChunkHeader(buffer, posn);
+ WriteToCore(buffer.AsSpan(posn));
- ushort startPosn = (ushort)(posn + SCTP_CHUNK_HEADER_LENGTH);
+ return GetChunkLength(true);
+ }
- NetConvert.ToBuffer(CumulativeTsnAck, buffer, startPosn);
- NetConvert.ToBuffer(ARwnd, buffer, startPosn + 4);
- NetConvert.ToBuffer((ushort)GapAckBlocks.Count, buffer, startPosn + 8);
- NetConvert.ToBuffer((ushort)DuplicateTSN.Count, buffer, startPosn + 10);
+ ///
+ /// Serialises the SACK chunk to a pre-allocated buffer.
+ ///
+ /// The buffer to write the serialised chunk bytes to. It
+ /// must have the required space already allocated.
+ /// The number of bytes, including padding, written to the buffer.
+ public override int WriteTo(Span buffer)
+ {
+ WriteToCore(buffer);
- int reportPosn = startPosn + FIXED_PARAMETERS_LENGTH;
+ return GetChunkLength(true);
+ }
+
+ private void WriteToCore(Span buffer)
+ {
+ var bytesWritten = WriteChunkHeader(buffer);
+
+ BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(SCTP_CHUNK_HEADER_LENGTH), CumulativeTsnAck);
+ BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(SCTP_CHUNK_HEADER_LENGTH + 4), ARwnd);
+ BinaryPrimitives.WriteUInt16BigEndian(buffer.Slice(SCTP_CHUNK_HEADER_LENGTH + 8), (ushort)GapAckBlocks.Count);
+ BinaryPrimitives.WriteUInt16BigEndian(buffer.Slice(SCTP_CHUNK_HEADER_LENGTH + 10), (ushort)DuplicateTSN.Count);
+
+ var reportPosn = SCTP_CHUNK_HEADER_LENGTH + FIXED_PARAMETERS_LENGTH;
foreach (var gapBlock in GapAckBlocks)
{
- NetConvert.ToBuffer(gapBlock.Start, buffer, reportPosn);
- NetConvert.ToBuffer(gapBlock.End, buffer, reportPosn + 2);
+ BinaryPrimitives.WriteUInt16BigEndian(buffer.Slice(reportPosn), gapBlock.Start);
+ BinaryPrimitives.WriteUInt16BigEndian(buffer.Slice(reportPosn + 2), gapBlock.End);
reportPosn += GAP_REPORT_LENGTH;
}
- foreach(var dupTSN in DuplicateTSN)
+ foreach (var dupTSN in DuplicateTSN)
{
- NetConvert.ToBuffer(dupTSN, buffer, reportPosn);
+ BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(reportPosn), dupTSN);
reportPosn += DUPLICATE_TSN_LENGTH;
}
-
- return GetChunkLength(true);
}
///
@@ -129,31 +148,38 @@ public override ushort WriteTo(byte[] buffer, int posn)
///
/// The buffer holding the serialised chunk.
/// The position to start parsing at.
+ [Obsolete("Use ParseChunk(ReadOnlySpan) instead.", false)]
+ [EditorBrowsable(EditorBrowsableState.Advanced)]
public static SctpSackChunk ParseChunk(byte[] buffer, int posn)
+ => ParseChunk(buffer.AsSpan(posn));
+
+ ///
+ /// Parses the SACK chunk fields.
+ ///
+ /// The buffer holding the serialised chunk.
+ public static SctpSackChunk ParseChunk(ReadOnlySpan buffer)
{
var sackChunk = new SctpSackChunk();
- ushort chunkLen = sackChunk.ParseFirstWord(buffer, posn);
-
- ushort startPosn = (ushort)(posn + SCTP_CHUNK_HEADER_LENGTH);
+ var chunkLen = sackChunk.ParseFirstWord(buffer);
- sackChunk.CumulativeTsnAck = NetConvert.ParseUInt32(buffer, startPosn);
- sackChunk.ARwnd = NetConvert.ParseUInt32(buffer, startPosn + 4);
- ushort numGapAckBlocks = NetConvert.ParseUInt16(buffer, startPosn + 8);
- ushort numDuplicateTSNs = NetConvert.ParseUInt16(buffer, startPosn + 10);
+ sackChunk.CumulativeTsnAck = BinaryPrimitives.ReadUInt32BigEndian(buffer.Slice((ushort)(SCTP_CHUNK_HEADER_LENGTH)));
+ sackChunk.ARwnd = BinaryPrimitives.ReadUInt32BigEndian(buffer.Slice(((ushort)(SCTP_CHUNK_HEADER_LENGTH)) + 4));
+ var numGapAckBlocks = BinaryPrimitives.ReadUInt16BigEndian(buffer.Slice(((ushort)(SCTP_CHUNK_HEADER_LENGTH)) + 8));
+ var numDuplicateTSNs = BinaryPrimitives.ReadUInt16BigEndian(buffer.Slice(((ushort)(SCTP_CHUNK_HEADER_LENGTH)) + 10));
- int reportPosn = startPosn + FIXED_PARAMETERS_LENGTH;
+ var reportPosn = ((ushort)(SCTP_CHUNK_HEADER_LENGTH)) + FIXED_PARAMETERS_LENGTH;
- for (int i=0; i < numGapAckBlocks; i++)
+ for (var i = 0; i < numGapAckBlocks; i++)
{
- ushort start = NetConvert.ParseUInt16(buffer, reportPosn);
- ushort end = NetConvert.ParseUInt16(buffer, reportPosn + 2);
+ var start = BinaryPrimitives.ReadUInt16BigEndian(buffer.Slice(reportPosn));
+ var end = BinaryPrimitives.ReadUInt16BigEndian(buffer.Slice(reportPosn + 2));
sackChunk.GapAckBlocks.Add(new SctpTsnGapBlock { Start = start, End = end });
reportPosn += GAP_REPORT_LENGTH;
}
- for(int j=0; j < numDuplicateTSNs; j++)
+ for (var j = 0; j < numDuplicateTSNs; j++)
{
- sackChunk.DuplicateTSN.Add(NetConvert.ParseUInt32(buffer, reportPosn));
+ sackChunk.DuplicateTSN.Add(BinaryPrimitives.ReadUInt32BigEndian(buffer.Slice(reportPosn)));
reportPosn += DUPLICATE_TSN_LENGTH;
}
diff --git a/src/net/SCTP/Chunks/SctpShutdownChunk.cs b/src/net/SCTP/Chunks/SctpShutdownChunk.cs
index 61cdb0a318..7110db49d5 100644
--- a/src/net/SCTP/Chunks/SctpShutdownChunk.cs
+++ b/src/net/SCTP/Chunks/SctpShutdownChunk.cs
@@ -17,7 +17,11 @@
// BSD 3-Clause "New" or "Revised" License, see included LICENSE.md file.
//-----------------------------------------------------------------------------
+using System;
+using System.Buffers.Binary;
+using System.ComponentModel;
using SIPSorcery.Sys;
+using static Org.BouncyCastle.Asn1.Cmp.Challenge;
namespace SIPSorcery.Net
{
@@ -69,20 +73,49 @@ public override ushort GetChunkLength(bool padded)
/// The number of bytes, including padding, written to the buffer.
public override ushort WriteTo(byte[] buffer, int posn)
{
- WriteChunkHeader(buffer, posn);
- NetConvert.ToBuffer(CumulativeTsnAck.GetValueOrDefault(), buffer, posn + SCTP_CHUNK_HEADER_LENGTH);
+ WriteToCore(buffer.AsSpan(posn));
+
return GetChunkLength(true);
}
+ ///
+ /// Serialises the SHUTDOWN chunk to a pre-allocated buffer.
+ ///
+ /// The buffer to write the serialised chunk bytes to. It
+ /// must have the required space already allocated.
+ /// The number of bytes, including padding, written to the buffer.
+ public override int WriteTo(Span buffer)
+ {
+ WriteToCore(buffer);
+
+ return GetChunkLength(true);
+ }
+
+ private void WriteToCore(Span buffer)
+ {
+ var bytesWritten = WriteChunkHeader(buffer);
+
+ BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(SCTP_CHUNK_HEADER_LENGTH), CumulativeTsnAck.GetValueOrDefault());
+ }
+
///
/// Parses the SHUTDOWN chunk fields.
///
/// The buffer holding the serialised chunk.
/// The position to start parsing at.
+ [Obsolete("Use ParseChunk(ReadOnlySpan) instead.", false)]
+ [EditorBrowsable(EditorBrowsableState.Advanced)]
public static SctpShutdownChunk ParseChunk(byte[] buffer, int posn)
+ => ParseChunk(buffer.AsSpan(posn));
+
+ ///
+ /// Parses the SHUTDOWN chunk fields.
+ ///
+ /// The buffer holding the serialised chunk.
+ public static SctpShutdownChunk ParseChunk(ReadOnlySpan buffer)
{
var shutdownChunk = new SctpShutdownChunk();
- shutdownChunk.CumulativeTsnAck = NetConvert.ParseUInt32(buffer, posn + SCTP_CHUNK_HEADER_LENGTH);
+ shutdownChunk.CumulativeTsnAck = BinaryPrimitives.ReadUInt32BigEndian(buffer.Slice(SCTP_CHUNK_HEADER_LENGTH));
return shutdownChunk;
}
}
diff --git a/src/net/SCTP/Chunks/SctpTlvChunkParameter.cs b/src/net/SCTP/Chunks/SctpTlvChunkParameter.cs
index 12f5e8f3c1..be155a39da 100644
--- a/src/net/SCTP/Chunks/SctpTlvChunkParameter.cs
+++ b/src/net/SCTP/Chunks/SctpTlvChunkParameter.cs
@@ -19,6 +19,8 @@
//-----------------------------------------------------------------------------
using System;
+using System.Buffers.Binary;
+using System.ComponentModel;
using System.Net;
using System.Net.Sockets;
using Microsoft.Extensions.Logging;
@@ -159,8 +161,18 @@ public virtual ushort GetParameterLength(bool padded)
/// The position in the buffer to write at.
protected void WriteParameterHeader(byte[] buffer, int posn)
{
- NetConvert.ToBuffer(ParameterType, buffer, posn);
- NetConvert.ToBuffer(GetParameterLength(false), buffer, posn + 2);
+ WriteParameterHeader(buffer.AsSpan(posn));
+ }
+
+ ///
+ /// Writes the parameter header to the buffer. All chunk parameters use the same two
+ /// header fields.
+ ///
+ /// The buffer to write the chunk parameter header to.
+ protected void WriteParameterHeader(Span buffer)
+ {
+ BinaryPrimitives.WriteUInt16BigEndian(buffer, ParameterType);
+ BinaryPrimitives.WriteUInt16BigEndian(buffer.Slice(2), GetParameterLength(false));
}
///
@@ -174,16 +186,38 @@ protected void WriteParameterHeader(byte[] buffer, int posn)
/// The number of bytes, including padding, written to the buffer.
public virtual int WriteTo(byte[] buffer, int posn)
{
- WriteParameterHeader(buffer, posn);
+ WriteToCore(buffer.AsSpan(posn));
- if (ParameterValue?.Length > 0)
- {
- Buffer.BlockCopy(ParameterValue, 0, buffer, posn + SCTP_PARAMETER_HEADER_LENGTH, ParameterValue.Length);
- }
+ return GetParameterLength(true);
+ }
+
+ ///
+ /// Serialises the chunk parameter to a pre-allocated buffer. This method gets overridden
+ /// by specialised SCTP chunk parameters that have their own data and need to be serialised
+ /// differently.
+ ///
+ /// The buffer to write the serialised chunk parameter bytes to. It
+ /// must have the required space already allocated.
+ /// The number of bytes, including padding, written to the buffer.
+ public virtual int WriteTo(Span buffer)
+ {
+ WriteToCore(buffer);
return GetParameterLength(true);
}
+ private void WriteToCore(Span buffer)
+ {
+ WriteParameterHeader(buffer);
+
+ if (ParameterValue is { Length: > 0 } parameterValue)
+ {
+ parameterValue.CopyTo(buffer.Slice(SCTP_PARAMETER_HEADER_LENGTH));
+ }
+ }
+
+ public int GetPacketSize() => GetParameterLength(true);
+
///
/// Serialises an SCTP chunk parameter to a byte array.
///
@@ -195,24 +229,49 @@ public byte[] GetBytes()
return buffer;
}
+ public int WriteBytes(Span buffer)
+ {
+ var size = GetPacketSize();
+
+ if (buffer.Length < size)
+ {
+ throw new ArgumentOutOfRangeException($"The buffer should have at least {size} bytes and had only {buffer.Length}.");
+ }
+
+ WriteBytesCore(buffer.Slice(0, size));
+
+ return size;
+ }
+
+ private void WriteBytesCore(Span buffer)
+ {
+ }
+
///
/// The first 32 bits of all chunk parameters represent the type and length. This method
/// parses those fields and sets them on the current instance.
///
/// The buffer holding the serialised chunk parameter.
/// The position in the buffer that indicates the start of the chunk parameter.
+ [Obsolete("Use ParseFirstWord(ReadOnlySpan) instead.", false)]
+ [EditorBrowsable(EditorBrowsableState.Advanced)]
public ushort ParseFirstWord(byte[] buffer, int posn)
+ => ParseFirstWord(buffer.AsSpan(posn));
+
+ ///
+ /// The first 32 bits of all chunk parameters represent the type and length. This method
+ /// parses those fields and sets them on the current instance.
+ ///
+ /// The buffer holding the serialised chunk parameter.
+ public ushort ParseFirstWord(ReadOnlySpan buffer)
{
- ParameterType = NetConvert.ParseUInt16(buffer, posn);
- ushort paramLen = NetConvert.ParseUInt16(buffer, posn + 2);
+ ParameterType = BinaryPrimitives.ReadUInt16BigEndian(buffer);
+ var paramLen = BinaryPrimitives.ReadUInt16BigEndian(buffer.Slice(2));
- if (paramLen > 0 && buffer.Length < posn + paramLen)
+ if (paramLen > 0 && buffer.Length < paramLen)
{
// The buffer was not big enough to supply the specified chunk parameter.
- int bytesRequired = paramLen;
- int bytesAvailable = buffer.Length - posn;
- throw new ApplicationException($"The SCTP chunk parameter buffer was too short. " +
- $"Required {bytesRequired} bytes but only {bytesAvailable} available.");
+ throw new ApplicationException($"The SCTP chunk parameter buffer was too short. Required {paramLen} bytes but only {buffer.Length} available.");
}
return paramLen;
@@ -224,20 +283,28 @@ public ushort ParseFirstWord(byte[] buffer, int posn)
/// The buffer holding the serialised TLV chunk parameter.
/// The position to start parsing at.
/// An SCTP TLV chunk parameter instance.
+ [Obsolete("Use ParseTlvParameter(ReadOnlySpan) instead.", false)]
+ [EditorBrowsable(EditorBrowsableState.Advanced)]
public static SctpTlvChunkParameter ParseTlvParameter(byte[] buffer, int posn)
+ => ParseTlvParameter(buffer.AsSpan(posn));
+
+ ///
+ /// Parses an SCTP Type-Length-Value (TLV) chunk parameter from a buffer.
+ ///
+ /// The buffer holding the serialised TLV chunk parameter.
+ /// An SCTP TLV chunk parameter instance.
+ public static SctpTlvChunkParameter ParseTlvParameter(ReadOnlySpan buffer)
{
- if (buffer.Length < posn + SCTP_PARAMETER_HEADER_LENGTH)
+ if (buffer.Length < SCTP_PARAMETER_HEADER_LENGTH)
{
throw new ApplicationException("Buffer did not contain the minimum of bytes for an SCTP TLV chunk parameter.");
}
var tlvParam = new SctpTlvChunkParameter();
- ushort paramLen = tlvParam.ParseFirstWord(buffer, posn);
+ var paramLen = tlvParam.ParseFirstWord(buffer);
if (paramLen > SCTP_PARAMETER_HEADER_LENGTH)
{
- tlvParam.ParameterValue = new byte[paramLen - SCTP_PARAMETER_HEADER_LENGTH];
- Buffer.BlockCopy(buffer, posn + SCTP_PARAMETER_HEADER_LENGTH, tlvParam.ParameterValue,
- 0, tlvParam.ParameterValue.Length);
+ tlvParam.ParameterValue = buffer.Slice(SCTP_PARAMETER_HEADER_LENGTH, paramLen - SCTP_PARAMETER_HEADER_LENGTH).ToArray();
}
return tlvParam;
}
diff --git a/src/net/SCTP/SctpDataReceiver.cs b/src/net/SCTP/SctpDataReceiver.cs
index 8f0b2aeb74..2153bbc9a4 100755
--- a/src/net/SCTP/SctpDataReceiver.cs
+++ b/src/net/SCTP/SctpDataReceiver.cs
@@ -523,7 +523,7 @@ private SctpDataFrame GetFragmentedChunk(Dictionary fragmen
tsn++;
}
- frame.UserData = frame.UserData.Take(posn).ToArray();
+ frame.UserData = frame.UserData.AsSpan(0, posn).ToArray();
return frame;
}
diff --git a/src/net/SCTP/SctpHeader.cs b/src/net/SCTP/SctpHeader.cs
index 569778d123..55c19f0ffb 100644
--- a/src/net/SCTP/SctpHeader.cs
+++ b/src/net/SCTP/SctpHeader.cs
@@ -18,6 +18,8 @@
//-----------------------------------------------------------------------------
using System;
+using System.Buffers.Binary;
+using System.ComponentModel;
using SIPSorcery.Sys;
namespace SIPSorcery.Net
@@ -56,9 +58,22 @@ public struct SctpHeader
/// bytes to.
public void WriteToBuffer(byte[] buffer, int posn)
{
- NetConvert.ToBuffer(SourcePort, buffer, posn);
- NetConvert.ToBuffer(DestinationPort, buffer, posn + 2);
- NetConvert.ToBuffer(VerificationTag, buffer, posn + 4);
+ _ = WriteBytes(buffer.AsSpan(posn));
+ }
+
+ ///
+ /// Serialises the header to a pre-allocated buffer.
+ ///
+ /// The buffer to write the SCTP header bytes to. It
+ /// must have the required space already allocated.
+ /// The number of bytes written.
+ public int WriteBytes(Span buffer)
+ {
+ BinaryPrimitives.WriteUInt16BigEndian(buffer, SourcePort);
+ BinaryPrimitives.WriteUInt16BigEndian(buffer.Slice(2), DestinationPort);
+ BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(4), VerificationTag);
+
+ return 8;
}
///
@@ -67,19 +82,29 @@ public void WriteToBuffer(byte[] buffer, int posn)
/// The buffer to parse the SCTP header from.
/// The position in the buffer to start parsing the header from.
/// A new SCTPHeaer instance.
+ [Obsolete("Use Parse(ReadOnlySpan) instead.", false)]
+ [EditorBrowsable(EditorBrowsableState.Advanced)]
public static SctpHeader Parse(byte[] buffer, int posn)
+ => Parse(buffer.AsSpan(posn));
+
+ ///
+ /// Parses the an SCTP header from a buffer.
+ ///
+ /// The buffer to parse the SCTP header from.
+ /// A new SCTPHeaer instance.
+ public static SctpHeader Parse(ReadOnlySpan buffer)
{
if (buffer.Length < SCTP_HEADER_LENGTH)
{
throw new ApplicationException("The buffer did not contain the minimum number of bytes for an SCTP header.");
}
- SctpHeader header = new SctpHeader();
+ var header = new SctpHeader();
- header.SourcePort = NetConvert.ParseUInt16(buffer, posn);
- header.DestinationPort = NetConvert.ParseUInt16(buffer, posn + 2);
- header.VerificationTag = NetConvert.ParseUInt32(buffer, posn + 4);
- header.Checksum = NetConvert.ParseUInt32(buffer, posn + 8);
+ header.SourcePort = BinaryPrimitives.ReadUInt16BigEndian(buffer);
+ header.DestinationPort = BinaryPrimitives.ReadUInt16BigEndian(buffer.Slice(2));
+ header.VerificationTag = BinaryPrimitives.ReadUInt32BigEndian(buffer.Slice(4));
+ header.Checksum = BinaryPrimitives.ReadUInt32BigEndian(buffer.Slice(8));
return header;
}
diff --git a/src/net/SCTP/SctpPacket.cs b/src/net/SCTP/SctpPacket.cs
index 49e1d76ee9..13e1124b37 100755
--- a/src/net/SCTP/SctpPacket.cs
+++ b/src/net/SCTP/SctpPacket.cs
@@ -18,6 +18,7 @@
//-----------------------------------------------------------------------------
using System;
+using System.Buffers.Binary;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.Logging;
@@ -47,7 +48,14 @@ static CRC32C()
public static uint Calculate(byte[] buffer, int offset, int length)
{
- uint crc = ~0u;
+ return Calculate(buffer.AsSpan(offset, length));
+ }
+
+ public static uint Calculate(ReadOnlySpan buffer)
+ {
+ var crc = ~0u;
+ var length = buffer.Length;
+ var offset = 0;
while (--length >= 0)
{
crc = _table[(crc ^ buffer[offset++]) & 0xff] ^ crc >> 8;
@@ -118,28 +126,50 @@ public SctpPacket(
UnrecognisedChunks = new List();
}
+ public int GetPacketSize() => SctpHeader.SCTP_HEADER_LENGTH + Chunks.Sum(x => x.GetChunkLength(true));
+
///
/// Serialises an SCTP packet to a byte array.
///
/// The byte array containing the serialised SCTP packet.
public byte[] GetBytes()
{
- int chunksLength = Chunks.Sum(x => x.GetChunkLength(true));
- byte[] buffer = new byte[SctpHeader.SCTP_HEADER_LENGTH + chunksLength];
+ var buffer = new byte[GetPacketSize()];
- Header.WriteToBuffer(buffer, 0);
+ WriteBytesCore(buffer);
- int writePosn = SctpHeader.SCTP_HEADER_LENGTH;
- foreach (var chunk in Chunks)
+ return buffer;
+ }
+
+ public int WriteBytes(Span buffer)
+ {
+ var size = GetPacketSize();
+
+ if (buffer.Length < size)
{
- writePosn += chunk.WriteTo(buffer, writePosn);
+ throw new ArgumentOutOfRangeException($"The buffer should have at least {size} bytes and had only {buffer.Length}.");
}
- NetConvert.ToBuffer(0U, buffer, CHECKSUM_BUFFER_POSITION);
- uint checksum = CRC32C.Calculate(buffer, 0, buffer.Length);
- NetConvert.ToBuffer(NetConvert.EndianFlip(checksum), buffer, CHECKSUM_BUFFER_POSITION);
+ WriteBytesCore(buffer.Slice(0, size));
- return buffer;
+ return size;
+ }
+
+ private void WriteBytesCore(Span buffer)
+ {
+ var bytesWritten = Header.WriteBytes(buffer);
+
+ var contentBuffer = buffer.Slice(SctpHeader.SCTP_HEADER_LENGTH);
+ foreach (var chunk in Chunks)
+ {
+ bytesWritten = chunk.WriteTo(contentBuffer);
+ contentBuffer = contentBuffer.Slice(bytesWritten);
+ }
+
+ var checksumBuffer = buffer.Slice(CHECKSUM_BUFFER_POSITION, sizeof(uint));
+ checksumBuffer.Clear();
+ var checksum = CRC32C.Calculate(buffer);
+ BinaryPrimitives.WriteUInt32LittleEndian(checksumBuffer, checksum);
}
///
diff --git a/src/net/STUN/STUNAttributes/STUNAttribute.cs b/src/net/STUN/STUNAttributes/STUNAttribute.cs
index 1d84d78151..b3bf1c0d20 100644
--- a/src/net/STUN/STUNAttributes/STUNAttribute.cs
+++ b/src/net/STUN/STUNAttributes/STUNAttribute.cs
@@ -37,7 +37,9 @@
//-----------------------------------------------------------------------------
using System;
+using System.Buffers.Binary;
using System.Collections.Generic;
+using System.ComponentModel;
using Microsoft.Extensions.Logging;
using SIPSorcery.Sys;
@@ -142,13 +144,15 @@ public STUNAttribute(STUNAttributeTypesEnum attributeType, byte[] value)
public STUNAttribute(STUNAttributeTypesEnum attributeType, ushort value)
{
AttributeType = attributeType;
- Value = NetConvert.GetBytes(value);
+ Value = new byte[sizeof(ushort)];
+ BinaryPrimitives.WriteUInt16BigEndian(Value, value);
}
public STUNAttribute(STUNAttributeTypesEnum attributeType, uint value)
{
AttributeType = attributeType;
- Value = NetConvert.GetBytes(value);
+ Value = new byte[sizeof(uint)];
+ BinaryPrimitives.WriteUInt32BigEndian(Value, value);
}
public STUNAttribute(STUNAttributeTypesEnum attributeType, ulong value)
@@ -157,80 +161,84 @@ public STUNAttribute(STUNAttributeTypesEnum attributeType, ulong value)
Value = NetConvert.GetBytes(value);
}
- public static List ParseMessageAttributes(byte[] buffer, int startIndex, int endIndex) => ParseMessageAttributes(buffer, startIndex, endIndex, null);
+ [Obsolete("Use ParseMessageAttributes(ReadOnlySpan) instead.", false)]
+ [EditorBrowsable(EditorBrowsableState.Advanced)]
+ public static List ParseMessageAttributes(byte[] buffer, int startIndex, int endIndex)
+ => ParseMessageAttributes(buffer.AsSpan(startIndex, endIndex - startIndex), null);
+ [Obsolete("Use ParseMessageAttributes(ReadOnlySpan, STUNHeader) instead.", false)]
+ [EditorBrowsable(EditorBrowsableState.Advanced)]
public static List ParseMessageAttributes(byte[] buffer, int startIndex, int endIndex, STUNHeader header)
+ => ParseMessageAttributes(buffer.AsSpan(startIndex, endIndex - startIndex), header);
+
+ public static List ParseMessageAttributes(ReadOnlySpan buffer)
+ => ParseMessageAttributes(buffer, null);
+
+ public static List? ParseMessageAttributes(ReadOnlySpan buffer, STUNHeader? header)
{
- if (buffer != null && buffer.Length > startIndex && buffer.Length >= endIndex)
+ if (buffer.IsEmpty)
{
- List attributes = new List();
- int startAttIndex = startIndex;
+ return null;
+ }
- while (startAttIndex < endIndex - 4)
- {
- UInt16 stunAttributeType = NetConvert.ParseUInt16(buffer, startAttIndex);
- UInt16 stunAttributeLength = NetConvert.ParseUInt16(buffer, startAttIndex + 2);
- byte[] stunAttributeValue = null;
+ var attributes = new List();
- STUNAttributeTypesEnum attributeType = STUNAttributeTypes.GetSTUNAttributeTypeForId(stunAttributeType);
+ while (buffer.Length >= 4)
+ {
+ var stunAttributeType = BinaryPrimitives.ReadUInt16BigEndian(buffer.Slice(0, 2));
+ var stunAttributeLength = BinaryPrimitives.ReadUInt16BigEndian(buffer.Slice(2, 2));
+ byte[]? stunAttributeValue = null;
- if (stunAttributeLength > 0)
- {
- if (stunAttributeLength + startAttIndex + 4 > endIndex)
- {
- logger.LogWarning("The attribute length on a STUN parameter was greater than the available number of bytes. Type: {AttributeType}", attributeType);
- }
- else
- {
- stunAttributeValue = new byte[stunAttributeLength];
- Buffer.BlockCopy(buffer, startAttIndex + 4, stunAttributeValue, 0, stunAttributeLength);
- }
- }
+ var attributeType = STUNAttributeTypes.GetSTUNAttributeTypeForId(stunAttributeType);
- if(stunAttributeValue == null && stunAttributeLength > 0)
+ if (stunAttributeLength > 0)
+ {
+ if (stunAttributeLength > buffer.Length - 4)
{
+ logger.LogWarning("The attribute length on a STUN parameter was greater than the available number of bytes. Type: {AttributeType}", attributeType);
break;
}
- STUNAttribute attribute = null;
- if (attributeType == STUNAttributeTypesEnum.ChangeRequest)
- {
- attribute = new STUNChangeRequestAttribute(stunAttributeValue);
- }
- else if (attributeType == STUNAttributeTypesEnum.MappedAddress || attributeType == STUNAttributeTypesEnum.AlternateServer)
- {
- attribute = new STUNAddressAttribute(attributeType, stunAttributeValue);
- }
- else if (attributeType == STUNAttributeTypesEnum.ErrorCode)
- {
- attribute = new STUNErrorCodeAttribute(stunAttributeValue);
- }
- else if (attributeType == STUNAttributeTypesEnum.XORMappedAddress || attributeType == STUNAttributeTypesEnum.XORPeerAddress || attributeType == STUNAttributeTypesEnum.XORRelayedAddress)
- {
- attribute = new STUNXORAddressAttribute(attributeType, stunAttributeValue, header.TransactionId);
- }
- else if(attributeType == STUNAttributeTypesEnum.ConnectionId)
- {
- attribute = new STUNConnectionIdAttribute(stunAttributeValue);
- }
else
{
- attribute = new STUNAttribute(attributeType, stunAttributeValue);
+ stunAttributeValue = buffer.Slice(4, stunAttributeLength).ToArray();
}
+ }
- attributes.Add(attribute);
+ STUNAttribute attribute;
+ if (attributeType == STUNAttributeTypesEnum.ChangeRequest)
+ {
+ attribute = new STUNChangeRequestAttribute(stunAttributeValue);
+ }
+ else if (attributeType is STUNAttributeTypesEnum.MappedAddress or STUNAttributeTypesEnum.AlternateServer)
+ {
+ attribute = new STUNAddressAttribute(attributeType, stunAttributeValue);
+ }
+ else if (attributeType == STUNAttributeTypesEnum.ErrorCode)
+ {
+ attribute = new STUNErrorCodeAttribute(stunAttributeValue);
+ }
+ else if (attributeType is STUNAttributeTypesEnum.XORMappedAddress or STUNAttributeTypesEnum.XORPeerAddress or STUNAttributeTypesEnum.XORRelayedAddress)
+ {
+ attribute = new STUNXORAddressAttribute(attributeType, stunAttributeValue, header.TransactionId);
+ }
+ else if (attributeType == STUNAttributeTypesEnum.ConnectionId)
+ {
+ attribute = new STUNConnectionIdAttribute(stunAttributeValue);
+ }
+ else
+ {
+ attribute = new STUNAttribute(attributeType, stunAttributeValue);
+ }
- // Attributes start on 32 bit word boundaries so where an attribute length is not a multiple of 4 it gets padded.
- int padding = (stunAttributeLength % 4 != 0) ? 4 - (stunAttributeLength % 4) : 0;
+ attributes.Add(attribute);
- startAttIndex = startAttIndex + 4 + stunAttributeLength + padding;
- }
+ // Attributes start on 32 bit word boundaries so where an attribute length is not a multiple of 4 it gets padded.
+ var padding = (4 - (stunAttributeLength & 0b11)) & 0b11;
- return attributes;
- }
- else
- {
- return null;
+ buffer = buffer.Slice(4 + stunAttributeLength + padding);
}
+
+ return attributes;
}
public virtual int ToByteBuffer(byte[] buffer, int startIndex)
diff --git a/src/net/STUN/STUNMessage.cs b/src/net/STUN/STUNMessage.cs
index e6ed1a02fb..8be2b1deb5 100644
--- a/src/net/STUN/STUNMessage.cs
+++ b/src/net/STUN/STUNMessage.cs
@@ -95,7 +95,7 @@ public static STUNMessage ParseSTUNMessage(byte[] buffer, int bufferLength)
if (buffer != null && buffer.Length > 0 && buffer.Length >= bufferLength)
{
STUNMessage stunMessage = new STUNMessage();
- stunMessage._receivedBuffer = buffer.Take(bufferLength).ToArray();
+ stunMessage._receivedBuffer = buffer.AsSpan(0, bufferLength).ToArray();
stunMessage.Header = STUNHeader.ParseSTUNHeader(buffer);
if (stunMessage.Header.MessageLength > 0)
@@ -108,7 +108,7 @@ public static STUNMessage ParseSTUNMessage(byte[] buffer, int bufferLength)
// Check fingerprint.
var fingerprintAttribute = stunMessage.Attributes.Last();
- var input = buffer.Take(buffer.Length - STUNAttribute.STUNATTRIBUTE_HEADER_LENGTH - FINGERPRINT_ATTRIBUTE_CRC32_LENGTH).ToArray();
+ var input = buffer.AsSpan(0, buffer.Length - STUNAttribute.STUNATTRIBUTE_HEADER_LENGTH - FINGERPRINT_ATTRIBUTE_CRC32_LENGTH).ToArray();
uint crc = Crc32.Compute(input) ^ FINGERPRINT_XOR;
byte[] fingerPrint = (BitConverter.IsLittleEndian) ? BitConverter.GetBytes(NetConvert.DoReverseEndian(crc)) : BitConverter.GetBytes(crc);
diff --git a/src/net/WebRTC/DCEP.cs b/src/net/WebRTC/DCEP.cs
index 952cc093c1..5857d08ad1 100644
--- a/src/net/WebRTC/DCEP.cs
+++ b/src/net/WebRTC/DCEP.cs
@@ -25,6 +25,7 @@
//-----------------------------------------------------------------------------
using System;
+using System.Buffers.Binary;
using System.Collections.Generic;
using System.Text;
using SIPSorcery.Sys;
@@ -189,42 +190,58 @@ public int GetLength()
/// The number of bytes, including padding, written to the buffer.
public ushort WriteTo(byte[] buffer, int posn)
{
- buffer[posn] = MessageType;
- buffer[posn + 1] = ChannelType;
- NetConvert.ToBuffer(Priority, buffer, posn + 2);
- NetConvert.ToBuffer(Reliability, buffer, posn + 4);
+ return (ushort)WriteTo(buffer.AsSpan(posn));
+ }
- ushort labelLength = (ushort)(Label != null ? Encoding.UTF8.GetByteCount(Label) : 0);
- ushort protocolLength = (ushort)(Protocol != null ? Encoding.UTF8.GetByteCount(Protocol) : 0);
+ ///
+ /// Serialises a Data Channel Establishment Protocol (DECP) OPEN message to a
+ /// pre-allocated buffer.
+ ///
+ /// The buffer to write the serialised chunk bytes to. It
+ /// must have the required space already allocated.
+ /// The number of bytes, including padding, written to the buffer.
+ public int WriteTo(Span buffer)
+ {
+ buffer[0] = MessageType;
+ buffer[1] = ChannelType;
+ BinaryPrimitives.WriteUInt16BigEndian(buffer.Slice(2), Priority);
+ BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(4), Reliability);
- NetConvert.ToBuffer(labelLength, buffer, posn + 8);
- NetConvert.ToBuffer(protocolLength, buffer, posn + 10);
+ var labelLength = (ushort)(Label != null ? Encoding.UTF8.GetByteCount(Label) : 0);
+ var protocolLength = (ushort)(Protocol != null ? Encoding.UTF8.GetByteCount(Protocol) : 0);
- posn += DCEP_OPEN_FIXED_PARAMETERS_LENGTH;
+ BinaryPrimitives.WriteUInt16BigEndian(buffer.Slice(8), labelLength);
+ BinaryPrimitives.WriteUInt16BigEndian(buffer.Slice(10), protocolLength);
+
+ var len = DCEP_OPEN_FIXED_PARAMETERS_LENGTH;
if (labelLength > 0)
{
- Buffer.BlockCopy(Encoding.UTF8.GetBytes(Label), 0, buffer, posn, labelLength);
- posn += labelLength;
+ Encoding.UTF8.GetBytes(Label.AsSpan(), buffer.Slice(len));
+ len += labelLength;
}
if (protocolLength > 0)
{
- Buffer.BlockCopy(Encoding.UTF8.GetBytes(Protocol), 0, buffer, posn, protocolLength);
- posn += protocolLength;
+ Encoding.UTF8.GetBytes(Protocol.AsSpan(), buffer.Slice(len));
+ len += protocolLength;
}
- return (ushort)posn;
+ return len;
}
+ public int GetPacketSize() => GetLength();
+
///
/// Serialises the DCEP OPEN message to a buffer.
///
public byte[] GetBytes()
{
var buffer = new byte[GetLength()];
- WriteTo(buffer, 0);
+ WriteTo(buffer.AsSpan());
return buffer;
}
+
+ public int WriteBytes(Span buffer) => WriteTo(buffer);
}
}
diff --git a/src/net/WebRTC/RTCPeerConnection.cs b/src/net/WebRTC/RTCPeerConnection.cs
index 60a87a6177..8e34abcd53 100755
--- a/src/net/WebRTC/RTCPeerConnection.cs
+++ b/src/net/WebRTC/RTCPeerConnection.cs
@@ -1386,7 +1386,7 @@ private void OnRTPDataReceived(int localPort, IPEndPoint remoteEP, byte[] buffer
if (_dtlsHandle != null)
{
//logger.LogDebug($"DTLS transport received {buffer.Length} bytes from {AudioDestinationEndPoint}.");
- _dtlsHandle.WriteToRecvStream(buffer);
+ _dtlsHandle.WriteToRecvStream(buffer.AsSpan());
}
else
{
diff --git a/src/sys/BinaryOperations.cs b/src/sys/BinaryOperations.cs
new file mode 100644
index 0000000000..10f35146f1
--- /dev/null
+++ b/src/sys/BinaryOperations.cs
@@ -0,0 +1,38 @@
+using System;
+using System.Buffers.Binary;
+
+namespace SIPSorcery.Sys
+{
+ internal class BinaryOperations
+ {
+ public static ushort ReadUInt16BigEndian(ref ReadOnlySpan buffer, int offset = 0)
+ {
+ buffer = buffer.Slice(offset);
+ var value = BinaryPrimitives.ReadUInt16BigEndian(buffer);
+ buffer = buffer.Slice(sizeof(ushort));
+ return value;
+ }
+
+ public static uint ReadUInt32BigEndian(ref ReadOnlySpan buffer, int offset = 0)
+ {
+ buffer = buffer.Slice(offset);
+ var value = BinaryPrimitives.ReadUInt32BigEndian(buffer);
+ buffer = buffer.Slice(sizeof(uint));
+ return value;
+ }
+
+ public static void WriteUInt16BigEndian(ref Span buffer, ushort value, int offset = 0)
+ {
+ buffer = buffer.Slice(offset);
+ BinaryPrimitives.WriteUInt16BigEndian(buffer, value);
+ buffer = buffer.Slice(sizeof(ushort));
+ }
+
+ public static void WriteUInt32BigEndian(ref Span buffer, uint value, int offset = 0)
+ {
+ buffer = buffer.Slice(offset);
+ BinaryPrimitives.WriteUInt32BigEndian(buffer, value);
+ buffer = buffer.Slice(sizeof(uint));
+ }
+ }
+}
diff --git a/src/sys/EncodingExtensions.cs b/src/sys/EncodingExtensions.cs
new file mode 100644
index 0000000000..79ea3e61b8
--- /dev/null
+++ b/src/sys/EncodingExtensions.cs
@@ -0,0 +1,27 @@
+using System;
+using System.Text;
+
+namespace SIPSorcery.Sys
+{
+ internal static class EncodingExtensions
+ {
+#if NETSTANDARD2_0 || NETFRAMEWORK
+ public unsafe static string GetString(this Encoding encoding, ReadOnlySpan bytes)
+ {
+ fixed (byte* ptr = bytes)
+ {
+ return encoding.GetString(ptr, bytes.Length);
+ }
+ }
+
+ public unsafe static int GetBytes(this Encoding encoding, ReadOnlySpan chars, Span bytes)
+ {
+ fixed (char* pChars = chars)
+ fixed (byte* pBytes = bytes)
+ {
+ return encoding.GetBytes(pChars, chars.Length, pBytes, bytes.Length);
+ }
+ }
+#endif
+ }
+}
diff --git a/src/sys/MemoryOperations.cs b/src/sys/MemoryOperations.cs
new file mode 100644
index 0000000000..0930aca781
--- /dev/null
+++ b/src/sys/MemoryOperations.cs
@@ -0,0 +1,48 @@
+using System;
+using System.Buffers.Binary;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace SIPSorcery.Sys;
+
+internal static class MemoryOperations
+{
+ public static byte[] ToLittleEndianBytes(this ReadOnlySpan shorts)
+ {
+ var bytes = new byte[shorts.Length * 2];
+
+ ref var source = ref MemoryMarshal.GetReference(MemoryMarshal.AsBytes(shorts));
+ var destination = bytes.AsSpan();
+
+ for (var i = shorts.Length; i > 0; i--)
+ {
+ var destSpan = destination.Slice(0, 2);
+ BinaryPrimitives.WriteInt16LittleEndian(destSpan, source);
+
+ source = ref Unsafe.Add(ref source, 1);
+ destination = destination.Slice(2);
+ }
+
+ return bytes;
+ }
+
+ public static byte[] ToBigEndianBytes(this ReadOnlySpan shorts)
+ {
+ var bytes = new byte[shorts.Length * 2];
+
+ ref var source = ref MemoryMarshal.GetReference(MemoryMarshal.AsBytes(shorts));
+ var destination = bytes.AsSpan();
+
+ for (var i = shorts.Length; i > 0; i--)
+ {
+ var destSpan = destination.Slice(0, 2);
+ BinaryPrimitives.WriteInt16BigEndian(destSpan, source);
+
+ source = ref Unsafe.Add(ref source, 1);
+ destination = destination.Slice(2);
+ }
+
+ return bytes;
+ }
+}
diff --git a/src/sys/Net/NetConvert.cs b/src/sys/Net/NetConvert.cs
index 8c223f9d4c..fa7bd5b139 100644
--- a/src/sys/Net/NetConvert.cs
+++ b/src/sys/Net/NetConvert.cs
@@ -14,6 +14,7 @@
//-----------------------------------------------------------------------------
using System;
+using System.Buffers.Binary;
using System.Linq;
namespace SIPSorcery.Sys
@@ -141,8 +142,8 @@ public static void ToBuffer(uint val, byte[] buffer, int posn)
/// A buffer representing the value in network order
public static byte[] GetBytes(uint val)
{
- var buffer = new byte[4];
- ToBuffer(val, buffer, 0);
+ var buffer = new byte[sizeof(uint)];
+ BinaryPrimitives.WriteUInt32BigEndian(buffer, val);
return buffer;
}
@@ -177,8 +178,8 @@ public static void ToBuffer(ulong val, byte[] buffer, int posn)
/// A buffer representing the value in network order
public static byte[] GetBytes(ulong val)
{
- var buffer = new byte[8];
- ToBuffer(val, buffer, 0);
+ var buffer = new byte[sizeof(ulong)];
+ BinaryPrimitives.WriteUInt64BigEndian(buffer, val);
return buffer;
}
diff --git a/src/sys/TypeExtensions.cs b/src/sys/TypeExtensions.cs
index 47d7e29998..b620357d6f 100755
--- a/src/sys/TypeExtensions.cs
+++ b/src/sys/TypeExtensions.cs
@@ -16,6 +16,7 @@
//-----------------------------------------------------------------------------
using System;
+using System.Buffers;
using System.Collections.Generic;
using System.Net;
@@ -55,7 +56,7 @@ public static class TypeExtensions
///
public static bool IsNullOrBlank(this string s)
{
- if (s == null || s.Trim(WhiteSpaceChars).Length == 0)
+ if (s == null || s.AsSpan().Trim(WhiteSpaceChars).Length == 0)
{
return true;
}
@@ -65,7 +66,7 @@ public static bool IsNullOrBlank(this string s)
public static bool NotNullOrBlank(this string s)
{
- if (s == null || s.Trim(WhiteSpaceChars).Length == 0)
+ if (s == null || s.AsSpan().Trim(WhiteSpaceChars).Length == 0)
{
return false;
}
@@ -178,6 +179,52 @@ static void PopulateNewStringWithoutSeparator(Span chars, (byte[] buffer,
}
}
+ public static string HexStr(this ReadOnlySpan buffer, char? separator = null)
+ {
+ if (separator is { } s)
+ {
+ var numberOfChars = buffer.Length * 3 - 1;
+ var rv = ArrayPool.Shared.Rent(numberOfChars);
+ try
+ {
+ for (int i = 0, j = 0; i < buffer.Length; i++)
+ {
+ var val = buffer[i];
+ rv[j++] = char.ToUpperInvariant(hexmap[val >> 4]);
+ rv[j++] = char.ToUpperInvariant(hexmap[val & 15]);
+ if (j < rv.Length)
+ {
+ rv[j++] = s;
+ }
+ }
+ return new string(rv, 0, numberOfChars);
+ }
+ finally
+ {
+ ArrayPool.Shared.Return(rv);
+ }
+ }
+ else
+ {
+ var numberOfChars = buffer.Length * 2;
+ var rv = ArrayPool.Shared.Rent(numberOfChars);
+ try
+ {
+ for (int i = 0, j = 0; i < buffer.Length; i++)
+ {
+ var val = buffer[i];
+ rv[j++] = char.ToUpperInvariant(hexmap[val >> 4]);
+ rv[j++] = char.ToUpperInvariant(hexmap[val & 15]);
+ }
+ return new string(rv, 0, numberOfChars);
+ }
+ finally
+ {
+ ArrayPool.Shared.Return(rv);
+ }
+ }
+ }
+
public static byte[] ParseHexStr(string hexStr)
{
List buffer = new List();
diff --git a/test/integration/SIPSorcery.IntegrationTests.csproj b/test/integration/SIPSorcery.IntegrationTests.csproj
index 79a95c5dae..be5c0f6ec2 100755
--- a/test/integration/SIPSorcery.IntegrationTests.csproj
+++ b/test/integration/SIPSorcery.IntegrationTests.csproj
@@ -4,6 +4,7 @@
net462;net8.0
false
true
+ $(NoWarn);CS0618
True
diff --git a/test/unit/SIPSorcery.UnitTests.csproj b/test/unit/SIPSorcery.UnitTests.csproj
index 256f16cadb..c92f5434ab 100755
--- a/test/unit/SIPSorcery.UnitTests.csproj
+++ b/test/unit/SIPSorcery.UnitTests.csproj
@@ -4,7 +4,9 @@
net462;net8.0
false
true
+ $(NoWarn);CS0618
True
+ $(WarningsNotAsErrors);CS0672