From d45794a79aadef074fac9061ac2a2f071653cf9f Mon Sep 17 00:00:00 2001 From: Paulo Morgado <470455+paulomorgado@users.noreply.github.com> Date: Tue, 26 May 2026 14:12:50 +0100 Subject: [PATCH] Reduce string allocations in parsers Use ordinal comparisons and span-based parsing to avoid temporary string generation across SIP, RTSP, SDP, RTP, and WebRTC paths. Replace allocation-heavy Trim, Substring, Split, regex, and string concatenation patterns where span slicing, destination-span split buffers, or StringBuilder preserve behavior with fewer intermediate strings. Convert staged string.Format and fixed concatenation sites to interpolation, and replace loop-based string concatenation with StringBuilder where strings are built incrementally. Avoid temporary string creation in SIP custom header prefix checks. Change SIPHeader and RTSPHeader ToString implementations to override object.ToString() so interpolating header instances preserves protocol serialization behavior. Replace span-trim length checks with string.IsNullOrWhiteSpace where only null/blank detection is needed. Keep SDP media status parsing close to the original switch structure by using guarded cases with ordinal ignore-case comparisons. Use discard patterns for guarded switch cases where the matched value is not used. Replace the long SIP/SDP test fixture with a raw string literal and normalize it with ReplaceLineEndings(CRLF) so the protocol payload stays stable across LF and CRLF checkouts. Set the unit test project to C# 14 and import Polyfills so the call also compiles for net462. Convert long SIP and SDP test payloads from CRLF-heavy interpolated strings to raw string literals normalized with ReplaceLineEndings. Share the repeated integration INVITE payload through a helper and update test projects to C# 14 so raw strings compile across target frameworks. Refactored logging in DtlsClient and DtlsServer to use structured message templates instead of string interpolation. Added a new Log.Debug overload and a helper to format templates with named placeholders, improving log consistency and enabling easier log analysis. --- src/SIPSorcery.VP8/DebugProbe.cs | 39 +- src/SIPSorcery.VP8/VP8Codec.cs | 7 +- .../net/DtlsSrtp/Lib/DTLS/DtlsClient.cs | 25 +- .../net/DtlsSrtp/Lib/DTLS/DtlsServer.cs | 21 +- src/SIPSorcery/net/DtlsSrtp/Lib/Log.cs | 21 + src/SIPSorcery/net/ICE/RTCIceCandidate.cs | 40 +- src/SIPSorcery/net/ICE/RtpIceChannel.cs | 15 +- src/SIPSorcery/net/RTCP/RTCPFeedback.cs | 16 +- src/SIPSorcery/net/RTCP/RTCPHeader.cs | 2 +- src/SIPSorcery/net/RTCP/RTCPTWCCFeedback.cs | 4 +- .../TransportWideCCExtension.cs | 11 +- src/SIPSorcery/net/RTP/RTPSession.cs | 34 +- src/SIPSorcery/net/RTP/Streams/AudioStream.cs | 6 +- src/SIPSorcery/net/RTSP/RTSPClient.cs | 50 +- src/SIPSorcery/net/RTSP/RTSPHeader.cs | 279 ++-- src/SIPSorcery/net/RTSP/RTSPMessage.cs | 2 +- src/SIPSorcery/net/RTSP/RTSPRequest.cs | 28 +- src/SIPSorcery/net/RTSP/RTSPResponse.cs | 24 +- src/SIPSorcery/net/RTSP/RTSPURL.cs | 6 +- .../net/SCTP/Chunks/SctpTlvChunkParameter.cs | 3 +- src/SIPSorcery/net/SCTP/SctpAssociation.cs | 14 +- src/SIPSorcery/net/SDP/SDP.cs | 1250 ++++++++++------- .../net/SDP/SDPAudioVideoMediaFormat.cs | 70 +- .../net/SDP/SDPConnectionInformation.cs | 31 +- .../net/SDP/SDPMediaAnnouncement.cs | 192 ++- .../net/SDP/SDPSecurityDescription.cs | 248 ++-- src/SIPSorcery/net/SDP/SDPTypes.cs | 10 +- src/SIPSorcery/net/STUN/STUNAppState.cs | 2 +- .../STUNAttributes/STUNAddressAttribute.cs | 2 +- .../net/STUN/STUNAttributes/STUNAttribute.cs | 4 +- .../STUNChangeRequestAttribute.cs | 2 +- .../STUNConnectionIdAttribute.cs | 2 +- .../STUNAttributes/STUNErrorCodeAttribute.cs | 2 +- .../STUNAttributes/STUNXORAddressAttribute.cs | 2 +- src/SIPSorcery/net/STUN/STUNMessage.cs | 6 +- src/SIPSorcery/net/WebRTC/RTCDataChannel.cs | 6 +- .../net/WebRTC/RTCPeerConnection.cs | 5 +- src/SIPSorcery/net/WebRTC/RTCSctpTransport.cs | 3 +- .../FFmpegCameraSource.cs | 6 +- .../FFmpegScreenSource.cs | 6 +- src/SIPSorceryMedia.FFmpeg/FfmpegInit.cs | 15 +- .../Interop/MacOs/AvFoundation.cs | 4 +- .../WindowsVideoEndPoint.cs | 15 +- test/FFmpegConsoleApp/Program.cs | 0 test/unit/net/STUN/STUNUnitTest.cs | 0 45 files changed, 1510 insertions(+), 1020 deletions(-) mode change 100755 => 100644 test/FFmpegConsoleApp/Program.cs mode change 100755 => 100644 test/unit/net/STUN/STUNUnitTest.cs diff --git a/src/SIPSorcery.VP8/DebugProbe.cs b/src/SIPSorcery.VP8/DebugProbe.cs index a70282a9e6..9a3afdfc4b 100644 --- a/src/SIPSorcery.VP8/DebugProbe.cs +++ b/src/SIPSorcery.VP8/DebugProbe.cs @@ -19,6 +19,7 @@ //----------------------------------------------------------------------------- using System.Diagnostics; +using System.Text; namespace Vpx.Net { @@ -30,13 +31,14 @@ public static void DumpMotionVectors(MODE_INFO[] mip, int macroBlockCols, int ma Debug.WriteLine("Macro Block Modes:"); for (int i = 0; i < macroBlockRows + 1; i++) { - string rowStr = $"Row {i} | "; + var rowBuilder = new StringBuilder().Append("Row ").Append(i).Append(" | "); for (int j = 0; j < macroBlockCols + 1; j++) { byte yMode = mip[i * (macroBlockRows + 1) + j].mbmi.mode; byte uvMode = mip[i * (macroBlockRows + 1) + j].mbmi.uv_mode; - rowStr += $"y={yMode}, uvMode={uvMode} | "; + rowBuilder.Append("y=").Append(yMode).Append(", uvMode=").Append(uvMode).Append(" | "); } + string rowStr = rowBuilder.ToString(); Debug.WriteLine(rowStr); } @@ -54,15 +56,20 @@ public static void DumpMotionVectors(MODE_INFO[] mip, int macroBlockCols, int ma public static string GetBModeInfoMatrix(b_mode_info[] bModes) { // The array will always be 16 elements. - string matrixStr = null; + var matrixBuilder = new StringBuilder(); for(int row=0; row<4; row++) { - matrixStr += $"[{bModes[row * 4].mv.as_int},{bModes[row * 4 + 1].mv.as_int}" + - $",{bModes[row * 4 +2].mv.as_int},{bModes[row * 4 + 3].mv.as_int}]\n"; + matrixBuilder + .Append('[') + .Append(bModes[row * 4].mv.as_int).Append(',') + .Append(bModes[row * 4 + 1].mv.as_int).Append(',') + .Append(bModes[row * 4 + 2].mv.as_int).Append(',') + .Append(bModes[row * 4 + 3].mv.as_int) + .Append("]\n"); } - return matrixStr; + return matrixBuilder.ToString(); } public static unsafe void DumpMacroBlock(MACROBLOCKD macroBlock, int macroBlockIndex) @@ -89,11 +96,12 @@ public static unsafe void DumpSubBlockCoefficients(MACROBLOCKD macroBlock) for(int i=0; i< macroBlock.block.Length; i++) { var subBlock = macroBlock.block[i]; - string qCoeff = null; + var qCoeffBuilder = new StringBuilder(); for(int j=subBlock.qcoeff.Index; j< subBlock.qcoeff.Index+16; j++) { - qCoeff += subBlock.qcoeff.src()[j].ToString() + ","; + qCoeffBuilder.Append(subBlock.qcoeff.src()[j]).Append(','); } + string qCoeff = qCoeffBuilder.ToString(); Debug.WriteLine($"block[{i}].qcoeff={qCoeff}"); } @@ -104,11 +112,12 @@ public static unsafe void DumpSubBlockCoefficients(MACROBLOCKD macroBlock) for (int i = 0; i < macroBlock.block.Length; i++) { var subBlock = macroBlock.block[i]; - string dqCoeff = null; + var dqCoeffBuilder = new StringBuilder(); for (int j = subBlock.dqcoeff.Index; j < subBlock.dqcoeff.Index + 16; j++) { - dqCoeff += subBlock.dqcoeff.src()[j].ToString() + ","; + dqCoeffBuilder.Append(subBlock.dqcoeff.src()[j]).Append(','); } + string dqCoeff = dqCoeffBuilder.ToString(); Debug.WriteLine($"block[{i}].dqcoeff={dqCoeff}"); } @@ -160,21 +169,21 @@ public static class DebugProbeHexStr public unsafe static string ToHexStr(byte* buffer, int length, char? separator = null) { - string rv = string.Empty; + var builder = new StringBuilder(length * (separator == null ? 2 : 3)); for (int i = 0; i < length; i++) { var val = buffer[i]; - rv += hexmap[val >> 4]; - rv += hexmap[val & 15]; + builder.Append(hexmap[val >> 4]); + builder.Append(hexmap[val & 15]); if (separator != null && i != length - 1) { - rv += separator; + builder.Append(separator); } } - return rv.ToLower(); + return builder.ToString().ToLower(); } } } diff --git a/src/SIPSorcery.VP8/VP8Codec.cs b/src/SIPSorcery.VP8/VP8Codec.cs index 07d348aee7..db0e5c41a7 100644 --- a/src/SIPSorcery.VP8/VP8Codec.cs +++ b/src/SIPSorcery.VP8/VP8Codec.cs @@ -155,7 +155,7 @@ public byte[] EncodeVideo(int width, int height, byte[] sample, VideoPixelFormat // The foundation encoder requires multiples of 16 — no // padding/cropping support yet. throw new NotSupportedException( - "Width and height must be positive multiples of 16. Got " + width + "x" + height + "."); + $"Width and height must be positive multiples of 16. Got {width}x{height}."); } // Convert the input sample into planar I420. @@ -168,8 +168,7 @@ public byte[] EncodeVideo(int width, int height, byte[] sample, VideoPixelFormat if (i420.Length != ySize + 2 * cSize) { throw new ArgumentException( - "I420 buffer length " + i420.Length + " does not match expected " + - (ySize + 2 * cSize) + " for " + width + "x" + height + "."); + $"I420 buffer length {i420.Length} does not match expected {ySize + 2 * cSize} for {width}x{height}."); } if (_srcY == null || _srcY.Length < ySize) { _srcY = new byte[ySize]; } @@ -228,7 +227,7 @@ public unsafe IEnumerable DecodeVideo(byte[] frame, VideoPixelForma //logger.LogDebug($"VP8 decode result {result}."); if (result != vpx_codec_err_t.VPX_CODEC_OK) { - logger.LogWarning($"VP8 decode of video sample failed with {result}."); + logger.LogWarning("VP8 decode of video sample failed with {Result}.", result); } } diff --git a/src/SIPSorcery/net/DtlsSrtp/Lib/DTLS/DtlsClient.cs b/src/SIPSorcery/net/DtlsSrtp/Lib/DTLS/DtlsClient.cs index 2eaae06132..eecf156726 100644 --- a/src/SIPSorcery/net/DtlsSrtp/Lib/DTLS/DtlsClient.cs +++ b/src/SIPSorcery/net/DtlsSrtp/Lib/DTLS/DtlsClient.cs @@ -171,13 +171,14 @@ public override void NotifyAlertRaised(short alertLevel, short alertDescription, { if (Log.DebugEnabled) { - Log.Debug("DTLS client raised alert: " + AlertLevel.GetText(alertLevel) + ", " + AlertDescription.GetText(alertDescription)); + Log.Debug("DTLS client raised alert: {AlertLevel}, {AlertDescription}.", + AlertLevel.GetText(alertLevel), AlertDescription.GetText(alertDescription)); } if (message != null) { if (Log.DebugEnabled) { - Log.Debug("> " + message); + Log.Debug("> {Message}", message); } } if (cause != null) @@ -193,7 +194,8 @@ public override void NotifyAlertReceived(short level, short alertDescription) { if (Log.DebugEnabled) { - Log.Debug("DTLS client received alert: " + AlertLevel.GetText(level) + ", " + AlertDescription.GetText(alertDescription)); + Log.Debug("DTLS client received alert: {AlertLevel}, {AlertDescription}.", + AlertLevel.GetText(level), AlertDescription.GetText(alertDescription)); } TlsAlertTypesEnum alertType = TlsAlertTypesEnum.Unassigned; @@ -217,7 +219,7 @@ public override void NotifyServerVersion(ProtocolVersion serverVersion) if (Log.DebugEnabled) { - Log.Debug("DTLS client negotiated " + serverVersion); + Log.Debug("DTLS client negotiated {ServerVersion}.", serverVersion); } } @@ -235,7 +237,7 @@ public override void NotifyHandshakeComplete() { if (Log.DebugEnabled) { - Log.Debug("Client ALPN: " + protocolName.GetUtf8Decoding()); + Log.Debug("Client ALPN: {ApplicationProtocol}.", protocolName.GetUtf8Decoding()); } } @@ -251,14 +253,14 @@ public override void NotifyHandshakeComplete() { if (Log.DebugEnabled) { - Log.Debug("Client resumed session: " + hex); + Log.Debug("Client resumed session: {SessionIdHex}.", hex); } } else { if (Log.DebugEnabled) { - Log.Debug("Client established session: " + hex); + Log.Debug("Client established session: {SessionIdHex}.", hex); } } @@ -270,14 +272,14 @@ public override void NotifyHandshakeComplete() { if (Log.DebugEnabled) { - Log.Debug("Client 'tls-server-end-point': " + ToHexString(tlsServerEndPoint)); + Log.Debug("Client 'tls-server-end-point': {TlsServerEndPoint}.", ToHexString(tlsServerEndPoint)); } } byte[] tlsUnique = m_context.ExportChannelBinding(ChannelBinding.tls_unique); if (Log.DebugEnabled) { - Log.Debug("Client 'tls-unique': " + ToHexString(tlsUnique)); + Log.Debug("Client 'tls-unique': {TlsUnique}.", ToHexString(tlsUnique)); } } @@ -326,14 +328,15 @@ public void NotifyServerCertificate(TlsServerCertificate serverCertificate) if (Log.DebugEnabled) { - Log.Debug("DTLS client received server certificate chain of length " + chain.Length); + Log.Debug("DTLS client received server certificate chain of length {CertificateCount}.", chain.Length); } for (int i = 0; i != chain.Length; i++) { X509CertificateStructure entry = X509CertificateStructure.GetInstance(chain[i].GetEncoded()); if (Log.DebugEnabled) { - Log.Debug("DTLS client fingerprint:SHA-256 " + DtlsCertificateUtils.Fingerprint(entry) + " (" + entry.Subject + ")"); + Log.Debug("DTLS client fingerprint:SHA-256 {Fingerprint} ({Subject}).", + DtlsCertificateUtils.Fingerprint(entry), entry.Subject); } } diff --git a/src/SIPSorcery/net/DtlsSrtp/Lib/DTLS/DtlsServer.cs b/src/SIPSorcery/net/DtlsSrtp/Lib/DTLS/DtlsServer.cs index 6b1b91262e..cc1e20ee9c 100644 --- a/src/SIPSorcery/net/DtlsSrtp/Lib/DTLS/DtlsServer.cs +++ b/src/SIPSorcery/net/DtlsSrtp/Lib/DTLS/DtlsServer.cs @@ -164,14 +164,15 @@ public override void NotifyAlertRaised(short alertLevel, short alertDescription, { if (Log.DebugEnabled) { - Log.Debug("DTLS server raised alert: " + AlertLevel.GetText(alertLevel) + ", " + AlertDescription.GetText(alertDescription)); + Log.Debug("DTLS server raised alert: {AlertLevel}, {AlertDescription}.", + AlertLevel.GetText(alertLevel), AlertDescription.GetText(alertDescription)); } if (message != null) { if (Log.DebugEnabled) { - Log.Debug("> " + message); + Log.Debug("> {Message}", message); } } if (cause != null) @@ -187,7 +188,8 @@ public override void NotifyAlertReceived(short level, short alertDescription) { if (Log.DebugEnabled) { - Log.Debug("DTLS server received alert: " + AlertLevel.GetText(level) + ", " + AlertDescription.GetText(alertDescription)); + Log.Debug("DTLS server received alert: {AlertLevel}, {AlertDescription}.", + AlertLevel.GetText(level), AlertDescription.GetText(alertDescription)); } TlsAlertTypesEnum alertType = TlsAlertTypesEnum.Unassigned; @@ -210,7 +212,7 @@ public override ProtocolVersion GetServerVersion() ProtocolVersion serverVersion = base.GetServerVersion(); if (Log.DebugEnabled) { - Log.Debug("DTLS server negotiated " + serverVersion); + Log.Debug("DTLS server negotiated {ServerVersion}.", serverVersion); } return serverVersion; } @@ -239,7 +241,7 @@ public override void NotifyClientCertificate(Certificate clientCertificate) if (Log.DebugEnabled) { - Log.Debug("DTLS server received client certificate chain of length " + chain.Length); + Log.Debug("DTLS server received client certificate chain of length {CertificateCount}.", chain.Length); } for (int i = 0; i != chain.Length; i++) @@ -247,7 +249,8 @@ public override void NotifyClientCertificate(Certificate clientCertificate) X509CertificateStructure entry = X509CertificateStructure.GetInstance(chain[i].GetEncoded()); if (Log.DebugEnabled) { - Log.Debug(" fingerprint:SHA-256 " + DtlsCertificateUtils.Fingerprint(entry) + " (" + entry.Subject + ")"); + Log.Debug("fingerprint:SHA-256 {Fingerprint} ({Subject}).", + DtlsCertificateUtils.Fingerprint(entry), entry.Subject); } } } @@ -261,20 +264,20 @@ public override void NotifyHandshakeComplete() { if (Log.DebugEnabled) { - Log.Debug("Server ALPN: " + protocolName.GetUtf8Decoding()); + Log.Debug("Server ALPN: {ApplicationProtocol}.", protocolName.GetUtf8Decoding()); } } byte[] tlsServerEndPoint = m_context.ExportChannelBinding(ChannelBinding.tls_server_end_point); if (Log.DebugEnabled) { - Log.Debug("Server 'tls-server-end-point': " + ToHexString(tlsServerEndPoint)); + Log.Debug("Server 'tls-server-end-point': {TlsServerEndPoint}.", ToHexString(tlsServerEndPoint)); } byte[] tlsUnique = m_context.ExportChannelBinding(ChannelBinding.tls_unique); if (Log.DebugEnabled) { - Log.Debug("Server 'tls-unique': " + ToHexString(tlsUnique)); + Log.Debug("Server 'tls-unique': {TlsUnique}.", ToHexString(tlsUnique)); } OnHandshakeCompleted?.Invoke(this, new DtlsHandshakeCompletedEventArgs(m_context.SecurityParameters)); diff --git a/src/SIPSorcery/net/DtlsSrtp/Lib/Log.cs b/src/SIPSorcery/net/DtlsSrtp/Lib/Log.cs index 765fa095f7..fb4e5bd7d9 100644 --- a/src/SIPSorcery/net/DtlsSrtp/Lib/Log.cs +++ b/src/SIPSorcery/net/DtlsSrtp/Lib/Log.cs @@ -20,6 +20,8 @@ // SOFTWARE. using System; +using System.Globalization; +using System.Text.RegularExpressions; namespace SIPSorcery.Net.SharpSRTP { @@ -53,6 +55,11 @@ public static void Debug(string message, Exception ex = null) SinkDebug(message, ex); } + public static void Debug(string messageTemplate, params object[] args) + { + SinkDebug(FormatTemplate(messageTemplate, args), null); + } + public static bool InfoEnabled { get; set; } #if DEBUG = true; @@ -67,5 +74,19 @@ public static void Info(string message, Exception ex = null) public static Action SinkTrace = new Action((m, ex) => { System.Diagnostics.Debug.WriteLine(m); }); public static Action SinkDebug = new Action((m, ex) => { System.Diagnostics.Debug.WriteLine(m); }); public static Action SinkInfo = new Action((m, ex) => { System.Diagnostics.Debug.WriteLine(m); }); + + private static readonly Regex MessageTemplateRegex = new Regex(@"(? $"{{{index++}}}"); + return string.Format(CultureInfo.InvariantCulture, compositeFormat, args); + } } } diff --git a/src/SIPSorcery/net/ICE/RTCIceCandidate.cs b/src/SIPSorcery/net/ICE/RTCIceCandidate.cs index 604637dc87..8c869775a3 100644 --- a/src/SIPSorcery/net/ICE/RTCIceCandidate.cs +++ b/src/SIPSorcery/net/ICE/RTCIceCandidate.cs @@ -248,24 +248,11 @@ public override string ToString() string candidateStr; if (protocol == RTCIceProtocol.tcp) { - candidateStr = String.Format("{0} {1} tcp {2} {3} {4} typ {5} tcptype {6} generation 0", - foundation, - component.GetHashCode(), - priority, - address, - port, - type, - tcpType); + candidateStr = $"{foundation} {component.GetHashCode()} tcp {priority} {address} {port} typ {type} tcptype {tcpType} generation 0"; } else { - candidateStr = String.Format("{0} {1} udp {2} {3} {4} typ {5} generation 0", - foundation, - component.GetHashCode(), - priority, - address, - port, - type); + candidateStr = $"{foundation} {component.GetHashCode()} udp {priority} {address} {port} typ {type} generation 0"; } return candidateStr; @@ -282,28 +269,11 @@ public override string ToString() string candidateStr; if (protocol == RTCIceProtocol.tcp) { - candidateStr = String.Format("{0} {1} tcp {2} {3} {4} typ {5} tcptype {6} raddr {7} rport {8} generation 0", - foundation, - component.GetHashCode(), - priority, - address, - port, - type, - tcpType, - relAddr, - relatedPort); + candidateStr = $"{foundation} {component.GetHashCode()} tcp {priority} {address} {port} typ {type} tcptype {tcpType} raddr {relAddr} rport {relatedPort} generation 0"; } else { - candidateStr = String.Format("{0} {1} udp {2} {3} {4} typ {5} raddr {6} rport {7} generation 0", - foundation, - component.GetHashCode(), - priority, - address, - port, - type, - relAddr, - relatedPort); + candidateStr = $"{foundation} {component.GetHashCode()} udp {priority} {address} {port} typ {type} raddr {relAddr} rport {relatedPort} generation 0"; } return candidateStr; @@ -383,7 +353,7 @@ public string toJSON() sdpMid = sdpMid ?? sdpMLineIndex.ToString(), sdpMLineIndex = sdpMLineIndex, usernameFragment = usernameFragment, - candidate = CANDIDATE_PREFIX + ":" + this.ToString() + candidate = $"{CANDIDATE_PREFIX}:{this}" }; return rtcCandInit.toJSON(); diff --git a/src/SIPSorcery/net/ICE/RtpIceChannel.cs b/src/SIPSorcery/net/ICE/RtpIceChannel.cs index fdae5ebd3b..29bc9d4864 100644 --- a/src/SIPSorcery/net/ICE/RtpIceChannel.cs +++ b/src/SIPSorcery/net/ICE/RtpIceChannel.cs @@ -1121,7 +1121,7 @@ private void RefreshTurn(Object state) } catch (Exception excp) { - logger.LogError(excp, "Exception " + nameof(RefreshTurn) + ". {ErrorMessage}", excp); + logger.LogError(excp, "Exception in {Method}.", nameof(RefreshTurn)); } } @@ -1308,12 +1308,12 @@ private async Task UpdateChecklist(RTCIceCandidate localCandidate, RTCIceCandida { if (localCandidate == null) { - logger.LogError(nameof(UpdateChecklist) + " the local candidate supplied to UpdateChecklist was null."); + logger.LogError("{Method} the local candidate supplied to UpdateChecklist was null.", nameof(UpdateChecklist)); return; } else if (remoteCandidate == null) { - logger.LogError(nameof(UpdateChecklist) + " the remote candidate supplied to UpdateChecklist was null."); + logger.LogError("{Method} the remote candidate supplied to UpdateChecklist was null.", nameof(UpdateChecklist)); return; } @@ -1323,7 +1323,7 @@ private async Task UpdateChecklist(RTCIceCandidate localCandidate, RTCIceCandida // Attempt to resolve the remote candidate address. if (!IPAddress.TryParse(remoteCandidate.address, out var remoteCandidateIPAddr)) { - if (remoteCandidate.address.ToLower().EndsWith(MDNS_TLD)) + if (remoteCandidate.address.EndsWith(MDNS_TLD, StringComparison.OrdinalIgnoreCase)) { var addresses = await ResolveMdnsName(remoteCandidate).ConfigureAwait(false); if (addresses.Length == 0) @@ -1418,7 +1418,7 @@ private async Task UpdateChecklist(RTCIceCandidate localCandidate, RTCIceCandida } catch (Exception excp) { - logger.LogError(excp, "Exception " + nameof(UpdateChecklist) + ". {ErrorMessage}", excp); + logger.LogError(excp, "Exception in {Method}.", nameof(UpdateChecklist)); } } @@ -1760,7 +1760,7 @@ private void SendSTUNBindingRequest(ChecklistEntry candidatePair, bool setUseCan { STUNMessage stunRequest = new STUNMessage(STUNMessageTypesEnum.BindingRequest); stunRequest.Header.TransactionId = Encoding.ASCII.GetBytes(candidatePair.RequestTransactionID); - stunRequest.AddUsernameAttribute(RemoteIceUser + ":" + LocalIceUser); + stunRequest.AddUsernameAttribute($"{RemoteIceUser}:{LocalIceUser}"); stunRequest.Attributes.Add(new STUNAttribute(STUNAttributeTypesEnum.Priority, BitConverter.GetBytes(candidatePair.LocalPriority))); if (IsController) @@ -2725,7 +2725,8 @@ private async Task ResolveMdnsName(RTCIceCandidate candidate) { if (MdnsResolve != null) { - logger.LogWarning("RTP ICE channel has both "+ nameof(MdnsGetAddresses) + " and " + nameof(MdnsGetAddresses) + " set. Only " + nameof(MdnsGetAddresses) + " will be used."); + logger.LogWarning("RTP ICE channel has both {PrimaryResolver} and {SecondaryResolver} set. Only {SelectedResolver} will be used.", + nameof(MdnsGetAddresses), nameof(MdnsResolve), nameof(MdnsGetAddresses)); } return await MdnsGetAddresses(candidate.address).ConfigureAwait(false); } diff --git a/src/SIPSorcery/net/RTCP/RTCPFeedback.cs b/src/SIPSorcery/net/RTCP/RTCPFeedback.cs index ba598d29f2..8f6542705f 100644 --- a/src/SIPSorcery/net/RTCP/RTCPFeedback.cs +++ b/src/SIPSorcery/net/RTCP/RTCPFeedback.cs @@ -172,23 +172,23 @@ public RTCPFeedback(byte[] packet) switch (Header) { - case var x when x.PacketType == RTCPReportTypesEnum.RTPFB && x.FeedbackMessageType == RTCPFeedbackTypesEnum.RTCP_SR_REQ: + case var _ when Header.PacketType == RTCPReportTypesEnum.RTPFB && Header.FeedbackMessageType == RTCPFeedbackTypesEnum.RTCP_SR_REQ: SENDER_PAYLOAD_SIZE = 8; // PLI feedback reports do no have any additional parameters. break; - case var x when x.PacketType == RTCPReportTypesEnum.RTPFB: + case var _ when Header.PacketType == RTCPReportTypesEnum.RTPFB: SENDER_PAYLOAD_SIZE = 12; PID = BinaryPrimitives.ReadUInt16BigEndian(packet.AsSpan(payloadIndex + 8)); BLP = BinaryPrimitives.ReadUInt16BigEndian(packet.AsSpan(payloadIndex + 10)); break; - case var x when x.PacketType == RTCPReportTypesEnum.PSFB && x.PayloadFeedbackMessageType == PSFBFeedbackTypesEnum.PLI: + case var _ when Header.PacketType == RTCPReportTypesEnum.PSFB && Header.PayloadFeedbackMessageType == PSFBFeedbackTypesEnum.PLI: SENDER_PAYLOAD_SIZE = 8; break; // We have a lot of different kind of extension messages // In case below we will handle the specific REMB Message https://datatracker.ietf.org/doc/html/draft-alvestrand-rmcat-remb-03#page-3 - case var x when x.PacketType == RTCPReportTypesEnum.PSFB && x.PayloadFeedbackMessageType == PSFBFeedbackTypesEnum.AFB: + case var _ when Header.PacketType == RTCPReportTypesEnum.PSFB && Header.PayloadFeedbackMessageType == PSFBFeedbackTypesEnum.AFB: // 0 1 2 3 // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 @@ -267,17 +267,17 @@ public byte[] GetBytes() switch (Header) { - case var x when x.PacketType == RTCPReportTypesEnum.RTPFB && x.FeedbackMessageType == RTCPFeedbackTypesEnum.RTCP_SR_REQ: + case var _ when Header.PacketType == RTCPReportTypesEnum.RTPFB && Header.FeedbackMessageType == RTCPFeedbackTypesEnum.RTCP_SR_REQ: // PLI feedback reports do no have any additional parameters. break; - case var x when x.PacketType == RTCPReportTypesEnum.RTPFB: + case var _ when Header.PacketType == RTCPReportTypesEnum.RTPFB: BinaryPrimitives.WriteUInt16BigEndian(buffer.AsSpan(payloadIndex + 8), PID); BinaryPrimitives.WriteUInt16BigEndian(buffer.AsSpan(payloadIndex + 10), BLP); break; - case var x when x.PacketType == RTCPReportTypesEnum.PSFB && x.PayloadFeedbackMessageType == PSFBFeedbackTypesEnum.PLI: + case var _ when Header.PacketType == RTCPReportTypesEnum.PSFB && Header.PayloadFeedbackMessageType == PSFBFeedbackTypesEnum.PLI: break; - case var x when x.PacketType == RTCPReportTypesEnum.PSFB && x.PayloadFeedbackMessageType == PSFBFeedbackTypesEnum.AFB: + case var _ when Header.PacketType == RTCPReportTypesEnum.PSFB && Header.PayloadFeedbackMessageType == PSFBFeedbackTypesEnum.AFB: // There's have a lot of different kind of extension messages // In case below we will handle the specific REMB Message https://datatracker.ietf.org/doc/html/draft-alvestrand-rmcat-remb-03#page-3 diff --git a/src/SIPSorcery/net/RTCP/RTCPHeader.cs b/src/SIPSorcery/net/RTCP/RTCPHeader.cs index ec59cfa53c..9ea82cbef9 100644 --- a/src/SIPSorcery/net/RTCP/RTCPHeader.cs +++ b/src/SIPSorcery/net/RTCP/RTCPHeader.cs @@ -177,7 +177,7 @@ public byte[] GetHeader(int receptionReportCount, UInt16 length) { if (receptionReportCount > MAX_RECEPTIONREPORT_COUNT) { - throw new ApplicationException("The Reception Report Count value cannot be larger than " + MAX_RECEPTIONREPORT_COUNT + "."); + throw new ApplicationException($"The Reception Report Count value cannot be larger than {MAX_RECEPTIONREPORT_COUNT}."); } ReceptionReportCount = receptionReportCount; diff --git a/src/SIPSorcery/net/RTCP/RTCPTWCCFeedback.cs b/src/SIPSorcery/net/RTCP/RTCPTWCCFeedback.cs index 319f8e1449..26be2150bd 100644 --- a/src/SIPSorcery/net/RTCP/RTCPTWCCFeedback.cs +++ b/src/SIPSorcery/net/RTCP/RTCPTWCCFeedback.cs @@ -524,9 +524,7 @@ public override string ToString() var packetStatusInfo = string.Join(", ", PacketStatuses.Select(ps => $"Seq:{ps.SequenceNumber}({ps.Status}{(ps.Delta.HasValue ? $",Δ:{ps.Delta.Value}" : "")})")); - return $"TWCC Feedback: SenderSSRC={SenderSSRC}, MediaSSRC={MediaSSRC}, BaseSeq={BaseSequenceNumber}, " + - $"StatusCount={PacketStatusCount}, RefTime={ReferenceTime} (1/64 sec), " + - $"FbkPktCount={FeedbackPacketCount}, PacketStatuses=[{packetStatusInfo}]"; + return $"TWCC Feedback: SenderSSRC={SenderSSRC}, MediaSSRC={MediaSSRC}, BaseSeq={BaseSequenceNumber}, StatusCount={PacketStatusCount}, RefTime={ReferenceTime} (1/64 sec), FbkPktCount={FeedbackPacketCount}, PacketStatuses=[{packetStatusInfo}]"; } #region Helper Methods diff --git a/src/SIPSorcery/net/RTP/RTPHeaderExtensions/TransportWideCCExtension.cs b/src/SIPSorcery/net/RTP/RTPHeaderExtensions/TransportWideCCExtension.cs index cd6138f00a..91a333340f 100644 --- a/src/SIPSorcery/net/RTP/RTPHeaderExtensions/TransportWideCCExtension.cs +++ b/src/SIPSorcery/net/RTP/RTPHeaderExtensions/TransportWideCCExtension.cs @@ -37,14 +37,9 @@ public class TransportWideCCExtension : RTPHeaderExtension public override bool MatchesExtension(string uri) { - switch (uri.ToLower()) - { - case RTP_HEADER_EXTENSION_URI: - case "urn:ietf:params:rtp-hdrext:transport-wide-cc": //official urn registered with IANA - case "http://www.webrtc.org/experiments/rtp-hdrext/transport-wide-cc-02": - return true; - } - return false; + return string.Equals(uri, RTP_HEADER_EXTENSION_URI, StringComparison.OrdinalIgnoreCase) || + string.Equals(uri, "urn:ietf:params:rtp-hdrext:transport-wide-cc", StringComparison.OrdinalIgnoreCase) || + string.Equals(uri, "http://www.webrtc.org/experiments/rtp-hdrext/transport-wide-cc-02", StringComparison.OrdinalIgnoreCase); } diff --git a/src/SIPSorcery/net/RTP/RTPSession.cs b/src/SIPSorcery/net/RTP/RTPSession.cs index 398d1d48ca..0eeb8797b9 100644 --- a/src/SIPSorcery/net/RTP/RTPSession.cs +++ b/src/SIPSorcery/net/RTP/RTPSession.cs @@ -24,6 +24,7 @@ using System.Collections.Generic; using System.Linq; using System.Net; +using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; @@ -666,35 +667,36 @@ protected void AddRemoteSDPSsrcAttributes(SDPMediaTypesEnum mediaType, List x.Name().ToLower() == SDP.TELEPHONE_EVENT_ATTRIBUTE)) + if (currentLocalTrackCapabilities.Any(x => string.Equals(x.Name(), SDP.TELEPHONE_EVENT_ATTRIBUTE, StringComparison.OrdinalIgnoreCase))) { - localRTPEventCapabilities = currentLocalTrackCapabilities.First(x => x.Name().ToLower() == SDP.TELEPHONE_EVENT_ATTRIBUTE); + localRTPEventCapabilities = currentLocalTrackCapabilities.First(x => string.Equals(x.Name(), SDP.TELEPHONE_EVENT_ATTRIBUTE, StringComparison.OrdinalIgnoreCase)); } else { localRTPEventCapabilities = MediaStream.DefaultRTPEventFormat; } - currentMediaStream.LocalTrack.Capabilities = capabilities.Where(x => x.Name().ToLower() != SDP.TELEPHONE_EVENT_ATTRIBUTE).ToList(); + currentMediaStream.LocalTrack.Capabilities = capabilities.Where(x => !string.Equals(x.Name(), SDP.TELEPHONE_EVENT_ATTRIBUTE, StringComparison.OrdinalIgnoreCase)).ToList(); if (localRTPEventCapabilities != null) { currentMediaStream.LocalTrack.Capabilities.Add(localRTPEventCapabilities.Value); @@ -1251,7 +1253,7 @@ public virtual SetDescriptionResultEnum SetRemoteDescription(SdpType sdpType, SD if (!commonEventFormat.IsEmpty()) { currentMediaStream.NegotiatedRtpEventPayloadID = commonEventFormat.ID; - currentMediaStream.LocalTrack.Capabilities.RemoveAll(x => x.Name().ToLower() == SDP.TELEPHONE_EVENT_ATTRIBUTE); + currentMediaStream.LocalTrack.Capabilities.RemoveAll(x => string.Equals(x.Name(), SDP.TELEPHONE_EVENT_ATTRIBUTE, StringComparison.OrdinalIgnoreCase)); currentMediaStream.LocalTrack.Capabilities.Add(commonEventFormat); } } @@ -1295,7 +1297,7 @@ public virtual SetDescriptionResultEnum SetRemoteDescription(SdpType sdpType, SD if (currentMediaStream.MediaType == SDPMediaTypesEnum.audio) { - if (capabilities?.Where(x => x.Name().ToLower() != SDP.TELEPHONE_EVENT_ATTRIBUTE).Count() == 0) + if (capabilities?.Where(x => !string.Equals(x.Name(), SDP.TELEPHONE_EVENT_ATTRIBUTE, StringComparison.OrdinalIgnoreCase)).Count() == 0) { return SetDescriptionResultEnum.AudioIncompatible; } diff --git a/src/SIPSorcery/net/RTP/Streams/AudioStream.cs b/src/SIPSorcery/net/RTP/Streams/AudioStream.cs index 7aa4702b9c..7b40b65618 100644 --- a/src/SIPSorcery/net/RTP/Streams/AudioStream.cs +++ b/src/SIPSorcery/net/RTP/Streams/AudioStream.cs @@ -201,7 +201,7 @@ public async Task SendDtmfEvent(RTPEvent rtpEvent, CancellationToken cancellatio { if (rtpEventInProgress) { - logger.LogWarning(nameof(SendDtmfEvent) + " an RTPEvent is already in progress."); + logger.LogWarning("{Method} an RTPEvent is already in progress.", nameof(SendDtmfEvent)); return; } @@ -285,12 +285,12 @@ public virtual Task SendDtmf(byte key, CancellationToken ct) public void CheckAudioFormatsNegotiation() { if (LocalTrack != null && - LocalTrack.Capabilities.Where(x => x.Name().ToLower() != SDP.TELEPHONE_EVENT_ATTRIBUTE).Count() > 0) + LocalTrack.Capabilities.Where(x => !string.Equals(x.Name(), SDP.TELEPHONE_EVENT_ATTRIBUTE, StringComparison.OrdinalIgnoreCase)).Count() > 0) { OnAudioFormatsNegotiatedByIndex?.Invoke( Index, LocalTrack.Capabilities - .Where(x => x.Name().ToLower() != SDP.TELEPHONE_EVENT_ATTRIBUTE) + .Where(x => !string.Equals(x.Name(), SDP.TELEPHONE_EVENT_ATTRIBUTE, StringComparison.OrdinalIgnoreCase)) .Select(x => x.ToAudioFormat()).ToList()); } } diff --git a/src/SIPSorcery/net/RTSP/RTSPClient.cs b/src/SIPSorcery/net/RTSP/RTSPClient.cs index b6adf8a70a..e7db300ee4 100644 --- a/src/SIPSorcery/net/RTSP/RTSPClient.cs +++ b/src/SIPSorcery/net/RTSP/RTSPClient.cs @@ -89,7 +89,7 @@ public string GetStreamDescription(string url) string hostname = Regex.Match(url, @"rtsp://(?\S+?)/").Result("${hostname}"); //IPEndPoint rtspEndPoint = DNSResolver.R(hostname, DNS_RESOLUTION_TIMEOUT); - logger.LogDebug("RTSP Client Connecting to " + hostname + "."); + logger.LogDebug("RTSP Client connecting to {Hostname}.", hostname); TcpClient rtspSocket = new TcpClient(hostname, RTSP_PORT); NetworkStream rtspStream = rtspSocket.GetStream(); @@ -118,14 +118,14 @@ public string GetStreamDescription(string url) if (rtspMessage.RTSPMessageType == RTSPMessageTypesEnum.Response) { rtspResponse = RTSPResponse.ParseRTSPResponse(rtspMessage); - logger.LogDebug("RTSP Response received: " + rtspResponse.StatusCode + " " + rtspResponse.Status + " " + rtspResponse.ReasonPhrase + "."); + logger.LogDebug("RTSP Response received: {StatusCode} {Status} {ReasonPhrase}.", rtspResponse.StatusCode, rtspResponse.Status, rtspResponse.ReasonPhrase); } rtspSDP = rtspResponse.Body; } else { - logger.LogWarning("Socket closed prematurely in GetStreamDescription for " + url + "."); + logger.LogWarning("Socket closed prematurely in {Method} for {Url}.", nameof(GetStreamDescription), url); } rtspSocket.Close(); @@ -134,7 +134,7 @@ public string GetStreamDescription(string url) } catch (Exception excp) { - logger.LogError("Exception GetStreamDescription. " + excp.Message); + logger.LogError(excp, "Exception in {Method}.", nameof(GetStreamDescription)); throw excp; } } @@ -147,7 +147,7 @@ public void Start(string url) if (!urlMatch.Success) { - throw new ApplicationException("The URL provided to the RTSP client was not recognised, " + url + "."); + throw new ApplicationException($"The URL provided to the RTSP client was not recognised, {url}."); } else { @@ -160,7 +160,7 @@ public void Start(string url) hostname = SIPSorcery.Sys.IPSocket.ParseHostFromSocket(hostname); } - logger.LogDebug("RTSP client connecting to " + hostname + ", port " + port + "."); + logger.LogDebug("RTSP client connecting to {Hostname}, port {Port}.", hostname, port); _rtspConnection = new TcpClient(hostname, port); _rtspStream = _rtspConnection.GetStream(); @@ -172,7 +172,7 @@ public void Start(string url) RTSPRequest rtspRequest = new RTSPRequest(RTSPMethodsEnum.SETUP, url); RTSPHeader rtspHeader = new RTSPHeader(_cseq++, null); - rtspHeader.Transport = new RTSPTransportHeader() { ClientRTPPortRange = _rtspSession.RTPPort + "-" + _rtspSession.ControlPort }; + rtspHeader.Transport = new RTSPTransportHeader() { ClientRTPPortRange = $"{_rtspSession.RTPPort}-{_rtspSession.ControlPort}" }; rtspRequest.Header = rtspHeader; string rtspReqStr = rtspRequest.ToString(); @@ -202,7 +202,8 @@ public void Start(string url) _rtspSession.RemoteEndPoint = new IPEndPoint((_rtspConnection.Client.RemoteEndPoint as IPEndPoint).Address, setupResponse.Header.Transport.GetServerRTPPort()); _rtspSession.Start(); - logger.LogDebug("RTSP Response received to SETUP: " + setupResponse.Status + ", session ID " + _rtspSession.SessionID + ", server RTP endpoint " + _rtspSession.RemoteEndPoint + "."); + logger.LogDebug("RTSP Response received to SETUP: {Status}, session ID {SessionId}, server RTP endpoint {RemoteEndPoint}.", + setupResponse.Status, _rtspSession.SessionID, _rtspSession.RemoteEndPoint); if (OnSetupSuccess != null) { @@ -211,8 +212,8 @@ public void Start(string url) } else { - logger.LogWarning("RTSP Response received to SETUP: " + setupResponse.Status + "."); - throw new ApplicationException("An error response of " + setupResponse.Status + " was received for an RTSP setup request."); + logger.LogWarning("RTSP Response received to SETUP: {Status}.", setupResponse.Status); + throw new ApplicationException($"An error response of {setupResponse.Status} was received for an RTSP setup request."); } } } @@ -239,7 +240,7 @@ private void RTPQueueFull() } catch (Exception excp) { - logger.LogError("Exception RTSPClient.RTPQueueFull. " + excp); + logger.LogError(excp, "Exception in {Method}.", nameof(RTPQueueFull)); } } @@ -274,7 +275,7 @@ public void Play() if (rtspMessage.RTSPMessageType == RTSPMessageTypesEnum.Response) { var playResponse = RTSPResponse.ParseRTSPResponse(rtspMessage); - logger.LogDebug("RTSP Response received to PLAY: " + playResponse.StatusCode + " " + playResponse.Status + " " + playResponse.ReasonPhrase + "."); + logger.LogDebug("RTSP Response received to PLAY: {StatusCode} {Status} {ReasonPhrase}.", playResponse.StatusCode, playResponse.Status, playResponse.ReasonPhrase); } } else @@ -292,7 +293,7 @@ private void Teardown() { if (_rtspStream != null && _rtspConnection.Connected) { - logger.LogDebug("RTSP client sending teardown request for " + _url + "."); + logger.LogDebug("RTSP client sending teardown request for {Url}.", _url); RTSPRequest teardownRequest = new RTSPRequest(RTSPMethodsEnum.TEARDOWN, _url); RTSPHeader teardownHeader = new RTSPHeader(_cseq++, _rtspSession.SessionID); @@ -305,12 +306,12 @@ private void Teardown() } else { - logger.LogDebug("RTSP client did not send teardown request for " + _url + ", the socket was closed."); + logger.LogDebug("RTSP client did not send teardown request for {Url}, the socket was closed.", _url); } } catch (Exception excp) { - logger.LogError("Exception RTSPClient.Teardown. " + excp); + logger.LogError(excp, "Exception in {Method}.", nameof(Teardown)); } } @@ -323,7 +324,7 @@ public void Close() { if (!_isClosed) { - logger.LogDebug("RTSP client, closing connection for " + _url + "."); + logger.LogDebug("RTSP client, closing connection for {Url}.", _url); _isClosed = true; _sendKeepAlivesMRE.Set(); @@ -343,7 +344,7 @@ public void Close() } catch (Exception rtpStreamExcp) { - logger.LogError("Exception RTSPClient.Close closing RTP stream. " + rtpStreamExcp); + logger.LogError(rtpStreamExcp, "Exception in {Method} closing RTP stream.", nameof(Close)); } } @@ -355,7 +356,7 @@ public void Close() } catch (Exception excp) { - logger.LogError("Exception RTSPClient.Close. " + excp); + logger.LogError(excp, "Exception in {Method}.", nameof(Close)); } } @@ -392,7 +393,7 @@ private void ProcessRTPPackets() } var abbrevURL = (_url.Length <= 50) ? _url : _url.Substring(0, 50); - string rtpTrackingText = String.Format("Url: {0}\r\nRcvd At: {1}\r\nSeq Num: {2}\r\nTS: {3}\r\nPayoad: {4}\r\nFrame Size: {5}\r\nBW: {6}\r\nFrame Rate: {7}", abbrevURL, DateTime.Now.ToString("HH:mm:ss:fff"), rtpPacket.Header.SequenceNumber, rtpPacket.Header.Timestamp, ((SDPMediaFormatsEnum)rtpPacket.Header.PayloadType).ToString(), _lastFrameSize + " bytes", _lastBWCalc.ToString("0.#") + "bps", _lastFrameRate.ToString("0.##") + "fps"); + string rtpTrackingText = $"Url: {abbrevURL}\r\nRcvd At: {DateTime.Now:HH:mm:ss:fff}\r\nSeq Num: {rtpPacket.Header.SequenceNumber}\r\nTS: {rtpPacket.Header.Timestamp}\r\nPayoad: {(SDPMediaFormatsEnum)rtpPacket.Header.PayloadType}\r\nFrame Size: {_lastFrameSize} bytes\r\nBW: {_lastBWCalc:0.#}bps\r\nFrame Rate: {_lastFrameRate:0.##}fps"; _rtpTrackingAction(rtpTrackingText); } @@ -440,7 +441,7 @@ private void ProcessRTPPackets() foreach (var oldFrame in _frames.Where(x => x.Timestamp <= rtpPacket.Header.Timestamp).ToList()) { System.Diagnostics.Debug.WriteLine("Discarding old frame for timestamp " + oldFrame.Timestamp + "."); - logger.LogWarning("Discarding old frame for timestamp " + oldFrame.Timestamp + "."); + logger.LogWarning("Discarding old frame for timestamp {Timestamp}.", oldFrame.Timestamp); _frames.Remove(oldFrame); } @@ -461,7 +462,7 @@ private void ProcessRTPPackets() } catch (Exception frameReadyExcp) { - logger.LogError("Exception RTSPClient.ProcessRTPPackets OnFrameReady. " + frameReadyExcp); + logger.LogError(frameReadyExcp, "Exception in {Method} OnFrameReady.", nameof(ProcessRTPPackets)); } } } @@ -471,7 +472,8 @@ private void ProcessRTPPackets() if (DateTime.Now.Subtract(_lastRTPReceivedAt).TotalSeconds > RTP_TIMEOUT_SECONDS) { - logger.LogWarning("No RTP packets were received on RTSP session " + _rtspSession.SessionID + " for " + RTP_TIMEOUT_SECONDS + ". The session will now be closed."); + logger.LogWarning("No RTP packets were received on RTSP session {SessionId} for {TimeoutSeconds}. The session will now be closed.", + _rtspSession.SessionID, RTP_TIMEOUT_SECONDS); Close(); } else @@ -482,7 +484,7 @@ private void ProcessRTPPackets() } catch (Exception excp) { - logger.LogError("Exception RTSPClient.ProcessRTPPackets. " + excp); + logger.LogError(excp, "Exception in {Method}.", nameof(ProcessRTPPackets)); } } @@ -538,7 +540,7 @@ private void SendKeepAlives() } catch (Exception excp) { - logger.LogError("Exception RTSPClient.SendKeepAlives. " + excp); + logger.LogError(excp, "Exception in {Method}.", nameof(SendKeepAlives)); } } } diff --git a/src/SIPSorcery/net/RTSP/RTSPHeader.cs b/src/SIPSorcery/net/RTSP/RTSPHeader.cs index 6c7c2d24da..b29c9031d9 100644 --- a/src/SIPSorcery/net/RTSP/RTSPHeader.cs +++ b/src/SIPSorcery/net/RTSP/RTSPHeader.cs @@ -17,8 +17,8 @@ using System.Collections.Generic; using System.Linq; using System.Text; -using System.Text.RegularExpressions; using Microsoft.Extensions.Logging; +using Polyfills; using SIPSorcery.Sys; namespace SIPSorcery.Net @@ -79,37 +79,60 @@ public static RTSPTransportHeader Parse(string header) { transportHeader.RawHeader = header; - string[] fields = header.Split(';'); + var fields = header.AsSpan(); + var fieldIndex = 0; + Span fieldKeyValueRange = stackalloc Range[3]; + foreach (var fieldRange in fields.Split(';')) + { + var field = fields[fieldRange]; + if (fieldIndex == 0) + { + transportHeader.TransportSpecifier = field.ToString(); + fieldIndex++; + continue; + } - transportHeader.TransportSpecifier = fields[0]; - transportHeader.BroadcastType = fields[1]; + if (fieldIndex == 1) + { + transportHeader.BroadcastType = field.ToString(); + fieldIndex++; + continue; + } - foreach (string field in fields.Where(x => x.Contains('='))) - { - string fieldName = field.Split('=')[0]; - string fieldValue = field.Split('=')[1]; + if (field.Split(fieldKeyValueRange, '=') < 2) + { + continue; + } - switch (fieldName.ToLower()) + var fieldName = field[fieldKeyValueRange[0]]; + var fieldValue = field[fieldKeyValueRange[1]]; + + if (fieldName.Equals(CLIENT_RTP_PORT_FIELD_NAME, StringComparison.OrdinalIgnoreCase)) + { + transportHeader.ClientRTPPortRange = fieldValue.Trim().ToString(); + } + else if (fieldName.Equals(DESTINATION_FIELD_NAME, StringComparison.OrdinalIgnoreCase)) { - case CLIENT_RTP_PORT_FIELD_NAME: - transportHeader.ClientRTPPortRange = fieldValue.Trim(); - break; - case DESTINATION_FIELD_NAME: - transportHeader.Destination = fieldValue.Trim(); - break; - case SERVER_RTP_PORT_FIELD_NAME: - transportHeader.ServerRTPPortRange = fieldValue.Trim(); - break; - case SOURCE_FIELD_NAME: - transportHeader.Source = fieldValue.Trim(); - break; - case MODE_FIELD_NAME: - transportHeader.Mode = fieldValue.Trim(); - break; - default: - logger.LogWarning("An RTSP Transport header parameter was not recognised: {Field}", field); - break; + transportHeader.Destination = fieldValue.Trim().ToString(); } + else if (fieldName.Equals(SERVER_RTP_PORT_FIELD_NAME, StringComparison.OrdinalIgnoreCase)) + { + transportHeader.ServerRTPPortRange = fieldValue.Trim().ToString(); + } + else if (fieldName.Equals(SOURCE_FIELD_NAME, StringComparison.OrdinalIgnoreCase)) + { + transportHeader.Source = fieldValue.Trim().ToString(); + } + else if (fieldName.Equals(MODE_FIELD_NAME, StringComparison.OrdinalIgnoreCase)) + { + transportHeader.Mode = fieldValue.Trim().ToString(); + } + else + { + logger.LogWarning("An RTSP Transport header parameter was not recognised: {Field}", field.ToString()); + } + + fieldIndex++; } } @@ -124,11 +147,7 @@ public int GetClientRTPPort() { if (ClientRTPPortRange.NotNullOrBlank()) { - int clientRTPPort = 0; - - var fields = ClientRTPPortRange.Split('-'); - - if (Int32.TryParse(fields[0], out clientRTPPort)) + if (TryParsePortRange(ClientRTPPortRange.AsSpan(), true, out var clientRTPPort)) { return clientRTPPort; } @@ -145,11 +164,7 @@ public int GetClientRtcpPort() { if (ClientRTPPortRange.NotNullOrBlank()) { - int clientRTCPPort = 0; - - var fields = ClientRTPPortRange.Split('-'); - - if (fields.Length > 1 && Int32.TryParse(fields[1], out clientRTCPPort)) + if (TryParsePortRange(ClientRTPPortRange.AsSpan(), false, out var clientRTCPPort)) { return clientRTCPPort; } @@ -167,11 +182,7 @@ public int GetServerRTPPort() { if (ServerRTPPortRange.NotNullOrBlank()) { - int serverRTPPort = 0; - - var fields = ServerRTPPortRange.Split('-'); - - if (Int32.TryParse(fields[0], out serverRTPPort)) + if (TryParsePortRange(ServerRTPPortRange.AsSpan(), true, out var serverRTPPort)) { return serverRTPPort; } @@ -188,11 +199,7 @@ public int GetServerRtcpPort() { if (ServerRTPPortRange.NotNullOrBlank()) { - int serverRtcpPort = 0; - - var fields = ServerRTPPortRange.Split('-'); - - if (fields.Length > 0 && Int32.TryParse(fields[1], out serverRtcpPort)) + if (TryParsePortRange(ServerRTPPortRange.AsSpan(), false, out var serverRtcpPort)) { return serverRtcpPort; } @@ -201,36 +208,48 @@ public int GetServerRtcpPort() return 0; } + private static bool TryParsePortRange(ReadOnlySpan portRange, bool useFirstPort, out int port) + { + port = 0; + portRange = portRange.Trim(); + Span portRangeFields = stackalloc Range[3]; + var portFieldCount = portRange.Split(portRangeFields, '-'); + var portIndex = useFirstPort ? 0 : 1; + + return portFieldCount > portIndex && int.TryParse(portRange[portRangeFields[portIndex]], out port); + } + public override string ToString() { - string transportHeader = TransportSpecifier + ";" + BroadcastType; + var transportHeader = new StringBuilder(); + transportHeader.Append(TransportSpecifier).Append(';').Append(BroadcastType); if (Destination.NotNullOrBlank()) { - transportHeader += String.Format(";{0}={1}", DESTINATION_FIELD_NAME, Destination); + transportHeader.Append(';').Append(DESTINATION_FIELD_NAME).Append('=').Append(Destination); } if (Source.NotNullOrBlank()) { - transportHeader += String.Format(";{0}={1}", SOURCE_FIELD_NAME, Source); + transportHeader.Append(';').Append(SOURCE_FIELD_NAME).Append('=').Append(Source); } if (ClientRTPPortRange.NotNullOrBlank()) { - transportHeader += String.Format(";{0}={1}", CLIENT_RTP_PORT_FIELD_NAME, ClientRTPPortRange); + transportHeader.Append(';').Append(CLIENT_RTP_PORT_FIELD_NAME).Append('=').Append(ClientRTPPortRange); } if (ServerRTPPortRange.NotNullOrBlank()) { - transportHeader += String.Format(";{0}={1}", SERVER_RTP_PORT_FIELD_NAME, ServerRTPPortRange); + transportHeader.Append(';').Append(SERVER_RTP_PORT_FIELD_NAME).Append('=').Append(ServerRTPPortRange); } if (Mode.NotNullOrBlank()) { - transportHeader += String.Format(";{0}={1}", MODE_FIELD_NAME, Mode); + transportHeader.Append(';').Append(MODE_FIELD_NAME).Append('=').Append(Mode); } - return transportHeader; + return transportHeader.ToString(); } } @@ -272,13 +291,71 @@ public void AddHeader(string headerName, string value) public static string[] SplitHeaders(string message) { + static string NormalizeFoldedHeaderLines(string headerBlock) + { + var normalised = default(StringBuilder); + var segmentStart = 0; + var position = 0; + + while (position < headerBlock.Length) + { + if (position + 2 < headerBlock.Length && + headerBlock[position] == '\r' && + headerBlock[position + 1] == '\n' && + char.IsWhiteSpace(headerBlock[position + 2])) + { + normalised ??= new StringBuilder(headerBlock.Length); + normalised.Append(headerBlock, segmentStart, position - segmentStart); + normalised.Append(' '); + + position += 2; + while (position < headerBlock.Length && char.IsWhiteSpace(headerBlock[position])) + { + position++; + } + + segmentStart = position; + continue; + } + + if (position + 1 < headerBlock.Length && + headerBlock[position] == '\r' && + headerBlock[position + 1] == ' ') + { + normalised ??= new StringBuilder(headerBlock.Length); + normalised.Append(headerBlock, segmentStart, position - segmentStart); + normalised.Append(m_CRLF); + + position += 2; + segmentStart = position; + continue; + } + + position++; + } + + if (normalised is null) + { + return headerBlock; + } + + normalised.Append(headerBlock, segmentStart, headerBlock.Length - segmentStart); + return normalised.ToString(); + } + // SIP headers can be extended across lines if the first character of the next line is at least on whitespace character. - message = Regex.Replace(message, m_CRLF + @"\s+", " ", RegexOptions.Singleline); + // Some user agents couldn't get the \r\n bit right; normalise those at the same time. + message = NormalizeFoldedHeaderLines(message); - // Some user agents couldn't get the \r\n bit right. - message = Regex.Replace(message, "\r ", m_CRLF, RegexOptions.Singleline); + var headers = new List(); + var messageSpan = message.AsSpan(); - return Regex.Split(message, m_CRLF); + foreach (var headerRange in messageSpan.Split(m_CRLF.AsSpan())) + { + headers.Add(messageSpan[headerRange].ToString()); + } + + return headers.ToArray(); } public static RTSPHeader ParseRTSPHeaders(string[] headersCollection) @@ -293,7 +370,7 @@ public static RTSPHeader ParseRTSPHeaders(string[] headersCollection) { string headerLine = headersCollection[lineIndex]; - if (headerLine == null || headerLine.Trim().Length == 0) + if (string.IsNullOrWhiteSpace(headerLine)) { // No point processing blank headers. continue; @@ -303,16 +380,17 @@ public static RTSPHeader ParseRTSPHeaders(string[] headersCollection) string headerValue = null; // If the first character of a line is whitespace it's a continuation of the previous line. - if (headerLine.StartsWith(" ")) + if (headerLine.StartsWith(" ", StringComparison.Ordinal)) { headerName = lastHeader; headerValue = headerLine.Trim(); } else { - string[] headerParts = headerLine.Trim().Split(delimiterChars, 2); + var headerLineSpan = headerLine.AsSpan().Trim(); + var delimiterIndex = headerLineSpan.IndexOf(':'); - if (headerParts == null || headerParts.Length < 2) + if (delimiterIndex == -1) { logger.LogError("Invalid RTSP header, ignoring. header={HeaderLine}.", headerLine); @@ -326,66 +404,64 @@ public static RTSPHeader ParseRTSPHeaders(string[] headersCollection) continue; } - headerName = headerParts[0].Trim(); - headerValue = headerParts[1].Trim(); + headerName = headerLineSpan.Slice(0, delimiterIndex).Trim().ToString(); + headerValue = headerLineSpan.Slice(delimiterIndex + 1).Trim().ToString(); } try { - string headerNameLower = headerName.ToLower(); - #region Accept - if (headerNameLower == RTSPHeaders.RTSP_HEADER_ACCEPT.ToLower()) + if (string.Equals(headerName, RTSPHeaders.RTSP_HEADER_ACCEPT, StringComparison.OrdinalIgnoreCase)) { rtspHeader.Accept = headerValue; } #endregion #region ContentType - if (headerNameLower == RTSPHeaders.RTSP_HEADER_CONTENTTYPE.ToLower()) + if (string.Equals(headerName, RTSPHeaders.RTSP_HEADER_CONTENTTYPE, StringComparison.OrdinalIgnoreCase)) { rtspHeader.ContentType = headerValue; } #endregion #region ContentLength - if (headerNameLower == RTSPHeaders.RTSP_HEADER_CONTENTLENGTH.ToLower()) + if (string.Equals(headerName, RTSPHeaders.RTSP_HEADER_CONTENTLENGTH, StringComparison.OrdinalIgnoreCase)) { rtspHeader.RawCSeq = headerValue; - if (headerValue == null || headerValue.Trim().Length == 0) + if (string.IsNullOrWhiteSpace(headerValue)) { - logger.LogWarning("Invalid RTSP header, the " + RTSPHeaders.RTSP_HEADER_CONTENTLENGTH + " was empty."); + logger.LogWarning("Invalid RTSP header, the {HeaderName} was empty.", RTSPHeaders.RTSP_HEADER_CONTENTLENGTH); } else if (!Int32.TryParse(headerValue.Trim(), out rtspHeader.ContentLength)) { - logger.LogWarning("Invalid RTSP header, the " + RTSPHeaders.RTSP_HEADER_CONTENTLENGTH + " was not a valid 32 bit integer, {HeaderValue}.", headerValue); + logger.LogWarning("Invalid RTSP header, the {HeaderName} was not a valid 32 bit integer, {HeaderValue}.", RTSPHeaders.RTSP_HEADER_CONTENTLENGTH, headerValue); } } #endregion #region CSeq - if (headerNameLower == RTSPHeaders.RTSP_HEADER_CSEQ.ToLower()) + if (string.Equals(headerName, RTSPHeaders.RTSP_HEADER_CSEQ, StringComparison.OrdinalIgnoreCase)) { rtspHeader.RawCSeq = headerValue; - if (headerValue == null || headerValue.Trim().Length == 0) + if (string.IsNullOrWhiteSpace(headerValue)) { rtspHeader.CSeqParserError = RTSPHeaderParserError.CSeqEmpty; - logger.LogWarning("Invalid RTSP header, the " + RTSPHeaders.RTSP_HEADER_CSEQ + " was empty."); + logger.LogWarning("Invalid RTSP header, the {HeaderName} was empty.", RTSPHeaders.RTSP_HEADER_CSEQ); } else if (!Int32.TryParse(headerValue.Trim(), out rtspHeader.CSeq)) { rtspHeader.CSeqParserError = RTSPHeaderParserError.CSeqNotValidInteger; - logger.LogWarning("Invalid SIP header, the " + RTSPHeaders.RTSP_HEADER_CSEQ + " was not a valid 32 bit integer, {HeaderValue}.", headerValue); + logger.LogWarning("Invalid SIP header, the {HeaderName} was not a valid 32 bit integer, {HeaderValue}.", RTSPHeaders.RTSP_HEADER_CSEQ, headerValue); } } #endregion #region Session - if (headerNameLower == RTSPHeaders.RTSP_HEADER_SESSION.ToLower()) + if (string.Equals(headerName, RTSPHeaders.RTSP_HEADER_SESSION, StringComparison.OrdinalIgnoreCase)) { rtspHeader.Session = headerValue; } #endregion #region Transport - if (headerNameLower == RTSPHeaders.RTSP_HEADER_TRANSPORT.ToLower()) + if (string.Equals(headerName, RTSPHeaders.RTSP_HEADER_TRANSPORT, StringComparison.OrdinalIgnoreCase)) { rtspHeader.Transport = RTSPTransportHeader.Parse(headerValue); } @@ -420,23 +496,50 @@ public static RTSPHeader ParseRTSPHeaders(string[] headersCollection) } - public new string ToString() + public override string ToString() { try { - StringBuilder headersBuilder = new StringBuilder(); - - headersBuilder.Append((CSeq > 0) ? RTSPHeaders.RTSP_HEADER_CSEQ + ": " + CSeq + m_CRLF : null); - headersBuilder.Append((Session != null) ? RTSPHeaders.RTSP_HEADER_SESSION + ": " + Session + m_CRLF : null); - headersBuilder.Append((Accept != null) ? RTSPHeaders.RTSP_HEADER_ACCEPT + ": " + Accept + m_CRLF : null); - headersBuilder.Append((ContentType != null) ? RTSPHeaders.RTSP_HEADER_CONTENTTYPE + ": " + ContentType + m_CRLF : null); - headersBuilder.Append((ContentLength != 0) ? RTSPHeaders.RTSP_HEADER_CONTENTLENGTH + ": " + ContentLength + m_CRLF : null); - headersBuilder.Append((Transport != null) ? RTSPHeaders.RTSP_HEADER_TRANSPORT + ": " + Transport.ToString() + m_CRLF : null); + var headersBuilder = new StringBuilder(); + + void AppendHeader(string headerName, T headerValue) => + headersBuilder.Append($"{headerName}: {headerValue}{m_CRLF}"); + + if (CSeq > 0) + { + AppendHeader(RTSPHeaders.RTSP_HEADER_CSEQ, CSeq); + } + + if (Session != null) + { + AppendHeader(RTSPHeaders.RTSP_HEADER_SESSION, Session); + } + + if (Accept != null) + { + AppendHeader(RTSPHeaders.RTSP_HEADER_ACCEPT, Accept); + } + + if (ContentType != null) + { + AppendHeader(RTSPHeaders.RTSP_HEADER_CONTENTTYPE, ContentType); + } + + if (ContentLength != 0) + { + AppendHeader(RTSPHeaders.RTSP_HEADER_CONTENTLENGTH, ContentLength); + } + + if (Transport != null) + { + AppendHeader(RTSPHeaders.RTSP_HEADER_TRANSPORT, Transport); + } + if (UnknownHeaders != null) { foreach (var unknownHeader in UnknownHeaders) { - headersBuilder.Append(unknownHeader + m_CRLF); + headersBuilder.Append(unknownHeader).Append(m_CRLF); } } diff --git a/src/SIPSorcery/net/RTSP/RTSPMessage.cs b/src/SIPSorcery/net/RTSP/RTSPMessage.cs index dc90850a8a..630261cd77 100644 --- a/src/SIPSorcery/net/RTSP/RTSPMessage.cs +++ b/src/SIPSorcery/net/RTSP/RTSPMessage.cs @@ -97,7 +97,7 @@ public static RTSPMessage ParseRTSPMessage(string message, IPEndPoint receivedFr { rtspMessage.FirstLine = message.Substring(0, endFistLinePosn); - if (rtspMessage.FirstLine.Substring(0, RTSP_RESPONSE_PREFIX.Length) == RTSP_RESPONSE_PREFIX) + if (rtspMessage.FirstLine.AsSpan(0, RTSP_RESPONSE_PREFIX.Length).Equals(RTSP_RESPONSE_PREFIX, StringComparison.Ordinal)) { rtspMessage.RTSPMessageType = RTSPMessageTypesEnum.Response; } diff --git a/src/SIPSorcery/net/RTSP/RTSPRequest.cs b/src/SIPSorcery/net/RTSP/RTSPRequest.cs index 7ebd92b964..24742bae98 100644 --- a/src/SIPSorcery/net/RTSP/RTSPRequest.cs +++ b/src/SIPSorcery/net/RTSP/RTSPRequest.cs @@ -72,13 +72,13 @@ public RTSPRequest(RTSPMethodsEnum method, string url) URL = RTSPURL.ParseRTSPURL(url, out urlParserError); if (urlParserError != RTSPHeaderParserError.None) { - throw new ApplicationException("Error parsing RTSP URL, " + urlParserError.ToString() + "."); + throw new ApplicationException($"Error parsing RTSP URL, {urlParserError.ToString()}."); } } } catch (Exception excp) { - logger.LogError(excp, "Exception RTSPRequest Ctor. {ErrorMessage}", excp.Message + "."); + logger.LogError(excp, "Exception in {Method}.", nameof(RTSPRequest)); } } @@ -104,11 +104,9 @@ public static RTSPRequest ParseRTSPRequest(RTSPMessage rtspMessage, out RTSPRequ { RTSPRequest rtspRequest = new RTSPRequest(); - string statusLine = rtspMessage.FirstLine; - - int firstSpacePosn = statusLine.IndexOf(" "); - - string method = statusLine.Substring(0, firstSpacePosn).Trim(); + var statusLine = rtspMessage.FirstLine.AsSpan(); + var firstSpacePosn = statusLine.IndexOf(' '); + var method = statusLine.Slice(0, firstSpacePosn).Trim().ToString(); rtspRequest.Method = RTSPMethods.GetMethod(method); if (rtspRequest.Method == RTSPMethodsEnum.UNKNOWN) { @@ -116,15 +114,15 @@ public static RTSPRequest ParseRTSPRequest(RTSPMessage rtspMessage, out RTSPRequ logger.LogWarning("Unknown RTSP method received {Method}.", rtspRequest.Method); } - statusLine = statusLine.Substring(firstSpacePosn).Trim(); - int secondSpacePosn = statusLine.IndexOf(" "); + statusLine = statusLine.Slice(firstSpacePosn).Trim(); + var secondSpacePosn = statusLine.IndexOf(' '); if (secondSpacePosn != -1) { - urlStr = statusLine.Substring(0, secondSpacePosn); + urlStr = statusLine.Slice(0, secondSpacePosn).ToString(); rtspRequest.URL = RTSPURL.ParseRTSPURL(urlStr); - rtspRequest.RTSPVersion = statusLine.Substring(secondSpacePosn, statusLine.Length - secondSpacePosn).Trim(); + rtspRequest.RTSPVersion = statusLine.Slice(secondSpacePosn).Trim().ToString(); rtspRequest.Header = (rtspMessage.RTSPHeaders != null) ? RTSPHeader.ParseRTSPHeaders(rtspMessage.RTSPHeaders) : new RTSPHeader(0, null); rtspRequest.Body = rtspMessage.Body; @@ -138,23 +136,23 @@ public static RTSPRequest ParseRTSPRequest(RTSPMessage rtspMessage, out RTSPRequ catch (Exception excp) { logger.LogError(excp, "Exception parsing RTSP request. URI, {Uri}. {ErrorMessage}", urlStr, excp.Message); - throw new ApplicationException("There was an exception parsing an RTSP request. " + excp.Message); + throw new ApplicationException($"There was an exception parsing an RTSP request. {excp.Message}"); } } - public new string ToString() + public override string ToString() { try { string methodStr = (Method != RTSPMethodsEnum.UNKNOWN) ? Method.ToString() : UnknownMethod; - string message = methodStr + (URL == null ? "" : (" " + URL?.ToString())) + " " + RTSPVersion + m_CRLF; + string message = $"{methodStr + (URL == null ? "" : ($" {URL?.ToString()}"))} {RTSPVersion}{m_CRLF}"; if (Header != null) { message += Header.ToString(); } if (Body != null) { - message +=m_CRLF+ Body; + message += m_CRLF + Body; } return message; diff --git a/src/SIPSorcery/net/RTSP/RTSPResponse.cs b/src/SIPSorcery/net/RTSP/RTSPResponse.cs index 6aeb1db24c..5912681e70 100644 --- a/src/SIPSorcery/net/RTSP/RTSPResponse.cs +++ b/src/SIPSorcery/net/RTSP/RTSPResponse.cs @@ -78,15 +78,14 @@ public static RTSPResponse ParseRTSPResponse(RTSPMessage rtspMessage, out RTSPRe { RTSPResponse rtspResponse = new RTSPResponse(); - string statusLine = rtspMessage.FirstLine; + var statusLine = rtspMessage.FirstLine.AsSpan(); + var firstSpacePosn = statusLine.IndexOf(' '); - int firstSpacePosn = statusLine.IndexOf(" "); - - rtspResponse.RTSPVersion = statusLine.Substring(0, firstSpacePosn).Trim(); - statusLine = statusLine.Substring(firstSpacePosn).Trim(); - rtspResponse.StatusCode = Convert.ToInt32(statusLine.Substring(0, 3)); + rtspResponse.RTSPVersion = statusLine.Slice(0, firstSpacePosn).Trim().ToString(); + statusLine = statusLine.Slice(firstSpacePosn).Trim(); + rtspResponse.StatusCode = Convert.ToInt32(statusLine.Slice(0, 3).ToString()); rtspResponse.Status = RTSPResponseStatusCodes.GetStatusTypeForCode(rtspResponse.StatusCode); - rtspResponse.ReasonPhrase = statusLine.Substring(3).Trim(); + rtspResponse.ReasonPhrase = statusLine.Slice(3).Trim().ToString(); rtspResponse.Header = RTSPHeader.ParseRTSPHeaders(rtspMessage.RTSPHeaders); rtspResponse.Body = rtspMessage.Body; @@ -96,19 +95,18 @@ public static RTSPResponse ParseRTSPResponse(RTSPMessage rtspMessage, out RTSPRe catch (Exception excp) { logger.LogError(excp, "Exception parsing RTSP response. {ErrorMessage}", excp.Message); - throw new ApplicationException("There was an exception parsing an RTSP response. " + excp.Message); + throw new ApplicationException($"There was an exception parsing an RTSP response. {excp.Message}"); } } - public new string ToString() + public override string ToString() { try { - string reasonPhrase = (!ReasonPhrase.IsNullOrBlank()) ? " " + ReasonPhrase : null; + string reasonPhrase = (!ReasonPhrase.IsNullOrBlank()) ? $" {ReasonPhrase}" : null; - string message = - RTSPVersion + "/" + RTSPMajorVersion + "." + RTSPMinorVersion + " " + StatusCode + reasonPhrase + m_CRLF + - this.Header.ToString(); + var message = + $"{RTSPVersion}/{RTSPMajorVersion}.{RTSPMinorVersion} {StatusCode}{reasonPhrase}{m_CRLF}{Header}"; if (Body != null) { diff --git a/src/SIPSorcery/net/RTSP/RTSPURL.cs b/src/SIPSorcery/net/RTSP/RTSPURL.cs index 4547f7c570..4ce2cf582a 100644 --- a/src/SIPSorcery/net/RTSP/RTSPURL.cs +++ b/src/SIPSorcery/net/RTSP/RTSPURL.cs @@ -73,7 +73,7 @@ public static RTSPURL ParseRTSPURL(string url, out RTSPHeaderParserError parserE RTSPURL rtspURL = new RTSPURL(); - if (url == null || url.Trim().Length == 0) + if (string.IsNullOrWhiteSpace(url)) { throw new ApplicationException("An RTSP URI cannot be parsed from an empty string."); } @@ -112,11 +112,11 @@ public static RTSPURL ParseRTSPURL(string url, out RTSPHeaderParserError parserE } catch (Exception excp) { - throw new ApplicationException("There was an exception parsing an RTSP URL. " + excp.Message + " url=" + url); + throw new ApplicationException($"There was an exception parsing an RTSP URL. {excp.Message} url={url}"); } } - public new string ToString() + public override string ToString() { try { diff --git a/src/SIPSorcery/net/SCTP/Chunks/SctpTlvChunkParameter.cs b/src/SIPSorcery/net/SCTP/Chunks/SctpTlvChunkParameter.cs index 0e86a5c3eb..094bf53e3a 100644 --- a/src/SIPSorcery/net/SCTP/Chunks/SctpTlvChunkParameter.cs +++ b/src/SIPSorcery/net/SCTP/Chunks/SctpTlvChunkParameter.cs @@ -211,8 +211,7 @@ public ushort ParseFirstWord(byte[] buffer, int posn) // The buffer was not big enough to supply the specified chunk parameter. int bytesRequired = paramLen; int bytesAvailable = buffer.Length - posn; - throw new ApplicationException($"The SCTP chunk parameter buffer was too short. " + - $"Required {bytesRequired} bytes but only {bytesAvailable} available."); + throw new ApplicationException($"The SCTP chunk parameter buffer was too short. Required {bytesRequired} bytes but only {bytesAvailable} available."); } return paramLen; diff --git a/src/SIPSorcery/net/SCTP/SctpAssociation.cs b/src/SIPSorcery/net/SCTP/SctpAssociation.cs index 21e4d0c383..ad6069ca46 100644 --- a/src/SIPSorcery/net/SCTP/SctpAssociation.cs +++ b/src/SIPSorcery/net/SCTP/SctpAssociation.cs @@ -400,13 +400,13 @@ internal void OnPacketReceived(SctpPacket packet) OnAbortReceived?.Invoke(abortReason); break; - case var ct when ct == SctpChunkType.COOKIE_ACK && State != SctpAssociationState.CookieEchoed: + case var _ when chunkType == SctpChunkType.COOKIE_ACK && State != SctpAssociationState.CookieEchoed: // https://tools.ietf.org/html/rfc4960#section-5.2.5 // At any state other than COOKIE-ECHOED, an endpoint should silently // discard a received COOKIE ACK chunk. break; - case var ct when ct == SctpChunkType.COOKIE_ACK && State == SctpAssociationState.CookieEchoed: + case var _ when chunkType == SctpChunkType.COOKIE_ACK && State == SctpAssociationState.CookieEchoed: SetState(SctpAssociationState.Established); CancelTimers(); _dataSender.StartSending(); @@ -468,13 +468,13 @@ internal void OnPacketReceived(SctpPacket packet) SendChunk(chunk); break; - case var ct when ct == SctpChunkType.INIT_ACK && State != SctpAssociationState.CookieWait: + case var _ when chunkType == SctpChunkType.INIT_ACK && State != SctpAssociationState.CookieWait: // https://tools.ietf.org/html/rfc4960#section-5.2.3 // If an INIT ACK is received by an endpoint in any state other than the // COOKIE - WAIT state, the endpoint should discard the INIT ACK chunk. break; - case var ct when ct == SctpChunkType.INIT_ACK && State == SctpAssociationState.CookieWait: + case var _ when chunkType == SctpChunkType.INIT_ACK && State == SctpAssociationState.CookieWait: if (_t1Init != null) { @@ -528,7 +528,7 @@ internal void OnPacketReceived(SctpPacket packet) } break; - case var ct when ct == SctpChunkType.INIT_ACK && State != SctpAssociationState.CookieWait: + case var _ when chunkType == SctpChunkType.INIT_ACK && State != SctpAssociationState.CookieWait: logger.LogWarning("SCTP association received INIT_ACK chunk in wrong state of {State}, ignoring.", State); break; @@ -536,7 +536,7 @@ internal void OnPacketReceived(SctpPacket packet) _dataSender.GotSack(chunk as SctpSackChunk); break; - case var ct when ct == SctpChunkType.SHUTDOWN && State == SctpAssociationState.Established: + case var _ when chunkType == SctpChunkType.SHUTDOWN && State == SctpAssociationState.Established: // TODO: Check outstanding data chunks. _dataSender?.Close(); var shutdownAck = new SctpChunk(SctpChunkType.SHUTDOWN_ACK); @@ -544,7 +544,7 @@ internal void OnPacketReceived(SctpPacket packet) SetState(SctpAssociationState.ShutdownAckSent); break; - case var ct when ct == SctpChunkType.SHUTDOWN_ACK && State == SctpAssociationState.ShutdownSent: + case var _ when chunkType == SctpChunkType.SHUTDOWN_ACK && State == SctpAssociationState.ShutdownSent: SetState(SctpAssociationState.Closed); var shutCompleteChunk = new SctpChunk(SctpChunkType.SHUTDOWN_COMPLETE, (byte)(_remoteVerificationTag != 0 ? SHUTDOWN_CHUNK_TBIT_FLAG : 0x00)); diff --git a/src/SIPSorcery/net/SDP/SDP.cs b/src/SIPSorcery/net/SDP/SDP.cs index 700328a010..9c32ea8a83 100644 --- a/src/SIPSorcery/net/SDP/SDP.cs +++ b/src/SIPSorcery/net/SDP/SDP.cs @@ -102,12 +102,14 @@ //----------------------------------------------------------------------------- using System; +using System.Buffers; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Sockets; -using System.Text.RegularExpressions; +using System.Text; using Microsoft.Extensions.Logging; +using Polyfills; using SIPSorcery.Sys; namespace SIPSorcery.Net @@ -153,7 +155,7 @@ public class SDP public string AddressOrHost; // IP Address or Host of the machine that created the session, either FQDN or dotted quad or textual for IPv6. public string Owner { - get { return Username + " " + SessionId + " " + AnnouncementVersion + " " + NetworkType + " " + AddressType + " " + AddressOrHost; } + get { return $"{Username} {SessionId} {AnnouncementVersion} {NetworkType} {AddressType} {AddressOrHost}"; } } public string SessionName = "sipsorcery"; // Common name of the session. @@ -205,7 +207,7 @@ public static SDP ParseSDPDescription(string sdpDescription) { try { - if (sdpDescription != null && sdpDescription.Trim().Length > 0) + if (!string.IsNullOrWhiteSpace(sdpDescription)) { SDP sdp = new SDP(); sdp.m_rawSdp = sdpDescription; @@ -216,580 +218,812 @@ public static SDP ParseSDPDescription(string sdpDescription) // in this dictionary. A dynamic media format type cannot be created without an rtpmap. Dictionary _pendingFmtp = new Dictionary(); - //string[] sdpLines = Regex.Split(sdpDescription, CRLF); - string[] sdpLines = sdpDescription.Split(new string[] { "\r\n", "\r", "\n" }, StringSplitOptions.RemoveEmptyEntries); + var sdpDescriptionSpan = sdpDescription.AsSpan(); + Span ownerFieldRanges = stackalloc Range[6]; + const StringSplitOptions TrimEntries = (StringSplitOptions)2; + const StringSplitOptions RemoveEmptyAndTrimSplitOptions = StringSplitOptions.RemoveEmptyEntries | TrimEntries; - foreach (string sdpLine in sdpLines) + static bool StartsWithAttribute(ReadOnlySpan line, string attributePrefix) => + line.StartsWith("a=", StringComparison.Ordinal) && + line.Slice(2).StartsWith(attributePrefix, StringComparison.Ordinal); + + static bool EqualsAttribute(ReadOnlySpan line, string attribute) => + line.Length == attribute.Length + 2 && + line.StartsWith("a=", StringComparison.Ordinal) && + line.Slice(2).Equals(attribute.AsSpan(), StringComparison.Ordinal); + + static ReadOnlySpan SliceAfterColon(ReadOnlySpan line) => + line.Slice(line.IndexOf(':') + 1); + + static bool TryReadToken(ReadOnlySpan value, ref int offset, out int tokenStart, out int tokenLength) + { + while (offset < value.Length && char.IsWhiteSpace(value[offset])) + { + offset++; + } + + if (offset == value.Length) + { + tokenStart = 0; + tokenLength = 0; + return false; + } + + tokenStart = offset; + var endIndex = offset; + while (endIndex < value.Length && !char.IsWhiteSpace(value[endIndex])) + { + endIndex++; + } + + tokenLength = endIndex - tokenStart; + offset = endIndex; + return true; + } + + static bool TrySplitAttributeValue( + ReadOnlySpan line, + int prefixLength, + out int idStart, + out int idLength, + out int attributeStart) { - string sdpLineTrimmed = sdpLine.Trim(); + var offset = prefixLength; + if (!TryReadToken(line, ref offset, out idStart, out idLength)) + { + attributeStart = 0; + return false; + } + + while (offset < line.Length && char.IsWhiteSpace(line[offset])) + { + offset++; + } - switch (sdpLineTrimmed) + attributeStart = offset; + return attributeStart < line.Length; + } + + static bool TryParseMediaLine( + ReadOnlySpan mediaLine, + out int mediaTypeStart, + out int mediaTypeLength, + out int port, + out int? portCount, + out int transportStart, + out int transportLength, + out int formatsStart) + { + mediaTypeStart = 0; + mediaTypeLength = 0; + port = 0; + portCount = null; + transportStart = 0; + transportLength = 0; + formatsStart = 0; + + var offset = 0; + if (!TryReadToken(mediaLine, ref offset, out mediaTypeStart, out mediaTypeLength) || + !TryReadToken(mediaLine, ref offset, out var portStart, out var portLength) || + !TryReadToken(mediaLine, ref offset, out transportStart, out transportLength)) + { + return false; + } + + var portToken = mediaLine.Slice(portStart, portLength); + var slashIndex = portToken.IndexOf('/'); + var portSpan = slashIndex == -1 ? portToken : portToken.Slice(0, slashIndex); + if (!int.TryParse(portSpan, out port)) + { + return false; + } + + if (slashIndex != -1) + { + var portCountSpan = portToken.Slice(slashIndex + 1); + if (portCountSpan.IsEmpty || !int.TryParse(portCountSpan, out var parsedPortCount)) + { + return false; + } + + portCount = parsedPortCount; + } + + while (offset < mediaLine.Length && char.IsWhiteSpace(mediaLine[offset])) + { + offset++; + } + + formatsStart = offset; + return true; + } + + static bool TryParseExtensionMap(ReadOnlySpan line, out int id, out int uriStart, out int uriLength) + { + id = 0; + uriStart = 0; + uriLength = 0; + var offset = SDPMediaAnnouncement.MEDIA_EXTENSION_MAP_ATTRIBUE_PREFIX.Length; + + if (!TryReadToken(line, ref offset, out var idStart, out var idLength) || + !int.TryParse(line.Slice(idStart, idLength), out id) || + !TryReadToken(line, ref offset, out uriStart, out uriLength)) { - case var l when l.StartsWith("v="): - if (!Decimal.TryParse(sdpLineTrimmed.Substring(2), out sdp.Version)) - { - logger.LogWarning("The Version value in an SDP description could not be parsed as a decimal: {sdpLine}.", sdpLine); - } - break; - - case var l when l.StartsWith("o="): - var ownerFields = sdpLineTrimmed.Substring(2).Split(new []{' '}, 6, StringSplitOptions.RemoveEmptyEntries); - - if (ownerFields.Length >= 5) - { - sdp.Username = ownerFields[0]; - sdp.SessionId = ownerFields[1]; - sdp.AnnouncementVersion = UInt64.TryParse(ownerFields[2], out var version) ? version : 0; - sdp.NetworkType = ownerFields[3]; - sdp.AddressType = ownerFields[4]; - sdp.AddressOrHost = ownerFields.ElementAtOrDefault(5); // Safely handle missing elements - } - else - { - logger.LogWarning("The SDP message had an invalid SDP line format for 'o=': {sdpLineTrimmed}", sdpLineTrimmed); - } - break; - - case var l when l.StartsWith("s="): - sdp.SessionName = sdpLineTrimmed.Substring(2); - break; - - case var l when l.StartsWith("i="): - if (activeAnnouncement != null) - { - activeAnnouncement.MediaDescription = sdpLineTrimmed.Substring(2); - } - else - { - sdp.SessionDescription = sdpLineTrimmed.Substring(2); - } - - break; - - case var l when l.StartsWith("c="): - - if (activeAnnouncement != null) - { - activeAnnouncement.Connection = SDPConnectionInformation.ParseConnectionInformation(sdpLineTrimmed); - } - else if (sdp.Connection == null) - { - sdp.Connection = SDPConnectionInformation.ParseConnectionInformation(sdpLineTrimmed); - } - else - { - logger.LogWarning("The SDP message had a duplicate connection attribute which was ignored."); - } - - break; - - case var l when l.StartsWith("b="): - if (activeAnnouncement != null) - { - if (l.StartsWith(SDPMediaAnnouncement.TIAS_BANDWIDTH_ATTRIBUE_PREFIX)) - { - if (uint.TryParse(l.Substring(l.IndexOf(':') + 1), out uint tias)) + return false; + } + + while (offset < line.Length && char.IsWhiteSpace(line[offset])) + { + offset++; + } + + return offset == line.Length; + } + + static bool TryParseMediaStreamStatus(ReadOnlySpan attribute, out MediaStreamStatusEnum mediaStreamStatus) + { + mediaStreamStatus = MediaStreamStatusEnum.SendRecv; + + if (attribute.Equals(MediaStreamStatusType.SEND_RECV_ATTRIBUTE.AsSpan(), StringComparison.OrdinalIgnoreCase)) + { + mediaStreamStatus = MediaStreamStatusEnum.SendRecv; + return true; + } + + if (attribute.Equals(MediaStreamStatusType.SEND_ONLY_ATTRIBUTE.AsSpan(), StringComparison.OrdinalIgnoreCase)) + { + mediaStreamStatus = MediaStreamStatusEnum.SendOnly; + return true; + } + + if (attribute.Equals(MediaStreamStatusType.RECV_ONLY_ATTRIBUTE.AsSpan(), StringComparison.OrdinalIgnoreCase)) + { + mediaStreamStatus = MediaStreamStatusEnum.RecvOnly; + return true; + } + + if (attribute.Equals(MediaStreamStatusType.INACTIVE_ATTRIBUTE.AsSpan(), StringComparison.OrdinalIgnoreCase)) + { + mediaStreamStatus = MediaStreamStatusEnum.Inactive; + return true; + } + + return false; + } + + var sdpLineRangeBuffer = ArrayPool.Shared.Rent(sdpDescriptionSpan.Length + 1); + try + { + var sdpLineRanges = sdpLineRangeBuffer.AsSpan(0, sdpDescriptionSpan.Length + 1); + var sdpLineCount = sdpDescriptionSpan.SplitAny( + sdpLineRanges, + "\r\n".AsSpan(), + RemoveEmptyAndTrimSplitOptions); + + for (var sdpLineIndex = 0; sdpLineIndex < sdpLineCount; sdpLineIndex++) + { + var sdpLineTrimmedSpan = sdpDescriptionSpan[sdpLineRanges[sdpLineIndex]]; + + switch (sdpLineTrimmedSpan) + { + case var _ when sdpLineTrimmedSpan.StartsWith("v=", StringComparison.Ordinal): + if (!Decimal.TryParse(sdpLineTrimmedSpan.Slice(2), out sdp.Version)) + { + logger.LogWarning("The Version value in an SDP description could not be parsed as a decimal: {sdpLine}.", sdpLineTrimmedSpan.ToString()); + } + break; + + case var _ when sdpLineTrimmedSpan.StartsWith("o=", StringComparison.Ordinal): + var ownerFieldsSpan = sdpLineTrimmedSpan.Slice(2); + var ownerFieldCount = ownerFieldsSpan.Split(ownerFieldRanges, ' ', StringSplitOptions.RemoveEmptyEntries); + + if (ownerFieldCount >= 5) + { + sdp.Username = ownerFieldsSpan[ownerFieldRanges[0]].ToString(); + sdp.SessionId = ownerFieldsSpan[ownerFieldRanges[1]].ToString(); + sdp.AnnouncementVersion = UInt64.TryParse(ownerFieldsSpan[ownerFieldRanges[2]].ToString(), out var version) ? version : 0; + sdp.NetworkType = ownerFieldsSpan[ownerFieldRanges[3]].ToString(); + sdp.AddressType = ownerFieldsSpan[ownerFieldRanges[4]].ToString(); + sdp.AddressOrHost = ownerFieldCount > 5 ? ownerFieldsSpan[ownerFieldRanges[5]].ToString() : null; + } + else + { + logger.LogWarning("The SDP message had an invalid SDP line format for 'o=': {sdpLineTrimmed}", sdpLineTrimmedSpan.ToString()); + } + break; + + case var _ when sdpLineTrimmedSpan.StartsWith("s=", StringComparison.Ordinal): + sdp.SessionName = sdpLineTrimmedSpan.Slice(2).ToString(); + break; + + case var _ when sdpLineTrimmedSpan.StartsWith("i=", StringComparison.Ordinal): + if (activeAnnouncement != null) + { + activeAnnouncement.MediaDescription = sdpLineTrimmedSpan.Slice(2).ToString(); + } + else + { + sdp.SessionDescription = sdpLineTrimmedSpan.Slice(2).ToString(); + } + + break; + + case var _ when sdpLineTrimmedSpan.StartsWith("c=", StringComparison.Ordinal): + + if (activeAnnouncement != null) + { + activeAnnouncement.Connection = SDPConnectionInformation.ParseConnectionInformation(sdpLineTrimmedSpan.ToString()); + } + else if (sdp.Connection == null) + { + sdp.Connection = SDPConnectionInformation.ParseConnectionInformation(sdpLineTrimmedSpan.ToString()); + } + else + { + logger.LogWarning("The SDP message had a duplicate connection attribute which was ignored."); + } + + break; + + case var l when l.StartsWith("b=", StringComparison.Ordinal): + if (activeAnnouncement != null) + { + if (l.StartsWith(SDPMediaAnnouncement.TIAS_BANDWIDTH_ATTRIBUE_PREFIX, StringComparison.Ordinal)) { - activeAnnouncement.TIASBandwidth = tias; + if (uint.TryParse(SliceAfterColon(l), out var tias)) + { + activeAnnouncement.TIASBandwidth = tias; + } + } + else + { + activeAnnouncement.BandwidthAttributes.Add(sdpLineTrimmedSpan.Slice(2).ToString()); } } else { - activeAnnouncement.BandwidthAttributes.Add(sdpLineTrimmed.Substring(2)); - } - } - else - { - sdp.BandwidthAttributes.Add(sdpLineTrimmed.Substring(2)); - } - break; - - case var l when l.StartsWith("t="): - sdp.Timing = sdpLineTrimmed.Substring(2); - break; - - case var l when l.StartsWith("m="): - Match mediaMatch = Regex.Match( - sdpLineTrimmed.Substring(2), - @"(?\w+)\s+(?\d+)(?:\/(?\d+))?\s+(?\S+)\s*(?.*)$" - ); - if (mediaMatch.Success) - { - SDPMediaAnnouncement announcement = new SDPMediaAnnouncement(); - announcement.MLineIndex = mLineIndex; - announcement.Media = SDPMediaTypes.GetSDPMediaType(mediaMatch.Result("${type}")); - - // Parse the primary port. - int.TryParse(mediaMatch.Result("${port}"), out announcement.Port); - if (mediaMatch.Groups["portCount"].Success) - { - int portCount; - if (Int32.TryParse(mediaMatch.Result("${portCount}"), out portCount)) + sdp.BandwidthAttributes.Add(sdpLineTrimmedSpan.Slice(2).ToString()); + } + break; + + case var _ when sdpLineTrimmedSpan.StartsWith("t=", StringComparison.Ordinal): + sdp.Timing = sdpLineTrimmedSpan.Slice(2).ToString(); + break; + + case var _ when sdpLineTrimmedSpan.StartsWith("m=", StringComparison.Ordinal): + var mediaLine = sdpLineTrimmedSpan.Slice(2); + if (TryParseMediaLine( + mediaLine, + out var mediaTypeStart, + out var mediaTypeLength, + out var port, + out var portCount, + out var transportStart, + out var transportLength, + out var formatsStart)) + { + var announcement = new SDPMediaAnnouncement(); + announcement.MLineIndex = mLineIndex; + announcement.Media = SDPMediaTypes.GetSDPMediaType(mediaLine.Slice(mediaTypeStart, mediaTypeLength).ToString()); + + // Parse the primary port. + announcement.Port = port; + if (portCount.HasValue) { - announcement.PortCount = portCount; + announcement.PortCount = portCount.Value; } + + announcement.Transport = mediaLine.Slice(transportStart, transportLength).ToString(); + announcement.ParseMediaFormats(mediaLine.Slice(formatsStart).ToString()); + if (announcement.Media == SDPMediaTypesEnum.audio || announcement.Media == SDPMediaTypesEnum.video || announcement.Media == SDPMediaTypesEnum.text) + { + announcement.MediaStreamStatus = sdp.SessionMediaStreamStatus != null ? sdp.SessionMediaStreamStatus.Value : + MediaStreamStatusEnum.SendRecv; + } + sdp.Media.Add(announcement); + + activeAnnouncement = announcement; + } + else + { + logger.LogWarning("A media line in SDP was invalid: {sdpLine}.", sdpLineTrimmedSpan.Slice(2).ToString()); + } + + mLineIndex++; + break; + + case var _ when StartsWithAttribute(sdpLineTrimmedSpan, GROUP_ATRIBUTE_PREFIX): + sdp.Group = SliceAfterColon(sdpLineTrimmedSpan).ToString(); + break; + case var _ when StartsWithAttribute(sdpLineTrimmedSpan, ICE_LITE_IMPLEMENTATION_ATTRIBUTE_PREFIX): + sdp.IceImplementation = IceImplementationEnum.lite; + break; + case var _ when StartsWithAttribute(sdpLineTrimmedSpan, ICE_UFRAG_ATTRIBUTE_PREFIX): + if (activeAnnouncement != null) + { + activeAnnouncement.IceUfrag = SliceAfterColon(sdpLineTrimmedSpan).ToString(); + } + else + { + sdp.IceUfrag = SliceAfterColon(sdpLineTrimmedSpan).ToString(); } + break; - announcement.Transport = mediaMatch.Result("${transport}"); - announcement.ParseMediaFormats(mediaMatch.Result("${formats}")); - if (announcement.Media == SDPMediaTypesEnum.audio || announcement.Media == SDPMediaTypesEnum.video || announcement.Media == SDPMediaTypesEnum.text) - { - announcement.MediaStreamStatus = sdp.SessionMediaStreamStatus != null ? sdp.SessionMediaStreamStatus.Value : - MediaStreamStatusEnum.SendRecv; - } - sdp.Media.Add(announcement); - - activeAnnouncement = announcement; - } - else - { - logger.LogWarning("A media line in SDP was invalid: {sdpLine}.", sdpLineTrimmed.Substring(2)); - } - - mLineIndex++; - break; - - case var x when x.StartsWith($"a={GROUP_ATRIBUTE_PREFIX}"): - sdp.Group = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1); - break; - case var x when x.StartsWith($"a={ICE_LITE_IMPLEMENTATION_ATTRIBUTE_PREFIX}"): - sdp.IceImplementation = IceImplementationEnum.lite; - break; - case var x when x.StartsWith($"a={ICE_UFRAG_ATTRIBUTE_PREFIX}"): - if (activeAnnouncement != null) - { - activeAnnouncement.IceUfrag = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1); - } - else - { - sdp.IceUfrag = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1); - } - break; - - case var x when x.StartsWith($"a={ICE_PWD_ATTRIBUTE_PREFIX}"): - if (activeAnnouncement != null) - { - activeAnnouncement.IcePwd = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1); - } - else - { - sdp.IcePwd = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1); - } - break; - - case var x when x.StartsWith($"a={ICE_SETUP_ATTRIBUTE_PREFIX}"): - int colonIndex = sdpLineTrimmed.IndexOf(':'); - if (colonIndex != -1 && sdpLineTrimmed.Length > colonIndex) - { - string iceRoleStr = sdpLineTrimmed.Substring(colonIndex + 1).Trim(); - if (Enum.TryParse(iceRoleStr, true, out var iceRole)) - { - if (activeAnnouncement != null) + case var _ when StartsWithAttribute(sdpLineTrimmedSpan, ICE_PWD_ATTRIBUTE_PREFIX): + if (activeAnnouncement != null) + { + activeAnnouncement.IcePwd = SliceAfterColon(sdpLineTrimmedSpan).ToString(); + } + else + { + sdp.IcePwd = SliceAfterColon(sdpLineTrimmedSpan).ToString(); + } + break; + + case var _ when StartsWithAttribute(sdpLineTrimmedSpan, ICE_SETUP_ATTRIBUTE_PREFIX): + var colonIndex = sdpLineTrimmedSpan.IndexOf(':'); + if (colonIndex != -1 && sdpLineTrimmedSpan.Length > colonIndex) + { + var iceRoleStr = sdpLineTrimmedSpan.Slice(colonIndex + 1).Trim().ToString(); + if (Enum.TryParse(iceRoleStr, true, out var iceRole)) { - activeAnnouncement.IceRole = iceRole; + if (activeAnnouncement != null) + { + activeAnnouncement.IceRole = iceRole; + } + else + { + sdp.IceRole = iceRole; + } } else { - sdp.IceRole = iceRole; + logger.LogWarning("ICE role was not recognised from SDP attribute: {sdpLineTrimmed}.", sdpLineTrimmedSpan.ToString()); } } else { - logger.LogWarning("ICE role was not recognised from SDP attribute: {sdpLineTrimmed}.", sdpLineTrimmed); - } - } - else - { - logger.LogWarning("ICE role SDP attribute was missing the mandatory colon: {sdpLineTrimmed}.", sdpLineTrimmed); - } - break; - - case var x when x.StartsWith($"a={DTLS_FINGERPRINT_ATTRIBUTE_PREFIX}"): - if (activeAnnouncement != null) - { - activeAnnouncement.DtlsFingerprint = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1); - } - else - { - sdp.DtlsFingerprint = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1); - } - break; - - case var x when x.StartsWith($"a={ICE_CANDIDATE_ATTRIBUTE_PREFIX}"): - if (activeAnnouncement != null) - { - if (activeAnnouncement.IceCandidates == null) - { - activeAnnouncement.IceCandidates = new List(); - } - activeAnnouncement.IceCandidates.Add(sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1)); - } - else - { - if (sdp.IceCandidates == null) - { - sdp.IceCandidates = new List(); - } - sdp.IceCandidates.Add(sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1)); - } - break; - - case var x when x == $"a={END_ICE_CANDIDATES_ATTRIBUTE}": - // TODO: Set a flag. - break; - case var l when l.StartsWith(SDPMediaAnnouncement.MEDIA_EXTENSION_MAP_ATTRIBUE_PREFIX): - if (activeAnnouncement != null) { - if (activeAnnouncement.Media == SDPMediaTypesEnum.audio || activeAnnouncement.Media == SDPMediaTypesEnum.video) { - var formatAttributeMatch = Regex.Match(sdpLineTrimmed, SDPMediaAnnouncement.MEDIA_EXTENSION_MAP_ATTRIBUE_PREFIX + @"(?\d+) (?\S+)$"); - if (formatAttributeMatch.Success) { - var extensionId = formatAttributeMatch.Result("${id}"); - var uri = formatAttributeMatch.Result("${url}"); - if (Int32.TryParse(extensionId, out var id)) { - var rtpExtension = RTPHeaderExtension.GetRTPHeaderExtension(id, uri, activeAnnouncement.Media); - if ( (rtpExtension != null) && !activeAnnouncement.HeaderExtensions.ContainsKey(id)) - { - activeAnnouncement.HeaderExtensions.Add(id, rtpExtension); - } - } - else { - logger.LogWarning("Invalid id of header extension in " + SDPMediaAnnouncement.MEDIA_EXTENSION_MAP_ATTRIBUE_PREFIX); - } - } - } - } - - break; - case var l when l.StartsWith(SDPMediaAnnouncement.MEDIA_FORMAT_ATTRIBUTE_PREFIX): - if (activeAnnouncement != null) - { - if (activeAnnouncement.Media == SDPMediaTypesEnum.audio || activeAnnouncement.Media == SDPMediaTypesEnum.video || activeAnnouncement.Media == SDPMediaTypesEnum.text) - { - // Parse the rtpmap attribute for audio/video announcements. - Match formatAttributeMatch = Regex.Match(sdpLineTrimmed, SDPMediaAnnouncement.MEDIA_FORMAT_ATTRIBUTE_PREFIX + @"(?\d+)\s+(?.*)$"); - if (formatAttributeMatch.Success) + logger.LogWarning("ICE role SDP attribute was missing the mandatory colon: {sdpLineTrimmed}.", sdpLineTrimmedSpan.ToString()); + } + break; + + case var _ when StartsWithAttribute(sdpLineTrimmedSpan, DTLS_FINGERPRINT_ATTRIBUTE_PREFIX): + if (activeAnnouncement != null) + { + activeAnnouncement.DtlsFingerprint = SliceAfterColon(sdpLineTrimmedSpan).ToString(); + } + else + { + sdp.DtlsFingerprint = SliceAfterColon(sdpLineTrimmedSpan).ToString(); + } + break; + + case var _ when StartsWithAttribute(sdpLineTrimmedSpan, ICE_CANDIDATE_ATTRIBUTE_PREFIX): + if (activeAnnouncement != null) + { + if (activeAnnouncement.IceCandidates == null) + { + activeAnnouncement.IceCandidates = new List(); + } + activeAnnouncement.IceCandidates.Add(SliceAfterColon(sdpLineTrimmedSpan).ToString()); + } + else + { + if (sdp.IceCandidates == null) { - string formatID = formatAttributeMatch.Result("${id}"); - string rtpmap = formatAttributeMatch.Result("${attribute}"); + sdp.IceCandidates = new List(); + } + sdp.IceCandidates.Add(SliceAfterColon(sdpLineTrimmedSpan).ToString()); + } + break; + + case var _ when EqualsAttribute(sdpLineTrimmedSpan, END_ICE_CANDIDATES_ATTRIBUTE): + // TODO: Set a flag. + break; + case var l when l.StartsWith(SDPMediaAnnouncement.MEDIA_EXTENSION_MAP_ATTRIBUE_PREFIX, StringComparison.Ordinal): + if (activeAnnouncement != null && + (activeAnnouncement.Media == SDPMediaTypesEnum.audio || activeAnnouncement.Media == SDPMediaTypesEnum.video) && + TryParseExtensionMap(l, out var extensionId, out var uriStart, out var uriLength)) + { + var rtpExtension = RTPHeaderExtension.GetRTPHeaderExtension(extensionId, l.Slice(uriStart, uriLength).ToString(), activeAnnouncement.Media); + if ((rtpExtension != null) && !activeAnnouncement.HeaderExtensions.ContainsKey(extensionId)) + { + activeAnnouncement.HeaderExtensions.Add(extensionId, rtpExtension); + } + } - if (Int32.TryParse(formatID, out int id)) + break; + case var l when l.StartsWith(SDPMediaAnnouncement.MEDIA_FORMAT_ATTRIBUTE_PREFIX, StringComparison.Ordinal): + if (activeAnnouncement != null) + { + if (activeAnnouncement.Media == SDPMediaTypesEnum.audio || activeAnnouncement.Media == SDPMediaTypesEnum.video || activeAnnouncement.Media == SDPMediaTypesEnum.text) + { + // Parse the rtpmap attribute for audio/video announcements. + if (TrySplitAttributeValue( + l, + SDPMediaAnnouncement.MEDIA_FORMAT_ATTRIBUTE_PREFIX.Length, + out var formatIDStart, + out var formatIDLength, + out var rtpmapStart)) { - if (activeAnnouncement.MediaFormats.ContainsKey(id)) + var formatID = l.Slice(formatIDStart, formatIDLength); + if (int.TryParse(formatID, out var mediaFormatId)) { - activeAnnouncement.MediaFormats[id] = activeAnnouncement.MediaFormats[id].WithUpdatedRtpmap(rtpmap); + var rtpmap = l.Slice(rtpmapStart).ToString(); + if (activeAnnouncement.MediaFormats.ContainsKey(mediaFormatId)) + { + activeAnnouncement.MediaFormats[mediaFormatId] = activeAnnouncement.MediaFormats[mediaFormatId].WithUpdatedRtpmap(rtpmap); + } + else + { + var fmtp = _pendingFmtp.ContainsKey(mediaFormatId) ? _pendingFmtp[mediaFormatId] : null; + activeAnnouncement.MediaFormats.Add(mediaFormatId, new SDPAudioVideoMediaFormat(activeAnnouncement.Media, mediaFormatId, rtpmap, fmtp)); + } } else { - string fmtp = _pendingFmtp.ContainsKey(id) ? _pendingFmtp[id] : null; - activeAnnouncement.MediaFormats.Add(id, new SDPAudioVideoMediaFormat(activeAnnouncement.Media, id, rtpmap, fmtp)); + logger.LogWarning("Non-numeric audio/video media format attribute in SDP: {sdpLine}", sdpLineTrimmedSpan.ToString()); } } else { - logger.LogWarning("Non-numeric audio/video media format attribute in SDP: {sdpLine}", sdpLine); + activeAnnouncement.AddExtra(sdpLineTrimmedSpan.ToString()); } } else { - activeAnnouncement.AddExtra(sdpLineTrimmed); + // Parse the rtpmap attribute for NON audio/video announcements. + if (TrySplitAttributeValue( + l, + SDPMediaAnnouncement.MEDIA_FORMAT_ATTRIBUTE_PREFIX.Length, + out var formatIDStart, + out var formatIDLength, + out var rtpmapStart)) + { + var formatID = l.Slice(formatIDStart, formatIDLength).ToString(); + var rtpmap = l.Slice(rtpmapStart).ToString(); + + if (activeAnnouncement.ApplicationMediaFormats.ContainsKey(formatID)) + { + activeAnnouncement.ApplicationMediaFormats[formatID] = activeAnnouncement.ApplicationMediaFormats[formatID].WithUpdatedRtpmap(rtpmap); + } + else + { + activeAnnouncement.ApplicationMediaFormats.Add(formatID, new SDPApplicationMediaFormat(formatID, rtpmap, null)); + } + } + else + { + activeAnnouncement.AddExtra(sdpLineTrimmedSpan.ToString()); + } } } else { - // Parse the rtpmap attribute for NON audio/video announcements. - Match formatAttributeMatch = Regex.Match(sdpLineTrimmed, SDPMediaAnnouncement.MEDIA_FORMAT_ATTRIBUTE_PREFIX + @"(?\S+)\s+(?.*)$"); - if (formatAttributeMatch.Success) - { - string formatID = formatAttributeMatch.Result("${id}"); - string rtpmap = formatAttributeMatch.Result("${attribute}"); + logger.LogWarning("There was no active media announcement for a media format attribute, ignoring."); + } + break; - if (activeAnnouncement.ApplicationMediaFormats.ContainsKey(formatID)) + case var l when l.StartsWith(SDPMediaAnnouncement.MEDIA_FORMAT_PARAMETERS_ATTRIBUE_PREFIX, StringComparison.Ordinal): + if (activeAnnouncement != null) + { + if (activeAnnouncement.Media == SDPMediaTypesEnum.audio || activeAnnouncement.Media == SDPMediaTypesEnum.video || activeAnnouncement.Media == SDPMediaTypesEnum.text) + { + // Parse the fmtp attribute for audio/video announcements. + if (TrySplitAttributeValue( + l, + SDPMediaAnnouncement.MEDIA_FORMAT_PARAMETERS_ATTRIBUE_PREFIX.Length, + out var avFormatIDStart, + out var avFormatIDLength, + out var fmtpStart)) { - activeAnnouncement.ApplicationMediaFormats[formatID] = activeAnnouncement.ApplicationMediaFormats[formatID].WithUpdatedRtpmap(rtpmap); + var avFormatID = l.Slice(avFormatIDStart, avFormatIDLength); + if (int.TryParse(avFormatID, out var fmtpFormatId)) + { + var fmtp = l.Slice(fmtpStart).ToString(); + if (activeAnnouncement.MediaFormats.ContainsKey(fmtpFormatId)) + { + activeAnnouncement.MediaFormats[fmtpFormatId] = activeAnnouncement.MediaFormats[fmtpFormatId].WithUpdatedFmtp(fmtp); + } + else + { + // Store the fmtp attribute for use when the rtpmap attribute turns up. + if (_pendingFmtp.ContainsKey(fmtpFormatId)) + { + _pendingFmtp.Remove(fmtpFormatId); + } + _pendingFmtp.Add(fmtpFormatId, fmtp); + } + } + else + { + logger.LogWarning("Invalid media format parameter attribute in SDP: {sdpLine}", sdpLineTrimmedSpan.ToString()); + } } else { - activeAnnouncement.ApplicationMediaFormats.Add(formatID, new SDPApplicationMediaFormat(formatID, rtpmap, null)); + activeAnnouncement.AddExtra(sdpLineTrimmedSpan.ToString()); } } else { - activeAnnouncement.AddExtra(sdpLineTrimmed); - } - } - } - else - { - logger.LogWarning("There was no active media announcement for a media format attribute, ignoring."); - } - break; - - case var l when l.StartsWith(SDPMediaAnnouncement.MEDIA_FORMAT_PARAMETERS_ATTRIBUE_PREFIX): - if (activeAnnouncement != null) - { - if (activeAnnouncement.Media == SDPMediaTypesEnum.audio || activeAnnouncement.Media == SDPMediaTypesEnum.video || activeAnnouncement.Media == SDPMediaTypesEnum.text) - { - // Parse the fmtp attribute for audio/video announcements. - Match formatAttributeMatch = Regex.Match(sdpLineTrimmed, SDPMediaAnnouncement.MEDIA_FORMAT_PARAMETERS_ATTRIBUE_PREFIX + @"(?\d+)\s+(?.*)$"); - if (formatAttributeMatch.Success) - { - string avFormatID = formatAttributeMatch.Result("${id}"); - string fmtp = formatAttributeMatch.Result("${attribute}"); - - if (Int32.TryParse(avFormatID, out int id)) + // Parse the fmtp attribute for NON audio/video announcements. + if (TrySplitAttributeValue( + l, + SDPMediaAnnouncement.MEDIA_FORMAT_PARAMETERS_ATTRIBUE_PREFIX.Length, + out var formatIDStart, + out var formatIDLength, + out var fmtpStart)) { - if (activeAnnouncement.MediaFormats.ContainsKey(id)) + var formatID = l.Slice(formatIDStart, formatIDLength).ToString(); + var fmtp = l.Slice(fmtpStart).ToString(); + + if (activeAnnouncement.ApplicationMediaFormats.ContainsKey(formatID)) { - activeAnnouncement.MediaFormats[id] = activeAnnouncement.MediaFormats[id].WithUpdatedFmtp(fmtp); + activeAnnouncement.ApplicationMediaFormats[formatID] = activeAnnouncement.ApplicationMediaFormats[formatID].WithUpdatedFmtp(fmtp); } else { - // Store the fmtp attribute for use when the rtpmap attribute turns up. - if (_pendingFmtp.ContainsKey(id)) - { - _pendingFmtp.Remove(id); - } - _pendingFmtp.Add(id, fmtp); + activeAnnouncement.ApplicationMediaFormats.Add(formatID, new SDPApplicationMediaFormat(formatID, null, fmtp)); } } else { - logger.LogWarning("Invalid media format parameter attribute in SDP: {sdpLine}", sdpLine); + activeAnnouncement.AddExtra(sdpLineTrimmedSpan.ToString()); } } - else + } + else + { + logger.LogWarning("There was no active media announcement for a media format parameter attribute, ignoring."); + } + break; + + case var _ when sdpLineTrimmedSpan.StartsWith(SDPSecurityDescription.CRYPTO_ATTRIBUE_PREFIX, StringComparison.Ordinal): + //2018-12-21 rj2: add a=crypto + if (activeAnnouncement != null) + { + try + { + activeAnnouncement.AddCryptoLine(sdpLineTrimmedSpan.ToString()); + } + catch (FormatException fex) { - activeAnnouncement.AddExtra(sdpLineTrimmed); + logger.LogWarning("Error Parsing SDP-Line(a=crypto) {Exception}", fex); } } + break; + + case var _ when StartsWithAttribute(sdpLineTrimmedSpan, MEDIA_ID_ATTRIBUTE_PREFIX): + if (activeAnnouncement != null) + { + activeAnnouncement.MediaID = SliceAfterColon(sdpLineTrimmedSpan).ToString(); + } else { - // Parse the fmtp attribute for NON audio/video announcements. - Match formatAttributeMatch = Regex.Match(sdpLineTrimmed, SDPMediaAnnouncement.MEDIA_FORMAT_PARAMETERS_ATTRIBUE_PREFIX + @"(?\S+)\s+(?.*)$"); - if (formatAttributeMatch.Success) + logger.LogWarning("A media ID can only be set on a media announcement."); + } + break; + + case var _ when sdpLineTrimmedSpan.StartsWith(SDPMediaAnnouncement.MEDIA_FORMAT_SSRC_GROUP_ATTRIBUE_PREFIX, StringComparison.Ordinal): + if (activeAnnouncement != null) + { + var fields = SliceAfterColon(sdpLineTrimmedSpan); + var fieldIndex = 0; + + // Set the ID. + foreach (var fieldRange in fields.Split(' ')) { - string formatID = formatAttributeMatch.Result("${id}"); - string fmtp = formatAttributeMatch.Result("${attribute}"); + var ssrcField = fields[fieldRange]; + if (fieldIndex == 0) + { + activeAnnouncement.SsrcGroupID = ssrcField.ToString(); + } + else if (uint.TryParse(ssrcField, out var ssrc)) + { + // Add attributes for each of the SSRC values. + activeAnnouncement.SsrcAttributes.Add(new SDPSsrcAttribute(ssrc, null, activeAnnouncement.SsrcGroupID)); + } + + fieldIndex++; + } + } + else + { + logger.LogWarning("A ssrc-group ID can only be set on a media announcement."); + } + break; + + case var _ when sdpLineTrimmedSpan.StartsWith(SDPMediaAnnouncement.MEDIA_FORMAT_SSRC_ATTRIBUE_PREFIX, StringComparison.Ordinal): + if (activeAnnouncement != null) + { + var ssrcFields = SliceAfterColon(sdpLineTrimmedSpan); + var ssrcField = default(ReadOnlySpan); + var cnameField = default(ReadOnlySpan); + var fieldIndex = 0; - if (activeAnnouncement.ApplicationMediaFormats.ContainsKey(formatID)) + foreach (var fieldRange in ssrcFields.Split(' ')) + { + if (fieldIndex == 0) { - activeAnnouncement.ApplicationMediaFormats[formatID] = activeAnnouncement.ApplicationMediaFormats[formatID].WithUpdatedFmtp(fmtp); + ssrcField = ssrcFields[fieldRange]; } - else + else if (fieldIndex == 1) { - activeAnnouncement.ApplicationMediaFormats.Add(formatID, new SDPApplicationMediaFormat(formatID, null, fmtp)); + cnameField = ssrcFields[fieldRange]; + break; } + + fieldIndex++; } - else + + if (uint.TryParse(ssrcField, out var ssrc)) { - activeAnnouncement.AddExtra(sdpLineTrimmed); + var ssrcAttribute = activeAnnouncement.SsrcAttributes.FirstOrDefault(x => x.SSRC == ssrc); + if (ssrcAttribute == null) + { + ssrcAttribute = new SDPSsrcAttribute(ssrc, null, null); + activeAnnouncement.SsrcAttributes.Add(ssrcAttribute); + } + + if (!cnameField.IsEmpty && + cnameField.StartsWith(SDPSsrcAttribute.MEDIA_CNAME_ATTRIBUE_PREFIX, StringComparison.Ordinal)) + { + ssrcAttribute.Cname = cnameField.Slice(cnameField.IndexOf(':') + 1).ToString(); + } } } - } - else - { - logger.LogWarning("There was no active media announcement for a media format parameter attribute, ignoring."); - } - break; + else + { + logger.LogWarning("An ssrc attribute can only be set on a media announcement."); + } + break; - case var l when l.StartsWith(SDPSecurityDescription.CRYPTO_ATTRIBUE_PREFIX): - //2018-12-21 rj2: add a=crypto - if (activeAnnouncement != null) - { - try + case var _ when TryParseMediaStreamStatus(sdpLineTrimmedSpan, out var mediaStreamStatus): + if (activeAnnouncement != null) { - activeAnnouncement.AddCryptoLine(sdpLineTrimmed); + activeAnnouncement.MediaStreamStatus = mediaStreamStatus; } - catch (FormatException fex) + else { - logger.LogWarning("Error Parsing SDP-Line(a=crypto) {Exception}", fex); + sdp.SessionMediaStreamStatus = mediaStreamStatus; } - } - break; + break; - case var x when x.StartsWith($"a={MEDIA_ID_ATTRIBUTE_PREFIX}"): - if (activeAnnouncement != null) - { - activeAnnouncement.MediaID = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1); - } - else - { - logger.LogWarning("A media ID can only be set on a media announcement."); - } - break; + case var _ when sdpLineTrimmedSpan.StartsWith(SDPMediaAnnouncement.MEDIA_FORMAT_SCTP_MAP_ATTRIBUE_PREFIX, StringComparison.Ordinal): + if (activeAnnouncement != null) + { + var sctpMapFields = SliceAfterColon(sdpLineTrimmedSpan); + activeAnnouncement.SctpMap = sctpMapFields.ToString(); - case var l when l.StartsWith(SDPMediaAnnouncement.MEDIA_FORMAT_SSRC_GROUP_ATTRIBUE_PREFIX): - if (activeAnnouncement != null) - { - string[] fields = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1).Split(' '); + var sctpPortField = default(ReadOnlySpan); + var maxMessageSizeField = default(ReadOnlySpan); + var fieldIndex = 0; - // Set the ID. - if (fields.Length > 0) + foreach (var fieldRange in sctpMapFields.Split(' ')) + { + if (fieldIndex == 0) + { + sctpPortField = sctpMapFields[fieldRange]; + } + else if (fieldIndex == 2) + { + maxMessageSizeField = sctpMapFields[fieldRange]; + break; + } + + fieldIndex++; + } + + if (ushort.TryParse(sctpPortField, out var sctpPort)) + { + activeAnnouncement.SctpPort = sctpPort; + } + else + { + logger.LogWarning("An sctp-port value of {sctpPortStr} was not recognised as a valid port.", sctpPortField.ToString()); + } + + if (!long.TryParse(maxMessageSizeField, out activeAnnouncement.MaxMessageSize)) + { + logger.LogWarning("A max-message-size value of {maxMessageSizeStr} was not recognised as a valid long.", maxMessageSizeField.ToString()); + } + } + else { - activeAnnouncement.SsrcGroupID = fields[0]; + logger.LogWarning("An sctpmap attribute can only be set on a media announcement."); } + break; - // Add attributes for each of the SSRC values. - for (int i = 1; i < fields.Length; i++) + case var _ when sdpLineTrimmedSpan.StartsWith(SDPMediaAnnouncement.MEDIA_FORMAT_SCTP_PORT_ATTRIBUE_PREFIX, StringComparison.Ordinal): + if (activeAnnouncement != null) { - if (uint.TryParse(fields[i], out var ssrc)) + var sctpPortStr = SliceAfterColon(sdpLineTrimmedSpan); + + if (ushort.TryParse(sctpPortStr, out var sctpPort)) + { + activeAnnouncement.SctpPort = sctpPort; + } + else { - activeAnnouncement.SsrcAttributes.Add(new SDPSsrcAttribute(ssrc, null, activeAnnouncement.SsrcGroupID)); + logger.LogWarning("An sctp-port value of {sctpPortStr} was not recognised as a valid port.", sctpPortStr.ToString()); } } - } - else - { - logger.LogWarning("A ssrc-group ID can only be set on a media announcement."); - } - break; - - case var l when l.StartsWith(SDPMediaAnnouncement.MEDIA_FORMAT_SSRC_ATTRIBUE_PREFIX): - if (activeAnnouncement != null) - { - string[] ssrcFields = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1).Split(' '); + else + { + logger.LogWarning("An sctp-port attribute can only be set on a media announcement."); + } + break; - if (ssrcFields.Length > 0 && uint.TryParse(ssrcFields[0], out var ssrc)) + case var _ when sdpLineTrimmedSpan.StartsWith(SDPMediaAnnouncement.MEDIA_FORMAT_MAX_MESSAGE_SIZE_ATTRIBUE_PREFIX, StringComparison.Ordinal): + if (activeAnnouncement != null) { - var ssrcAttribute = activeAnnouncement.SsrcAttributes.FirstOrDefault(x => x.SSRC == ssrc); - if (ssrcAttribute == null) + var maxMessageSizeStr = SliceAfterColon(sdpLineTrimmedSpan); + if (!long.TryParse(maxMessageSizeStr, out activeAnnouncement.MaxMessageSize)) { - ssrcAttribute = new SDPSsrcAttribute(ssrc, null, null); - activeAnnouncement.SsrcAttributes.Add(ssrcAttribute); + logger.LogWarning("A max-message-size value of {maxMessageSizeStr} was not recognised as a valid long.", maxMessageSizeStr.ToString()); } + } + else + { + logger.LogWarning("A max-message-size attribute can only be set on a media announcement."); + } + break; - if (ssrcFields.Length > 1) + case var _ when sdpLineTrimmedSpan.StartsWith(SDPMediaAnnouncement.MEDIA_FORMAT_PATH_ACCEPT_TYPES_PREFIX, StringComparison.Ordinal): + if (activeAnnouncement != null) + { + var acceptTypes = SliceAfterColon(sdpLineTrimmedSpan).Trim(); + var acceptTypesList = new List(); + foreach (var acceptTypeRange in acceptTypes.Split(' ')) { - if (ssrcFields[1].StartsWith(SDPSsrcAttribute.MEDIA_CNAME_ATTRIBUE_PREFIX)) - { - ssrcAttribute.Cname = ssrcFields[1].Substring(ssrcFields[1].IndexOf(':') + 1); - } + acceptTypesList.Add(acceptTypes[acceptTypeRange].ToString()); } + activeAnnouncement.MessageMediaFormat.AcceptTypes = acceptTypesList; } - } - else - { - logger.LogWarning("An ssrc attribute can only be set on a media announcement."); - } - break; + else + { + logger.LogWarning("A accept-types attribute can only be set on a media announcement."); + } + break; - case var x when MediaStreamStatusType.IsMediaStreamStatusAttribute(x, out var mediaStreamStatus): - if (activeAnnouncement != null) - { - activeAnnouncement.MediaStreamStatus = mediaStreamStatus; - } - else - { - sdp.SessionMediaStreamStatus = mediaStreamStatus; - } - break; + case var _ when sdpLineTrimmedSpan.StartsWith(SDPMediaAnnouncement.MEDIA_FORMAT_PATH_MSRP_PREFIX, StringComparison.Ordinal): + if (activeAnnouncement != null) + { + var pathStr = SliceAfterColon(sdpLineTrimmedSpan); + var pathTrimmedStr = pathStr.Slice(pathStr.IndexOf(':') + 3); + activeAnnouncement.MessageMediaFormat.IP = pathTrimmedStr.Slice(0, pathTrimmedStr.IndexOf(':')).ToString(); - case var l when l.StartsWith(SDPMediaAnnouncement.MEDIA_FORMAT_SCTP_MAP_ATTRIBUE_PREFIX): - if (activeAnnouncement != null) - { - activeAnnouncement.SctpMap = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1); + pathTrimmedStr = pathTrimmedStr.Slice(pathTrimmedStr.IndexOf(':') + 1); + activeAnnouncement.MessageMediaFormat.Port = pathTrimmedStr.Slice(0, pathTrimmedStr.IndexOf('/')).ToString(); - (var sctpPortStr, _, var maxMessageSizeStr) = activeAnnouncement.SctpMap.Split(' '); + pathTrimmedStr = pathTrimmedStr.Slice(pathTrimmedStr.IndexOf('/') + 1); + activeAnnouncement.MessageMediaFormat.Endpoint = pathTrimmedStr.ToString(); - if (ushort.TryParse(sctpPortStr, out var sctpPort)) - { - activeAnnouncement.SctpPort = sctpPort; } else { - logger.LogWarning("An sctp-port value of {sctpPortStr} was not recognised as a valid port.", sctpPortStr); - } - - if (!long.TryParse(maxMessageSizeStr, out activeAnnouncement.MaxMessageSize)) - { - logger.LogWarning("A max-message-size value of {maxMessageSizeStr} was not recognised as a valid long.", maxMessageSizeStr); + logger.LogWarning("A path attribute can only be set on a media announcement."); } - } - else - { - logger.LogWarning("An sctpmap attribute can only be set on a media announcement."); - } - break; - - case var l when l.StartsWith(SDPMediaAnnouncement.MEDIA_FORMAT_SCTP_PORT_ATTRIBUE_PREFIX): - if (activeAnnouncement != null) - { - string sctpPortStr = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1); + break; - if (ushort.TryParse(sctpPortStr, out var sctpPort)) + default: + if (activeAnnouncement != null) { - activeAnnouncement.SctpPort = sctpPort; + activeAnnouncement.AddExtra(sdpLineTrimmedSpan.ToString()); } else { - logger.LogWarning("An sctp-port value of {sctpPortStr} was not recognised as a valid port.", sctpPortStr); - } - } - else - { - logger.LogWarning("An sctp-port attribute can only be set on a media announcement."); - } - break; - - case var l when l.StartsWith(SDPMediaAnnouncement.MEDIA_FORMAT_MAX_MESSAGE_SIZE_ATTRIBUE_PREFIX): - if (activeAnnouncement != null) - { - string maxMessageSizeStr = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1); - if (!long.TryParse(maxMessageSizeStr, out activeAnnouncement.MaxMessageSize)) - { - logger.LogWarning("A max-message-size value of {maxMessageSizeStr} was not recognised as a valid long.", maxMessageSizeStr); - } - } - else - { - logger.LogWarning("A max-message-size attribute can only be set on a media announcement."); - } - break; - - case var l when l.StartsWith(SDPMediaAnnouncement.MEDIA_FORMAT_PATH_ACCEPT_TYPES_PREFIX): - if (activeAnnouncement != null) - { - string acceptTypesStr = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1); - var acceptTypesList = acceptTypesStr.Trim().Split(' ').ToList(); - activeAnnouncement.MessageMediaFormat.AcceptTypes = acceptTypesList; - } - else - { - logger.LogWarning("A accept-types attribute can only be set on a media announcement."); - } - break; - - case var l when l.StartsWith(SDPMediaAnnouncement.MEDIA_FORMAT_PATH_MSRP_PREFIX): - if (activeAnnouncement != null) - { - string pathStr = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1); - string pathTrimmedStr = pathStr.Substring(pathStr.IndexOf(':') + 3); - activeAnnouncement.MessageMediaFormat.IP = pathTrimmedStr.Substring(0, pathTrimmedStr.IndexOf(':')); - - pathTrimmedStr = pathTrimmedStr.Substring(pathTrimmedStr.IndexOf(':') + 1); - activeAnnouncement.MessageMediaFormat.Port = pathTrimmedStr.Substring(0, pathTrimmedStr.IndexOf('/')); - - pathTrimmedStr = pathTrimmedStr.Substring(pathTrimmedStr.IndexOf('/') + 1); - activeAnnouncement.MessageMediaFormat.Endpoint = pathTrimmedStr; - - } - else - { - logger.LogWarning("A path attribute can only be set on a media announcement."); - } - break; - - default: - if (activeAnnouncement != null) - { - activeAnnouncement.AddExtra(sdpLineTrimmed); - } - else - { - sdp.AddExtra(sdpLineTrimmed); - } - break; + sdp.AddExtra(sdpLineTrimmedSpan.ToString()); + } + break; + } } } + finally + { + ArrayPool.Shared.Return(sdpLineRangeBuffer); + } return sdp; } @@ -824,37 +1058,69 @@ public string RawString() public override string ToString() { - string sdp = - "v=" + SDP_PROTOCOL_VERSION + CRLF + - "o=" + Owner + CRLF + - "s=" + SessionName + CRLF + - ((Connection != null) ? Connection.ToString() : null); + var sdp = new StringBuilder(); + sdp.Append("v=").Append(SDP_PROTOCOL_VERSION).Append(CRLF) + .Append("o=").Append(Owner).Append(CRLF) + .Append("s=").Append(SessionName).Append(CRLF); + + if (Connection != null) + { + sdp.Append(Connection); + } + foreach (string bandwidth in BandwidthAttributes) { - sdp += "b=" + bandwidth + CRLF; + sdp.Append("b=").Append(bandwidth).Append(CRLF); + } + + sdp.Append("t=").Append(Timing).Append(CRLF); + + if (!string.IsNullOrWhiteSpace(IceUfrag)) + { + sdp.Append("a=").Append(ICE_UFRAG_ATTRIBUTE_PREFIX).Append(':').Append(IceUfrag).Append(CRLF); + } + + if (!string.IsNullOrWhiteSpace(IcePwd)) + { + sdp.Append("a=").Append(ICE_PWD_ATTRIBUTE_PREFIX).Append(':').Append(IcePwd).Append(CRLF); + } + + if (IceRole != null) + { + sdp.Append("a=").Append(SDP.ICE_SETUP_ATTRIBUTE_PREFIX).Append(':').Append(IceRole).Append(CRLF); } - sdp += "t=" + Timing + CRLF; + if (!string.IsNullOrWhiteSpace(DtlsFingerprint)) + { + sdp.Append("a=").Append(DTLS_FINGERPRINT_ATTRIBUTE_PREFIX).Append(':').Append(DtlsFingerprint).Append(CRLF); + } - sdp += !string.IsNullOrWhiteSpace(IceUfrag) ? "a=" + ICE_UFRAG_ATTRIBUTE_PREFIX + ":" + IceUfrag + CRLF : null; - sdp += !string.IsNullOrWhiteSpace(IcePwd) ? "a=" + ICE_PWD_ATTRIBUTE_PREFIX + ":" + IcePwd + CRLF : null; - sdp += IceRole != null ? $"a={SDP.ICE_SETUP_ATTRIBUTE_PREFIX}:{IceRole}{CRLF}" : null; - sdp += !string.IsNullOrWhiteSpace(DtlsFingerprint) ? "a=" + DTLS_FINGERPRINT_ATTRIBUTE_PREFIX + ":" + DtlsFingerprint + CRLF : null; if (IceCandidates?.Count > 0) { foreach (var candidate in IceCandidates) { - sdp += $"a={SDP.ICE_CANDIDATE_ATTRIBUTE_PREFIX}:{candidate}{CRLF}"; + sdp.Append("a=").Append(SDP.ICE_CANDIDATE_ATTRIBUTE_PREFIX).Append(':').Append(candidate).Append(CRLF); } } - sdp += string.IsNullOrWhiteSpace(SessionDescription) ? null : "i=" + SessionDescription + CRLF; - sdp += string.IsNullOrWhiteSpace(URI) ? null : "u=" + URI + CRLF; + + if (!string.IsNullOrWhiteSpace(SessionDescription)) + { + sdp.Append("i=").Append(SessionDescription).Append(CRLF); + } + + if (!string.IsNullOrWhiteSpace(URI)) + { + sdp.Append("u=").Append(URI).Append(CRLF); + } if (OriginatorEmailAddresses != null && OriginatorEmailAddresses.Length > 0) { foreach (string originatorAddress in OriginatorEmailAddresses) { - sdp += string.IsNullOrWhiteSpace(originatorAddress) ? null : "e=" + originatorAddress + CRLF; + if (!string.IsNullOrWhiteSpace(originatorAddress)) + { + sdp.Append("e=").Append(originatorAddress).Append(CRLF); + } } } @@ -862,29 +1128,41 @@ public override string ToString() { foreach (string originatorNumber in OriginatorPhoneNumbers) { - sdp += string.IsNullOrWhiteSpace(originatorNumber) ? null : "p=" + originatorNumber + CRLF; + if (!string.IsNullOrWhiteSpace(originatorNumber)) + { + sdp.Append("p=").Append(originatorNumber).Append(CRLF); + } } } - sdp += (Group == null) ? null : $"a={GROUP_ATRIBUTE_PREFIX}:{Group}" + CRLF; + if (Group != null) + { + sdp.Append("a=").Append(GROUP_ATRIBUTE_PREFIX).Append(':').Append(Group).Append(CRLF); + } foreach (string extra in ExtraSessionAttributes) { - sdp += string.IsNullOrWhiteSpace(extra) ? null : extra + CRLF; + if (!string.IsNullOrWhiteSpace(extra)) + { + sdp.Append(extra).Append(CRLF); + } } if (SessionMediaStreamStatus != null) { - sdp += MediaStreamStatusType.GetAttributeForMediaStreamStatus(SessionMediaStreamStatus.Value) + CRLF; + sdp.Append(MediaStreamStatusType.GetAttributeForMediaStreamStatus(SessionMediaStreamStatus.Value)).Append(CRLF); } //foreach (SDPMediaAnnouncement media in Media.OrderBy(x => x.MLineIndex).ThenBy(x => x.MediaID)) foreach (SDPMediaAnnouncement media in Media.OrderBy(x => x.MLineIndex).ThenBy(x => x.MediaID)) { - sdp += (media == null) ? null : media.ToString(); + if (media != null) + { + sdp.Append(media); + } } - return sdp; + return sdp.ToString(); } /// @@ -901,7 +1179,7 @@ public IPEndPoint GetSDPRTPEndPoint() { return new IPEndPoint(IPAddress.Parse(sessionConnection.ConnectionAddress), firstMediaOffer.Port); } - else if(firstMediaOffer != null && firstMediaOffer.Connection != null) + else if (firstMediaOffer != null && firstMediaOffer.Connection != null) { return new IPEndPoint(IPAddress.Parse(firstMediaOffer.Connection.ConnectionAddress), firstMediaOffer.Port); } diff --git a/src/SIPSorcery/net/SDP/SDPAudioVideoMediaFormat.cs b/src/SIPSorcery/net/SDP/SDPAudioVideoMediaFormat.cs index 6ba8342c93..1e0da6761a 100644 --- a/src/SIPSorcery/net/SDP/SDPAudioVideoMediaFormat.cs +++ b/src/SIPSorcery/net/SDP/SDPAudioVideoMediaFormat.cs @@ -17,6 +17,8 @@ using System; using System.Collections.Generic; using System.Linq; +using Polyfills; +using SIPSorcery.Sys; using SIPSorceryMedia.Abstractions; namespace SIPSorcery.Net @@ -148,7 +150,7 @@ public bool IsH264 { get { - return (Rtpmap ?? "").ToUpperInvariant().Trim().StartsWith("H264"); + return Rtpmap.AsSpan().Trim().StartsWith("H264", StringComparison.OrdinalIgnoreCase); } } @@ -164,7 +166,7 @@ public bool IsMJPEG { get { - return (Rtpmap ?? "").ToUpperInvariant().Trim().StartsWith("JPEG"); + return Rtpmap.AsSpan().Trim().StartsWith("JPEG", StringComparison.OrdinalIgnoreCase); } } @@ -172,7 +174,7 @@ public bool isH265 { get { - return (Rtpmap ?? "").ToUpperInvariant().Trim().StartsWith("H265"); + return Rtpmap.AsSpan().Trim().StartsWith("H265", StringComparison.OrdinalIgnoreCase); } } @@ -194,18 +196,20 @@ public bool CheckCompatible() private static Dictionary ParseWebRtcParameters(string input) { - var parameters = new Dictionary(); + var parameters = new Dictionary(StringComparer.OrdinalIgnoreCase); if (string.IsNullOrEmpty(input)) { return parameters; } - foreach (var pair in input.Split(';')) + Span keyValueRange = stackalloc Range[3]; + var inputSpan = input.AsSpan(); + foreach (var pairRange in inputSpan.Split(';')) { - var keyValue = pair.Split('='); - if (keyValue.Length == 2) + var pair = inputSpan[pairRange]; + if (pair.Split(keyValueRange, '=') == 2) { - parameters[keyValue[0].Trim().ToLowerInvariant()] = keyValue[1].Trim(); + parameters[pair[keyValueRange[0]].Trim().ToString()] = pair[keyValueRange[1]].Trim().ToString(); } } @@ -314,7 +318,7 @@ public SDPAudioVideoMediaFormat(TextFormat textFormat) { Kind = SDPMediaTypesEnum.text; ID = textFormat.FormatID; - Rtpmap = null; + Rtpmap = null; Fmtp = textFormat.Parameters; _isEmpty = false; @@ -458,7 +462,7 @@ public static bool AreMatch(SDPAudioVideoMediaFormat format1, SDPAudioVideoMedia // rtpmap takes priority as well known format ID's can be overruled. if (format1.Rtpmap != null && format2.Rtpmap != null) { - if (string.Equals(format1.Rtpmap.Trim(), format2.Rtpmap.Trim(), StringComparison.OrdinalIgnoreCase)) + if (format1.Rtpmap.AsSpan().Trim().Equals(format2.Rtpmap.AsSpan().Trim(), StringComparison.OrdinalIgnoreCase)) { return true; } @@ -471,7 +475,7 @@ public static bool AreMatch(SDPAudioVideoMediaFormat format1, SDPAudioVideoMedia return true; } return false; - + } /// @@ -558,24 +562,46 @@ public static bool TryParseRtpmap(string rtpmap, out string name, out int clockR } else { - string[] fields = rtpmap.Trim().Split('/'); + var rtpmapSpan = rtpmap.AsSpan().Trim(); + var nameSpan = default(ReadOnlySpan); + var clockRateSpan = default(ReadOnlySpan); + var channelsSpan = default(ReadOnlySpan); + var fieldIndex = 0; + + foreach (var fieldRange in rtpmapSpan.Split('/')) + { + var field = rtpmapSpan[fieldRange].Trim(); + + if (fieldIndex == 0) + { + nameSpan = field; + } + else if (fieldIndex == 1) + { + clockRateSpan = field; + } + else if (fieldIndex == 2) + { + channelsSpan = field; + break; + } + + fieldIndex++; + } - if (fields.Length >= 2) + if (fieldIndex >= 1) { - name = fields[0].Trim(); - if (!int.TryParse(fields[1].Trim(), out clockRate)) + if (!int.TryParse(clockRateSpan, out clockRate)) { return false; } - if (fields.Length >= 3) + if (!channelsSpan.IsEmpty && !int.TryParse(channelsSpan, out channels)) { - if (!int.TryParse(fields[2].Trim(), out channels)) - { - return false; - } + return false; } + name = nameSpan.ToString(); return true; } else @@ -630,8 +656,8 @@ public static SDPAudioVideoMediaFormat GetFormatForName(List x.Name()?.ToLower() == formatName?.ToLower()) ? - formats.First(x => x.Name()?.ToLower() == formatName?.ToLower()) : + return formats.Any(x => string.Equals(x.Name(), formatName, StringComparison.OrdinalIgnoreCase)) ? + formats.First(x => string.Equals(x.Name(), formatName, StringComparison.OrdinalIgnoreCase)) : Empty; } } diff --git a/src/SIPSorcery/net/SDP/SDPConnectionInformation.cs b/src/SIPSorcery/net/SDP/SDPConnectionInformation.cs index b3f12d7898..4f64aafe20 100644 --- a/src/SIPSorcery/net/SDP/SDPConnectionInformation.cs +++ b/src/SIPSorcery/net/SDP/SDPConnectionInformation.cs @@ -13,8 +13,11 @@ // BSD 3-Clause "New" or "Revised" License, see included LICENSE.md file. //----------------------------------------------------------------------------- +using System; using System.Net; using System.Net.Sockets; +using Polyfills; +using SIPSorcery.Sys; namespace SIPSorcery.Net { @@ -52,16 +55,34 @@ public SDPConnectionInformation(IPAddress connectionAddress) public static SDPConnectionInformation ParseConnectionInformation(string connectionLine) { SDPConnectionInformation connectionInfo = new SDPConnectionInformation(); - string[] connectionFields = connectionLine.Substring(2).Trim().Split(' '); - connectionInfo.ConnectionNetworkType = connectionFields[0].Trim(); - connectionInfo.ConnectionAddressType = connectionFields[1].Trim(); - connectionInfo.ConnectionAddress = connectionFields[2].Trim(); + var connectionFields = connectionLine.AsSpan(2).Trim(); + var fieldIndex = 0; + foreach (var fieldRange in connectionFields.Split(' ')) + { + var field = connectionFields[fieldRange].Trim().ToString(); + if (fieldIndex == 0) + { + connectionInfo.ConnectionNetworkType = field; + } + else if (fieldIndex == 1) + { + connectionInfo.ConnectionAddressType = field; + } + else if (fieldIndex == 2) + { + connectionInfo.ConnectionAddress = field; + break; + } + + fieldIndex++; + } + return connectionInfo; } public override string ToString() { - return "c=" + ConnectionNetworkType + " " + ConnectionAddressType + " " + ConnectionAddress + m_CRLF; + return $"c={ConnectionNetworkType} {ConnectionAddressType} {ConnectionAddress}{m_CRLF}"; } } } diff --git a/src/SIPSorcery/net/SDP/SDPMediaAnnouncement.cs b/src/SIPSorcery/net/SDP/SDPMediaAnnouncement.cs index c2ea655393..1f536cd9d0 100644 --- a/src/SIPSorcery/net/SDP/SDPMediaAnnouncement.cs +++ b/src/SIPSorcery/net/SDP/SDPMediaAnnouncement.cs @@ -30,8 +30,9 @@ using System.Collections.Generic; using System.Linq; using System.Text; -using System.Text.RegularExpressions; using Microsoft.Extensions.Logging; +using Polyfills; +using SIPSorcery.Sys; using SIPSorceryMedia.Abstractions; namespace SIPSorcery.Net @@ -238,41 +239,42 @@ public SDPMediaAnnouncement(SDPMediaTypesEnum mediaType, SDPConnectionInformatio Port = port; Connection = connection; - MessageMediaFormat = messageMediaFormat; + MessageMediaFormat = messageMediaFormat; } public void ParseMediaFormats(string formatList) { if (!String.IsNullOrWhiteSpace(formatList)) { - string[] formatIDs = Regex.Split(formatList, @"\s"); - if (formatIDs != null) + var formatListSpan = formatList.AsSpan(); + foreach (var formatIDRange in formatListSpan.SplitAny()) { - foreach (string formatID in formatIDs) + var formatIDSpan = formatListSpan[formatIDRange]; + + if (Media == SDPMediaTypesEnum.application) { - if (Media == SDPMediaTypesEnum.application) - { - ApplicationMediaFormats.Add(formatID, new SDPApplicationMediaFormat(formatID)); - } - else if (Media == SDPMediaTypesEnum.message) - { - //TODO - } - else + var formatID = formatIDSpan.ToString(); + ApplicationMediaFormats.Add(formatID, new SDPApplicationMediaFormat(formatID)); + } + else if (Media == SDPMediaTypesEnum.message) + { + //TODO + } + else + { + if (int.TryParse(formatIDSpan, out var id) + && !MediaFormats.ContainsKey(id) + && id < SDPAudioVideoMediaFormat.DYNAMIC_ID_MIN) { - if (Int32.TryParse(formatID, out int id) - && !MediaFormats.ContainsKey(id) - && id < SDPAudioVideoMediaFormat.DYNAMIC_ID_MIN) + var formatID = formatIDSpan.ToString(); + if (Enum.IsDefined(typeof(SDPWellKnownMediaFormatsEnum), id) && + Enum.TryParse(formatID, out var wellKnown)) + { + MediaFormats.Add(id, new SDPAudioVideoMediaFormat(wellKnown)); + } + else { - if (Enum.IsDefined(typeof(SDPWellKnownMediaFormatsEnum), id) && - Enum.TryParse(formatID, out var wellKnown)) - { - MediaFormats.Add(id, new SDPAudioVideoMediaFormat(wellKnown)); - } - else - { - logger.LogWarning("Excluding unrecognised well known media format ID {FormatID}.", id); - } + logger.LogWarning("Excluding unrecognised well known media format ID {FormatID}.", id); } } } @@ -282,73 +284,107 @@ public void ParseMediaFormats(string formatList) public override string ToString() { - string announcement = "m=" + Media + " " + Port + " " + Transport + " " + GetFormatListToString() + m_CRLF; + var announcement = new StringBuilder(); + announcement.Append("m=").Append(Media).Append(' ').Append(Port).Append(' ').Append(Transport).Append(' ') + .Append(GetFormatListToString()).Append(m_CRLF); - announcement += !string.IsNullOrWhiteSpace(MediaDescription) ? "i=" + MediaDescription + m_CRLF : null; + if (!string.IsNullOrWhiteSpace(MediaDescription)) + { + announcement.Append("i=").Append(MediaDescription).Append(m_CRLF); + } - announcement += (Connection == null) ? null : Connection.ToString(); + if (Connection != null) + { + announcement.Append(Connection); + } if (TIASBandwidth > 0) { - announcement += TIAS_BANDWIDTH_ATTRIBUE_PREFIX + TIASBandwidth + m_CRLF; + announcement.Append(TIAS_BANDWIDTH_ATTRIBUE_PREFIX).Append(TIASBandwidth).Append(m_CRLF); } foreach (string bandwidthAttribute in BandwidthAttributes) { - announcement += "b=" + bandwidthAttribute + m_CRLF; + announcement.Append("b=").Append(bandwidthAttribute).Append(m_CRLF); + } + + if (!string.IsNullOrWhiteSpace(IceUfrag)) + { + announcement.Append("a=").Append(SDP.ICE_UFRAG_ATTRIBUTE_PREFIX).Append(':').Append(IceUfrag).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)) + { + announcement.Append("a=").Append(SDP.ICE_PWD_ATTRIBUTE_PREFIX).Append(':').Append(IcePwd).Append(m_CRLF); + } + + if (!string.IsNullOrWhiteSpace(DtlsFingerprint)) + { + announcement.Append("a=").Append(SDP.DTLS_FINGERPRINT_ATTRIBUTE_PREFIX).Append(':').Append(DtlsFingerprint).Append(m_CRLF); + } + + if (IceRole != null) + { + announcement.Append("a=").Append(SDP.ICE_SETUP_ATTRIBUTE_PREFIX).Append(':').Append(IceRole).Append(m_CRLF); + } if (IceCandidates?.Count() > 0) { foreach (var candidate in IceCandidates) { - announcement += $"a={SDP.ICE_CANDIDATE_ATTRIBUTE_PREFIX}:{candidate}{m_CRLF}"; + announcement.Append("a=").Append(SDP.ICE_CANDIDATE_ATTRIBUTE_PREFIX).Append(':').Append(candidate).Append(m_CRLF); } } if (IceOptions != null) { - announcement += $"a={SDP.ICE_OPTIONS}:" + IceOptions + m_CRLF; + announcement.Append("a=").Append(SDP.ICE_OPTIONS).Append(':').Append(IceOptions).Append(m_CRLF); } if (IceEndOfCandidates) { - announcement += $"a={SDP.END_ICE_CANDIDATES_ATTRIBUTE}" + m_CRLF; + announcement.Append("a=").Append(SDP.END_ICE_CANDIDATES_ATTRIBUTE).Append(m_CRLF); + } + + if (!string.IsNullOrWhiteSpace(MediaID)) + { + announcement.Append("a=").Append(SDP.MEDIA_ID_ATTRIBUTE_PREFIX).Append(':').Append(MediaID).Append(m_CRLF); } - announcement += !string.IsNullOrWhiteSpace(MediaID) ? "a=" + SDP.MEDIA_ID_ATTRIBUTE_PREFIX + ":" + MediaID + m_CRLF : null; + announcement.Append(GetFormatListAttributesToString()); - announcement += GetFormatListAttributesToString(); + foreach (var headerExtension in HeaderExtensions) + { + announcement.Append(MEDIA_EXTENSION_MAP_ATTRIBUE_PREFIX).Append(headerExtension.Value.Id).Append(' ') + .Append(headerExtension.Value.Uri).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)) + { + announcement.Append(extra).Append(m_CRLF); + } } foreach (SDPSecurityDescription desc in this.SecurityDescriptions) { - announcement += desc.ToString() + m_CRLF; + announcement.Append(desc.ToString()).Append(m_CRLF); } if (MediaStreamStatus != null) { - announcement += MediaStreamStatusType.GetAttributeForMediaStreamStatus(MediaStreamStatus.Value) + m_CRLF; + announcement.Append(MediaStreamStatusType.GetAttributeForMediaStreamStatus(MediaStreamStatus.Value)).Append(m_CRLF); } if (SsrcGroupID != null && SsrcAttributes.Count > 0) { - announcement += MEDIA_FORMAT_SSRC_GROUP_ATTRIBUE_PREFIX + SsrcGroupID; + announcement.Append(MEDIA_FORMAT_SSRC_GROUP_ATTRIBUE_PREFIX).Append(SsrcGroupID); foreach (var ssrcAttr in SsrcAttributes) { - announcement += $" {ssrcAttr.SSRC}"; + announcement.Append(' ').Append(ssrcAttr.SSRC); } - announcement += m_CRLF; + announcement.Append(m_CRLF); } if (SsrcAttributes.Count > 0) @@ -357,11 +393,12 @@ public override string ToString() { if (!string.IsNullOrWhiteSpace(ssrcAttr.Cname)) { - announcement += $"{MEDIA_FORMAT_SSRC_ATTRIBUE_PREFIX}{ssrcAttr.SSRC} {SDPSsrcAttribute.MEDIA_CNAME_ATTRIBUE_PREFIX}:{ssrcAttr.Cname}" + m_CRLF; + announcement.Append(MEDIA_FORMAT_SSRC_ATTRIBUE_PREFIX).Append(ssrcAttr.SSRC).Append(' ') + .Append(SDPSsrcAttribute.MEDIA_CNAME_ATTRIBUE_PREFIX).Append(':').Append(ssrcAttr.Cname).Append(m_CRLF); } else { - announcement += $"{MEDIA_FORMAT_SSRC_ATTRIBUE_PREFIX}{ssrcAttr.SSRC}" + m_CRLF; + announcement.Append(MEDIA_FORMAT_SSRC_ATTRIBUE_PREFIX).Append(ssrcAttr.SSRC).Append(m_CRLF); } } } @@ -371,22 +408,22 @@ 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; + announcement.Append(MEDIA_FORMAT_SCTP_MAP_ATTRIBUE_PREFIX).Append(SctpMap).Append(m_CRLF); } else { if (SctpPort != null) { - announcement += $"{MEDIA_FORMAT_SCTP_PORT_ATTRIBUE_PREFIX}{SctpPort}" + m_CRLF; + announcement.Append(MEDIA_FORMAT_SCTP_PORT_ATTRIBUE_PREFIX).Append(SctpPort).Append(m_CRLF); } if (MaxMessageSize != 0) { - announcement += $"{MEDIA_FORMAT_MAX_MESSAGE_SIZE_ATTRIBUE_PREFIX}{MaxMessageSize}" + m_CRLF; + announcement.Append(MEDIA_FORMAT_MAX_MESSAGE_SIZE_ATTRIBUE_PREFIX).Append(MaxMessageSize).Append(m_CRLF); } } - return announcement; + return announcement.ToString(); } public string GetFormatListToString() @@ -408,13 +445,20 @@ public string GetFormatListToString() } else { - string mediaFormatList = null; + var mediaFormatList = default(StringBuilder); foreach (var mediaFormat in MediaFormats) { - mediaFormatList += mediaFormat.Key + " "; + mediaFormatList ??= new StringBuilder(); + mediaFormatList.Append(mediaFormat.Key).Append(' '); } - return (mediaFormatList != null) ? mediaFormatList.Trim() : null; + if (mediaFormatList == null) + { + return null; + } + + mediaFormatList.Length--; + return mediaFormatList.ToString(); } } @@ -429,12 +473,14 @@ public string GetFormatListAttributesToString() { if (appFormat.Value.Rtpmap != null) { - sb.Append($"{MEDIA_FORMAT_ATTRIBUTE_PREFIX}{appFormat.Key} {appFormat.Value.Rtpmap}{m_CRLF}"); + sb.Append(MEDIA_FORMAT_ATTRIBUTE_PREFIX).Append(appFormat.Key).Append(' ') + .Append(appFormat.Value.Rtpmap).Append(m_CRLF); } if (appFormat.Value.Fmtp != null) { - sb.Append($"{MEDIA_FORMAT_PARAMETERS_ATTRIBUE_PREFIX}{appFormat.Key} {appFormat.Value.Fmtp}{m_CRLF}"); + sb.Append(MEDIA_FORMAT_PARAMETERS_ATTRIBUE_PREFIX).Append(appFormat.Key).Append(' ') + .Append(appFormat.Value.Fmtp).Append(m_CRLF); } } @@ -451,41 +497,45 @@ public string GetFormatListAttributesToString() var mediaFormat = MessageMediaFormat; var acceptTypes = mediaFormat.AcceptTypes; - if (acceptTypes != null && acceptTypes.Count >0) + if (acceptTypes != null && acceptTypes.Count > 0) { sb.Append(MEDIA_FORMAT_PATH_ACCEPT_TYPES_PREFIX); foreach (var type in acceptTypes) { - sb.Append($"{type} "); + sb.Append(type).Append(' '); } - sb.Append($"{m_CRLF}"); + sb.Append(m_CRLF); } - if (mediaFormat.Endpoint != null ) + if (mediaFormat.Endpoint != null) { - sb.Append($"{MEDIA_FORMAT_PATH_MSRP_PREFIX}//{Connection.ConnectionAddress}:{Port}/{mediaFormat.Endpoint}{m_CRLF}"); + sb.Append(MEDIA_FORMAT_PATH_MSRP_PREFIX).Append("//").Append(Connection.ConnectionAddress).Append(':') + .Append(Port).Append('/').Append(mediaFormat.Endpoint).Append(m_CRLF); } - + return sb.ToString(); } else { - string formatAttributes = null; + var formatAttributes = default(StringBuilder); if (MediaFormats != null) { foreach (var mediaFormat in MediaFormats.Select(y => y.Value)) { + formatAttributes ??= new StringBuilder(); if (mediaFormat.Rtpmap == null) { // Well known media formats are not required to add an rtpmap but we do so any way as some SIP // stacks don't work without it. - formatAttributes += MEDIA_FORMAT_ATTRIBUTE_PREFIX + mediaFormat.ID + " " + mediaFormat.Name() + "/" + mediaFormat.ClockRate() + m_CRLF; + formatAttributes.Append(MEDIA_FORMAT_ATTRIBUTE_PREFIX).Append(mediaFormat.ID).Append(' ') + .Append(mediaFormat.Name()).Append('/').Append(mediaFormat.ClockRate()).Append(m_CRLF); } else { - formatAttributes += MEDIA_FORMAT_ATTRIBUTE_PREFIX + mediaFormat.ID + " " + mediaFormat.Rtpmap + m_CRLF; + formatAttributes.Append(MEDIA_FORMAT_ATTRIBUTE_PREFIX).Append(mediaFormat.ID).Append(' ') + .Append(mediaFormat.Rtpmap).Append(m_CRLF); } // Leaving out the feedback attribute for now. It should only be added where it's present in a parsed SDP packet or @@ -495,18 +545,20 @@ public string GetFormatListAttributesToString() { foreach (var rtcpFeedbackMessage in mediaFormat.SupportedRtcpFeedbackMessages) { - formatAttributes += MEDIA_FORMAT_FEEDBACK_PREFIX + mediaFormat.ID + " " + rtcpFeedbackMessage + m_CRLF; + formatAttributes.Append(MEDIA_FORMAT_FEEDBACK_PREFIX).Append(mediaFormat.ID).Append(' ') + .Append(rtcpFeedbackMessage).Append(m_CRLF); } } if (mediaFormat.Fmtp != null) { - formatAttributes += MEDIA_FORMAT_PARAMETERS_ATTRIBUE_PREFIX + mediaFormat.ID + " " + mediaFormat.Fmtp + m_CRLF; + formatAttributes.Append(MEDIA_FORMAT_PARAMETERS_ATTRIBUE_PREFIX).Append(mediaFormat.ID).Append(' ') + .Append(mediaFormat.Fmtp).Append(m_CRLF); } } } - return formatAttributes; + return formatAttributes?.ToString(); } } diff --git a/src/SIPSorcery/net/SDP/SDPSecurityDescription.cs b/src/SIPSorcery/net/SDP/SDPSecurityDescription.cs index 4b8a716036..03a38552dc 100644 --- a/src/SIPSorcery/net/SDP/SDPSecurityDescription.cs +++ b/src/SIPSorcery/net/SDP/SDPSecurityDescription.cs @@ -10,6 +10,8 @@ using System; using System.Collections.Generic; using System.Text; +using Polyfills; +using SIPSorcery.Sys; namespace SIPSorcery.Net { @@ -177,7 +179,7 @@ public string LifeTimeString } set { - if (!TryParseLifeTimeString(value, out ulong lifeTime)) + if (!TryParseLifeTimeString(value, out var lifeTime)) { throw new ArgumentException("LifeTimeString must be in format '2^n' where n is a positive integer", "LifeTimeString"); } @@ -229,22 +231,23 @@ public KeyParameter(byte[] key, byte[] salt) public override string ToString() { - string s = KEY_METHOD + COLON + this.KeySaltBase64; + var s = new StringBuilder(); + s.Append(KEY_METHOD).Append(COLON).Append(this.KeySaltBase64); if (!string.IsNullOrWhiteSpace(this.LifeTimeString)) { - s += PIPE + this.LifeTimeString; + s.Append(PIPE).Append(this.LifeTimeString); } else if (this.LifeTime > 0) { - s += PIPE + this.LifeTime; + s.Append(PIPE).Append(this.LifeTime); } if (this.MkiLength > 0 && this.MkiValue > 0) { - s += PIPE + this.MkiValue + COLON + this.MkiLength; + s.Append(PIPE).Append(this.MkiValue).Append(COLON).Append(this.MkiLength); } - return s; + return s.ToString(); } public static KeyParameter Parse(string keyParamString, CryptoSuites cryptoSuite = CryptoSuites.AES_CM_128_HMAC_SHA1_80) @@ -260,20 +263,87 @@ public static bool TryParse(string keyParamString, out KeyParameter keyParam, Cr { keyParam = null; + static bool CheckValidKeyInfoCharacters(ReadOnlySpan keyInfo) + { + foreach (var c in keyInfo) + { + if (c < 0x21 || c > 0x7e) + { + return false; + } + } + return true; + } + + static bool ParseKeyInfo(ReadOnlySpan keyInfo, out string mkiValue, out string mkiLen, out string lifeTimeString, out string base64KeySalt) + { + mkiValue = null; + mkiLen = null; + lifeTimeString = null; + base64KeySalt = null; + //KeyInfo must only contain visible printing characters + //and 40 char long, as its is the base64representation of concatenated Key and Salt + var pospipe1 = keyInfo.IndexOf(PIPE); + if (pospipe1 > 0) + { + base64KeySalt = keyInfo.Slice(0, pospipe1).ToString(); + //find lifetime and mki + //both may be omitted, but mki is recognized by a colon + //usually lifetime comes before mki, if specified + var afterFirstPipe = pospipe1 + 1; + var keyInfoTail = keyInfo.Slice(afterFirstPipe); + var relativeColon = keyInfoTail.IndexOf(COLON); + var relativePipe = keyInfoTail.IndexOf(PIPE); + var posclnmki = relativeColon == -1 ? -1 : afterFirstPipe + relativeColon; + var pospipe2 = relativePipe == -1 ? -1 : afterFirstPipe + relativePipe; + + if (posclnmki > 0 && pospipe2 < 0) + { + mkiValue = keyInfo.Slice(pospipe1 + 1, posclnmki - pospipe1 - 1).ToString(); + mkiLen = keyInfo.Slice(posclnmki + 1).ToString(); + } + else if (posclnmki > 0 && pospipe2 < posclnmki) + { + lifeTimeString = keyInfo.Slice(pospipe1 + 1, pospipe2 - pospipe1 - 1).ToString(); + mkiValue = keyInfo.Slice(pospipe2 + 1, posclnmki - pospipe2 - 1).ToString(); + mkiLen = keyInfo.Slice(posclnmki + 1).ToString(); + } + else if (posclnmki > 0 && pospipe2 > posclnmki) + { + mkiValue = keyInfo.Slice(pospipe1 + 1, posclnmki - pospipe1 - 1).ToString(); + mkiLen = keyInfo.Slice(posclnmki + 1, pospipe2 - posclnmki - 1).ToString(); + lifeTimeString = keyInfo.Slice(pospipe2 + 1).ToString(); + } + else if (posclnmki < 0 && pospipe2 < 0) + { + lifeTimeString = keyInfo.Slice(pospipe1 + 1).ToString(); + } + else if (posclnmki < 0 && pospipe2 > 0) + { + return false; + } + } + else + { + base64KeySalt = keyInfo.ToString(); + } + + return true; + } + if (!string.IsNullOrWhiteSpace(keyParamString)) { - string p = keyParamString.Trim(); - if (p.StartsWith(KEY_METHOD)) + var p = keyParamString.AsSpan().Trim(); + if (p.StartsWith(KEY_METHOD, StringComparison.Ordinal)) { - string sKeyMethod = KEY_METHOD; int poscln = p.IndexOf(COLON); - if (poscln == sKeyMethod.Length) + if (poscln == KEY_METHOD.Length) { - string sKeyInfo = p.Substring(poscln + 1); - if (!sKeyInfo.Contains(";")) + var sKeyInfo = p.Slice(poscln + 1); + if (!sKeyInfo.Contains(SEMI_COLON)) { - if ((!checkValidKeyInfoCharacters(sKeyInfo)) - || (!parseKeyInfo(sKeyInfo, out var sMkiVal, out var sMkiLen, out var sLifeTime, out var sBase64KeySalt))) + if ((!CheckValidKeyInfoCharacters(sKeyInfo)) + || (!ParseKeyInfo(sKeyInfo, out var sMkiVal, out var sMkiLen, out var sLifeTime, out var sBase64KeySalt))) { return false; } @@ -296,8 +366,8 @@ public static bool TryParse(string keyParamString, out KeyParameter keyParam, Cr if (!string.IsNullOrWhiteSpace(sMkiVal) && !string.IsNullOrWhiteSpace(sMkiLen)) { - if (!uint.TryParse(sMkiVal, out uint mkiValue) - || !uint.TryParse(sMkiLen, out uint mkiLen) + if (!uint.TryParse(sMkiVal, out var mkiValue) + || !uint.TryParse(sMkiLen, out var mkiLen) || !(mkiLen > 0 && mkiLen <= 128)) { keyParam = null; @@ -312,7 +382,7 @@ public static bool TryParse(string keyParamString, out KeyParameter keyParam, Cr { if (sLifeTime.Contains("^")) { - if (!TryParseLifeTimeString(sLifeTime, out ulong lifeTime)) + if (!TryParseLifeTimeString(sLifeTime, out var lifeTime)) { keyParam = null; return false; @@ -322,7 +392,7 @@ public static bool TryParse(string keyParamString, out KeyParameter keyParam, Cr } else { - if (!uint.TryParse(sLifeTime, out uint lifeTime) + if (!uint.TryParse(sLifeTime, out var lifeTime) || !IsValidLifeTime(lifeTime)) { keyParam = null; @@ -411,18 +481,6 @@ private static bool parseKeySaltBase64(CryptoSuites cryptoSuite, string base64Ke return true; } - private static bool checkValidKeyInfoCharacters(string keyInfo) - { - foreach (char c in keyInfo) - { - if (c < 0x21 || c > 0x7e) - { - return false; - } - } - return true; - } - private static bool IsValidLifeTime(ulong value) { return value >= 2 && (value & (value - 1)) == 0; @@ -442,13 +500,13 @@ private static bool TryParseLifeTimeString(string lifeTimeString, out ulong life { lifeTime = 0; - if (string.IsNullOrWhiteSpace(lifeTimeString) || !lifeTimeString.StartsWith("2^")) + var lifeTimeSpan = lifeTimeString.AsSpan().Trim(); + if (lifeTimeSpan.IsEmpty || !lifeTimeSpan.StartsWith("2^", StringComparison.Ordinal)) { return false; } - string exponentPart = lifeTimeString.Substring(2); - if (!ulong.TryParse(exponentPart, out ulong exponent) || exponent < 1 || exponent >= 64) + if (!ulong.TryParse(lifeTimeSpan.Slice(2), out var exponent) || exponent < 1 || exponent >= 64) { return false; } @@ -457,58 +515,6 @@ private static bool TryParseLifeTimeString(string lifeTimeString, out ulong life return true; } - private static bool parseKeyInfo(string keyInfo, out string mkiValue, out string mkiLen, out string lifeTimeString, out string base64KeySalt) - { - mkiValue = null; - mkiLen = null; - lifeTimeString = null; - base64KeySalt = null; - //KeyInfo must only contain visible printing characters - //and 40 char long, as its is the base64representation of concatenated Key and Salt - int pospipe1 = keyInfo.IndexOf(PIPE); - if (pospipe1 > 0) - { - base64KeySalt = keyInfo.Substring(0, pospipe1); - //find lifetime and mki - //both may be omitted, but mki is recognized by a colon - //usually lifetime comes before mki, if specified - int posclnmki = keyInfo.IndexOf(COLON, pospipe1 + 1); - int pospipe2 = keyInfo.IndexOf(PIPE, pospipe1 + 1); - - if (posclnmki > 0 && pospipe2 < 0) - { - mkiValue = keyInfo.Substring(pospipe1 + 1, posclnmki - pospipe1 - 1); - mkiLen = keyInfo.Substring(posclnmki + 1); - } - else if (posclnmki > 0 && pospipe2 < posclnmki) - { - lifeTimeString = keyInfo.Substring(pospipe1 + 1, pospipe2 - pospipe1 - 1); - mkiValue = keyInfo.Substring(pospipe2 + 1, posclnmki - pospipe2 - 1); - mkiLen = keyInfo.Substring(posclnmki + 1); - } - else if (posclnmki > 0 && pospipe2 > posclnmki) - { - mkiValue = keyInfo.Substring(pospipe1 + 1, posclnmki - pospipe1 - 1); - mkiLen = keyInfo.Substring(posclnmki + 1, pospipe2 - posclnmki - 1); - lifeTimeString = keyInfo.Substring(pospipe2 + 1); - } - else if (posclnmki < 0 && pospipe2 < 0) - { - lifeTimeString = keyInfo.Substring(pospipe1 + 1); - } - else if (posclnmki < 0 && pospipe2 > 0) - { - return false; - } - } - else - { - base64KeySalt = keyInfo; - } - - return true; - } - public static KeyParameter CreateNew(CryptoSuites cryptoSuite, string key = null, string salt = null) { switch (cryptoSuite) @@ -719,29 +725,27 @@ public static bool TryParse(string sessionParam, out SessionParameter result, Cr return true; } - string p = sessionParam.Trim(); + var p = sessionParam.AsSpan().Trim(); SessionParameter.SrtpSessionParams paramType = SrtpSessionParams.unknown; - if (p.StartsWith(KDR_PREFIX)) + if (p.StartsWith(KDR_PREFIX, StringComparison.Ordinal)) { - string sKdr = p.Substring(KDR_PREFIX.Length); - if (uint.TryParse(sKdr, out uint kdr)) + if (uint.TryParse(p.Slice(KDR_PREFIX.Length), out var kdr)) { result = new SessionParameter(SrtpSessionParams.kdr) { Kdr = kdr }; return true; } } - else if (p.StartsWith(WSH_PREFIX)) + else if (p.StartsWith(WSH_PREFIX, StringComparison.Ordinal)) { - string sWsh = p.Substring(WSH_PREFIX.Length); - if (uint.TryParse(sWsh, out uint wsh)) + if (uint.TryParse(p.Slice(WSH_PREFIX.Length), out var wsh)) { result = new SessionParameter(SrtpSessionParams.wsh) { Wsh = wsh }; return true; } } - else if (p.StartsWith(FEC_KEY_PREFIX)) + else if (p.StartsWith(FEC_KEY_PREFIX, StringComparison.Ordinal)) { - string sFecKey = p.Substring(FEC_KEY_PREFIX.Length); + var sFecKey = p.Slice(FEC_KEY_PREFIX.Length).ToString(); if (!KeyParameter.TryParse(sFecKey, out var fecKey, cryptoSuite)) { return false; @@ -749,9 +753,9 @@ public static bool TryParse(string sessionParam, out SessionParameter result, Cr result = new SessionParameter(SrtpSessionParams.fec_key) { FecKey = fecKey }; return true; } - else if (p.StartsWith(FEC_ORDER_PREFIX)) + else if (p.StartsWith(FEC_ORDER_PREFIX, StringComparison.Ordinal)) { - string sFecOrder = p.Substring(FEC_ORDER_PREFIX.Length); + var sFecOrder = p.Slice(FEC_ORDER_PREFIX.Length).ToString(); if (!s_fecTypesLookup.TryGetValue(sFecOrder, out var fecOrder)) { return false; @@ -762,7 +766,8 @@ public static bool TryParse(string sessionParam, out SessionParameter result, Cr } else { - if (!Enum.TryParse(p, out paramType) || paramType.ToString() != p) + var paramString = p.ToString(); + if (!Enum.TryParse(paramString, out paramType) || paramType.ToString() != paramString) { return false; } @@ -844,21 +849,22 @@ public override string ToString() return null; } - string s = CRYPTO_ATTRIBUE_PREFIX + this.Tag + WHITE_SPACE + this.CryptoSuite.ToString() + WHITE_SPACE; + var s = new StringBuilder(); + s.Append(CRYPTO_ATTRIBUE_PREFIX).Append(this.Tag).Append(WHITE_SPACE).Append(this.CryptoSuite).Append(WHITE_SPACE); for (int i = 0; i < this.KeyParams.Count; i++) { if (i > 0) { - s += SEMI_COLON; + s.Append(SEMI_COLON); } - s += this.KeyParams[i].ToString(); + s.Append(this.KeyParams[i].ToString()); } if (this.SessionParam != null) { - s += WHITE_SPACE + this.SessionParam.ToString(); + s.Append(WHITE_SPACE).Append(this.SessionParam.ToString()); } - return s; + return s.ToString(); } public static SDPSecurityDescription Parse(string cryptoLine) @@ -884,50 +890,60 @@ public static bool TryParse(string cryptoLine, out SDPSecurityDescription securi return false; } - string sCryptoValue = cryptoLine.Substring(cryptoLine.IndexOf(COLON) + 1); + var sCryptoValue = cryptoLine.AsSpan(cryptoLine.IndexOf(COLON) + 1); securityDescription = new SDPSecurityDescription(); - string[] sCryptoParts = sCryptoValue.Split(WHITE_SPACES, StringSplitOptions.RemoveEmptyEntries); + Span sCryptoPartRanges = stackalloc Range[5]; + var sCryptoPartCount = sCryptoValue.SplitAny(sCryptoPartRanges, WHITE_SPACES.AsSpan(), StringSplitOptions.RemoveEmptyEntries); if (sCryptoValue.Length < 2) { return false; } - if (!uint.TryParse(sCryptoParts[0], out var tag)) + if (sCryptoPartCount < 2) { return false; } - securityDescription.Tag = tag; - if (!s_cryptoSuiteLookup.TryGetValue(sCryptoParts[1], out var cryptoSuite)) + if (!uint.TryParse(sCryptoValue[sCryptoPartRanges[0]], out var tag)) { return false; } - securityDescription.CryptoSuite = cryptoSuite; + securityDescription.Tag = tag; - if (sCryptoParts.Length < 3) + if (!s_cryptoSuiteLookup.TryGetValue(sCryptoValue[sCryptoPartRanges[1]].ToString(), out var cryptoSuite)) { return false; } + securityDescription.CryptoSuite = cryptoSuite; - string[] sKeyParams = sCryptoParts[2].Split(SEMI_COLON); - if (sKeyParams.Length < 1) + if (sCryptoPartCount < 3) { - securityDescription = null; return false; } - foreach (string kp in sKeyParams) + + var sKeyParams = sCryptoValue[sCryptoPartRanges[2]]; + var hasKeyParam = false; + foreach (var keyParamRange in sKeyParams.Split(SEMI_COLON)) { - if (!KeyParameter.TryParse(kp, out var keyParam, securityDescription.CryptoSuite)) + hasKeyParam = true; + if (!KeyParameter.TryParse(sKeyParams[keyParamRange].ToString(), out var keyParam, securityDescription.CryptoSuite)) { securityDescription = null; return false; } securityDescription.KeyParams.Add(keyParam); } - if (sCryptoParts.Length > 3) + + if (!hasKeyParam) + { + securityDescription = null; + return false; + } + + if (sCryptoPartCount > 3) { - if (!SessionParameter.TryParse(sCryptoParts[3], out var sessionParam, securityDescription.CryptoSuite)) + if (!SessionParameter.TryParse(sCryptoValue[sCryptoPartRanges[3]].ToString(), out var sessionParam, securityDescription.CryptoSuite)) { securityDescription = null; return false; diff --git a/src/SIPSorcery/net/SDP/SDPTypes.cs b/src/SIPSorcery/net/SDP/SDPTypes.cs index 635bc712d3..f577e1c1b7 100644 --- a/src/SIPSorcery/net/SDP/SDPTypes.cs +++ b/src/SIPSorcery/net/SDP/SDPTypes.cs @@ -72,18 +72,18 @@ public static bool IsMediaStreamStatusAttribute(string attributeString, out Medi } else { - switch (attributeString.ToLower()) + switch (attributeString) { - case SEND_RECV_ATTRIBUTE: + case var _ when SEND_RECV_ATTRIBUTE.Equals(attributeString, StringComparison.OrdinalIgnoreCase): mediaStreamStatus = MediaStreamStatusEnum.SendRecv; return true; - case SEND_ONLY_ATTRIBUTE: + case var _ when SEND_ONLY_ATTRIBUTE.Equals(attributeString, StringComparison.OrdinalIgnoreCase): mediaStreamStatus = MediaStreamStatusEnum.SendOnly; return true; - case RECV_ONLY_ATTRIBUTE: + case var _ when RECV_ONLY_ATTRIBUTE.Equals(attributeString, StringComparison.OrdinalIgnoreCase): mediaStreamStatus = MediaStreamStatusEnum.RecvOnly; return true; - case INACTIVE_ATTRIBUTE: + case var _ when INACTIVE_ATTRIBUTE.Equals(attributeString, StringComparison.OrdinalIgnoreCase): mediaStreamStatus = MediaStreamStatusEnum.Inactive; return true; default: diff --git a/src/SIPSorcery/net/STUN/STUNAppState.cs b/src/SIPSorcery/net/STUN/STUNAppState.cs index 715ec84bf2..7b20c1714e 100644 --- a/src/SIPSorcery/net/STUN/STUNAppState.cs +++ b/src/SIPSorcery/net/STUN/STUNAppState.cs @@ -40,7 +40,7 @@ public static string PrintBuffer(byte[] buffer) if (byteStr.Length == 1) { - bufferStr += "0" + byteStr; + bufferStr += $"0{byteStr}"; } else { diff --git a/src/SIPSorcery/net/STUN/STUNAttributes/STUNAddressAttribute.cs b/src/SIPSorcery/net/STUN/STUNAttributes/STUNAddressAttribute.cs index 620b267dff..0fb559f686 100644 --- a/src/SIPSorcery/net/STUN/STUNAttributes/STUNAddressAttribute.cs +++ b/src/SIPSorcery/net/STUN/STUNAttributes/STUNAddressAttribute.cs @@ -95,7 +95,7 @@ public override int ToByteBuffer(byte[] buffer, int startIndex) public override string ToString() { - string attrDescrStr = "STUN Attribute: " + base.AttributeType + ", address=" + Address.ToString() + ", port=" + Port + "."; + string attrDescrStr = $"STUN Attribute: {base.AttributeType}, address={Address.ToString()}, port={Port}."; return attrDescrStr; } diff --git a/src/SIPSorcery/net/STUN/STUNAttributes/STUNAttribute.cs b/src/SIPSorcery/net/STUN/STUNAttributes/STUNAttribute.cs index 8df861b5ba..74dd29ccaa 100644 --- a/src/SIPSorcery/net/STUN/STUNAttributes/STUNAttribute.cs +++ b/src/SIPSorcery/net/STUN/STUNAttributes/STUNAttribute.cs @@ -220,7 +220,7 @@ public static List ParseMessageAttributes(byte[] buffer, int star attributes.Add(attribute); - // Attributes start on 32 bit word boundaries so where an attribute length is not a multiple of 4 it gets padded. + // Attributes start on 32 bit word boundaries so where an attribute length is not a multiple of 4 it gets padded. int padding = (stunAttributeLength % 4 != 0) ? 4 - (stunAttributeLength % 4) : 0; startAttIndex = startAttIndex + 4 + stunAttributeLength + padding; @@ -258,7 +258,7 @@ public virtual int ToByteBuffer(byte[] buffer, int startIndex) public new virtual string ToString() { - string attrDescrString = "STUN Attribute: " + AttributeType.ToString() + ", length=" + PaddedLength + "."; + string attrDescrString = $"STUN Attribute: {AttributeType.ToString()}, length={PaddedLength}."; return attrDescrString; } diff --git a/src/SIPSorcery/net/STUN/STUNAttributes/STUNChangeRequestAttribute.cs b/src/SIPSorcery/net/STUN/STUNAttributes/STUNChangeRequestAttribute.cs index 94608dfad0..b41738deb0 100644 --- a/src/SIPSorcery/net/STUN/STUNAttributes/STUNChangeRequestAttribute.cs +++ b/src/SIPSorcery/net/STUN/STUNAttributes/STUNChangeRequestAttribute.cs @@ -53,7 +53,7 @@ public STUNChangeRequestAttribute(byte[] attributeValue) public override string ToString() { - string attrDescrStr = "STUN Attribute: " + STUNAttributeTypesEnum.ChangeRequest.ToString() + ", key byte=" + m_changeRequestByte.ToString("X") + ", change address=" + ChangeAddress + ", change port=" + ChangePort + "."; + string attrDescrStr = $"STUN Attribute: {STUNAttributeTypesEnum.ChangeRequest.ToString()}, key byte={m_changeRequestByte.ToString("X")}, change address={ChangeAddress}, change port={ChangePort}."; return attrDescrStr; } diff --git a/src/SIPSorcery/net/STUN/STUNAttributes/STUNConnectionIdAttribute.cs b/src/SIPSorcery/net/STUN/STUNAttributes/STUNConnectionIdAttribute.cs index 78244fd7e0..bce3d091cc 100644 --- a/src/SIPSorcery/net/STUN/STUNAttributes/STUNConnectionIdAttribute.cs +++ b/src/SIPSorcery/net/STUN/STUNAttributes/STUNConnectionIdAttribute.cs @@ -44,7 +44,7 @@ public STUNConnectionIdAttribute(uint connectionId) public override string ToString() { - string attrDescrStr = "STUN CONNECTION_ID Attribute: value=" + ConnectionId + "."; + string attrDescrStr = $"STUN CONNECTION_ID Attribute: value={ConnectionId}."; return attrDescrStr; } diff --git a/src/SIPSorcery/net/STUN/STUNAttributes/STUNErrorCodeAttribute.cs b/src/SIPSorcery/net/STUN/STUNAttributes/STUNErrorCodeAttribute.cs index 097486d2d7..709af72a10 100644 --- a/src/SIPSorcery/net/STUN/STUNAttributes/STUNErrorCodeAttribute.cs +++ b/src/SIPSorcery/net/STUN/STUNAttributes/STUNErrorCodeAttribute.cs @@ -60,7 +60,7 @@ private static byte[] BuildValue(int errorCode, string reasonPhrase) public override string ToString() { - string attrDescrStr = "STUN ERROR_CODE_ADDRESS Attribute: error code=" + ErrorCode + ", reason phrase=" + ReasonPhrase + "."; + string attrDescrStr = $"STUN ERROR_CODE_ADDRESS Attribute: error code={ErrorCode}, reason phrase={ReasonPhrase}."; return attrDescrStr; } diff --git a/src/SIPSorcery/net/STUN/STUNAttributes/STUNXORAddressAttribute.cs b/src/SIPSorcery/net/STUN/STUNAttributes/STUNXORAddressAttribute.cs index e76a7b1e94..544256c421 100644 --- a/src/SIPSorcery/net/STUN/STUNAttributes/STUNXORAddressAttribute.cs +++ b/src/SIPSorcery/net/STUN/STUNAttributes/STUNXORAddressAttribute.cs @@ -133,7 +133,7 @@ public override int ToByteBuffer(byte[] buffer, int startIndex) public override string ToString() { - string attrDescrStr = "STUN XOR_MAPPED_ADDRESS Attribute: " + base.AttributeType + ", address=" + Address.ToString() + ", port=" + Port + "."; + string attrDescrStr = $"STUN XOR_MAPPED_ADDRESS Attribute: {base.AttributeType}, address={Address.ToString()}, port={Port}."; return attrDescrStr; } diff --git a/src/SIPSorcery/net/STUN/STUNMessage.cs b/src/SIPSorcery/net/STUN/STUNMessage.cs index 2bdcdd5410..ebde015494 100644 --- a/src/SIPSorcery/net/STUN/STUNMessage.cs +++ b/src/SIPSorcery/net/STUN/STUNMessage.cs @@ -198,13 +198,13 @@ public byte[] ToByteBuffer(byte[] messageIntegrityKey, bool addFingerprint) return buffer; } - public new string ToString() + public override string ToString() { - string messageDescr = "STUN Message: " + Header.MessageType.ToString() + ", length=" + Header.MessageLength; + string messageDescr = $"STUN Message: {Header.MessageType.ToString()}, length={Header.MessageLength}"; foreach (STUNAttribute attribute in Attributes) { - messageDescr += "\n " + attribute.ToString(); + messageDescr += $"\n {attribute.ToString()}"; } return messageDescr; diff --git a/src/SIPSorcery/net/WebRTC/RTCDataChannel.cs b/src/SIPSorcery/net/WebRTC/RTCDataChannel.cs index 8a0d0b570c..c4b44b429f 100644 --- a/src/SIPSorcery/net/WebRTC/RTCDataChannel.cs +++ b/src/SIPSorcery/net/WebRTC/RTCDataChannel.cs @@ -134,8 +134,7 @@ public void send(string message) { if (message != null && Encoding.UTF8.GetByteCount(message) > _transport.maxMessageSize) { - throw new ApplicationException($"Data channel {label} was requested to send data of length {Encoding.UTF8.GetByteCount(message)} " + - $" that exceeded the maximum allowed message size of {_transport.maxMessageSize}."); + throw new ApplicationException($"Data channel {label} was requested to send data of length {Encoding.UTF8.GetByteCount(message)} that exceeded the maximum allowed message size of {_transport.maxMessageSize}."); } else if (_transport.state != RTCSctpTransportState.Connected) { @@ -173,8 +172,7 @@ public void send(byte[] data, int offset = 0, int count = -1) if (effectiveCount > _transport.maxMessageSize) { - throw new ApplicationException($"Data channel {label} was requested to send data of length {effectiveCount} " + - $" that exceeded the maximum allowed message size of {_transport.maxMessageSize}."); + throw new ApplicationException($"Data channel {label} was requested to send data of length {effectiveCount} that exceeded the maximum allowed message size of {_transport.maxMessageSize}."); } else if (_transport.state != RTCSctpTransportState.Connected) { diff --git a/src/SIPSorcery/net/WebRTC/RTCPeerConnection.cs b/src/SIPSorcery/net/WebRTC/RTCPeerConnection.cs index 4c2b39b72d..80866db1c7 100644 --- a/src/SIPSorcery/net/WebRTC/RTCPeerConnection.cs +++ b/src/SIPSorcery/net/WebRTC/RTCPeerConnection.cs @@ -1876,12 +1876,13 @@ private bool DoDtlsHandshake(DtlsSrtpTransport dtlsHandle) } else { - logger.LogDebug($"RTCPeerConnection DTLS handshake result {handshakeResult}, is handshake complete {dtlsHandle.IsHandshakeComplete()}."); + logger.LogDebug("RTCPeerConnection DTLS handshake result {HandshakeResult}, is handshake complete {IsHandshakeComplete}.", + handshakeResult, dtlsHandle.IsHandshakeComplete()); var expectedFp = RemotePeerDtlsFingerprint; var remoteFingerprint = DtlsUtils.Fingerprint(expectedFp.algorithm, dtlsHandle.GetRemoteCertificate().GetCertificateAt(0)); - if (remoteFingerprint.value?.ToUpper() != expectedFp.value?.ToUpper()) + if (!string.Equals(remoteFingerprint.value, expectedFp.value, StringComparison.OrdinalIgnoreCase)) { logger.LogWarning("RTCPeerConnection remote certificate fingerprint mismatch, expected {ExpectedFingerprint}, actual {RemoteFingerprint}.", expectedFp, remoteFingerprint); Close("dtls fingerprint mismatch"); diff --git a/src/SIPSorcery/net/WebRTC/RTCSctpTransport.cs b/src/SIPSorcery/net/WebRTC/RTCSctpTransport.cs index e8088412ac..91739fcbe2 100644 --- a/src/SIPSorcery/net/WebRTC/RTCSctpTransport.cs +++ b/src/SIPSorcery/net/WebRTC/RTCSctpTransport.cs @@ -380,8 +380,7 @@ public override void Send(string associationID, byte[] buffer, int offset, int l { if (length > maxMessageSize) { - throw new ApplicationException($"RTCSctpTransport was requested to send data of length {length} " + - $" that exceeded the maximum allowed message size of {maxMessageSize}."); + throw new ApplicationException($"RTCSctpTransport was requested to send data of length {length} that exceeded the maximum allowed message size of {maxMessageSize}."); } if (!_isClosed) diff --git a/src/SIPSorceryMedia.FFmpeg/FFmpegCameraSource.cs b/src/SIPSorceryMedia.FFmpeg/FFmpegCameraSource.cs index 3a85b39197..d6e68f9607 100644 --- a/src/SIPSorceryMedia.FFmpeg/FFmpegCameraSource.cs +++ b/src/SIPSorceryMedia.FFmpeg/FFmpegCameraSource.cs @@ -42,9 +42,7 @@ public unsafe FFmpegCameraSource(Camera camera) string inputFormat = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "dshow" : RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? "v4l2" : RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? "avfoundation" - : throw new NotSupportedException($"Cannot find adequate input format" + - $" - OSArchitecture:[{RuntimeInformation.OSArchitecture}]" + - $" - OSDescription:[{RuntimeInformation.OSDescription}]"); + : throw new NotSupportedException($"Cannot find adequate input format - OSArchitecture:[{RuntimeInformation.OSArchitecture}] - OSDescription:[{RuntimeInformation.OSDescription}]"); var _aVInputFormat = ffmpeg.av_find_input_format(inputFormat); @@ -126,7 +124,7 @@ public bool RestrictCameraOptions(Func, bool> optFilt ); if (filtered is null) - logger.LogWarning($"No camera/input device options to be used."); + logger.LogWarning("No camera/input device options to be used."); return SetCameraDeviceOptions(filtered); } diff --git a/src/SIPSorceryMedia.FFmpeg/FFmpegScreenSource.cs b/src/SIPSorceryMedia.FFmpeg/FFmpegScreenSource.cs index 33d6191550..1681993535 100644 --- a/src/SIPSorceryMedia.FFmpeg/FFmpegScreenSource.cs +++ b/src/SIPSorceryMedia.FFmpeg/FFmpegScreenSource.cs @@ -28,7 +28,7 @@ public unsafe FFmpegScreenSource(string path, Rectangle rect, int frameRate = 20 { ["offset_x"] = rect.X.ToString(), ["offset_y"] = rect.Y.ToString(), - ["video_size"] = $"{rect.Width.ToString()}X{rect.Height.ToString()}", + ["video_size"] = $"{rect.Width}X{rect.Height}", ["framerate"] = frameRate.ToString() }; } @@ -37,7 +37,7 @@ public unsafe FFmpegScreenSource(string path, Rectangle rect, int frameRate = 20 inputFormat = "avfoundation"; options = new Dictionary() { - ["vf"] = $"crop={rect.Width.ToString()}:{rect.Height.ToString()}:{rect.X.ToString()}:{rect.Y.ToString()}", + ["vf"] = $"crop={rect.Width}:{rect.Height}:{rect.X}:{rect.Y}", ["framerate"] = frameRate.ToString() }; } @@ -47,7 +47,7 @@ public unsafe FFmpegScreenSource(string path, Rectangle rect, int frameRate = 20 //https://superuser.com/questions/1562228/how-to-specify-the-size-to-record-the-screen-with-ffmpeg options = new Dictionary() { - ["video_size"] = $"{rect.Width.ToString()}X{rect.Height.ToString()}", + ["video_size"] = $"{rect.Width}X{rect.Height}", ["grab_x"] = rect.X.ToString(), ["grab_y"] = rect.Y.ToString(), ["framerate"] = frameRate.ToString() diff --git a/src/SIPSorceryMedia.FFmpeg/FfmpegInit.cs b/src/SIPSorceryMedia.FFmpeg/FfmpegInit.cs index cc1c04eee4..2b27a64e0e 100644 --- a/src/SIPSorceryMedia.FFmpeg/FfmpegInit.cs +++ b/src/SIPSorceryMedia.FFmpeg/FfmpegInit.cs @@ -83,7 +83,7 @@ public static void Initialise(FfmpegLogLevelEnum? logLevel = null, String? libPa RegisterFFmpegBinaries(libPath); - logger.LogInformation($"FFmpeg version info: {ffmpeg.av_version_info()}"); + logger.LogInformation("FFmpeg version info: {VersionInfo}", ffmpeg.av_version_info()); if (logLevel.HasValue) { @@ -105,10 +105,7 @@ internal static void SetFFmpegBinariesPath(string path) catch (Exception e) { throw new DllNotFoundException( - "Check the dependencies of FFmpeg libraries and make sure they are " + - "searchable by the operating system's library loader." - + "\nOn linux you can use 'ldd' & 'strace'." - + "\nOn Windows you can use 'Dependencies'." + $"Check the dependencies of FFmpeg libraries and make sure they are searchable by the operating system's library loader.\nOn linux you can use 'ldd' & 'strace'.\nOn Windows you can use 'Dependencies'." , e); } } @@ -124,12 +121,12 @@ internal static void RegisterFFmpegBinaries(String? libPath = null) string ffmpegExecutable = "ffmpeg"; string? path = Environment.GetEnvironmentVariable("PATH")? .Split([';'], StringSplitOptions.RemoveEmptyEntries) - .Where(s => File.Exists(Path.Combine(s, ffmpegExecutable)) || File.Exists(Path.Combine(s, ffmpegExecutable + ".exe"))) + .Where(s => File.Exists(Path.Combine(s, ffmpegExecutable)) || File.Exists(Path.Combine(s, $"{ffmpegExecutable}.exe"))) .FirstOrDefault(); if (path != null) { - logger.LogInformation($"FFmpeg binaries found in system path at: {path}"); + logger.LogInformation("FFmpeg binaries found in system path at: {Path}", path); SetFFmpegBinariesPath(path); return; } @@ -142,7 +139,7 @@ internal static void RegisterFFmpegBinaries(String? libPath = null) var ffmpegBinaryPath = Path.Combine(current, probe); if (Directory.Exists(ffmpegBinaryPath)) { - logger.LogInformation($"FFmpeg binaries found in: {ffmpegBinaryPath}"); + logger.LogInformation("FFmpeg binaries found in: {Path}", ffmpegBinaryPath); SetFFmpegBinariesPath(ffmpegBinaryPath); return; } @@ -154,7 +151,7 @@ internal static void RegisterFFmpegBinaries(String? libPath = null) { if (Directory.Exists(libPath)) { - logger.LogInformation($"FFmpeg binaries path set to: {libPath}"); + logger.LogInformation("FFmpeg binaries path set to: {Path}", libPath); SetFFmpegBinariesPath(libPath); return; } diff --git a/src/SIPSorceryMedia.FFmpeg/Interop/MacOs/AvFoundation.cs b/src/SIPSorceryMedia.FFmpeg/Interop/MacOs/AvFoundation.cs index 8a9e67ec48..6c5d44ee77 100644 --- a/src/SIPSorceryMedia.FFmpeg/Interop/MacOs/AvFoundation.cs +++ b/src/SIPSorceryMedia.FFmpeg/Interop/MacOs/AvFoundation.cs @@ -85,7 +85,7 @@ private static unsafe String GetAvFoundationLogsAboutDevicesList() { index = ln.IndexOf("]"); string name = ln.Substring(index + 2); - string path = ln.Substring(1, index - 1) + ":"; + string path = $"{ln.Substring(1, index - 1)}:"; Monitor monitor = new Monitor { @@ -149,7 +149,7 @@ private static unsafe String GetAvFoundationLogsAboutDevicesList() { index = ln.IndexOf("]"); string name = ln.Substring(index+2); - string path = ln.Substring(1, index - 1) + ":"; + string path = $"{ln.Substring(1, index - 1)}:"; Camera camera = new Camera { diff --git a/src/SIPSorceryMedia.Windows/WindowsVideoEndPoint.cs b/src/SIPSorceryMedia.Windows/WindowsVideoEndPoint.cs index f6d58152c4..a2ea1f0648 100644 --- a/src/SIPSorceryMedia.Windows/WindowsVideoEndPoint.cs +++ b/src/SIPSorceryMedia.Windows/WindowsVideoEndPoint.cs @@ -231,11 +231,11 @@ private async Task InitialiseDevice(uint width, uint height, uint fps) if (vidDevice == null) { - logger.LogWarning($"Could not find video capture device for specified ID {_videoDeviceID}, using default device."); + logger.LogWarning("Could not find video capture device for specified ID {VideoDeviceId}, using default device.", _videoDeviceID); } else { - logger.LogInformation($"Video capture device {vidDevice.Name} selected."); + logger.LogInformation("Video capture device {VideoDeviceName} selected.", vidDevice.Name); mediaCaptureSettings.VideoDeviceId = vidDevice.Id; } } @@ -277,7 +277,8 @@ private async Task InitialiseDevice(uint width, uint height, uint fps) if (preferredFormat == null) { // Still can't get what we want. Log a warning message and take the default. - logger.LogWarning($"The video capture device did not support the requested format (or better) {_width}x{_height} {fps}fps. Using default mode."); + logger.LogWarning("The video capture device did not support the requested format (or better) {Width}x{Height} {Fps}fps. Using default mode.", + _width, _height, fps); preferredFormat = colorFrameSource.SupportedFormats.First(); } @@ -455,7 +456,8 @@ public static async Task ListDevicesAndFormats() var vidFmt = srcFmt.VideoFormat; float vidFps = vidFmt.MediaFrameFormat.FrameRate.Numerator / vidFmt.MediaFrameFormat.FrameRate.Denominator; string pixFmt = vidFmt.MediaFrameFormat.Subtype == MF_I420_PIXEL_FORMAT ? "I420" : vidFmt.MediaFrameFormat.Subtype; - logger.LogDebug($"Video Capture device {vidCapDevice.Name} format {vidFmt.Width}x{vidFmt.Height} {vidFps:0.##}fps {pixFmt}"); + logger.LogDebug("Video Capture device {VideoDeviceName} format {Width}x{Height} {Fps:0.##}fps {PixelFormat}", + vidCapDevice.Name, vidFmt.Width, vidFmt.Height, vidFps, pixFmt); } } } @@ -478,7 +480,7 @@ public static async Task> GetDeviceFrameFormats(stri var vidCapDevices = await DeviceInformation.FindAllAsync(DeviceClass.VideoCapture); foreach (var vidCapDevice in vidCapDevices) { - if(vidCapDevice.Name.ToLower() == deviceName.ToLower()) + if(string.Equals(vidCapDevice.Name, deviceName, StringComparison.OrdinalIgnoreCase)) { var mediaCaptureSettings = new MediaCaptureInitializationSettings() { @@ -685,7 +687,8 @@ private void PrintFrameSourceInfo(MediaFrameSource frameSource) string pixFmt = frameSource.CurrentFormat.Subtype; string deviceName = frameSource.Info.DeviceInformation.Name; - logger.LogInformation($"Video capture device {deviceName} successfully initialised: {width}x{height} {fps:0.##}fps pixel format {pixFmt}."); + logger.LogInformation("Video capture device {VideoDeviceName} successfully initialised: {Width}x{Height} {Fps:0.##}fps pixel format {PixelFormat}.", + deviceName, width, height, fps, pixFmt); } public void Dispose() diff --git a/test/FFmpegConsoleApp/Program.cs b/test/FFmpegConsoleApp/Program.cs old mode 100755 new mode 100644 diff --git a/test/unit/net/STUN/STUNUnitTest.cs b/test/unit/net/STUN/STUNUnitTest.cs old mode 100755 new mode 100644