diff --git a/src/SIPSorcery.VP8/DebugProbe.cs b/src/SIPSorcery.VP8/DebugProbe.cs index a70282a9e..9a3afdfc4 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 07d348aee..db0e5c41a 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 2eaae0613..eecf15672 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 6b1b91262..cc1e20ee9 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 765fa095f..fb4e5bd7d 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 604637dc8..8c869775a 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 fdae5ebd3..29bc9d486 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 ba598d29f..8f6542705 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 ec59cfa53..9ea82cbef 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 319f8e144..26be2150b 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 cd6138f00..91a333340 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 398d1d48c..0eeb8797b 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 7aa4702b9..7b40b6561 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 b6adf8a70..e7db300ee 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 6c7c2d24d..b29c9031d 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 dc90850a8..630261cd7 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 7ebd92b96..24742bae9 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 6aeb1db24..5912681e7 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 4547f7c57..4ce2cf582 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 0e86a5c3e..094bf53e3 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 21e4d0c38..ad6069ca4 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 700328a01..9c32ea8a8 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 6ba8342c9..1e0da6761 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 b3f12d789..4f64aafe2 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 c2ea65539..1f536cd9d 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 4b8a71603..03a38552d 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 635bc712d..f577e1c1b 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 715ec84bf..7b20c1714 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 620b267df..0fb559f68 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 8df861b5b..74dd29cca 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 94608dfad..b41738deb 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 78244fd7e..bce3d091c 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 097486d2d..709af72a1 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 e76a7b1e9..544256c42 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 2bdcdd541..ebde01549 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 8a0d0b570..c4b44b429 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 4c2b39b72..80866db1c 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 e8088412a..91739fcbe 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 3a85b3919..d6e68f960 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 33d619155..168199353 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 cc1c04eee..2b27a64e0 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 8a9e67ec4..6c5d44ee7 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 f6d58152c..a2ea1f064 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