From f4fcd2ae8ea6e1426a1bbaa1b25be632ea2578ad Mon Sep 17 00:00:00 2001 From: Paulo Morgado Date: Tue, 20 May 2025 21:06:02 +0100 Subject: [PATCH] Refactor and optimize memory handling across SIPSorcery using Span APIs Enhanced performance and memory efficiency throughout the SIPSorcery project by replacing traditional byte array handling with Span and ReadOnlySpan in multiple components, including RTCP, SCTP, and AudioEncoder classes. Refactored packet parsing and serialization logic using BinaryPrimitives for endian conversions, and updated NetConvert accordingly. Introduced utility methods in EncodingExtensions, TypeExtensions, BinaryOperations, and MemoryOperations to streamline string, byte array, and endian-aware operations. Marked legacy methods as obsolete to guide migration to span-based APIs. Updated SIPSorcery.csproj to include System.Memory and Microsoft.Bcl.HashCode packages, and enabled AllowUnsafeBlocks for performance-critical code paths. --- .editorconfig | 20 +- src/SIPSorcery.csproj | 4 +- src/app/Media/Codecs/AudioEncoder.cs | 12 +- src/app/Media/Codecs/G729Encoder.cs | 2 +- .../SIP/Channels/SIPClientWebSocketChannel.cs | 2 +- src/core/SIP/Channels/SIPWebSocketChannel.cs | 2 +- src/net/DtlsSrtp/DtlsSrtpTransport.cs | 9 +- src/net/HEP/HepPacket.cs | 2 +- src/net/ICE/RtpIceChannel.cs | 12 +- src/net/RTCP/RTCPBye.cs | 73 ++-- src/net/RTCP/RTCPCompoundPacket.cs | 239 ++++++++---- src/net/RTCP/RTCPFeedback.cs | 191 ++++----- src/net/RTCP/RTCPHeader.cs | 73 ++-- src/net/RTCP/RTCPReceiverReport.cs | 79 ++-- src/net/RTCP/RTCPSdesReport.cs | 71 ++-- src/net/RTCP/RTCPSenderReport.cs | 104 ++--- src/net/RTCP/RTCPTWCCFeedback.cs | 365 +++++++++++------- src/net/RTCP/ReceptionReport.cs | 88 +++-- src/net/RTP/MediaStream.cs | 6 +- src/net/RTP/Packetisation/H264Packetiser.cs | 5 +- src/net/RTP/Packetisation/H265Packetiser.cs | 5 +- .../RTP/Packetisation/MJPEGDepacketiser.cs | 4 +- src/net/RTP/Packetisation/MJPEGPacketiser.cs | 10 +- src/net/RTP/Packetisation/RtpVideoFramer.cs | 2 +- src/net/RTP/RTPHeader.cs | 51 +-- .../AbsSendTimeExtension.cs | 8 +- src/net/RTP/RTPPacket.cs | 30 +- src/net/RTP/RTPSession.cs | 7 +- src/net/RTP/VideoStream.cs | 8 +- src/net/SCTP/Chunks/SctpChunk.cs | 264 +++++++++---- src/net/SCTP/Chunks/SctpDataChunk.cs | 67 ++-- src/net/SCTP/Chunks/SctpErrorCauses.cs | 160 ++++++-- src/net/SCTP/Chunks/SctpErrorChunk.cs | 85 ++-- src/net/SCTP/Chunks/SctpInitChunk.cs | 92 +++-- src/net/SCTP/Chunks/SctpSackChunk.cs | 80 ++-- src/net/SCTP/Chunks/SctpShutdownChunk.cs | 39 +- src/net/SCTP/Chunks/SctpTlvChunkParameter.cs | 105 ++++- src/net/SCTP/SctpDataReceiver.cs | 2 +- src/net/SCTP/SctpHeader.cs | 41 +- src/net/SCTP/SctpPacket.cs | 52 ++- src/net/STUN/STUNAttributes/STUNAttribute.cs | 122 +++--- src/net/STUN/STUNMessage.cs | 4 +- src/net/WebRTC/DCEP.cs | 47 ++- src/net/WebRTC/RTCPeerConnection.cs | 2 +- src/sys/BinaryOperations.cs | 38 ++ src/sys/EncodingExtensions.cs | 27 ++ src/sys/MemoryOperations.cs | 48 +++ src/sys/Net/NetConvert.cs | 9 +- src/sys/TypeExtensions.cs | 51 ++- .../SIPSorcery.IntegrationTests.csproj | 1 + test/unit/SIPSorcery.UnitTests.csproj | 2 + 51 files changed, 1889 insertions(+), 933 deletions(-) create mode 100644 src/sys/BinaryOperations.cs create mode 100644 src/sys/EncodingExtensions.cs create mode 100644 src/sys/MemoryOperations.cs 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