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: