From 743031c6025d4afd4cbc00d48456e302eceedf7b Mon Sep 17 00:00:00 2001 From: Paulo Morgado <470455+paulomorgado@users.noreply.github.com> Date: Mon, 1 Jun 2026 12:50:51 +0100 Subject: [PATCH] Refactor SDP parsing to use Span and StringBuilder Replaced Regex and string.Split with Span-based parsing for SDP lines and attributes. Updated ToString methods to use StringBuilder, reducing allocations and improving performance. Standardized string comparisons and improved parsing robustness. Changes applied across SDP, media format, connection, and security description classes for better efficiency and maintainability. --- src/SIPSorcery/net/SDP/SDP.cs | 1250 ++++++++++------- .../net/SDP/SDPAudioVideoMediaFormat.cs | 70 +- .../net/SDP/SDPConnectionInformation.cs | 31 +- .../net/SDP/SDPMediaAnnouncement.cs | 192 ++- .../net/SDP/SDPSecurityDescription.cs | 248 ++-- src/SIPSorcery/net/SDP/SDPTypes.cs | 10 +- 6 files changed, 1097 insertions(+), 704 deletions(-) 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: