diff --git a/src/SIPSorcery.csproj b/src/SIPSorcery.csproj
index 4fb7ad2eaa..9c09dee2c7 100755
--- a/src/SIPSorcery.csproj
+++ b/src/SIPSorcery.csproj
@@ -21,6 +21,7 @@
+
@@ -35,15 +36,22 @@
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
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);NU1510;CS0809;CS0618;CS8632
true
- $(NoWarn);CS1591;CS1573;CS1587
Aaron Clauson, Christophe Irles, Rafael Soares & Contributors
Copyright © 2010-2025 Aaron Clauson
BSD-3-Clause
@@ -94,6 +102,7 @@
true
snupkg
true
+ true
diff --git a/src/core/SIP/SIPAuthorisationDigest.cs b/src/core/SIP/SIPAuthorisationDigest.cs
index 02c9cb3f79..6cd760757e 100644
--- a/src/core/SIP/SIPAuthorisationDigest.cs
+++ b/src/core/SIP/SIPAuthorisationDigest.cs
@@ -256,24 +256,116 @@ public string GetDigest()
public override string ToString()
{
- string authHeader = AuthHeaders.AUTH_DIGEST_KEY + " ";
-
- authHeader += (Username != null && Username.Trim().Length != 0) ? AuthHeaders.AUTH_USERNAME_KEY + "=\"" + Username + "\"" : null;
- authHeader += (authHeader.IndexOf('=') != -1) ? "," + AuthHeaders.AUTH_REALM_KEY + "=\"" + Realm + "\"" : AuthHeaders.AUTH_REALM_KEY + "=\"" + Realm + "\"";
- authHeader += (Nonce != null) ? "," + AuthHeaders.AUTH_NONCE_KEY + "=\"" + Nonce + "\"" : null;
- authHeader += (URI != null && URI.Trim().Length != 0) ? "," + AuthHeaders.AUTH_URI_KEY + "=\"" + URI + "\"" : null;
- authHeader += (Response != null && Response.Length != 0) ? "," + AuthHeaders.AUTH_RESPONSE_KEY + "=\"" + Response + "\"" : null;
- authHeader += (Cnonce != null) ? "," + AuthHeaders.AUTH_CNONCE_KEY + "=\"" + Cnonce + "\"" : null;
- authHeader += (NonceCount != 0) ? "," + AuthHeaders.AUTH_NONCECOUNT_KEY + "=" + GetPaddedNonceCount(NonceCount) : null;
- authHeader += (Qop != null) ? "," + AuthHeaders.AUTH_QOP_KEY + "=" + Qop : null;
- authHeader += (Opaque != null) ? "," + AuthHeaders.AUTH_OPAQUE_KEY + "=\"" + Opaque + "\"" : null;
-
- string algorithmID = (DigestAlgorithm == DigestAlgorithmsEnum.SHA256) ? SHA256_ALGORITHM_ID : DigestAlgorithm.ToString();
- authHeader += (Response != null) ? "," + AuthHeaders.AUTH_ALGORITHM_KEY + "=" + algorithmID : null;
-
- return authHeader;
+ var builder = new ValueStringBuilder();
+
+ try
+ {
+ ToString(ref builder);
+
+ return builder.ToString();
+ }
+ finally
+ {
+ builder.Dispose();
+ }
+ }
+
+ internal void ToString(ref ValueStringBuilder builder)
+ {
+ builder.Append(AuthHeaders.AUTH_DIGEST_KEY);
+ builder.Append(' ');
+
+ bool hasUsername = !string.IsNullOrWhiteSpace(Username);
+ if (hasUsername)
+ {
+ builder.Append(AuthHeaders.AUTH_USERNAME_KEY);
+ builder.Append("=\"");
+ builder.Append(Username);
+ builder.Append('"');
+ }
+
+ builder.Append(hasUsername ? ',' : '\0');
+ builder.Append(AuthHeaders.AUTH_REALM_KEY);
+ builder.Append("=\"");
+ builder.Append(Realm);
+ builder.Append('"');
+
+ if (Nonce != null)
+ {
+ builder.Append(',');
+ builder.Append(AuthHeaders.AUTH_NONCE_KEY);
+ builder.Append("=\"");
+ builder.Append(Nonce);
+ builder.Append('"');
+ }
+
+ if (!string.IsNullOrWhiteSpace(URI))
+ {
+ builder.Append(',');
+ builder.Append(AuthHeaders.AUTH_URI_KEY);
+ builder.Append("=\"");
+ builder.Append(URI);
+ builder.Append('"');
+ }
+
+ if (!string.IsNullOrEmpty(Response))
+ {
+ builder.Append(',');
+ builder.Append(AuthHeaders.AUTH_RESPONSE_KEY);
+ builder.Append("=\"");
+ builder.Append(Response);
+ builder.Append('"');
+ }
+
+ if (Cnonce != null)
+ {
+ builder.Append(',');
+ builder.Append(AuthHeaders.AUTH_CNONCE_KEY);
+ builder.Append("=\"");
+ builder.Append(Cnonce);
+ builder.Append('"');
+ }
+
+ if (NonceCount != 0)
+ {
+ builder.Append(',');
+ builder.Append(AuthHeaders.AUTH_NONCECOUNT_KEY);
+ builder.Append('=');
+ builder.Append(GetPaddedNonceCount(NonceCount));
+ }
+
+ if (Qop != null)
+ {
+ builder.Append(',');
+ builder.Append(AuthHeaders.AUTH_QOP_KEY);
+ builder.Append('=');
+ builder.Append(Qop);
+ }
+
+ if (Opaque != null)
+ {
+ builder.Append(',');
+ builder.Append(AuthHeaders.AUTH_OPAQUE_KEY);
+ builder.Append("=\"");
+ builder.Append(Opaque);
+ builder.Append('"');
+ }
+
+ if (Response != null)
+ {
+ builder.Append(',');
+ builder.Append(AuthHeaders.AUTH_ALGORITHM_KEY);
+ builder.Append('=');
+
+ string algorithmID = (DigestAlgorithm == DigestAlgorithmsEnum.SHA256)
+ ? SHA256_ALGORITHM_ID
+ : DigestAlgorithm.ToString();
+
+ builder.Append(algorithmID);
+ }
}
+
public SIPAuthorisationDigest CopyOf()
{
var copy = new SIPAuthorisationDigest(AuthorisationType, Realm, Username, Password, URI, Nonce, RequestType, DigestAlgorithm);
@@ -385,7 +477,7 @@ public static string GetHashHex(DigestAlgorithmsEnum hashAlg, string val)
case DigestAlgorithmsEnum.SHA256:
using (var hash = new SHA256CryptoServiceProvider())
{
- return hash.ComputeHash(Encoding.UTF8.GetBytes(val)).HexStr().ToLower();
+ return hash.ComputeHash(Encoding.UTF8.GetBytes(val)).AsSpan().HexStr(lowercase: true);
}
// This is commented because RFC8760 does not have an SHA-512 option. Instead it's HSA-512-sess which
// means the SIP request body needs to be included in the digest as well. Including the body will require
@@ -393,13 +485,13 @@ public static string GetHashHex(DigestAlgorithmsEnum hashAlg, string val)
//case DigestAlgorithmsEnum.SHA512:
// using (var hash = new SHA512CryptoServiceProvider())
// {
- // return hash.ComputeHash(Encoding.UTF8.GetBytes(val)).HexStr().ToLower();
+ // return hash.ComputeHash(Encoding.UTF8.GetBytes(val)).HexStr(lowercase: false);
// }
case DigestAlgorithmsEnum.MD5:
default:
using (var hash = new MD5CryptoServiceProvider())
{
- return hash.ComputeHash(Encoding.UTF8.GetBytes(val)).HexStr().ToLower();
+ return hash.ComputeHash(Encoding.UTF8.GetBytes(val)).AsSpan().HexStr(lowercase: true);
}
}
#pragma warning restore SYSLIB0021
diff --git a/src/core/SIP/SIPConstants.cs b/src/core/SIP/SIPConstants.cs
index 32da80841d..5caa269c54 100644
--- a/src/core/SIP/SIPConstants.cs
+++ b/src/core/SIP/SIPConstants.cs
@@ -541,17 +541,68 @@ public static string SIPURIUserUnescape(string escapedString)
public static string SIPURIParameterEscape(string unescapedString)
{
- string result = unescapedString;
- if (!result.IsNullOrBlank())
+ // Characters that need escaping
+ ReadOnlySpan specialChars = stackalloc char[] { ';', '?', '@', '=', ',', ' ' };
+
+ // Early exit if no special characters are found
+ var unescapedSpan = unescapedString.AsSpan();
+ int nextIndex = unescapedSpan.IndexOfAny(specialChars);
+ if (nextIndex == -1)
{
- result = result.Replace(";", "%3B");
- result = result.Replace("?", "%3F");
- result = result.Replace("@", "%40");
- result = result.Replace("=", "%3D");
- result = result.Replace(",", "%2C");
- result = result.Replace(" ", "%20");
+ // No escaping needed
+ return unescapedString;
+ }
+
+ var builder = new ValueStringBuilder();
+
+ try
+ {
+ SIPURIParameterEscape(ref builder, unescapedSpan);
+
+ return builder.ToString();
+ }
+ finally
+ {
+ builder.Dispose();
+ }
+ }
+
+ internal static void SIPURIParameterEscape(ref ValueStringBuilder builder, ReadOnlySpan unescapedSpan)
+ {
+ // Characters that need escaping
+ ReadOnlySpan specialChars = stackalloc char[] { ';', '?', '@', '=', ',', ' ' };
+
+ var currentIndex = 0;
+ var nextIndex = unescapedSpan.IndexOfAny(specialChars);
+ while (nextIndex != -1)
+ {
+ // Append everything before the special character
+ builder.Append(unescapedSpan.Slice(currentIndex, nextIndex - currentIndex));
+
+ // Escape the special character
+ switch (unescapedSpan[nextIndex])
+ {
+ case ';': builder.Append("%3B"); break;
+ case '?': builder.Append("%3F"); break;
+ case '@': builder.Append("%40"); break;
+ case '=': builder.Append("%3D"); break;
+ case ',': builder.Append("%2C"); break;
+ case ' ': builder.Append("%20"); break;
+ }
+
+ currentIndex = nextIndex + 1;
+ nextIndex = unescapedSpan.Slice(currentIndex).IndexOfAny(specialChars);
+ if (nextIndex != -1)
+ {
+ nextIndex += currentIndex; // Adjust relative index to absolute
+ }
+ }
+
+ // Append the remaining part
+ if (currentIndex < unescapedSpan.Length)
+ {
+ builder.Append(unescapedSpan.Slice(currentIndex));
}
- return result;
}
public static string SIPURIParameterUnescape(string escapedString)
diff --git a/src/core/SIP/SIPHeader.cs b/src/core/SIP/SIPHeader.cs
index e46e041006..1eb72d4cc5 100644
--- a/src/core/SIP/SIPHeader.cs
+++ b/src/core/SIP/SIPHeader.cs
@@ -18,7 +18,6 @@
using System.Net;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
-using System.Text;
using System.Text.RegularExpressions;
using Microsoft.Extensions.Logging;
using SIPSorcery.Sys;
@@ -355,10 +354,32 @@ public static SIPViaHeader[] ParseSIPViaHeader(string viaHeaderStr)
public new string ToString()
{
- string sipViaHeader = SIPHeaders.SIP_HEADER_VIA + ": " + this.Version + "/" + this.Transport.ToString().ToUpper() + " " + ContactAddress;
- sipViaHeader += (ViaParameters != null && ViaParameters.Count > 0) ? ViaParameters.ToString() : null;
+ var builder = new ValueStringBuilder();
+ try
+ {
+ ToString(ref builder);
+ return builder.ToString();
+ }
+ finally
+ {
+ builder.Dispose();
+ }
+ }
+
+ internal void ToString(ref ValueStringBuilder builder)
+ {
+ builder.Append(SIPHeaders.SIP_HEADER_VIA);
+ builder.Append(": ");
+ builder.Append(Version);
+ builder.Append('/');
+ builder.Append(Transport.ToString().ToUpperInvariant());
+ builder.Append(' ');
+ builder.Append(ContactAddress);
- return sipViaHeader;
+ if (ViaParameters != null && ViaParameters.Count > 0)
+ {
+ builder.Append(ViaParameters.ToString());
+ }
}
}
@@ -466,7 +487,23 @@ public static SIPFromHeader ParseFromHeader(string fromHeaderStr)
public override string ToString()
{
- return m_userField.ToString();
+ var builder = new ValueStringBuilder();
+
+ try
+ {
+ ToString(ref builder);
+
+ return builder.ToString();
+ }
+ finally
+ {
+ builder.Dispose();
+ }
+ }
+
+ internal void ToString(ref ValueStringBuilder builder)
+ {
+ m_userField.ToString(ref builder);
}
///
@@ -574,7 +611,23 @@ public static SIPToHeader ParseToHeader(string toHeaderStr)
public override string ToString()
{
- return m_userField.ToString();
+ var builder = new ValueStringBuilder();
+
+ try
+ {
+ ToString(ref builder);
+
+ return builder.ToString();
+ }
+ finally
+ {
+ builder.Dispose();
+ }
+ }
+
+ internal void ToString(ref ValueStringBuilder builder)
+ {
+ m_userField.ToString(ref builder);
}
}
@@ -797,16 +850,30 @@ public override string ToString()
{
return SIPConstants.SIP_REGISTER_REMOVEALL;
}
+
+ var builder = new ValueStringBuilder();
+
+ try
+ {
+ ToString(ref builder);
+
+ return builder.ToString();
+ }
+ finally
+ {
+ builder.Dispose();
+ }
+ }
+
+ internal void ToString(ref ValueStringBuilder builder)
+ {
+ if (m_userField.URI.Host == SIPConstants.SIP_REGISTER_REMOVEALL)
+ {
+ builder.Append(SIPConstants.SIP_REGISTER_REMOVEALL);
+ }
else
{
- //if (m_userField.URI.Protocol == SIPProtocolsEnum.UDP)
- //{
- return m_userField.ToString();
- //}
- //else
- //{
- // return m_userField.ToContactString();
- //}
+ m_userField.ToString(ref builder);
}
}
@@ -902,21 +969,38 @@ private static string BuildAuthorisationHeaderName(SIPAuthorisationHeadersEnum a
}
public override string ToString()
+ {
+ var builder = new ValueStringBuilder();
+
+ try
+ {
+ ToString(ref builder);
+
+ return builder.ToString();
+ }
+ finally
+ {
+ builder.Dispose();
+ }
+ }
+
+ internal void ToString(ref ValueStringBuilder builder)
{
if (SIPDigest != null)
{
- var authorisationHeaderType = (SIPDigest.AuthorisationResponseType != SIPAuthorisationHeadersEnum.Unknown) ? SIPDigest.AuthorisationResponseType : SIPDigest.AuthorisationType;
+ var authorisationHeaderType = (SIPDigest.AuthorisationResponseType != SIPAuthorisationHeadersEnum.Unknown)
+ ? SIPDigest.AuthorisationResponseType
+ : SIPDigest.AuthorisationType;
+
string authHeader = BuildAuthorisationHeaderName(authorisationHeaderType);
- return authHeader + SIPDigest.ToString();
+ builder.Append(authHeader);
+ SIPDigest.ToString(ref builder);
}
else if (!string.IsNullOrEmpty(Value))
{
string authHeader = BuildAuthorisationHeaderName(AuthorisationType);
- return authHeader + Value;
- }
- else
- {
- return null;
+ builder.Append(authHeader);
+ builder.Append(Value);
}
}
}
@@ -1153,7 +1237,7 @@ public void RemoveBottomRoute()
if (m_sipRoutes.Count > 0)
{
m_sipRoutes.RemoveAt(m_sipRoutes.Count - 1);
- };
+ }
}
public SIPRouteSet Reversed()
@@ -1191,19 +1275,36 @@ public void ReplaceRoute(string origSocket, string replacementSocket)
}
}
- public new string ToString()
+ public override string ToString()
+ {
+ var builder = new ValueStringBuilder();
+
+ try
+ {
+ ToString(ref builder);
+
+ return builder.ToString();
+ }
+ finally
{
- string routeStr = null;
+ builder.Dispose();
+ }
+ }
+ internal void ToString(ref ValueStringBuilder builder)
+ {
if (m_sipRoutes != null && m_sipRoutes.Count > 0)
{
for (int routeIndex = 0; routeIndex < m_sipRoutes.Count; routeIndex++)
{
- routeStr += (routeStr != null) ? "," + m_sipRoutes[routeIndex].ToString() : m_sipRoutes[routeIndex].ToString();
+ if (routeIndex > 0)
+ {
+ builder.Append(",");
+ }
+
+ builder.Append(m_sipRoutes[routeIndex].ToString());
}
}
-
- return routeStr;
}
}
@@ -1307,17 +1408,30 @@ public void PushViaHeader(SIPViaHeader viaHeader)
public new string ToString()
{
- string viaStr = null;
+ var builder = new ValueStringBuilder();
+
+ try
+ {
+ ToString(ref builder);
+
+ return builder.ToString();
+ }
+ finally
+ {
+ builder.Dispose();
+ }
+ }
+ internal void ToString(ref ValueStringBuilder builder)
+ {
if (m_viaHeaders != null && m_viaHeaders.Count > 0)
{
for (int viaIndex = 0; viaIndex < m_viaHeaders.Count; viaIndex++)
{
- viaStr += (m_viaHeaders[viaIndex]).ToString() + m_CRLF;
+ m_viaHeaders[viaIndex].ToString(ref builder);
+ builder.Append(m_CRLF);
}
}
-
- return viaStr;
}
}
@@ -2201,148 +2315,531 @@ public static SIPHeader ParseSIPHeaders(string[] headersCollection)
/// String representing the SIP headers.
public new string ToString()
{
+ var builder = new ValueStringBuilder();
+
try
{
- StringBuilder headersBuilder = new StringBuilder();
+ ToString(ref builder);
+
+ return builder.ToString();
+ }
+ catch (Exception excp)
+ {
+ logger.LogError(excp, "Exception SIPHeader ToString. Exception: {ErrorMessage}", excp.Message);
+ throw;
+ }
+ finally
+ {
+ builder.Dispose();
+ }
+ }
+
+ internal void ToString(ref ValueStringBuilder builder)
+ {
+ Vias.ToString(ref builder);
+
+ if (To != null)
+ {
+ builder.Append(SIPHeaders.SIP_HEADER_TO);
+ builder.Append(": ");
+ To.ToString(ref builder);
+ builder.Append(m_CRLF);
+ }
+
+ if (From != null)
+ {
+ builder.Append(SIPHeaders.SIP_HEADER_FROM);
+ builder.Append(": ");
+ From.ToString(ref builder);
+ builder.Append(m_CRLF);
+ }
+
+ if (CallId != null)
+ {
+ builder.Append(SIPHeaders.SIP_HEADER_CALLID);
+ builder.Append(": ");
+ builder.Append(CallId);
+ builder.Append(m_CRLF);
+ }
- headersBuilder.Append(Vias.ToString());
+ if (CSeq >= 0)
+ {
+ builder.Append(SIPHeaders.SIP_HEADER_CSEQ);
+ builder.Append(": ");
+ builder.Append(CSeq);
- string cseqField = null;
- if (this.CSeq >= 0)
+ if (CSeqMethod != SIPMethodsEnum.NONE)
{
- cseqField = (this.CSeqMethod != SIPMethodsEnum.NONE) ? this.CSeq + " " + this.CSeqMethod.ToString() : this.CSeq.ToString();
+ builder.Append(' ');
+ builder.Append(CSeqMethod.ToString());
}
- headersBuilder.Append((To != null) ? SIPHeaders.SIP_HEADER_TO + ": " + this.To.ToString() + m_CRLF : null);
- headersBuilder.Append((From != null) ? SIPHeaders.SIP_HEADER_FROM + ": " + this.From.ToString() + m_CRLF : null);
- headersBuilder.Append((CallId != null) ? SIPHeaders.SIP_HEADER_CALLID + ": " + this.CallId + m_CRLF : null);
- headersBuilder.Append((CSeq >= 0) ? SIPHeaders.SIP_HEADER_CSEQ + ": " + cseqField + m_CRLF : null);
+ builder.Append(m_CRLF);
+ }
#region Appending Contact header.
if (Contact != null && Contact.Count == 1)
{
- headersBuilder.Append(SIPHeaders.SIP_HEADER_CONTACT + ": " + Contact[0].ToString() + m_CRLF);
+ builder.Append(SIPHeaders.SIP_HEADER_CONTACT);
+ builder.Append(": ");
+ Contact[0].ToString(ref builder);
+ builder.Append(m_CRLF);
}
else if (Contact != null && Contact.Count > 1)
{
- StringBuilder contactsBuilder = new StringBuilder();
- contactsBuilder.Append(SIPHeaders.SIP_HEADER_CONTACT + ": ");
+ builder.Append(SIPHeaders.SIP_HEADER_CONTACT);
+ builder.Append(": ");
bool firstContact = true;
foreach (SIPContactHeader contactHeader in Contact)
{
- if (firstContact)
+ if (!firstContact)
{
- contactsBuilder.Append(contactHeader.ToString());
+ builder.Append(',');
}
- else
+
+ contactHeader.ToString(ref builder);
+ firstContact = false;
+ }
+
+ builder.Append(m_CRLF);
+ }
+
+ #endregion
+
+ if (MaxForwards >= 0)
+ {
+ builder.Append(SIPHeaders.SIP_HEADER_MAXFORWARDS);
+ builder.Append(": ");
+ builder.Append(MaxForwards);
+ builder.Append(m_CRLF);
+ }
+
+ if (Routes != null && Routes.Length > 0)
+ {
+ builder.Append(SIPHeaders.SIP_HEADER_ROUTE);
+ builder.Append(": ");
+ Routes.ToString(ref builder);
+ builder.Append(m_CRLF);
+ }
+
+ if (RecordRoutes != null && RecordRoutes.Length > 0)
+ {
+ builder.Append(SIPHeaders.SIP_HEADER_RECORDROUTE);
+ builder.Append(": ");
+ RecordRoutes.ToString(ref builder);
+ builder.Append(m_CRLF);
+ }
+
+ if (UserAgent != null && UserAgent.Trim().Length != 0)
+ {
+ builder.Append(SIPHeaders.SIP_HEADER_USERAGENT);
+ builder.Append(": ");
+ builder.Append(UserAgent);
+ builder.Append(m_CRLF);
+ }
+
+ if (Expires != -1)
+ {
+ builder.Append(SIPHeaders.SIP_HEADER_EXPIRES);
+ builder.Append(": ");
+ builder.Append(Expires);
+ builder.Append(m_CRLF);
+ }
+
+ if (MinExpires != -1)
+ {
+ builder.Append(SIPHeaders.SIP_HEADER_MINEXPIRES);
+ builder.Append(": ");
+ builder.Append(MinExpires);
+ builder.Append(m_CRLF);
+ }
+
+ if (Accept != null)
+ {
+ builder.Append(SIPHeaders.SIP_HEADER_ACCEPT);
+ builder.Append(": ");
+ builder.Append(Accept);
+ builder.Append(m_CRLF);
+ }
+
+ if (AcceptEncoding != null)
{
- contactsBuilder.Append("," + contactHeader.ToString());
+ builder.Append(SIPHeaders.SIP_HEADER_ACCEPTENCODING);
+ builder.Append(": ");
+ builder.Append(AcceptEncoding);
+ builder.Append(m_CRLF);
}
- firstContact = false;
+ if (AcceptLanguage != null)
+ {
+ builder.Append(SIPHeaders.SIP_HEADER_ACCEPTLANGUAGE);
+ builder.Append(": ");
+ builder.Append(AcceptLanguage);
+ builder.Append(m_CRLF);
}
- headersBuilder.Append(contactsBuilder.ToString() + m_CRLF);
+ if (Allow != null)
+ {
+ builder.Append(SIPHeaders.SIP_HEADER_ALLOW);
+ builder.Append(": ");
+ builder.Append(Allow);
+ builder.Append(m_CRLF);
}
- #endregion
-
- headersBuilder.Append((MaxForwards >= 0) ? SIPHeaders.SIP_HEADER_MAXFORWARDS + ": " + this.MaxForwards + m_CRLF : null);
- headersBuilder.Append((Routes != null && Routes.Length > 0) ? SIPHeaders.SIP_HEADER_ROUTE + ": " + Routes.ToString() + m_CRLF : null);
- headersBuilder.Append((RecordRoutes != null && RecordRoutes.Length > 0) ? SIPHeaders.SIP_HEADER_RECORDROUTE + ": " + RecordRoutes.ToString() + m_CRLF : null);
- headersBuilder.Append((UserAgent != null && UserAgent.Trim().Length != 0) ? SIPHeaders.SIP_HEADER_USERAGENT + ": " + this.UserAgent + m_CRLF : null);
- headersBuilder.Append((Expires != -1) ? SIPHeaders.SIP_HEADER_EXPIRES + ": " + this.Expires + m_CRLF : null);
- headersBuilder.Append((MinExpires != -1) ? SIPHeaders.SIP_HEADER_MINEXPIRES + ": " + this.MinExpires + m_CRLF : null);
- headersBuilder.Append((Accept != null) ? SIPHeaders.SIP_HEADER_ACCEPT + ": " + this.Accept + m_CRLF : null);
- headersBuilder.Append((AcceptEncoding != null) ? SIPHeaders.SIP_HEADER_ACCEPTENCODING + ": " + this.AcceptEncoding + m_CRLF : null);
- headersBuilder.Append((AcceptLanguage != null) ? SIPHeaders.SIP_HEADER_ACCEPTLANGUAGE + ": " + this.AcceptLanguage + m_CRLF : null);
- headersBuilder.Append((Allow != null) ? SIPHeaders.SIP_HEADER_ALLOW + ": " + this.Allow + m_CRLF : null);
- headersBuilder.Append((AlertInfo != null) ? SIPHeaders.SIP_HEADER_ALERTINFO + ": " + this.AlertInfo + m_CRLF : null);
- headersBuilder.Append((AuthenticationInfo != null) ? SIPHeaders.SIP_HEADER_AUTHENTICATIONINFO + ": " + this.AuthenticationInfo + m_CRLF : null);
+ if (AlertInfo != null)
+ {
+ builder.Append(SIPHeaders.SIP_HEADER_ALERTINFO);
+ builder.Append(": ");
+ builder.Append(AlertInfo);
+ builder.Append(m_CRLF);
+ }
+
+ if (AuthenticationInfo != null)
+ {
+ builder.Append(SIPHeaders.SIP_HEADER_AUTHENTICATIONINFO);
+ builder.Append(": ");
+ builder.Append(AuthenticationInfo);
+ builder.Append(m_CRLF);
+ }
if (AuthenticationHeaders.Count > 0)
{
foreach (var authHeader in AuthenticationHeaders)
{
- var value = authHeader.ToString();
- if (value != null)
+ if (authHeader is not null)
{
- headersBuilder.Append(authHeader.ToString() + m_CRLF);
+ authHeader.ToString(ref builder);
+ builder.Append(m_CRLF);
+ }
+ }
+ }
+
+ if (CallInfo != null)
+ {
+ builder.Append(SIPHeaders.SIP_HEADER_CALLINFO);
+ builder.Append(": ");
+ builder.Append(CallInfo);
+ builder.Append(m_CRLF);
+ }
+
+ if (ContentDisposition != null)
+ {
+ builder.Append(SIPHeaders.SIP_HEADER_CONTENT_DISPOSITION);
+ builder.Append(": ");
+ builder.Append(ContentDisposition);
+ builder.Append(m_CRLF);
+ }
+
+ if (ContentEncoding != null)
+ {
+ builder.Append(SIPHeaders.SIP_HEADER_CONTENT_ENCODING);
+ builder.Append(": ");
+ builder.Append(ContentEncoding);
+ builder.Append(m_CRLF);
+ }
+
+ if (ContentLanguage != null)
+ {
+ builder.Append(SIPHeaders.SIP_HEADER_CONTENT_LANGUAGE);
+ builder.Append(": ");
+ builder.Append(ContentLanguage);
+ builder.Append(m_CRLF);
+ }
+
+ if (Date != null)
+ {
+ builder.Append(SIPHeaders.SIP_HEADER_DATE);
+ builder.Append(": ");
+ builder.Append(Date);
+ builder.Append(m_CRLF);
+ }
+
+ if (ErrorInfo != null)
+ {
+ builder.Append(SIPHeaders.SIP_HEADER_ERROR_INFO);
+ builder.Append(": ");
+ builder.Append(ErrorInfo);
+ builder.Append(m_CRLF);
+ }
+
+ if (InReplyTo != null)
+ {
+ builder.Append(SIPHeaders.SIP_HEADER_IN_REPLY_TO);
+ builder.Append(": ");
+ builder.Append(InReplyTo);
+ builder.Append(m_CRLF);
+ }
+
+ if (Organization != null)
+ {
+ builder.Append(SIPHeaders.SIP_HEADER_ORGANIZATION);
+ builder.Append(": ");
+ builder.Append(Organization);
+ builder.Append(m_CRLF);
+ }
+
+ if (Priority != null)
+ {
+ builder.Append(SIPHeaders.SIP_HEADER_PRIORITY);
+ builder.Append(": ");
+ builder.Append(Priority);
+ builder.Append(m_CRLF);
}
+
+ if (ProxyRequire != null)
+ {
+ builder.Append(SIPHeaders.SIP_HEADER_PROXY_REQUIRE);
+ builder.Append(": ");
+ builder.Append(ProxyRequire);
+ builder.Append(m_CRLF);
+ }
+
+ if (ReplyTo != null)
+ {
+ builder.Append(SIPHeaders.SIP_HEADER_REPLY_TO);
+ builder.Append(": ");
+ builder.Append(ReplyTo);
+ builder.Append(m_CRLF);
+ }
+
+ if (Require != null)
+ {
+ builder.Append(SIPHeaders.SIP_HEADER_REQUIRE);
+ builder.Append(": ");
+ builder.Append(Require);
+ builder.Append(m_CRLF);
+ }
+
+ if (RetryAfter != null)
+ {
+ builder.Append(SIPHeaders.SIP_HEADER_RETRY_AFTER);
+ builder.Append(": ");
+ builder.Append(RetryAfter);
+ builder.Append(m_CRLF);
+ }
+
+ if (Server != null && Server.Trim().Length != 0)
+ {
+ builder.Append(SIPHeaders.SIP_HEADER_SERVER);
+ builder.Append(": ");
+ builder.Append(Server);
+ builder.Append(m_CRLF);
+ }
+
+ if (Subject != null)
+ {
+ builder.Append(SIPHeaders.SIP_HEADER_SUBJECT);
+ builder.Append(": ");
+ builder.Append(Subject);
+ builder.Append(m_CRLF);
}
+
+ if (Supported != null)
+ {
+ builder.Append(SIPHeaders.SIP_HEADER_SUPPORTED);
+ builder.Append(": ");
+ builder.Append(Supported);
+ builder.Append(m_CRLF);
}
- headersBuilder.Append((CallInfo != null) ? SIPHeaders.SIP_HEADER_CALLINFO + ": " + this.CallInfo + m_CRLF : null);
- headersBuilder.Append((ContentDisposition != null) ? SIPHeaders.SIP_HEADER_CONTENT_DISPOSITION + ": " + this.ContentDisposition + m_CRLF : null);
- headersBuilder.Append((ContentEncoding != null) ? SIPHeaders.SIP_HEADER_CONTENT_ENCODING + ": " + this.ContentEncoding + m_CRLF : null);
- headersBuilder.Append((ContentLanguage != null) ? SIPHeaders.SIP_HEADER_CONTENT_LANGUAGE + ": " + this.ContentLanguage + m_CRLF : null);
- headersBuilder.Append((Date != null) ? SIPHeaders.SIP_HEADER_DATE + ": " + Date + m_CRLF : null);
- headersBuilder.Append((ErrorInfo != null) ? SIPHeaders.SIP_HEADER_ERROR_INFO + ": " + this.ErrorInfo + m_CRLF : null);
- headersBuilder.Append((InReplyTo != null) ? SIPHeaders.SIP_HEADER_IN_REPLY_TO + ": " + this.InReplyTo + m_CRLF : null);
- headersBuilder.Append((Organization != null) ? SIPHeaders.SIP_HEADER_ORGANIZATION + ": " + this.Organization + m_CRLF : null);
- headersBuilder.Append((Priority != null) ? SIPHeaders.SIP_HEADER_PRIORITY + ": " + Priority + m_CRLF : null);
- headersBuilder.Append((ProxyRequire != null) ? SIPHeaders.SIP_HEADER_PROXY_REQUIRE + ": " + this.ProxyRequire + m_CRLF : null);
- headersBuilder.Append((ReplyTo != null) ? SIPHeaders.SIP_HEADER_REPLY_TO + ": " + this.ReplyTo + m_CRLF : null);
- headersBuilder.Append((Require != null) ? SIPHeaders.SIP_HEADER_REQUIRE + ": " + Require + m_CRLF : null);
- headersBuilder.Append((RetryAfter != null) ? SIPHeaders.SIP_HEADER_RETRY_AFTER + ": " + this.RetryAfter + m_CRLF : null);
- headersBuilder.Append((Server != null && Server.Trim().Length != 0) ? SIPHeaders.SIP_HEADER_SERVER + ": " + this.Server + m_CRLF : null);
- headersBuilder.Append((Subject != null) ? SIPHeaders.SIP_HEADER_SUBJECT + ": " + Subject + m_CRLF : null);
- headersBuilder.Append((Supported != null) ? SIPHeaders.SIP_HEADER_SUPPORTED + ": " + Supported + m_CRLF : null);
- headersBuilder.Append((Timestamp != null) ? SIPHeaders.SIP_HEADER_TIMESTAMP + ": " + Timestamp + m_CRLF : null);
- headersBuilder.Append((Unsupported != null) ? SIPHeaders.SIP_HEADER_UNSUPPORTED + ": " + Unsupported + m_CRLF : null);
- headersBuilder.Append((Warning != null) ? SIPHeaders.SIP_HEADER_WARNING + ": " + Warning + m_CRLF : null);
- headersBuilder.Append((ETag != null) ? SIPHeaders.SIP_HEADER_ETAG + ": " + ETag + m_CRLF : null);
- headersBuilder.Append(SIPHeaders.SIP_HEADER_CONTENTLENGTH + ": " + this.ContentLength + m_CRLF);
- if (this.ContentType != null && this.ContentType.Trim().Length > 0)
+
+ if (Timestamp != null)
+ {
+ builder.Append(SIPHeaders.SIP_HEADER_TIMESTAMP);
+ builder.Append(": ");
+ builder.Append(Timestamp);
+ builder.Append(m_CRLF);
+ }
+
+ if (Unsupported != null)
+ {
+ builder.Append(SIPHeaders.SIP_HEADER_UNSUPPORTED);
+ builder.Append(": ");
+ builder.Append(Unsupported);
+ builder.Append(m_CRLF);
+ }
+
+ if (Warning != null)
+ {
+ builder.Append(SIPHeaders.SIP_HEADER_WARNING);
+ builder.Append(": ");
+ builder.Append(Warning);
+ builder.Append(m_CRLF);
+ }
+
+ if (ETag != null)
+ {
+ builder.Append(SIPHeaders.SIP_HEADER_ETAG);
+ builder.Append(": ");
+ builder.Append(ETag);
+ builder.Append(m_CRLF);
+ }
+
+ builder.Append(SIPHeaders.SIP_HEADER_CONTENTLENGTH);
+ builder.Append(": ");
+ builder.Append(ContentLength);
+ builder.Append(m_CRLF);
+
+ if (ContentType != null && ContentType.Trim().Length > 0)
{
- headersBuilder.Append(SIPHeaders.SIP_HEADER_CONTENTTYPE + ": " + this.ContentType + m_CRLF);
+ builder.Append(SIPHeaders.SIP_HEADER_CONTENTTYPE);
+ builder.Append(": ");
+ builder.Append(ContentType);
+ builder.Append(m_CRLF);
}
// Non-core SIP headers.
- headersBuilder.Append((AllowEvents != null) ? SIPHeaders.SIP_HEADER_ALLOW_EVENTS + ": " + AllowEvents + m_CRLF : null);
- headersBuilder.Append((Event != null) ? SIPHeaders.SIP_HEADER_EVENT + ": " + Event + m_CRLF : null);
- headersBuilder.Append((SubscriptionState != null) ? SIPHeaders.SIP_HEADER_SUBSCRIPTION_STATE + ": " + SubscriptionState + m_CRLF : null);
- headersBuilder.Append((ReferSub != null) ? SIPHeaders.SIP_HEADER_REFERSUB + ": " + ReferSub + m_CRLF : null);
- headersBuilder.Append((ReferTo != null) ? SIPHeaders.SIP_HEADER_REFERTO + ": " + ReferTo + m_CRLF : null);
- headersBuilder.Append((ReferredBy != null) ? SIPHeaders.SIP_HEADER_REFERREDBY + ": " + ReferredBy + m_CRLF : null);
- headersBuilder.Append((Replaces != null) ? SIPHeaders.SIP_HEADER_REPLACES + ": " + Replaces + m_CRLF : null);
- headersBuilder.Append((Reason != null) ? SIPHeaders.SIP_HEADER_REASON + ": " + Reason + m_CRLF : null);
- headersBuilder.Append((RSeq != -1) ? SIPHeaders.SIP_HEADER_RELIABLE_SEQ + ": " + RSeq + m_CRLF : null);
- headersBuilder.Append((RAckRSeq != -1) ? SIPHeaders.SIP_HEADER_RELIABLE_ACK + ": " + RAckRSeq + " " + RAckCSeq + " " + RAckCSeqMethod + m_CRLF : null);
+ if (AllowEvents != null)
+ {
+ builder.Append(SIPHeaders.SIP_HEADER_ALLOW_EVENTS);
+ builder.Append(": ");
+ builder.Append(AllowEvents);
+ builder.Append(m_CRLF);
+ }
+
+ if (Event != null)
+ {
+ builder.Append(SIPHeaders.SIP_HEADER_EVENT);
+ builder.Append(": ");
+ builder.Append(Event);
+ builder.Append(m_CRLF);
+ }
+
+ if (SubscriptionState != null)
+ {
+ builder.Append(SIPHeaders.SIP_HEADER_SUBSCRIPTION_STATE);
+ builder.Append(": ");
+ builder.Append(SubscriptionState);
+ builder.Append(m_CRLF);
+ }
+
+ if (ReferSub != null)
+ {
+ builder.Append(SIPHeaders.SIP_HEADER_REFERSUB);
+ builder.Append(": ");
+ builder.Append(ReferSub);
+ builder.Append(m_CRLF);
+ }
+
+ if (ReferTo != null)
+ {
+ builder.Append(SIPHeaders.SIP_HEADER_REFERTO);
+ builder.Append(": ");
+ builder.Append(ReferTo);
+ builder.Append(m_CRLF);
+ }
+
+ if (ReferredBy != null)
+ {
+ builder.Append(SIPHeaders.SIP_HEADER_REFERREDBY);
+ builder.Append(": ");
+ builder.Append(ReferredBy);
+ builder.Append(m_CRLF);
+ }
+
+ if (Replaces != null)
+ {
+ builder.Append(SIPHeaders.SIP_HEADER_REPLACES);
+ builder.Append(": ");
+ builder.Append(Replaces);
+ builder.Append(m_CRLF);
+ }
+
+ if (Reason != null)
+ {
+ builder.Append(SIPHeaders.SIP_HEADER_REASON);
+ builder.Append(": ");
+ builder.Append(Reason);
+ builder.Append(m_CRLF);
+ }
+
+ if (RSeq != -1)
+ {
+ builder.Append(SIPHeaders.SIP_HEADER_RELIABLE_SEQ);
+ builder.Append(": ");
+ builder.Append(RSeq);
+ builder.Append(m_CRLF);
+ }
+
+ if (RAckRSeq != -1)
+ {
+ builder.Append(SIPHeaders.SIP_HEADER_RELIABLE_ACK);
+ builder.Append(": ");
+ builder.Append(RAckRSeq);
+ builder.Append(' ');
+ builder.Append(RAckCSeq);
+ builder.Append(' ');
+ builder.Append(RAckCSeqMethod.ToString());
+ builder.Append(m_CRLF);
+ }
foreach (var PAI in PassertedIdentity)
{
- headersBuilder.Append(SIPHeaders.SIP_HEADER_PASSERTED_IDENTITY + ": " + PAI + m_CRLF);
+ if (PAI != null)
+ {
+ builder.Append(SIPHeaders.SIP_HEADER_PASSERTED_IDENTITY);
+ builder.Append(": ");
+ builder.Append(PAI.ToString());
+ builder.Append(m_CRLF);
+ }
}
foreach (var HistInfo in HistoryInfo)
{
- headersBuilder.Append(SIPHeaders.SIP_HEADER_HISTORY_INFO + ": " + HistInfo + m_CRLF);
+ if (HistInfo != null)
+ {
+ builder.Append(SIPHeaders.SIP_HEADER_HISTORY_INFO);
+ builder.Append(": ");
+ builder.Append(HistInfo.ToString());
+ builder.Append(m_CRLF);
+ }
}
foreach (var DiversionHeader in Diversion)
{
- headersBuilder.Append(SIPHeaders.SIP_HEADER_DIVERSION + ": " + DiversionHeader + m_CRLF);
+ if (DiversionHeader != null)
+ {
+ builder.Append(SIPHeaders.SIP_HEADER_DIVERSION);
+ builder.Append(": ");
+ builder.Append(DiversionHeader.ToString());
+ builder.Append(m_CRLF);
+ }
}
// Custom SIP headers.
- headersBuilder.Append((ProxyReceivedFrom != null) ? SIPHeaders.SIP_HEADER_PROXY_RECEIVEDFROM + ": " + ProxyReceivedFrom + m_CRLF : null);
- headersBuilder.Append((ProxyReceivedOn != null) ? SIPHeaders.SIP_HEADER_PROXY_RECEIVEDON + ": " + ProxyReceivedOn + m_CRLF : null);
- headersBuilder.Append((ProxySendFrom != null) ? SIPHeaders.SIP_HEADER_PROXY_SENDFROM + ": " + ProxySendFrom + m_CRLF : null);
+ if (ProxyReceivedFrom != null)
+ {
+ builder.Append(SIPHeaders.SIP_HEADER_PROXY_RECEIVEDFROM);
+ builder.Append(": ");
+ builder.Append(ProxyReceivedFrom);
+ builder.Append(m_CRLF);
+ }
- // Unknown SIP headers
- foreach (string unknownHeader in UnknownHeaders)
+ if (ProxyReceivedOn != null)
{
- headersBuilder.Append(unknownHeader + m_CRLF);
+ builder.Append(SIPHeaders.SIP_HEADER_PROXY_RECEIVEDON);
+ builder.Append(": ");
+ builder.Append(ProxyReceivedOn);
+ builder.Append(m_CRLF);
}
- return headersBuilder.ToString();
+ if (ProxySendFrom != null)
+ {
+ builder.Append(SIPHeaders.SIP_HEADER_PROXY_SENDFROM);
+ builder.Append(": ");
+ builder.Append(ProxySendFrom);
+ builder.Append(m_CRLF);
}
- catch (Exception excp)
+
+ // Unknown SIP headers
+ foreach (string unknownHeader in UnknownHeaders)
{
- logger.LogError(excp, "Exception SIPHeader ToString. Exception: {ErrorMessage}", excp.Message);
- throw;
+ if (unknownHeader != null)
+ {
+ builder.Append(unknownHeader);
+ builder.Append(m_CRLF);
+ }
}
}
diff --git a/src/core/SIP/SIPMessageBase.cs b/src/core/SIP/SIPMessageBase.cs
index 5bdb00da29..bb8b019f38 100644
--- a/src/core/SIP/SIPMessageBase.cs
+++ b/src/core/SIP/SIPMessageBase.cs
@@ -1,32 +1,32 @@
-//-----------------------------------------------------------------------------
-// Filename: SIPMessageBase.cs
-//
-// Description: Common base class for SIPRequest and SIPResponse classes.
-//
-// Author(s):
+//-----------------------------------------------------------------------------
+// Filename: SIPMessageBase.cs
+//
+// Description: Common base class for SIPRequest and SIPResponse classes.
+//
+// Author(s):
// Salih YILDIRIM (github: salihy)
-// Aaron Clauson (aaron@sipsorcery.com)
-//
-// History:
+// Aaron Clauson (aaron@sipsorcery.com)
+//
+// History:
// 26 Nov 2019 Salih YILDIRIM Created.
-// 26 Nov 2019 Aaron Clauson Converted from interface to base class to extract common properties.
-//
-// License:
-// BSD 3-Clause "New" or "Revised" License, see included LICENSE.md file.
-//-----------------------------------------------------------------------------
+// 26 Nov 2019 Aaron Clauson Converted from interface to base class to extract common properties.
+//
+// License:
+// BSD 3-Clause "New" or "Revised" License, see included LICENSE.md file.
+//-----------------------------------------------------------------------------
using System;
using System.Text;
using Microsoft.Extensions.Logging;
using SIPSorcery.Sys;
-
-namespace SIPSorcery.SIP
-{
- public class SIPMessageBase
+
+namespace SIPSorcery.SIP
+{
+ public class SIPMessageBase
{
protected static ILogger logger = Log.Logger;
- protected const string m_CRLF = SIPConstants.CRLF;
+ protected const string m_CRLF = SIPConstants.CRLF;
protected const string m_sipFullVersion = SIPConstants.SIP_FULLVERSION_STRING;
protected const string m_allowedSIPMethods = SIPConstants.ALLOWED_SIP_METHODS;
@@ -43,15 +43,15 @@ public SIPMessageBase(Encoding sipEncoding, Encoding sipBodyEncoding)
SIPBodyEncoding = sipBodyEncoding;
}
- ///
- /// The SIP request/response's headers collection.
- ///
+ ///
+ /// The SIP request/response's headers collection.
+ ///
public SIPHeader Header;
- ///
+ ///
/// The optional body or payload for the SIP request/response. This Body property
- /// should be used for Session Description Protocol (SDP) and other string payloads.
- ///
+ /// should be used for Session Description Protocol (SDP) and other string payloads.
+ ///
public string Body
{
get
@@ -87,51 +87,65 @@ public byte[] BodyBuffer
{
get => _body;
set => _body = value;
- }
-
- ///
- /// Timestamp for the SIP request/response's creation.
- ///
- public DateTime Created = DateTime.Now;
-
- ///
- /// The remote SIP socket the request/response was received from.
- ///
- public SIPEndPoint RemoteSIPEndPoint { get; protected set; }
-
- ///
- /// The local SIP socket the request/response was received on.
- ///
+ }
+
+ ///
+ /// Timestamp for the SIP request/response's creation.
+ ///
+ public DateTime Created = DateTime.Now;
+
+ ///
+ /// The remote SIP socket the request/response was received from.
+ ///
+ public SIPEndPoint RemoteSIPEndPoint { get; protected set; }
+
+ ///
+ /// The local SIP socket the request/response was received on.
+ ///
public SIPEndPoint LocalSIPEndPoint { get; protected set; }
- ///
+ ///
/// When the SIP transport layer has multiple channels it will use this ID hint to choose amongst them when
- /// sending this request/response.
- ///
- public string SendFromHintChannelID;
-
- ///
+ /// sending this request/response.
+ ///
+ public string SendFromHintChannelID;
+
+ ///
/// For connection oriented SIP transport channels this ID provides a hint about the specific connection to use
- /// when sending this request/response.
- ///
+ /// when sending this request/response.
+ ///
public string SendFromHintConnectionID;
protected byte[] GetBytes(string firstLine)
{
- string headers = firstLine + this.Header.ToString() + m_CRLF;
+ var builder = new ValueStringBuilder();
- if (_body != null && _body.Length > 0)
+ try
{
- var headerBytes = SIPEncoding.GetBytes(headers);
- byte[] buffer = new byte[headerBytes.Length + _body.Length];
- Buffer.BlockCopy(headerBytes, 0, buffer, 0, headerBytes.Length);
- Buffer.BlockCopy(_body, 0, buffer, headerBytes.Length, _body.Length);
+ builder.Append(firstLine);
+ this.Header.ToString(ref builder);
+ builder.Append(m_CRLF);
+
+ if (_body is { Length: > 0 })
+ {
+ var headerByteCount = SIPEncoding.GetByteCount(builder.AsSpan());
+ var buffer = new byte[headerByteCount + _body.Length];
+ SIPEncoding.GetBytes(builder.AsSpan(), buffer.AsSpan());
+ _body.CopyTo(buffer.AsSpan(headerByteCount));
return buffer;
}
else
{
- return SIPEncoding.GetBytes(headers);
+ var headerByteCount = SIPEncoding.GetByteCount(builder.AsSpan());
+ var buffer = new byte[headerByteCount];
+ SIPEncoding.GetBytes(builder.AsSpan(), buffer.AsSpan());
+ return buffer;
+ }
+ }
+ finally
+ {
+ builder.Dispose();
}
}
- }
-}
+ }
+}
diff --git a/src/core/SIP/SIPParameters.cs b/src/core/SIP/SIPParameters.cs
index ab5f45aa93..f1951a57df 100644
--- a/src/core/SIP/SIPParameters.cs
+++ b/src/core/SIP/SIPParameters.cs
@@ -289,24 +289,51 @@ public string[] GetKeys()
public override string ToString()
{
- string paramStr = null;
+ var builder = new ValueStringBuilder();
+ try
+ {
+ ToString(ref builder, TagDelimiter);
+
+ return builder.ToString();
+ }
+ finally
+ {
+ builder.Dispose();
+ }
+ }
+
+ internal void ToString(ref ValueStringBuilder builder, char firstDelimiter)
+ {
if (m_dictionary != null)
{
- foreach (KeyValuePair param in m_dictionary)
+ bool isFirst = true;
+
+ foreach (var (key, value) in m_dictionary)
{
- if (param.Value != null && param.Value.Trim().Length > 0)
+ if (isFirst)
{
- paramStr += TagDelimiter + param.Key + TAG_NAME_VALUE_SEPERATOR + SIPEscape.SIPURIParameterEscape(param.Value);
+ builder.Append(firstDelimiter);
+ isFirst = false;
}
else
{
- paramStr += TagDelimiter + param.Key;
+ builder.Append(TagDelimiter);
+ }
+
+ builder.Append(key);
+
+ if (value is not null)
+ {
+ var valueSpan = value.AsSpan();
+ if (!valueSpan.Trim().IsEmpty)
+ {
+ builder.Append(TAG_NAME_VALUE_SEPERATOR);
+ SIPEscape.SIPURIParameterEscape(ref builder, valueSpan);
+ }
}
}
}
-
- return paramStr;
}
public override int GetHashCode()
diff --git a/src/core/SIP/SIPURI.cs b/src/core/SIP/SIPURI.cs
index e7cff1ae70..d44c960044 100644
--- a/src/core/SIP/SIPURI.cs
+++ b/src/core/SIP/SIPURI.cs
@@ -485,35 +485,57 @@ public static bool TryParse(string uriStr, out SIPURI uri)
public override string ToString()
{
+ var builder = new ValueStringBuilder();
+
try
{
- string uriStr = Scheme.ToString() + SCHEME_ADDR_SEPARATOR;
+ ToString(ref builder);
- uriStr = (User != null) ? uriStr + User + USER_HOST_SEPARATOR + Host : uriStr + Host;
+ return builder.ToString();
+ }
+ catch (Exception excp)
+ {
+ logger.LogError(excp, "Exception SIPURI ToString. {ErrorMessage}", excp.Message);
+ throw;
+ }
+ finally
+ {
+ builder.Dispose();
+ }
+ }
- if (Parameters != null && Parameters.Count > 0)
+ internal void ToString(ref ValueStringBuilder builder)
{
- uriStr += Parameters.ToString();
+ builder.Append(Scheme.ToString());
+ builder.Append(SCHEME_ADDR_SEPARATOR);
+
+ if (User != null)
+ {
+ builder.Append(User);
+ builder.Append(USER_HOST_SEPARATOR);
}
- // If the URI's protocol is not implied already set the transport parameter.
- if (Scheme != SIPSchemesEnum.sips && Protocol != SIPProtocolsEnum.udp && !Parameters.Has(m_uriParamTransportKey))
+ builder.Append(Host);
+
+ if (Parameters != null && Parameters.Count > 0)
{
- uriStr += PARAM_TAG_DELIMITER + m_uriParamTransportKey + TAG_NAME_VALUE_SEPERATOR + Protocol.ToString();
+ builder.Append(Parameters.ToString());
}
- if (Headers != null && Headers.Count > 0)
- {
- string headerStr = Headers.ToString();
- uriStr += HEADER_START_DELIMITER + headerStr.Substring(1);
+ // If the URI's protocol is not implied already, set the transport parameter.
+ if (Scheme != SIPSchemesEnum.sips &&
+ Protocol != SIPProtocolsEnum.udp &&
+ !Parameters.Has(m_uriParamTransportKey))
+ {
+ builder.Append(PARAM_TAG_DELIMITER);
+ builder.Append(m_uriParamTransportKey);
+ builder.Append(TAG_NAME_VALUE_SEPERATOR);
+ builder.Append(Protocol.ToString());
}
- return uriStr;
- }
- catch (Exception excp)
+ if (Headers != null && Headers.Count > 0)
{
- logger.LogError(excp, "Exception SIPURI ToString. {ErrorMessage}", excp.Message);
- throw;
+ Headers.ToString(ref builder, HEADER_START_DELIMITER);
}
}
diff --git a/src/core/SIP/SIPUserField.cs b/src/core/SIP/SIPUserField.cs
index 6901c3f1ff..5d1146b0d6 100644
--- a/src/core/SIP/SIPUserField.cs
+++ b/src/core/SIP/SIPUserField.cs
@@ -142,33 +142,38 @@ public static SIPUserField ParseSIPUserField(string userFieldStr)
public override string ToString()
{
+ var builder = new ValueStringBuilder();
+
try
{
- string userFieldStr = null;
+ ToString(ref builder);
- if (Name != null)
+ return builder.ToString();
+ }
+ catch (Exception excp)
{
- /*if(Regex.Match(Name, @"\s").Success)
+ logger.LogError(excp, "Exception SIPUserField ToString. {Message}", excp.Message);
+ throw;
+ }
+ finally
{
- userFieldStr = "\"" + Name + "\" ";
+ builder.Dispose();
}
- else
- {
- userFieldStr = Name + " ";
- }*/
-
- userFieldStr = "\"" + Name + "\" ";
}
- userFieldStr += "<" + URI.ToString() + ">" + Parameters.ToString();
-
- return userFieldStr;
- }
- catch (Exception excp)
+ internal void ToString(ref ValueStringBuilder builder)
+ {
+ if (Name != null)
{
- logger.LogError(excp, "Exception SIPUserField ToString. {Message}", excp.Message);
- throw;
+ builder.Append("\"");
+ builder.Append(Name);
+ builder.Append("\" ");
}
+
+ builder.Append('<');
+ builder.Append(URI.ToString());
+ builder.Append('>');
+ builder.Append(Parameters.ToString());
}
public string ToParameterlessString()
diff --git a/src/net/SDP/SDPAudioVideoMediaFormat.cs b/src/net/SDP/SDPAudioVideoMediaFormat.cs
index 34d3ee79a7..6d3378281d 100755
--- a/src/net/SDP/SDPAudioVideoMediaFormat.cs
+++ b/src/net/SDP/SDPAudioVideoMediaFormat.cs
@@ -38,7 +38,7 @@ public struct SDPAudioVideoMediaFormat
public const int DYNAMIC_ID_MAX = 127;
public const int DEFAULT_AUDIO_CHANNEL_COUNT = 1;
- public static SDPAudioVideoMediaFormat Empty = new SDPAudioVideoMediaFormat() { _isEmpty = true };
+ public static SDPAudioVideoMediaFormat Empty = new SDPAudioVideoMediaFormat();
///
/// Indicates whether the format is for audio or video.
@@ -116,7 +116,7 @@ public IEnumerable SupportedRtcpFeedbackMessages
///
//public string Name { get; set; }
- private bool _isEmpty;
+ private bool _isNotEmpty;
///
/// Creates a new SDP media format for a well known media type. Well known type are those that use
@@ -130,7 +130,7 @@ public SDPAudioVideoMediaFormat(SDPWellKnownMediaFormatsEnum knownFormat)
ID = (int)knownFormat;
Rtpmap = null;
Fmtp = null;
- _isEmpty = false;
+ _isNotEmpty = true;
if (Kind == SDPMediaTypesEnum.audio)
{
@@ -144,28 +144,20 @@ public SDPAudioVideoMediaFormat(SDPWellKnownMediaFormatsEnum knownFormat)
}
}
- public bool IsH264
- {
- get
- {
- return (Rtpmap ?? "").ToUpperInvariant().Trim().StartsWith("H264");
- }
- }
+ public bool IsH264 => RtmapIs("H264");
- public bool IsMJPEG
- {
- get
- {
- return (Rtpmap ?? "").ToUpperInvariant().Trim().StartsWith("JPEG");
- }
- }
+ public bool IsMJPEG => RtmapIs("JPEG");
+
+ public bool isH265 => RtmapIs("H265");
- public bool isH265
+ private bool RtmapIs(string codec)
{
- get
+ if (Rtpmap is null)
{
- return (Rtpmap ?? "").ToUpperInvariant().Trim().StartsWith("H265");
- }
+ return false;
+ }
+
+ return Rtpmap.AsSpan().TrimStart().StartsWith(codec.AsSpan(), StringComparison.OrdinalIgnoreCase);
}
public bool CheckCompatible()
@@ -224,7 +216,7 @@ public SDPAudioVideoMediaFormat(SDPMediaTypesEnum kind, int id, string rtpmap, s
ID = id;
Rtpmap = rtpmap;
Fmtp = fmtp;
- _isEmpty = false;
+ _isNotEmpty = true;
}
///
@@ -246,7 +238,7 @@ public SDPAudioVideoMediaFormat(SDPMediaTypesEnum kind, int id, string name, int
ID = id;
Rtpmap = null;
Fmtp = fmtp;
- _isEmpty = false;
+ _isNotEmpty = true;
Rtpmap = SetRtpmap(name, clockRate, channels);
}
@@ -263,7 +255,7 @@ public SDPAudioVideoMediaFormat(AudioFormat audioFormat)
ID = audioFormat.FormatID;
Rtpmap = null;
Fmtp = audioFormat.Parameters;
- _isEmpty = false;
+ _isNotEmpty = true;
Rtpmap = SetRtpmap(audioFormat.FormatName, audioFormat.RtpClockRate, audioFormat.ChannelCount);
}
@@ -280,7 +272,7 @@ public SDPAudioVideoMediaFormat(VideoFormat videoFormat)
ID = videoFormat.FormatID;
Rtpmap = null;
Fmtp = videoFormat.Parameters;
- _isEmpty = false;
+ _isNotEmpty = true;
Rtpmap = SetRtpmap(videoFormat.FormatName, videoFormat.ClockRate);
}
@@ -291,7 +283,7 @@ public SDPAudioVideoMediaFormat(TextFormat textFormat)
ID = textFormat.FormatID;
Rtpmap = null;
Fmtp = textFormat.Parameters;
- _isEmpty = false;
+ _isNotEmpty = true;
Rtpmap = SetRtpmap(textFormat.FormatName, textFormat.ClockRate);
}
@@ -300,7 +292,7 @@ private string SetRtpmap(string name, int clockRate, int channels = DEFAULT_AUDI
? $"{name}/{clockRate}"
: (channels == DEFAULT_AUDIO_CHANNEL_COUNT) ? $"{name}/{clockRate}" : $"{name}/{clockRate}/{channels}";
- public bool IsEmpty() => _isEmpty;
+ public bool IsEmpty() => !_isNotEmpty;
public int ClockRate()
{
if (Kind == SDPMediaTypesEnum.video)
@@ -599,16 +591,20 @@ public static SDPAudioVideoMediaFormat GetCommonRtpEventFormat(ListIf found the matching format or the empty format if not.
public static SDPAudioVideoMediaFormat GetFormatForName(List formats, string formatName)
{
- if (formats == null || formats.Count == 0)
+ if (formats != null && formats.Count != 0 && formatName != null)
{
+ foreach (var format in formats)
+ {
+ if (string.Equals(format.Name(), formatName, StringComparison.OrdinalIgnoreCase))
+ {
+ return format;
+ }
+ }
+
return Empty;
}
- else
- {
- return formats.Any(x => x.Name()?.ToLower() == formatName?.ToLower()) ?
- formats.First(x => x.Name()?.ToLower() == formatName?.ToLower()) :
- Empty;
- }
+
+ return Empty;
}
}
}
diff --git a/src/net/SDP/SDPConnectionInformation.cs b/src/net/SDP/SDPConnectionInformation.cs
index 537dd3950e..dd3d57540e 100644
--- a/src/net/SDP/SDPConnectionInformation.cs
+++ b/src/net/SDP/SDPConnectionInformation.cs
@@ -15,6 +15,7 @@
using System.Net;
using System.Net.Sockets;
+using SIPSorcery.Sys;
namespace SIPSorcery.Net
{
@@ -63,5 +64,17 @@ public override string ToString()
{
return "c=" + ConnectionNetworkType + " " + ConnectionAddressType + " " + ConnectionAddress + m_CRLF;
}
+
+ internal void ToString(ref ValueStringBuilder builder)
+ {
+ builder.Append("c=");
+ builder.Append(ConnectionNetworkType);
+ builder.Append(' ');
+ builder.Append(ConnectionAddressType);
+ builder.Append(' ');
+ builder.Append(ConnectionAddress);
+ builder.Append(m_CRLF);
+
+ }
}
}
diff --git a/src/net/SDP/SDPMediaAnnouncement.cs b/src/net/SDP/SDPMediaAnnouncement.cs
index c31bbde8bc..7a8f31ac56 100755
--- a/src/net/SDP/SDPMediaAnnouncement.cs
+++ b/src/net/SDP/SDPMediaAnnouncement.cs
@@ -32,6 +32,7 @@
using System.Text;
using System.Text.RegularExpressions;
using Microsoft.Extensions.Logging;
+using SIPSorcery.Sys;
using SIPSorceryMedia.Abstractions;
namespace SIPSorcery.Net
@@ -282,87 +283,189 @@ public void ParseMediaFormats(string formatList)
public override string ToString()
{
- string announcement = "m=" + Media + " " + Port + " " + Transport + " " + GetFormatListToString() + m_CRLF;
+ var builder = new ValueStringBuilder();
- announcement += !string.IsNullOrWhiteSpace(MediaDescription) ? "i=" + MediaDescription + m_CRLF : null;
+ try
+ {
+ ToString(ref builder);
- announcement += (Connection == null) ? null : Connection.ToString();
+ return builder.ToString();
+ }
+ finally
+ {
+ builder.Dispose();
+ }
+ }
+
+ internal void ToString(ref ValueStringBuilder builder)
+ {
+ builder.Append("m=");
+ builder.Append(Media.ToString());
+ builder.Append(' ');
+ builder.Append(Port);
+ builder.Append(' ');
+ builder.Append(Transport);
+ builder.Append(' ');
+ WriteFormatListString(ref builder);
+ builder.Append(m_CRLF);
+
+ if (!string.IsNullOrWhiteSpace(MediaDescription))
+ {
+ builder.Append("i=");
+ builder.Append(MediaDescription);
+ builder.Append(m_CRLF);
+ }
+
+ if (Connection != null)
+ {
+ builder.Append(Connection.ToString());
+ }
if (TIASBandwidth > 0)
{
- announcement += TIAS_BANDWIDTH_ATTRIBUE_PREFIX + TIASBandwidth + m_CRLF;
+ builder.Append(TIAS_BANDWIDTH_ATTRIBUE_PREFIX);
+ builder.Append(TIASBandwidth);
+ builder.Append(m_CRLF);
}
foreach (string bandwidthAttribute in BandwidthAttributes)
{
- announcement += "b=" + bandwidthAttribute + m_CRLF;
+ builder.Append("b=");
+ builder.Append(bandwidthAttribute);
+ builder.Append(m_CRLF);
+ }
+
+ if (!string.IsNullOrWhiteSpace(IceUfrag))
+ {
+ builder.Append("a=");
+ builder.Append(SDP.ICE_UFRAG_ATTRIBUTE_PREFIX);
+ builder.Append(':');
+ builder.Append(IceUfrag);
+ builder.Append(m_CRLF);
}
- announcement += !string.IsNullOrWhiteSpace(IceUfrag) ? "a=" + SDP.ICE_UFRAG_ATTRIBUTE_PREFIX + ":" + IceUfrag + m_CRLF : null;
- announcement += !string.IsNullOrWhiteSpace(IcePwd) ? "a=" + SDP.ICE_PWD_ATTRIBUTE_PREFIX + ":" + IcePwd + m_CRLF : null;
- announcement += !string.IsNullOrWhiteSpace(DtlsFingerprint) ? "a=" + SDP.DTLS_FINGERPRINT_ATTRIBUTE_PREFIX + ":" + DtlsFingerprint + m_CRLF : null;
- announcement += IceRole != null ? $"a={SDP.ICE_SETUP_ATTRIBUTE_PREFIX}:{IceRole}{m_CRLF}" : null;
+ if (!string.IsNullOrWhiteSpace(IcePwd))
+ {
+ builder.Append("a=");
+ builder.Append(SDP.ICE_PWD_ATTRIBUTE_PREFIX);
+ builder.Append(':');
+ builder.Append(IcePwd);
+ builder.Append(m_CRLF);
+ }
+
+ if (!string.IsNullOrWhiteSpace(DtlsFingerprint))
+ {
+ builder.Append("a=");
+ builder.Append(SDP.DTLS_FINGERPRINT_ATTRIBUTE_PREFIX);
+ builder.Append(':');
+ builder.Append(DtlsFingerprint);
+ builder.Append(m_CRLF);
+ }
+
+ if (IceRole != null)
+ {
+ builder.Append("a=");
+ builder.Append(SDP.ICE_SETUP_ATTRIBUTE_PREFIX);
+ builder.Append(':');
+ builder.Append(IceRole.ToString());
+ builder.Append(m_CRLF);
+ }
- if (IceCandidates?.Count() > 0)
+ if (IceCandidates?.Any() == true)
{
foreach (var candidate in IceCandidates)
{
- announcement += $"a={SDP.ICE_CANDIDATE_ATTRIBUTE_PREFIX}:{candidate}{m_CRLF}";
+ builder.Append("a=");
+ builder.Append(SDP.ICE_CANDIDATE_ATTRIBUTE_PREFIX);
+ builder.Append(':');
+ builder.Append(candidate);
+ builder.Append(m_CRLF);
}
}
if (IceOptions != null)
{
- announcement += $"a={SDP.ICE_OPTIONS}:" + IceOptions + m_CRLF;
+ builder.Append("a=");
+ builder.Append(SDP.ICE_OPTIONS);
+ builder.Append(':');
+ builder.Append(IceOptions);
+ builder.Append(m_CRLF);
}
if (IceEndOfCandidates)
{
- announcement += $"a={SDP.END_ICE_CANDIDATES_ATTRIBUTE}" + m_CRLF;
+ builder.Append("a=");
+ builder.Append(SDP.END_ICE_CANDIDATES_ATTRIBUTE);
+ builder.Append(m_CRLF);
}
- announcement += !string.IsNullOrWhiteSpace(MediaID) ? "a=" + SDP.MEDIA_ID_ATTRIBUTE_PREFIX + ":" + MediaID + m_CRLF : null;
+ if (!string.IsNullOrWhiteSpace(MediaID))
+ {
+ builder.Append("a=");
+ builder.Append(SDP.MEDIA_ID_ATTRIBUTE_PREFIX);
+ builder.Append(':');
+ builder.Append(MediaID);
+ builder.Append(m_CRLF);
+ }
- announcement += GetFormatListAttributesToString();
+ builder.Append(GetFormatListAttributesToString());
+
+ foreach (var ext in HeaderExtensions)
+ {
+ builder.Append(MEDIA_EXTENSION_MAP_ATTRIBUE_PREFIX);
+ builder.Append(ext.Value.Id);
+ builder.Append(' ');
+ builder.Append(ext.Value.Uri);
+ builder.Append(m_CRLF);
+ }
- announcement += string.Join("", HeaderExtensions.Select(x => $"{MEDIA_EXTENSION_MAP_ATTRIBUE_PREFIX}{x.Value.Id} {x.Value.Uri}{m_CRLF}"));
foreach (string extra in ExtraMediaAttributes)
{
- announcement += string.IsNullOrWhiteSpace(extra) ? null : extra + m_CRLF;
+ if (!string.IsNullOrWhiteSpace(extra))
+ {
+ builder.Append(extra);
+ builder.Append(m_CRLF);
+ }
}
foreach (SDPSecurityDescription desc in this.SecurityDescriptions)
{
- announcement += desc.ToString() + m_CRLF;
+ builder.Append(desc.ToString());
+ builder.Append(m_CRLF);
}
if (MediaStreamStatus != null)
{
- announcement += MediaStreamStatusType.GetAttributeForMediaStreamStatus(MediaStreamStatus.Value) + m_CRLF;
+ builder.Append(MediaStreamStatusType.GetAttributeForMediaStreamStatus(MediaStreamStatus.Value));
+ builder.Append(m_CRLF);
}
if (SsrcGroupID != null && SsrcAttributes.Count > 0)
{
- announcement += MEDIA_FORMAT_SSRC_GROUP_ATTRIBUE_PREFIX + SsrcGroupID;
+ builder.Append(MEDIA_FORMAT_SSRC_GROUP_ATTRIBUE_PREFIX);
+ builder.Append(SsrcGroupID);
foreach (var ssrcAttr in SsrcAttributes)
{
- announcement += $" {ssrcAttr.SSRC}";
+ builder.Append(' ');
+ builder.Append(ssrcAttr.SSRC);
}
- announcement += m_CRLF;
+ builder.Append(m_CRLF);
}
if (SsrcAttributes.Count > 0)
{
foreach (var ssrcAttr in SsrcAttributes)
{
+ builder.Append(MEDIA_FORMAT_SSRC_ATTRIBUE_PREFIX);
+ builder.Append(ssrcAttr.SSRC);
if (!string.IsNullOrWhiteSpace(ssrcAttr.Cname))
{
- announcement += $"{MEDIA_FORMAT_SSRC_ATTRIBUE_PREFIX}{ssrcAttr.SSRC} {SDPSsrcAttribute.MEDIA_CNAME_ATTRIBUE_PREFIX}:{ssrcAttr.Cname}" + m_CRLF;
- }
- else
- {
- announcement += $"{MEDIA_FORMAT_SSRC_ATTRIBUE_PREFIX}{ssrcAttr.SSRC}" + m_CRLF;
+ builder.Append(' ');
+ builder.Append(SDPSsrcAttribute.MEDIA_CNAME_ATTRIBUE_PREFIX);
+ builder.Append(':');
+ builder.Append(ssrcAttr.Cname);
}
+ builder.Append(m_CRLF);
}
}
@@ -371,50 +474,87 @@ public override string ToString()
// an application sets it then it's likely to be for a specific reason.
if (SctpMap != null)
{
- announcement += $"{MEDIA_FORMAT_SCTP_MAP_ATTRIBUE_PREFIX}{SctpMap}" + m_CRLF;
+ builder.Append(MEDIA_FORMAT_SCTP_MAP_ATTRIBUE_PREFIX);
+ builder.Append(SctpMap);
+ builder.Append(m_CRLF);
}
else
{
if (SctpPort != null)
{
- announcement += $"{MEDIA_FORMAT_SCTP_PORT_ATTRIBUE_PREFIX}{SctpPort}" + m_CRLF;
+ builder.Append(MEDIA_FORMAT_SCTP_PORT_ATTRIBUE_PREFIX);
+ builder.Append(SctpPort);
+ builder.Append(m_CRLF);
}
if (MaxMessageSize != 0)
{
- announcement += $"{MEDIA_FORMAT_MAX_MESSAGE_SIZE_ATTRIBUE_PREFIX}{MaxMessageSize}" + m_CRLF;
+ builder.Append(MEDIA_FORMAT_MAX_MESSAGE_SIZE_ATTRIBUE_PREFIX);
+ builder.Append(MaxMessageSize);
+ builder.Append(m_CRLF);
+ }
}
}
- return announcement;
+ public string GetFormatListToString()
+ {
+ if (Media == SDPMediaTypesEnum.message)
+ {
+ return "*";
}
- public string GetFormatListToString()
+ var builder = new ValueStringBuilder();
+
+ try
{
+ WriteFormatListString(ref builder);
+
if (Media == SDPMediaTypesEnum.application)
{
- StringBuilder sb = new StringBuilder();
- foreach (var appFormat in ApplicationMediaFormats)
+ return builder.ToString();
+ }
+ else
+ {
+ return builder.Length > 0 ? builder.ToString() : null;
+ }
+ }
+ finally
{
- sb.Append(appFormat.Key);
- sb.Append(" ");
+ builder.Dispose();
+ }
}
- return sb.ToString().Trim();
+ internal void WriteFormatListString(ref ValueStringBuilder builder)
+ {
+ if (Media == SDPMediaTypesEnum.message)
+ {
+ builder.Append('*');
}
- else if (Media == SDPMediaTypesEnum.message)
+ else if (Media == SDPMediaTypesEnum.application)
{
- return "*";
+ var first = true;
+ foreach (var appFormat in ApplicationMediaFormats)
+ {
+ if (!first)
+ {
+ builder.Append(' ');
+ }
+ builder.Append(appFormat.Key);
+ first = false;
+ }
}
else
{
- string mediaFormatList = null;
+ var first = true;
foreach (var mediaFormat in MediaFormats)
{
- mediaFormatList += mediaFormat.Key + " ";
+ if (!first)
+ {
+ builder.Append(' ');
+ }
+ builder.Append(mediaFormat.Key);
+ first = false;
}
-
- return (mediaFormatList != null) ? mediaFormatList.Trim() : null;
}
}
diff --git a/src/net/SDP/SDPSecurityDescription.cs b/src/net/SDP/SDPSecurityDescription.cs
index a8025e57d9..e7f3434ef1 100644
--- a/src/net/SDP/SDPSecurityDescription.cs
+++ b/src/net/SDP/SDPSecurityDescription.cs
@@ -388,7 +388,7 @@ private static void parseKeySaltBase64(CryptoSuites cryptoSuite, string base64Ke
private static void checkValidKeyInfoCharacters(string keyParameter, string keyInfo)
{
- foreach (char c in keyInfo.ToCharArray())
+ foreach (char c in keyInfo.AsSpan())
{
if (c < 0x21 || c > 0x7e)
{
diff --git a/src/net/STUN/STUNAttributes/STUNAddressAttribute.cs b/src/net/STUN/STUNAttributes/STUNAddressAttribute.cs
index ff00d9ed72..0ccf839dde 100755
--- a/src/net/STUN/STUNAttributes/STUNAddressAttribute.cs
+++ b/src/net/STUN/STUNAttributes/STUNAddressAttribute.cs
@@ -122,11 +122,12 @@ public override int ToByteBuffer(byte[] buffer, int startIndex)
return STUNAttribute.STUNATTRIBUTE_HEADER_LENGTH + ADDRESS_ATTRIBUTE_IPV4_LENGTH;
}
- public override string ToString()
+ private protected override void ValueToString(ref ValueStringBuilder sb)
{
- string attrDescrStr = "STUN Attribute: " + base.AttributeType + ", address=" + Address.ToString() + ", port=" + Port + ".";
-
- return attrDescrStr;
+ sb.Append("Address=");
+ sb.Append(Address.ToString());
+ sb.Append(", Port=");
+ sb.Append(Port);
}
}
}
diff --git a/src/net/STUN/STUNAttributes/STUNAddressAttributeBase.cs b/src/net/STUN/STUNAttributes/STUNAddressAttributeBase.cs
index 6fea343082..1f95a3f5fb 100644
--- a/src/net/STUN/STUNAttributes/STUNAddressAttributeBase.cs
+++ b/src/net/STUN/STUNAttributes/STUNAddressAttributeBase.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Net;
using System.Text;
+using SIPSorcery.Sys;
namespace SIPSorcery.Net
{
@@ -37,5 +38,15 @@ public STUNAddressAttributeBase(STUNAttributeTypesEnum attributeType, byte[] val
: base(attributeType, value)
{
}
+
+ private protected override void ValueToString(ref ValueStringBuilder sb)
+ {
+ sb.Append("Address=");
+ sb.Append(Address.ToString());
+ sb.Append(", Port=");
+ sb.Append(Port);
+ sb.Append(", Family=");
+ sb.Append(Family switch { 1 => "IPV4", 2 => "IPV6", _ => "Invalid", });
+ }
}
}
diff --git a/src/net/STUN/STUNAttributes/STUNAttribute.cs b/src/net/STUN/STUNAttributes/STUNAttribute.cs
index 1d84d78151..e1ebac04d9 100644
--- a/src/net/STUN/STUNAttributes/STUNAttribute.cs
+++ b/src/net/STUN/STUNAttributes/STUNAttribute.cs
@@ -272,11 +272,36 @@ public virtual int ToByteBuffer(byte[] buffer, int startIndex)
return STUNAttribute.STUNATTRIBUTE_HEADER_LENGTH + PaddedLength;
}
- public new virtual string ToString()
+ public override string ToString()
{
- string attrDescrString = "STUN Attribute: " + AttributeType.ToString() + ", length=" + PaddedLength + ".";
+ var sb = new ValueStringBuilder(stackalloc char[256]);
- return attrDescrString;
+ try
+ {
+ ToString(ref sb);
+
+ return sb.ToString();
+ }
+ finally
+ {
+ sb.Dispose();
+ }
+ }
+
+ internal void ToString(ref ValueStringBuilder sb)
+ {
+ sb.Append("STUN Attribute: ");
+ sb.Append(AttributeType.ToString());
+ sb.Append(", ");
+ ValueToString(ref sb);
+ sb.Append('.');
+ }
+
+ private protected virtual void ValueToString(ref ValueStringBuilder sb)
+ {
+ sb.Append(Value);
+ sb.Append(", length=");
+ sb.Append(PaddedLength);
}
}
}
diff --git a/src/net/STUN/STUNAttributes/STUNChangeRequestAttribute.cs b/src/net/STUN/STUNAttributes/STUNChangeRequestAttribute.cs
index fff4156578..dd7378943f 100644
--- a/src/net/STUN/STUNAttributes/STUNChangeRequestAttribute.cs
+++ b/src/net/STUN/STUNAttributes/STUNChangeRequestAttribute.cs
@@ -14,6 +14,7 @@
//-----------------------------------------------------------------------------
using System;
+using SIPSorcery.Sys;
namespace SIPSorcery.Net
{
@@ -51,11 +52,14 @@ public STUNChangeRequestAttribute(byte[] attributeValue)
}
}
- public override string ToString()
+ private protected override void ValueToString(ref ValueStringBuilder sb)
{
- string attrDescrStr = "STUN Attribute: " + STUNAttributeTypesEnum.ChangeRequest.ToString() + ", key byte=" + m_changeRequestByte.ToString("X") + ", change address=" + ChangeAddress + ", change port=" + ChangePort + ".";
-
- return attrDescrStr;
+ sb.Append("key byte=");
+ sb.Append(m_changeRequestByte, "X");
+ sb.Append(", change address=");
+ sb.Append(ChangeAddress);
+ sb.Append(", change port=");
+ sb.Append(ChangePort);
}
}
}
diff --git a/src/net/STUN/STUNAttributes/STUNConnectionIdAttribute.cs b/src/net/STUN/STUNAttributes/STUNConnectionIdAttribute.cs
index 33114ab83f..eced72b610 100644
--- a/src/net/STUN/STUNAttributes/STUNConnectionIdAttribute.cs
+++ b/src/net/STUN/STUNAttributes/STUNConnectionIdAttribute.cs
@@ -45,11 +45,10 @@ public STUNConnectionIdAttribute(uint connectionId)
ConnectionId = connectionId;
}
- public override string ToString()
+ private protected override void ValueToString(ref ValueStringBuilder sb)
{
- string attrDescrStr = "STUN CONNECTION_ID Attribute: value=" + ConnectionId + ".";
-
- return attrDescrStr;
+ sb.Append("connection ID=");
+ sb.Append(ConnectionId);
}
}
}
diff --git a/src/net/STUN/STUNAttributes/STUNErrorCodeAttribute.cs b/src/net/STUN/STUNAttributes/STUNErrorCodeAttribute.cs
index b97bedb815..ee893f563d 100755
--- a/src/net/STUN/STUNAttributes/STUNErrorCodeAttribute.cs
+++ b/src/net/STUN/STUNAttributes/STUNErrorCodeAttribute.cs
@@ -15,6 +15,7 @@
using System;
using System.Text;
+using SIPSorcery.Sys;
namespace SIPSorcery.Net
{
@@ -61,11 +62,12 @@ public override int ToByteBuffer(byte[] buffer, int startIndex)
return STUNAttribute.STUNATTRIBUTE_HEADER_LENGTH + 4 + reasonPhraseBytes.Length;
}
- public override string ToString()
+ private protected override void ValueToString(ref ValueStringBuilder sb)
{
- string attrDescrStr = "STUN ERROR_CODE_ADDRESS Attribute: error code=" + ErrorCode + ", reason phrase=" + ReasonPhrase + ".";
-
- return attrDescrStr;
+ sb.Append("error code=");
+ sb.Append(ErrorCode);
+ sb.Append(", reason phrase=");
+ sb.Append(ReasonPhrase);
}
}
}
diff --git a/src/net/STUN/STUNAttributes/STUNXORAddressAttribute.cs b/src/net/STUN/STUNAttributes/STUNXORAddressAttribute.cs
index c6cc025c0e..21c0f0eed2 100644
--- a/src/net/STUN/STUNAttributes/STUNXORAddressAttribute.cs
+++ b/src/net/STUN/STUNAttributes/STUNXORAddressAttribute.cs
@@ -173,5 +173,7 @@ public IPEndPoint GetIPEndPoint()
return null;
}
}
+
+ private protected override void ValueToString(ref ValueStringBuilder sb) => base.ValueToString(ref sb);
}
}
diff --git a/src/net/STUN/STUNDns.cs b/src/net/STUN/STUNDns.cs
index 7e316ea601..4598912539 100644
--- a/src/net/STUN/STUNDns.cs
+++ b/src/net/STUN/STUNDns.cs
@@ -185,7 +185,7 @@ private static async Task Resolve(STUNUri uri, QueryType queryType)
{
ServiceHostEntry srvResult = null;
// No explicit port so use a SRV -> (A | AAAA -> A) record lookup.
- var result = await _lookupClient.ResolveServiceAsync(uri.Host, uri.Scheme.ToString(), uri.Protocol.ToString().ToLower()).ConfigureAwait(false);
+ var result = await _lookupClient.ResolveServiceAsync(uri.Host, uri.Scheme.ToString(), uri.Protocol.ToLowerString()).ConfigureAwait(false);
if (result == null || result.Count() == 0)
{
//logger.LogDebug("STUNDns SRV lookup returned no results for {uri}.", uri);
diff --git a/src/sys/CRC32.cs b/src/sys/CRC32.cs
index 52df73bd51..ff89a9008d 100644
--- a/src/sys/CRC32.cs
+++ b/src/sys/CRC32.cs
@@ -1,6 +1,7 @@
// from http://damieng.com/blog/2006/08/08/Calculating_CRC32_in_C_and_NET
using System;
+using System.Buffers.Binary;
using System.Security.Cryptography;
namespace SIPSorcery.Sys
@@ -36,7 +37,18 @@ public override void Initialize()
protected override void HashCore(byte[] buffer, int start, int length)
{
- hash = CalculateHash(table, hash, buffer, start, length);
+ hash = CalculateHash(table, hash, buffer.AsSpan(start, length));
+ }
+
+ protected
+#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER || NET5_0_OR_GREATER
+ override
+#else
+ virtual
+#endif
+ void HashCore(ReadOnlySpan buffer)
+ {
+ hash = CalculateHash(table, hash, buffer);
}
protected override byte[] HashFinal()
@@ -53,17 +65,32 @@ public override int HashSize
public static UInt32 Compute(byte[] buffer)
{
- return ~CalculateHash(InitializeTable(DefaultPolynomial), DefaultSeed, buffer, 0, buffer.Length);
+ return Compute(buffer.AsSpan(0, buffer.Length));
}
public static UInt32 Compute(UInt32 seed, byte[] buffer)
{
- return ~CalculateHash(InitializeTable(DefaultPolynomial), seed, buffer, 0, buffer.Length);
+ return Compute(seed, buffer.AsSpan());
}
public static UInt32 Compute(UInt32 polynomial, UInt32 seed, byte[] buffer)
{
- return ~CalculateHash(InitializeTable(polynomial), seed, buffer, 0, buffer.Length);
+ return Compute(polynomial, seed, buffer.AsSpan());
+ }
+
+ public static uint Compute(ReadOnlySpan buffer)
+ {
+ return ~CalculateHash(InitializeTable(DefaultPolynomial), DefaultSeed, buffer);
+ }
+
+ public static uint Compute(uint seed, ReadOnlySpan buffer)
+ {
+ return ~CalculateHash(InitializeTable(DefaultPolynomial), seed, buffer);
+ }
+
+ public static uint Compute(uint polynomial, uint seed, ReadOnlySpan buffer)
+ {
+ return ~CalculateHash(InitializeTable(polynomial), seed, buffer);
}
private static UInt32[] InitializeTable(UInt32 polynomial)
@@ -99,27 +126,31 @@ private static UInt32[] InitializeTable(UInt32 polynomial)
return createTable;
}
- private static UInt32 CalculateHash(UInt32[] table, UInt32 seed, byte[] buffer, int start, int size)
+ private static UInt32 CalculateHash(ReadOnlySpan table, uint seed, ReadOnlySpan buffer)
{
- UInt32 crc = seed;
- for (int i = start; i < size; i++)
+ /*
+ if (Sse42.IsSupported)
+ {
+ uint crc = Sse42.Crc32(seed, value);
+ }
+ */
+
+ var crc = seed;
+ for (int i = 0; i < buffer.Length; i++)
{
unchecked
{
- crc = (crc >> 8) ^ table[buffer[i] ^ crc & 0xff];
+ crc = (crc >> 8) ^ table[buffer[i] ^ (byte)(crc & 0xff)];
}
}
return crc;
}
- private byte[] UInt32ToBigEndianBytes(UInt32 x)
+ private byte[] UInt32ToBigEndianBytes(uint x)
{
- return new byte[] {
- (byte)((x >> 24) & 0xff),
- (byte)((x >> 16) & 0xff),
- (byte)((x >> 8) & 0xff),
- (byte)(x & 0xff)
- };
+ var result = new byte[sizeof(uint)];
+ BinaryPrimitives.WriteUInt32BigEndian(result, x);
+ return result;
}
}
}
diff --git a/src/sys/EncodingExtensions.cs b/src/sys/EncodingExtensions.cs
new file mode 100644
index 0000000000..c285be0cf0
--- /dev/null
+++ b/src/sys/EncodingExtensions.cs
@@ -0,0 +1,57 @@
+using System;
+using System.Text;
+
+namespace SIPSorcery.Sys
+{
+ ///
+ /// Extension methods for .
+ ///
+ internal static class EncodingExtensions
+ {
+#if NETSTANDARD2_0 || NETFRAMEWORK
+ ///
+ /// Decodes a sequence of bytes from a read-only span into a string.
+ ///
+ /// The encoding to use for the conversion.
+ /// The span containing the sequence of bytes to decode.
+ /// A string containing the decoded characters.
+ public unsafe static string GetString(this Encoding encoding, ReadOnlySpan bytes)
+ {
+ fixed (byte* ptr = bytes)
+ {
+ return encoding.GetString(ptr, bytes.Length);
+ }
+ }
+
+ ///
+ /// Encodes a set of characters from a read-only span into a sequence of bytes.
+ ///
+ /// The encoding to use for the conversion.
+ /// The span containing the set of characters to encode.
+ /// The span to contain the resulting sequence of bytes.
+ /// The actual number of bytes written into the byte span.
+ 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);
+ }
+ }
+
+ ///
+ /// Calculates the number of bytes needed to encode a set of characters.
+ ///
+ /// The encoding to use for the calculation.
+ /// The span containing the set of characters to encode.
+ /// The number of bytes needed to encode the specified characters.
+ public unsafe static int GetByteCount(this Encoding encoding, ReadOnlySpan chars)
+ {
+ fixed (char* pChars = chars)
+ {
+ return encoding.GetByteCount(pChars, chars.Length);
+ }
+ }
+#endif
+ }
+}
diff --git a/src/sys/EnumExtensions.cs b/src/sys/EnumExtensions.cs
new file mode 100644
index 0000000000..8cacca3f0a
--- /dev/null
+++ b/src/sys/EnumExtensions.cs
@@ -0,0 +1,55 @@
+using System.Net.Sockets;
+
+namespace SIPSorcery.Sys;
+
+///
+/// Extension methods for enumeration types used in the system.
+///
+internal static class EnumExtensions
+{
+ ///
+ /// Converts a ProtocolType enumeration value to its lowercase string representation.
+ ///
+ /// The ProtocolType enumeration value to convert.
+ /// A lowercase string representation of the protocol type. For most protocols,
+ /// returns the standard abbreviated form (e.g. "tcp", "udp", "ipv6"). For unrecognized
+ /// protocol types, returns the enum value converted to lowercase.
+ ///
+ /// This method provides standardized string representations for network protocols,
+ /// particularly useful for logging, configuration, and protocol-specific formatting needs.
+ ///
+ public static string ToLowerString(this ProtocolType protocolType)
+ {
+ return protocolType switch
+ {
+ ProtocolType.IP => "ip",
+
+ ProtocolType.Icmp => "icmp",
+ ProtocolType.Igmp => "igmp",
+ ProtocolType.Ggp => "ggp",
+
+ ProtocolType.IPv4 => "ipv4",
+ ProtocolType.Tcp => "tcp",
+ ProtocolType.Pup => "pup",
+ ProtocolType.Udp => "udp",
+ ProtocolType.Idp => "idp",
+ ProtocolType.IPv6 => "ipv6",
+ ProtocolType.IPv6RoutingHeader => "routing",
+ ProtocolType.IPv6FragmentHeader => "fragment",
+ ProtocolType.IPSecEncapsulatingSecurityPayload => "ipsecencapsulatingsecuritypayload",
+ ProtocolType.IPSecAuthenticationHeader => "ipsecauthenticationheader",
+ ProtocolType.IcmpV6 => "icmpv6",
+ ProtocolType.IPv6NoNextHeader => "nonext",
+ ProtocolType.IPv6DestinationOptions => "dstopts",
+ ProtocolType.ND => "nd",
+ ProtocolType.Raw => "raw",
+
+ ProtocolType.Ipx => "ipx",
+ ProtocolType.Spx => "spx",
+ ProtocolType.SpxII => "spx2",
+ ProtocolType.Unknown => "unknown",
+
+ _ => protocolType.ToString().ToLowerInvariant()
+ };
+ }
+}
diff --git a/src/sys/JSONWriter.cs b/src/sys/JSONWriter.cs
index 50c5d2ab09..4435504729 100644
--- a/src/sys/JSONWriter.cs
+++ b/src/sys/JSONWriter.cs
@@ -15,11 +15,13 @@
//-----------------------------------------------------------------------------
using System;
+using System.Buffers;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.Serialization;
using System.Text;
+using SIPSorcery.Sys;
namespace TinyJson
{
@@ -31,203 +33,250 @@ public static class JSONWriter
{
public static string ToJson(this object item)
{
- StringBuilder stringBuilder = new StringBuilder();
- AppendValue(stringBuilder, item);
- return stringBuilder.ToString();
+ var builder = new ValueStringBuilder();
+
+ try
+ {
+ AppendValue(ref builder, item);
+
+ return builder.ToString();
+ }
+ finally
+ {
+ builder.Dispose();
+ }
}
- static void AppendValue(StringBuilder stringBuilder, object item)
+ static void AppendValue(ref ValueStringBuilder builder, object item)
{
if (item == null)
{
- stringBuilder.Append("null");
+ builder.Append("null");
return;
}
- Type type = item.GetType();
- if (type == typeof(string) || type == typeof(char))
+ var type = item.GetType();
+
+ if (type.IsEnum)
{
- stringBuilder.Append('"');
- string str = item.ToString();
- for (int i = 0; i < str.Length; ++i)
- {
- if (str[i] < ' ' || str[i] == '"' || str[i] == '\\')
+ builder.Append('"');
+ builder.Append(item.ToString());
+ builder.Append('"');
+ return;
+ }
+
+ var typeCode = Type.GetTypeCode(type);
+
+ switch (typeCode)
+ {
+
+ case TypeCode.String:
{
- stringBuilder.Append('\\');
- int j = "\"\\\n\r\t\b\f".IndexOf(str[i]);
- if (j >= 0)
+ builder.Append('"');
+ var str = ((string)item).AsSpan();
+ for (var i = 0; i < str.Length; i++)
{
- stringBuilder.Append("\"\\nrtbf"[j]);
- }
- else
- {
- stringBuilder.AppendFormat("u{0:X4}", (UInt32)str[i]);
+ AppendEscapedChar(ref builder, str[i]);
}
+ builder.Append('"');
+ return;
}
- else
+
+ case TypeCode.Char:
{
- stringBuilder.Append(str[i]);
+ builder.Append('"');
+ AppendEscapedChar(ref builder, (char)item);
+ builder.Append('"');
+ return;
+
+ }
+
+ case TypeCode.Boolean:
+ {
+ builder.Append((bool)item ? "true" : "false");
+ return;
+ }
+
+ case TypeCode.Single:
+ {
+ builder.Append((float)item, provider: System.Globalization.CultureInfo.InvariantCulture);
+ return;
+ }
+
+ case TypeCode.Double:
+ {
+ builder.Append((double)item, provider: System.Globalization.CultureInfo.InvariantCulture);
+ return;
+ }
+
+ case TypeCode.Decimal:
+ {
+ builder.Append((decimal)item, provider: System.Globalization.CultureInfo.InvariantCulture);
+ return;
+ }
+
+ case TypeCode.Byte:
+ case TypeCode.SByte:
+ case TypeCode.Int16:
+ case TypeCode.UInt16:
+ case TypeCode.Int32:
+ case TypeCode.UInt32:
+ case TypeCode.Int64:
+ case TypeCode.UInt64:
+ {
+ builder.Append(item.ToString());
+ return;
+ }
+
+ case TypeCode.DBNull:
+ case TypeCode.Empty:
+ {
+ builder.Append("null");
+ return;
}
- }
- stringBuilder.Append('"');
- }
- else if (type == typeof(byte) || type == typeof(sbyte))
- {
- stringBuilder.Append(item.ToString());
- }
- else if (type == typeof(short) || type == typeof(ushort))
- {
- stringBuilder.Append(item.ToString());
- }
- else if (type == typeof(int) || type == typeof(uint))
- {
- stringBuilder.Append(item.ToString());
- }
- else if (type == typeof(long) || type == typeof(ulong))
- {
- stringBuilder.Append(item.ToString());
- }
- else if (type == typeof(float))
- {
- stringBuilder.Append(((float)item).ToString(System.Globalization.CultureInfo.InvariantCulture));
- }
- else if (type == typeof(double))
- {
- stringBuilder.Append(((double)item).ToString(System.Globalization.CultureInfo.InvariantCulture));
- }
- else if (type == typeof(decimal))
- {
- stringBuilder.Append(((decimal)item).ToString(System.Globalization.CultureInfo.InvariantCulture));
- }
- else if (type == typeof(bool))
- {
- stringBuilder.Append(((bool)item) ? "true" : "false");
}
- else if (type.IsEnum)
+
+ static void AppendEscapedChar(ref ValueStringBuilder builder, char ch)
{
- stringBuilder.Append('"');
- stringBuilder.Append(item.ToString());
- stringBuilder.Append('"');
+ if (ch is >= ' ' and not '"' and not '\\')
+ {
+ builder.Append(ch);
+ }
+ else
+ {
+
+ builder.Append('\\');
+ switch (ch)
+ {
+ case '"': builder.Append('"'); break;
+ case '\\': builder.Append('\\'); break;
+ case '\n': builder.Append('n'); break;
+ case '\r': builder.Append('r'); break;
+ case '\t': builder.Append('t'); break;
+ case '\b': builder.Append('b'); break;
+ case '\f': builder.Append('f'); break;
+ default:
+ builder.Append('u');
+ builder.Append(((uint)ch).ToString("X4"));
+ break;
+ }
+ }
}
- else if (item is IList)
+
+ if (item is IList list)
{
- stringBuilder.Append('[');
- bool isFirst = true;
- IList list = item as IList;
- for (int i = 0; i < list.Count; i++)
+ builder.Append('[');
+ var isFirst = true;
+ for (var i = 0; i < list.Count; i++)
{
- if (isFirst)
+ if (!isFirst)
{
- isFirst = false;
+ builder.Append(',');
}
else
{
- stringBuilder.Append(',');
+ isFirst = false;
}
- AppendValue(stringBuilder, list[i]);
+ AppendValue(ref builder, list[i]);
}
- stringBuilder.Append(']');
+ builder.Append(']');
}
else if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<,>))
{
- Type keyType = type.GetGenericArguments()[0];
-
- //Refuse to output dictionary keys that aren't of type string
+ var keyType = type.GetGenericArguments()[0];
if (keyType != typeof(string))
{
- stringBuilder.Append("{}");
+ builder.Append("{}");
return;
}
- stringBuilder.Append('{');
- IDictionary dict = item as IDictionary;
- bool isFirst = true;
- foreach (object key in dict.Keys)
+ var dict = item as IDictionary;
+ builder.Append('{');
+ var isFirst = true;
+ foreach (var key in dict.Keys)
{
- if (isFirst)
+ if (!isFirst)
{
- isFirst = false;
+ builder.Append(',');
}
else
{
- stringBuilder.Append(',');
+ isFirst = false;
}
- stringBuilder.Append('\"');
- stringBuilder.Append((string)key);
- stringBuilder.Append("\":");
- AppendValue(stringBuilder, dict[key]);
+ builder.Append('\"');
+ builder.Append((string)key);
+ builder.Append("\":");
+ AppendValue(ref builder, dict[key]);
}
- stringBuilder.Append('}');
+ builder.Append('}');
}
else
{
- stringBuilder.Append('{');
+ builder.Append('{');
+ var isFirst = true;
- bool isFirst = true;
- FieldInfo[] fieldInfos = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy);
- for (int i = 0; i < fieldInfos.Length; i++)
+ var fieldInfos = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy);
+ foreach (var field in fieldInfos)
{
- if (fieldInfos[i].IsDefined(typeof(IgnoreDataMemberAttribute), true))
+ if (field.IsDefined(typeof(IgnoreDataMemberAttribute), true))
{
continue;
}
- object value = fieldInfos[i].GetValue(item);
+ var value = field.GetValue(item);
if (value != null)
{
- if (isFirst)
+ if (!isFirst)
{
- isFirst = false;
+ builder.Append(',');
}
else
{
- stringBuilder.Append(',');
+ isFirst = false;
}
- stringBuilder.Append('\"');
- stringBuilder.Append(GetMemberName(fieldInfos[i]));
- stringBuilder.Append("\":");
- AppendValue(stringBuilder, value);
+ builder.Append('\"');
+ builder.Append(GetMemberName(field));
+ builder.Append("\":");
+ AppendValue(ref builder, value);
}
}
- PropertyInfo[] propertyInfo = type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy);
- for (int i = 0; i < propertyInfo.Length; i++)
+
+ var propertyInfos = type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy);
+ foreach (var prop in propertyInfos)
{
- if (!propertyInfo[i].CanRead || propertyInfo[i].IsDefined(typeof(IgnoreDataMemberAttribute), true))
+ if (!prop.CanRead || prop.IsDefined(typeof(IgnoreDataMemberAttribute), true))
{
continue;
}
- object value = propertyInfo[i].GetValue(item, null);
+ var value = prop.GetValue(item, null);
if (value != null)
{
- if (isFirst)
+ if (!isFirst)
{
- isFirst = false;
+ builder.Append(',');
}
else
{
- stringBuilder.Append(',');
+ isFirst = false;
}
- stringBuilder.Append('\"');
- stringBuilder.Append(GetMemberName(propertyInfo[i]));
- stringBuilder.Append("\":");
- AppendValue(stringBuilder, value);
+ builder.Append('\"');
+ builder.Append(GetMemberName(prop));
+ builder.Append("\":");
+ AppendValue(ref builder, value);
}
}
- stringBuilder.Append('}');
+ builder.Append('}');
}
}
static string GetMemberName(MemberInfo member)
{
- if (member.IsDefined(typeof(DataMemberAttribute), true))
+ if (Attribute.GetCustomAttribute(member, typeof(DataMemberAttribute), true) is DataMemberAttribute attr &&
+ !string.IsNullOrEmpty(attr.Name))
{
- DataMemberAttribute dataMemberAttribute = (DataMemberAttribute)Attribute.GetCustomAttribute(member, typeof(DataMemberAttribute), true);
- if (!string.IsNullOrEmpty(dataMemberAttribute.Name))
- {
- return dataMemberAttribute.Name;
- }
+ return attr.Name;
}
return member.Name;
diff --git a/src/sys/TypeExtensions.cs b/src/sys/TypeExtensions.cs
index 47d7e29998..cfadfc42c6 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;
}
@@ -123,59 +124,29 @@ public static string Slice(this string s, char startDelimiter, char endDelimeter
public static string HexStr(this byte[] buffer, char? separator = null)
{
- return buffer.HexStr(buffer.Length, separator);
+ return HexStr(buffer.AsSpan(), separator: separator, lowercase: false);
}
public static string HexStr(this byte[] buffer, int length, char? separator = null)
{
- if (separator is { } s)
- {
- int numberOfChars = length * 3 - 1;
-#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
- return string.Create(numberOfChars, (buffer, length, s), PopulateNewStringWithSeparator);
-#else
- var rv = new char[numberOfChars];
- PopulateNewStringWithSeparator(rv, (buffer, length, s));
- return new string(rv);
-#endif
- }
- else
- {
- int numberOfChars = length * 2;
-#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
- return string.Create(numberOfChars, (buffer, length), PopulateNewStringWithoutSeparator);
-#else
- var rv = new char[numberOfChars];
- PopulateNewStringWithoutSeparator(rv, (buffer, length));
- return new string(rv);
-#endif
- }
+ return HexStr(buffer.AsSpan(0, buffer.Length), separator: separator, lowercase: false);
+ }
- static void PopulateNewStringWithSeparator(Span chars, (byte[] buffer, int length, char separator) state)
- {
- var (buffer, length, s) = state;
- for (int i = 0, j = 0; i < length; i++)
- {
- var val = buffer[i];
- chars[j++] = char.ToUpperInvariant(hexmap[val >> 4]);
- chars[j++] = char.ToUpperInvariant(hexmap[val & 15]);
- if (j < chars.Length)
- {
- chars[j++] = s;
- }
- }
- }
+ public static string HexStr(this byte[] buffer, int length, char? separator = null, bool lowercase = false)
+ {
+ return HexStr(buffer.AsSpan(0, length), separator: separator, lowercase: lowercase);
+ }
- static void PopulateNewStringWithoutSeparator(Span chars, (byte[] buffer, int length) state)
- {
- var (buffer, length) = state;
- for (int i = 0, j = 0; i < length; i++)
- {
- var val = buffer[i];
- chars[j++] = char.ToUpperInvariant(hexmap[val >> 4]);
- chars[j++] = char.ToUpperInvariant(hexmap[val & 15]);
- }
- }
+ public static string HexStr(this Span buffer, char? separator = null, bool lowercase = false)
+ {
+ return HexStr((ReadOnlySpan)buffer, separator: separator, lowercase: lowercase);
+ }
+
+ public static string HexStr(this ReadOnlySpan buffer, char? separator = null, bool lowercase = false)
+ {
+ using var sb = new ValueStringBuilder(stackalloc char[256]);
+ sb.Append(buffer, separator, lowercase);
+ return sb.ToString();
}
public static byte[] ParseHexStr(string hexStr)
diff --git a/src/sys/ValueStringBuilder.AppendSpanFormattable.cs b/src/sys/ValueStringBuilder.AppendSpanFormattable.cs
new file mode 100644
index 0000000000..9498a559fa
--- /dev/null
+++ b/src/sys/ValueStringBuilder.AppendSpanFormattable.cs
@@ -0,0 +1,30 @@
+using System;
+using System.Runtime.CompilerServices;
+
+namespace SIPSorcery.Sys
+{
+#if NET6_0_OR_GREATER
+ internal ref partial struct ValueStringBuilder
+ {
+ ///
+ /// Appends a value that implements ISpanFormattable to the string builder using span-based formatting.
+ /// If span formatting fails, falls back to regular string formatting.
+ ///
+ /// The type of the value to format. Must implement ISpanFormattable.
+ /// The value to append.
+ /// A format string that defines the formatting to apply, or null to use default formatting.
+ /// An object that supplies culture-specific formatting information, or null to use default formatting.
+ internal void AppendSpanFormattable(T value, string? format = null, IFormatProvider? provider = null) where T : ISpanFormattable
+ {
+ if (value.TryFormat(_chars.Slice(_pos), out int charsWritten, format, provider))
+ {
+ _pos += charsWritten;
+ }
+ else
+ {
+ Append(value.ToString(format, provider));
+ }
+ }
+ }
+#endif
+}
diff --git a/src/sys/ValueStringBuilder.Bytes.cs b/src/sys/ValueStringBuilder.Bytes.cs
new file mode 100644
index 0000000000..ccf0da837b
--- /dev/null
+++ b/src/sys/ValueStringBuilder.Bytes.cs
@@ -0,0 +1,75 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace SIPSorcery.Sys
+{
+ internal ref partial struct ValueStringBuilder
+ {
+ ///
+ /// Character array for uppercase hexadecimal representation (0-9, A-F).
+ ///
+ private static readonly char[] upperHexmap = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
+
+ ///
+ /// Character array for lowercase hexadecimal representation (0-9, a-f).
+ ///
+ private static readonly char[] lowerHexmap = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
+
+ ///
+ /// Appends a byte array to the string builder as hexadecimal characters.
+ ///
+ /// The byte array to append. Can be null.
+ /// Optional separator character to insert between bytes.
+ public void Append(byte[]? bytes, char? separator = null)
+ {
+ if (bytes is { Length: > 0 })
+ {
+ Append(bytes.AsSpan(), separator);
+ }
+ }
+
+ ///
+ /// Appends a span of bytes to the string builder as hexadecimal characters.
+ ///
+ /// The span of bytes to append.
+ /// Optional separator character to insert between bytes.
+ /// If true, uses lowercase hex characters (a-f); if false, uses uppercase (A-F).
+ ///
+ /// Each byte is converted to two hexadecimal characters. If a separator is specified,
+ /// it will be inserted between each pair of hex characters representing a byte.
+ /// For example, with separator '-': "AA-BB-CC"
+ ///
+ public void Append(ReadOnlySpan bytes, char? separator = null, bool lowercase = false)
+ {
+ var hexmap = lowercase ? lowerHexmap : upperHexmap;
+
+ if (bytes.IsEmpty)
+ {
+ return;
+ }
+
+ if (separator is { } s)
+ {
+ for (int i = 0; i < bytes.Length;)
+ {
+ var b = bytes[i];
+ Append(hexmap[(int)b >> 4]);
+ Append(hexmap[(int)b & 0b1111]);
+ if (++i < bytes.Length)
+ {
+ Append(s);
+ }
+ }
+ }
+ else
+ {
+ for (var i = 0; i < bytes.Length; i++)
+ {
+ var b = bytes[i];
+ Append(hexmap[(int)b >> 4]);
+ Append(hexmap[(int)b & 0b1111]);
+ }
+ }
+ }
+ }
+}
diff --git a/src/sys/ValueStringBuilder.cs b/src/sys/ValueStringBuilder.cs
new file mode 100644
index 0000000000..bcbf87ddc4
--- /dev/null
+++ b/src/sys/ValueStringBuilder.cs
@@ -0,0 +1,529 @@
+// Based on System.Text.ValueStringBuilder - System.Console
+
+using System;
+using System.Buffers;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace SIPSorcery.Sys
+{
+ ///
+ /// A ref struct that provides a low-allocation way to build strings.
+ /// Similar to StringBuilder but stackalloc-based for better performance.
+ ///
+ internal ref partial struct ValueStringBuilder
+ {
+ /// The array to return to the array pool, if one was rented.
+ private char[]? _arrayToReturnToPool;
+ /// The span containing the characters written so far.
+ private Span _chars;
+ /// The current position within the span.
+ private int _pos;
+
+ ///
+ /// Initializes a new instance of ValueStringBuilder with a provided character buffer.
+ ///
+ /// The initial buffer to use for storing characters.
+ public ValueStringBuilder(Span initialBuffer)
+ {
+ _arrayToReturnToPool = null;
+ _chars = initialBuffer;
+ _pos = 0;
+ }
+
+ ///
+ /// Initializes a new instance of ValueStringBuilder with a specified initial capacity.
+ ///
+ /// The initial capacity of the internal buffer.
+ public ValueStringBuilder(int initialCapacity)
+ {
+ _arrayToReturnToPool = ArrayPool.Shared.Rent(initialCapacity);
+ _chars = _arrayToReturnToPool;
+ _pos = 0;
+ }
+
+ ///
+ /// Gets or sets the length of the current builder's content.
+ ///
+ public int Length
+ {
+ get => _pos;
+ set
+ {
+ Debug.Assert(value >= 0);
+ Debug.Assert(value <= _chars.Length);
+ _pos = value;
+ }
+ }
+
+ ///
+ /// Gets the total capacity of the builder's buffer.
+ ///
+ public int Capacity => _chars.Length;
+
+ ///
+ /// Gets a read-only span containing the builder's characters.
+ ///
+ public ReadOnlySpan Chars => _chars;
+
+ ///
+ /// Ensures the builder has enough capacity to accommodate a specified total number of characters.
+ ///
+ /// The minimum capacity needed.
+ public void EnsureCapacity(int capacity)
+ {
+ // This is not expected to be called this with negative capacity
+ Debug.Assert(capacity >= 0);
+
+ // If the caller has a bug and calls this with negative capacity, make sure to call Grow to throw an exception.
+ if ((uint)capacity > (uint)_chars.Length)
+ {
+ Grow(capacity - _pos);
+ }
+ }
+
+ ///
+ /// Get a pinnable reference to the builder.
+ /// Does not ensure there is a null char after
+ /// This overload is pattern matched in the C# 7.3+ compiler so you can omit
+ /// the explicit method call, and write eg "fixed (char* c = builder)"
+ ///
+ /// A reference to the underlying characters.
+ public ref char GetPinnableReference() => ref MemoryMarshal.GetReference(_chars);
+
+ ///
+ /// Get a pinnable reference to the builder.
+ ///
+ /// If , ensures that the builder has a null char after
+ /// A reference to the underlying characters.
+ public ref char GetPinnableReference(bool terminate)
+ {
+ if (terminate)
+ {
+ EnsureCapacity(Length + 1);
+ _chars[Length] = '\0';
+ }
+ return ref MemoryMarshal.GetReference(_chars);
+ }
+
+ ///
+ /// Gets a reference to the character at the specified position.
+ ///
+ /// The zero-based index of the character to get.
+ /// A reference to the character at the specified position.
+ public ref char this[int index]
+ {
+ get
+ {
+ Debug.Assert(index < _pos);
+ return ref _chars[index];
+ }
+ }
+
+ ///
+ /// Returns the built string and disposes the builder.
+ ///
+ /// The final string.
+ public new string ToString()
+ {
+ var s = _chars.Slice(0, _pos).ToString();
+ Dispose();
+ return s;
+ }
+
+ ///
+ /// Returns the underlying storage of the builder.
+ ///
+ public Span RawChars => _chars;
+
+ ///
+ /// Returns a span around the contents of the builder.
+ ///
+ /// If , ensures that the builder has a null char after
+ /// A read-only span of the builder's content.
+ public ReadOnlySpan AsSpan(bool terminate)
+ {
+ if (terminate)
+ {
+ EnsureCapacity(Length + 1);
+ _chars[Length] = '\0';
+ }
+ return _chars.Slice(0, _pos);
+ }
+
+ /// Returns a read-only span of the builder's content.
+ public ReadOnlySpan AsSpan() => _chars.Slice(0, _pos);
+
+ /// Returns a read-only span starting at the specified index.
+ /// The starting index.
+ public ReadOnlySpan AsSpan(int start) => _chars.Slice(start, _pos - start);
+
+ /// Returns a read-only span of the specified length starting at the specified index.
+ /// The starting index.
+ /// The length of the span.
+ public ReadOnlySpan AsSpan(int start, int length) => _chars.Slice(start, length);
+
+ ///
+ /// Attempts to copy the builder's contents to a destination span.
+ ///
+ /// The destination span.
+ /// When this method returns, contains the number of characters that were copied.
+ /// if the copy was successful; otherwise, .
+ public bool TryCopyTo(Span destination, out int charsWritten)
+ {
+ if (_chars.Slice(0, _pos).TryCopyTo(destination))
+ {
+ charsWritten = _pos;
+ Dispose();
+ return true;
+ }
+ else
+ {
+ charsWritten = 0;
+ Dispose();
+ return false;
+ }
+ }
+
+ ///
+ /// Inserts a repeated character at the specified position.
+ ///
+ /// The position to insert at.
+ /// The character to insert.
+ /// The number of times to insert the character.
+ public void Insert(int index, char value, int count)
+ {
+ if (_pos > _chars.Length - count)
+ {
+ Grow(count);
+ }
+
+ var remaining = _pos - index;
+ _chars.Slice(index, remaining).CopyTo(_chars.Slice(index + count));
+ _chars.Slice(index, count).Fill(value);
+ _pos += count;
+ }
+
+ ///
+ /// Inserts a string at the specified position.
+ ///
+ /// The position to insert at.
+ /// The string to insert.
+ public void Insert(int index, string? s)
+ {
+ if (s == null)
+ {
+ return;
+ }
+
+ var count = s.Length;
+
+ if (_pos > (_chars.Length - count))
+ {
+ Grow(count);
+ }
+
+ var remaining = _pos - index;
+ _chars.Slice(index, remaining).CopyTo(_chars.Slice(index + count));
+ s.AsSpan().CopyTo(_chars.Slice(index));
+ _pos += count;
+ }
+
+ ///
+ /// Appends a boolean value as its string representation.
+ ///
+ /// The value to append.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Append(bool value) => Append(value ? "true" : "false");
+
+ ///
+ /// Appends a character to the builder.
+ ///
+ /// The character to append.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Append(char c)
+ {
+ var pos = _pos;
+ var chars = _chars;
+ if ((uint)pos < (uint)chars.Length)
+ {
+ chars[pos] = c;
+ _pos = pos + 1;
+ }
+ else
+ {
+ GrowAndAppend(c);
+ }
+ }
+
+ ///
+ /// Appends a string to the builder.
+ ///
+ /// The string to append.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Append(string? s)
+ {
+ if (s == null)
+ {
+ return;
+ }
+
+ var pos = _pos;
+ if (s.Length == 1 && (uint)pos < (uint)_chars.Length) // very common case, e.g. appending strings from NumberFormatInfo like separators, percent symbols, etc.
+ {
+ _chars[pos] = s[0];
+ _pos = pos + 1;
+ }
+ else
+ {
+ AppendSlow(s);
+ }
+ }
+
+ ///
+ /// Slow path for appending a string when the fast path isn't applicable.
+ ///
+ private void AppendSlow(string s)
+ {
+ var pos = _pos;
+ if (pos > _chars.Length - s.Length)
+ {
+ Grow(s.Length);
+ }
+
+ s.AsSpan().CopyTo(_chars.Slice(pos));
+ _pos += s.Length;
+ }
+
+ ///
+ /// Appends a character multiple times to the builder.
+ ///
+ /// The character to append.
+ /// The number of times to append the character.
+ public void Append(char c, int count)
+ {
+ if (_pos > _chars.Length - count)
+ {
+ Grow(count);
+ }
+
+ var dst = _chars.Slice(_pos, count);
+ for (var i = 0; i < dst.Length; i++)
+ {
+ dst[i] = c;
+ }
+ _pos += count;
+ }
+
+ ///
+ /// Appends a span of characters to the builder.
+ ///
+ /// The span to append.
+ public void Append(scoped ReadOnlySpan value)
+ {
+ var pos = _pos;
+ if (pos > _chars.Length - value.Length)
+ {
+ Grow(value.Length);
+ }
+
+ value.CopyTo(_chars.Slice(_pos));
+ _pos += value.Length;
+ }
+
+ ///
+ /// Reserves space for a span of characters and returns a span that can be written to.
+ ///
+ /// The number of characters to reserve space for.
+ /// A span that can be written to.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Span AppendSpan(int length)
+ {
+ var origPos = _pos;
+ if (origPos > _chars.Length - length)
+ {
+ Grow(length);
+ }
+
+ _pos = origPos + length;
+ return _chars.Slice(origPos, length);
+ }
+
+ ///
+ /// Appends an value to the builder.
+ ///
+ /// The value to append.
+ /// An optional format string that guides the formatting, or null to use default formatting.
+ /// An optional object that provides culture-specific formatting services, or null to use default formatting.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+#if NET6_0_OR_GREATER
+ public void Append(int value, string? format = null, IFormatProvider? provider = null) => AppendSpanFormattable(value, format, provider);
+#else
+ public void Append(int value, string? format = null, IFormatProvider? provider = null) => Append(value.ToString(format, provider));
+#endif
+
+ ///
+ /// Appends an value to the builder.
+ ///
+ /// The value to append.
+ /// An optional format string that guides the formatting, or null to use default formatting.
+ /// An optional object that provides culture-specific formatting services, or null to use default formatting.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+#if NET6_0_OR_GREATER
+ public void Append(uint value, string? format = null, IFormatProvider? provider = null) => AppendSpanFormattable(value, format, provider);
+#else
+ public void Append(uint value, string? format = null, IFormatProvider? provider = null) => Append(value.ToString(format, provider));
+#endif
+
+ ///
+ /// Appends an value to the builder.
+ ///
+ /// The value to append.
+ /// An optional format string that guides the formatting, or null to use default formatting.
+ /// An optional object that provides culture-specific formatting services, or null to use default formatting.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+#if NET6_0_OR_GREATER
+ public void Append(ushort value, string? format = null, IFormatProvider? provider = null) => AppendSpanFormattable(value, format, provider);
+#else
+ public void Append(ushort value, string? format = null, IFormatProvider? provider = null) => Append(value.ToString(format, provider));
+#endif
+
+ ///
+ /// Appends an value to the builder.
+ ///
+ /// The value to append.
+ /// An optional format string that guides the formatting, or null to use default formatting.
+ /// An optional object that provides culture-specific formatting services, or null to use default formatting.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+#if NET6_0_OR_GREATER
+ public void Append(ushort? value, string? format = null, IFormatProvider? provider = null)
+ {
+ if (value is { } v)
+ {
+ AppendSpanFormattable(v, format, provider);
+ }
+ }
+#else
+ public void Append(ushort? value, string? format = null, IFormatProvider? provider = null)
+ {
+ if (value is { } v)
+ {
+ Append(v.ToString(format, provider));
+ }
+ }
+#endif
+
+ ///
+ /// Appends an value to the builder.
+ ///
+ /// The value to append.
+ /// An optional format string that guides the formatting, or null to use default formatting.
+ /// An optional object that provides culture-specific formatting services, or null to use default formatting.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+#if NET6_0_OR_GREATER
+ public void Append(long value, string? format = null, IFormatProvider? provider = null) => AppendSpanFormattable(value, format, provider);
+#else
+ public void Append(long value, string? format = null, IFormatProvider? provider = null) => Append(value.ToString(format, provider));
+#endif
+
+ ///
+ /// Appends an value to the builder.
+ ///
+ /// The value to append.
+ /// An optional format string that guides the formatting, or null to use default formatting.
+ /// An optional object that provides culture-specific formatting services, or null to use default formatting.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+#if NET6_0_OR_GREATER
+ public void Append(float value, string? format = null, IFormatProvider? provider = null) => AppendSpanFormattable(value, format, provider);
+#else
+ public void Append(float value, string? format = null, IFormatProvider? provider = null) => Append(value.ToString(format, provider));
+#endif
+
+ ///
+ /// Appends an value to the builder.
+ ///
+ /// The value to append.
+ /// An optional format string that guides the formatting, or null to use default formatting.
+ /// An optional object that provides culture-specific formatting services, or null to use default formatting.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+#if NET6_0_OR_GREATER
+ public void Append(double value, string? format = null, IFormatProvider? provider = null) => AppendSpanFormattable(value, format, provider);
+#else
+ public void Append(double value, string? format = null, IFormatProvider? provider = null) => Append(value.ToString(format, provider));
+#endif
+
+ ///
+ /// Appends an value to the builder.
+ ///
+ /// The value to append.
+ /// An optional format string that guides the formatting, or null to use default formatting.
+ /// An optional object that provides culture-specific formatting services, or null to use default formatting.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+#if NET6_0_OR_GREATER
+ public void Append(decimal value, string? format = null, IFormatProvider? provider = null) => AppendSpanFormattable(value, format, provider);
+#else
+ public void Append(decimal value, string? format = null, IFormatProvider? provider = null) => Append(value.ToString(format, provider));
+#endif
+
+ ///
+ /// Grows the buffer and appends a single character.
+ ///
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private void GrowAndAppend(char c)
+ {
+ Grow(1);
+ Append(c);
+ }
+
+ ///
+ /// Resize the internal buffer either by doubling current buffer size or
+ /// by adding to
+ /// whichever is greater.
+ ///
+ ///
+ /// Number of chars requested beyond current position.
+ ///
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private void Grow(int additionalCapacityBeyondPos)
+ {
+ Debug.Assert(additionalCapacityBeyondPos > 0);
+ Debug.Assert(_pos > _chars.Length - additionalCapacityBeyondPos, "Grow called incorrectly, no resize is needed.");
+
+ const uint ArrayMaxLength = 0x7FFFFFC7; // same as Array.MaxLength
+
+ // Increase to at least the required size (_pos + additionalCapacityBeyondPos), but try
+ // to double the size if possible, bounding the doubling to not go beyond the max array length.
+ var newCapacity = (int)Math.Max(
+ (uint)(_pos + additionalCapacityBeyondPos),
+ Math.Min((uint)_chars.Length * 2, ArrayMaxLength));
+
+ // Make sure to let Rent throw an exception if the caller has a bug and the desired capacity is negative.
+ // This could also go negative if the actual required length wraps around.
+ var poolArray = ArrayPool.Shared.Rent(newCapacity);
+
+ _chars.Slice(0, _pos).CopyTo(poolArray);
+
+ var toReturn = _arrayToReturnToPool;
+ _chars = _arrayToReturnToPool = poolArray;
+ if (toReturn != null)
+ {
+ ArrayPool.Shared.Return(toReturn);
+ }
+ }
+
+ ///
+ /// Disposes the builder, returning any rented array to the pool.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Dispose()
+ {
+ var toReturn = _arrayToReturnToPool;
+ this = default; // for safety, to avoid using pooled array if this instance is erroneously appended to again
+ if (toReturn != null)
+ {
+ ArrayPool.Shared.Return(toReturn);
+ }
+ }
+ }
+}
diff --git a/test/unit/net/RTCP/RTCPHeaderUnitTest.cs b/test/unit/net/RTCP/RTCPHeaderUnitTest.cs
index 023dc76efc..b7d98a1a65 100644
--- a/test/unit/net/RTCP/RTCPHeaderUnitTest.cs
+++ b/test/unit/net/RTCP/RTCPHeaderUnitTest.cs
@@ -13,6 +13,7 @@
// BSD 3-Clause "New" or "Revised" License, see included LICENSE.md file.
//-----------------------------------------------------------------------------
+using System;
using Microsoft.Extensions.Logging;
using SIPSorcery.Sys;
using Xunit;
@@ -62,7 +63,7 @@ public void RTCPHeaderRoundTripTest()
logger.LogDebug("PacketType: {SrcPacketType}, {DstPacketType}", src.PacketType, dst.PacketType);
logger.LogDebug("Length: {SrcLength}, {DstLength}", src.Length, dst.Length);
- logger.LogDebug("Raw Header: {RawHeader}", headerBuffer.HexStr(headerBuffer.Length));
+ logger.LogDebug("Raw Header: {RawHeader}", headerBuffer.AsSpan(0, headerBuffer.Length).HexStr());
Assert.True(src.Version == dst.Version, "Version was mismatched.");
Assert.True(src.PaddingFlag == dst.PaddingFlag, "PaddingFlag was mismatched.");