Skip to content

Commit f4fcd2a

Browse files
author
Paulo Morgado
committed
Refactor and optimize memory handling across SIPSorcery using Span APIs
Enhanced performance and memory efficiency throughout the SIPSorcery project by replacing traditional byte array handling with Span<byte> and ReadOnlySpan<byte> in multiple components, including RTCP, SCTP, and AudioEncoder classes. Refactored packet parsing and serialization logic using BinaryPrimitives for endian conversions, and updated NetConvert accordingly. Introduced utility methods in EncodingExtensions, TypeExtensions, BinaryOperations, and MemoryOperations to streamline string, byte array, and endian-aware operations. Marked legacy methods as obsolete to guide migration to span-based APIs. Updated SIPSorcery.csproj to include System.Memory and Microsoft.Bcl.HashCode packages, and enabled AllowUnsafeBlocks for performance-critical code paths.
1 parent b0248b8 commit f4fcd2a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+1889
-933
lines changed

.editorconfig

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,22 @@ csharp_preserve_single_line_statements = true
2424
csharp_preserve_single_line_blocks = true
2525

2626
# Don't allow single line if/else blocks without braces.
27-
csharp_prefer_braces = true:error
27+
csharp_prefer_braces = true:error
28+
29+
# IDE0007: Use implicit type
30+
dotnet_diagnostic.IDE0007.severity = suggestion
31+
32+
# IDE0019: Use pattern matching
33+
dotnet_diagnostic.IDE0019.severity = suggestion
34+
35+
# IDE0030: Use coalesce expression
36+
dotnet_diagnostic.IDE0030.severity = suggestion
37+
38+
# IDE0031: Use null propagation
39+
dotnet_diagnostic.IDE0031.severity = suggestion
40+
41+
# IDE0078: Use pattern matching
42+
dotnet_diagnostic.IDE0078.severity = suggestion
43+
44+
# IDE0083: Use pattern matching
45+
dotnet_diagnostic.IDE0083.severity = suggestion

src/SIPSorcery.csproj

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,9 @@
3939
<TargetFrameworks>netstandard2.0;netstandard2.1;netcoreapp3.1;net462;net5.0;net6.0;net8.0</TargetFrameworks>
4040
<LangVersion>12.0</LangVersion>
4141
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
42-
<NoWarn>$(NoWarn);SYSLIB0050</NoWarn>
42+
<NoWarn>$(NoWarn);SYSLIB0050;CS1591;CS1573;CS1587</NoWarn>
4343
<TreatWarningsAsErrors>True</TreatWarningsAsErrors>
44+
<WarningsNotAsErrors>$(WarningsNotAsErrors);CS0809;CS0618;CS8632</WarningsNotAsErrors>
4445
<GenerateDocumentationFile>true</GenerateDocumentationFile>
4546
<!-- Disable warning for missing XML doc comments. -->
4647
<NoWarn>$(NoWarn);CS1591;CS1573;CS1587</NoWarn>
@@ -93,6 +94,7 @@
9394
<IncludeSymbols>true</IncludeSymbols>
9495
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
9596
<ContinuousIntegrationBuild Condition="'$(CI)' == 'true'">true</ContinuousIntegrationBuild>
97+
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
9698
</PropertyGroup>
9799

98100
</Project>

src/app/Media/Codecs/AudioEncoder.cs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
using System.Collections.Generic;
1919
using System.Linq;
2020
using SIPSorceryMedia.Abstractions;
21+
using SIPSorcery.Sys;
2122
using Concentus.Enums;
2223

2324
namespace SIPSorcery.Media
@@ -121,16 +122,13 @@ public byte[] EncodeAudio(short[] pcm, AudioFormat format)
121122
}
122123
else if (format.Codec == AudioCodecsEnum.L16)
123124
{
124-
// When netstandard2.1 can be used.
125-
//return MemoryMarshal.Cast<short, byte>(pcm)
126-
127125
// Put on the wire in network byte order (big endian).
128-
return pcm.SelectMany(x => new byte[] { (byte)(x >> 8), (byte)(x) }).ToArray();
126+
return MemoryOperations.ToBigEndianBytes(pcm);
129127
}
130128
else if (format.Codec == AudioCodecsEnum.PCM_S16LE)
131129
{
132130
// Put on the wire as little endian.
133-
return pcm.SelectMany(x => new byte[] { (byte)(x), (byte)(x >> 8) }).ToArray();
131+
return MemoryOperations.ToLittleEndianBytes(pcm);
134132
}
135133
else if (format.Codec == AudioCodecsEnum.OPUS)
136134
{
@@ -148,7 +146,7 @@ public byte[] EncodeAudio(short[] pcm, AudioFormat format)
148146

149147
byte[] encodedSample = new byte[pcm.Length];
150148
int encodedLength = _opusEncoder.Encode(pcmFloat, pcmFloat.Length / format.ChannelCount, encodedSample, encodedSample.Length);
151-
return encodedSample.Take(encodedLength).ToArray();
149+
return encodedSample.AsSpan(0, encodedLength).ToArray();
152150
}
153151
else
154152
{
@@ -174,7 +172,7 @@ public short[] DecodeAudio(byte[] encodedSample, AudioFormat format)
174172
short[] decodedPcm = new short[encodedSample.Length * 2];
175173
int decodedSampleCount = _g722Decoder.Decode(_g722DecoderState, decodedPcm, encodedSample, encodedSample.Length);
176174

177-
return decodedPcm.Take(decodedSampleCount).ToArray();
175+
return decodedPcm.AsSpan(0, decodedSampleCount).ToArray();
178176
}
179177
if (format.Codec == AudioCodecsEnum.G729)
180178
{

src/app/Media/Codecs/G729Encoder.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ public class G729Encoder : Ld8k
5555
* Initialization of the coder.
5656
*/
5757

58-
private byte[] _leftover = new byte[0];
58+
private byte[] _leftover = Array.Empty<byte>();
5959

6060
/**
6161
* Init the Ld8k Coder

src/core/SIP/Channels/SIPClientWebSocketChannel.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -376,7 +376,7 @@ private void MonitorReceiveTasks()
376376
if (receiveTask.IsCompleted)
377377
{
378378
logger.LogDebug("Client web socket connection to {ServerUri} received {BytesReceived} bytes.", conn.ServerUri, receiveTask.Result.Count);
379-
//SIPMessageReceived(this, conn.LocalEndPoint, conn.RemoteEndPoint, conn.ReceiveBuffer.Take(receiveTask.Result.Count).ToArray()).Wait();
379+
//SIPMessageReceived(this, conn.LocalEndPoint, conn.RemoteEndPoint, conn.ReceiveBuffer.AsSpan(0, receiveTask.Result.Count).ToArray()).Wait();
380380
ExtractSIPMessages(this, conn, conn.ReceiveBuffer, receiveTask.Result.Count);
381381
conn.ReceiveTask = conn.Client.ReceiveAsync(conn.ReceiveBuffer, m_cts.Token);
382382
}

src/core/SIP/Channels/SIPWebSocketChannel.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ protected override void OnError(ErrorEventArgs e)
122122

123123
public void Send(byte[] buffer, int offset, int length)
124124
{
125-
base.Send(buffer.Skip(offset).Take(length).ToArray());
125+
base.Send(buffer.AsSpan(offset, length).ToArray());
126126
}
127127
}
128128

src/net/DtlsSrtp/DtlsSrtpTransport.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
using System;
2020
using System.Collections.Concurrent;
21+
using System.ComponentModel;
2122
using Microsoft.Extensions.Logging;
2223
using Org.BouncyCastle.Tls;
2324
using SIPSorcery.Sys;
@@ -482,11 +483,15 @@ public int GetSendLimit()
482483
return this._sendLimit;
483484
}
484485

485-
public void WriteToRecvStream(byte[] buf)
486+
[Obsolete("Use WriteToRecvStream(ReadOnlySpan<byte>) instead.", false)]
487+
[EditorBrowsable(EditorBrowsableState.Advanced)]
488+
public void WriteToRecvStream(byte[] buf) => WriteToRecvStream(buf.AsSpan());
489+
490+
public void WriteToRecvStream(ReadOnlySpan<byte> buf)
486491
{
487492
if (!_isClosed)
488493
{
489-
_chunks.Add(buf);
494+
_chunks.Add(buf.ToArray());
490495
}
491496
}
492497

src/net/HEP/HepPacket.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,7 @@ public static byte[] GetBytes(SIPEndPoint srcEndPoint, SIPEndPoint dstEndPoint,
316316
Buffer.BlockCopy(BitConverter.GetBytes((ushort)offset), 0, packetBuffer, 4, 2);
317317
}
318318

319-
return packetBuffer.Take(offset).ToArray();
319+
return packetBuffer.AsSpan(0, offset).ToArray();
320320
}
321321
}
322322
}

src/net/ICE/RtpIceChannel.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -904,7 +904,7 @@ public void AddRemoteCandidate(RTCIceCandidate candidate)
904904
{
905905
OnIceCandidateError?.Invoke(candidate, $"Remote ICE candidate had an invalid port {candidate.port}.");
906906
}
907-
else if(IPAddress.TryParse(candidate.address, out var addrIPv6) &&
907+
else if (IPAddress.TryParse(candidate.address, out var addrIPv6) &&
908908
addrIPv6.AddressFamily == AddressFamily.InterNetworkV6 &&
909909
!Socket.OSSupportsIPv6 &&
910910
NetServices.HasActiveIPv6Address())
@@ -1123,7 +1123,7 @@ private void CheckIceServers(Object state)
11231123
{
11241124
if (_activeIceServer == null || _activeIceServer.Error != SocketError.Success)
11251125
{
1126-
if (_iceServerResolver.IceServers.Count(x => x.Value.Error == SocketError.Success) == 0)
1126+
if (!_iceServerResolver.IceServers.Any(x => x.Value.Error == SocketError.Success))
11271127
{
11281128
logger.LogDebug("RTP ICE Channel all ICE server connection checks failed, stopping ICE servers timer.");
11291129
_processIceServersTimer.Dispose();
@@ -1136,7 +1136,7 @@ private void CheckIceServers(Object state)
11361136
.OrderByDescending(x => x.Value._uri.Scheme) // TURN serves take priority.
11371137
.FirstOrDefault();
11381138

1139-
if (!entry.Equals(default(KeyValuePair<STUNUri, IceServer>)))
1139+
if (entry.Key is not null && entry.Value is not null)
11401140
{
11411141
_activeIceServer = entry.Value;
11421142
}
@@ -2168,7 +2168,7 @@ private ChecklistEntry GetChecklistEntryForStunResponse(byte[] transactionID)
21682168
/// <returns>If found a matching state object or null if not.</returns>
21692169
private IceServer GetIceServerForTransactionID(byte[] transactionID)
21702170
{
2171-
if (_iceServerResolver.IceServers.Count() == 0)
2171+
if (_iceServerResolver.IceServers.Count == 0)
21722172
{
21732173
return null;
21742174
}
@@ -2180,7 +2180,7 @@ private IceServer GetIceServerForTransactionID(byte[] transactionID)
21802180
.Where(x => x.Value.IsTransactionIDMatch(txID))
21812181
.SingleOrDefault();
21822182

2183-
if (!entry.Equals(default(KeyValuePair<STUNUri, IceServer>)))
2183+
if (entry.Key is not null && entry.Value is not null)
21842184
{
21852185
return entry.Value;
21862186
}
@@ -2576,7 +2576,7 @@ private async Task<IPAddress[]> ResolveMdnsName(RTCIceCandidate candidate)
25762576
{
25772577
if (MdnsResolve != null)
25782578
{
2579-
logger.LogWarning("RTP ICE channel has both "+ nameof(MdnsGetAddresses) + " and " + nameof(MdnsGetAddresses) + " set. Only " + nameof(MdnsGetAddresses) + " will be used.");
2579+
logger.LogWarning("RTP ICE channel has both " + nameof(MdnsGetAddresses) + " and " + nameof(MdnsGetAddresses) + " set. Only " + nameof(MdnsGetAddresses) + " will be used.");
25802580
}
25812581
return await MdnsGetAddresses(candidate.address).ConfigureAwait(false);
25822582
}

src/net/RTCP/RTCPBye.cs

Lines changed: 45 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//-----------------------------------------------------------------------------
1+
//-----------------------------------------------------------------------------
22
// Filename: RTCPBye.cs
33
//
44
// Description: RTCP Goodbye packet as defined in RFC3550.
@@ -27,6 +27,9 @@
2727
//-----------------------------------------------------------------------------
2828

2929
using System;
30+
using System.Buffers.Binary;
31+
using System.ComponentModel;
32+
using System.Diagnostics;
3033
using System.Text;
3134
using SIPSorcery.Sys;
3235

@@ -73,7 +76,17 @@ public RTCPBye(uint ssrc, string reason)
7376
/// Create a new RTCP Goodbye packet from a serialised byte array.
7477
/// </summary>
7578
/// <param name="packet">The byte array holding the Goodbye packet.</param>
76-
public RTCPBye(byte[] packet)
79+
[Obsolete("Use RTCPBye(ReadOnlySpan<byte> packet) instead.", false)]
80+
[EditorBrowsable(EditorBrowsableState.Advanced)]
81+
public RTCPBye(byte[] packet) : this(new ReadOnlySpan<byte>(packet))
82+
{
83+
}
84+
85+
/// <summary>
86+
/// Create a new RTCP Goodbye packet from a serialised byte array.
87+
/// </summary>
88+
/// <param name="packet">The byte array holding the Goodbye packet.</param>
89+
public RTCPBye(ReadOnlySpan<byte> packet)
7790
{
7891
if (packet.Length < MIN_PACKET_SIZE)
7992
{
@@ -82,56 +95,60 @@ public RTCPBye(byte[] packet)
8295

8396
Header = new RTCPHeader(packet);
8497

85-
if (BitConverter.IsLittleEndian)
86-
{
87-
SSRC = NetConvert.DoReverseEndian(BitConverter.ToUInt32(packet, 4));
88-
}
89-
else
90-
{
91-
SSRC = BitConverter.ToUInt32(packet, 4);
92-
}
98+
SSRC = BinaryPrimitives.ReadUInt32BigEndian(packet.Slice(4));
9399

94100
if (packet.Length > MIN_PACKET_SIZE)
95101
{
96102
int reasonLength = packet[8];
97103

98104
if (packet.Length - MIN_PACKET_SIZE - 1 >= reasonLength)
99105
{
100-
Reason = Encoding.UTF8.GetString(packet, 9, reasonLength);
106+
Reason = Encoding.UTF8.GetString(packet.Slice(9, reasonLength));
101107
}
102108
}
103109
}
104110

111+
public int GetPacketSize() => RTCPHeader.HEADER_BYTES_LENGTH + GetPaddedLength(((Reason is not null) ? Encoding.UTF8.GetByteCount(Reason) : 0));
112+
105113
/// <summary>
106114
/// Gets the raw bytes for the Goodbye packet.
107115
/// </summary>
108116
/// <returns>A byte array.</returns>
109117
public byte[] GetBytes()
110118
{
111-
byte[] reasonBytes = (Reason != null) ? Encoding.UTF8.GetBytes(Reason) : null;
112-
int reasonLength = (reasonBytes != null) ? reasonBytes.Length : 0;
113-
byte[] buffer = new byte[RTCPHeader.HEADER_BYTES_LENGTH + GetPaddedLength(reasonLength)];
114-
Header.SetLength((ushort)(buffer.Length / 4 - 1));
119+
var buffer = new byte[GetPacketSize()];
115120

116-
Buffer.BlockCopy(Header.GetBytes(), 0, buffer, 0, RTCPHeader.HEADER_BYTES_LENGTH);
117-
int payloadIndex = RTCPHeader.HEADER_BYTES_LENGTH;
121+
WriteBytesCore(buffer);
118122

119-
if (BitConverter.IsLittleEndian)
120-
{
121-
Buffer.BlockCopy(BitConverter.GetBytes(NetConvert.DoReverseEndian(SSRC)), 0, buffer, payloadIndex, 4);
122-
}
123-
else
123+
return buffer;
124+
}
125+
126+
public int WriteBytes(Span<byte> buffer)
127+
{
128+
var size = GetPacketSize();
129+
130+
if (buffer.Length < size)
124131
{
125-
Buffer.BlockCopy(BitConverter.GetBytes(SSRC), 0, buffer, payloadIndex, 4);
132+
throw new ArgumentOutOfRangeException($"The buffer should have at least {size} bytes and had only {buffer.Length}.");
126133
}
127134

128-
if (reasonLength > 0)
135+
WriteBytesCore(buffer.Slice(0, size));
136+
137+
return size;
138+
}
139+
140+
private void WriteBytesCore(Span<byte> buffer)
141+
{
142+
Header.SetLength((ushort)(buffer.Length / 4 - 1));
143+
_ = Header.WriteBytes(buffer);
144+
145+
BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(RTCPHeader.HEADER_BYTES_LENGTH), SSRC);
146+
147+
if (Reason is not null)
129148
{
130-
buffer[payloadIndex + 4] = (byte)reasonLength;
131-
Buffer.BlockCopy(reasonBytes, 0, buffer, payloadIndex + 5, reasonBytes.Length);
149+
buffer[RTCPHeader.HEADER_BYTES_LENGTH + 4] =
150+
(byte)Encoding.UTF8.GetBytes(Reason.AsSpan(), buffer.Slice(RTCPHeader.HEADER_BYTES_LENGTH + 5));
132151
}
133-
134-
return buffer;
135152
}
136153

137154
/// <summary>

0 commit comments

Comments
 (0)