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