From d0e8bafcf60b6480bc122ad6a2f39f579c751158 Mon Sep 17 00:00:00 2001 From: cam Date: Tue, 27 Jun 2023 18:03:12 +1000 Subject: [PATCH 01/88] update bouncycastle 1.9.0 -> 2.2.1 --- src/SIPSorcery.csproj | 2 +- src/net/DtlsSrtp/DtlsSrtpClient.cs | 103 +-- src/net/DtlsSrtp/DtlsSrtpServer.cs | 677 +++++++++--------- src/net/DtlsSrtp/DtlsSrtpTransport.cs | 31 +- src/net/DtlsSrtp/DtlsUtils.cs | 188 +++-- src/net/DtlsSrtp/SrtpParameters.cs | 2 +- src/net/WebRTC/IRTCPeerConnection.cs | 9 +- src/net/WebRTC/RTCPeerConnection.cs | 69 +- src/net/WebRTC/RTCSctpTransport.cs | 2 +- .../net/DtlsSrtp/DtlsSrtpTransportUnitTest.cs | 16 +- .../net/DtlsSrtp/DtlsUtilsUnitTest.cs | 9 +- 11 files changed, 523 insertions(+), 585 deletions(-) diff --git a/src/SIPSorcery.csproj b/src/SIPSorcery.csproj index 4a6db6481..b0fb2fb5f 100755 --- a/src/SIPSorcery.csproj +++ b/src/SIPSorcery.csproj @@ -17,7 +17,7 @@ - + diff --git a/src/net/DtlsSrtp/DtlsSrtpClient.cs b/src/net/DtlsSrtp/DtlsSrtpClient.cs index cca09f22a..e5a6504f7 100644 --- a/src/net/DtlsSrtp/DtlsSrtpClient.cs +++ b/src/net/DtlsSrtp/DtlsSrtpClient.cs @@ -16,11 +16,13 @@ using System; using System.Collections; using Microsoft.Extensions.Logging; -using Org.BouncyCastle.Crypto; -using Org.BouncyCastle.Crypto.Tls; +using Org.BouncyCastle.Tls; using Org.BouncyCastle.Security; using Org.BouncyCastle.Utilities; using SIPSorcery.Sys; +using System.Collections.Generic; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Tls.Crypto; namespace SIPSorcery.Net { @@ -36,7 +38,7 @@ internal DtlsSrtpTlsAuthentication(DtlsSrtpClient client) this.mContext = client.TlsContext; } - public virtual void NotifyServerCertificate(Certificate serverCertificate) + public virtual void NotifyServerCertificate(TlsServerCertificate serverCertificate) { //Console.WriteLine("DTLS client received server certificate chain of length " + chain.Length); mClient.ServerCertificate = serverCertificate; @@ -44,7 +46,7 @@ public virtual void NotifyServerCertificate(Certificate serverCertificate) public virtual TlsCredentials GetClientCredentials(CertificateRequest certificateRequest) { - byte[] certificateTypes = certificateRequest.CertificateTypes; + short[] certificateTypes = certificateRequest.CertificateTypes; if (certificateTypes == null || !Arrays.Contains(certificateTypes, ClientCertificateType.rsa_sign)) { return null; @@ -56,11 +58,6 @@ public virtual TlsCredentials GetClientCredentials(CertificateRequest certificat mClient.mCertificateChain, mClient.mPrivateKey); } - - public TlsCredentials GetClientCredentials(TlsContext context, CertificateRequest certificateRequest) - { - return GetClientCredentials(certificateRequest); - } }; public class DtlsSrtpClient : DefaultTlsClient, IDtlsSrtpPeer @@ -72,7 +69,7 @@ public class DtlsSrtpClient : DefaultTlsClient, IDtlsSrtpPeer internal TlsClientContext TlsContext { - get { return mContext; } + get { return m_context; } } protected internal TlsSession mSession; @@ -80,7 +77,7 @@ internal TlsClientContext TlsContext public bool ForceUseExtendedMasterSecret { get; set; } = true; //Received from server - public Certificate ServerCertificate { get; internal set; } + public TlsServerCertificate ServerCertificate { get; internal set; } public RTCDtlsFingerprint Fingerprint { get; private set; } @@ -105,36 +102,37 @@ internal TlsClientContext TlsContext /// public event Action OnAlert; - public DtlsSrtpClient() : - this(null, null, null) + public DtlsSrtpClient(TlsCrypto crypto) : + this(crypto, null, null, null) { } - public DtlsSrtpClient(System.Security.Cryptography.X509Certificates.X509Certificate2 certificate) : - this(DtlsUtils.LoadCertificateChain(certificate), DtlsUtils.LoadPrivateKeyResource(certificate)) + public DtlsSrtpClient(TlsCrypto crypto, System.Security.Cryptography.X509Certificates.X509Certificate2 certificate) : + this(crypto, DtlsUtils.LoadCertificateChain(crypto, certificate), DtlsUtils.LoadPrivateKeyResource(certificate)) { } - public DtlsSrtpClient(string certificatePath, string keyPath) : - this(new string[] { certificatePath }, keyPath) + public DtlsSrtpClient(TlsCrypto crypto, string certificatePath, string keyPath) : + this(crypto, new string[] { certificatePath }, keyPath) { } - public DtlsSrtpClient(string[] certificatesPath, string keyPath) : - this(DtlsUtils.LoadCertificateChain(certificatesPath), DtlsUtils.LoadPrivateKeyResource(keyPath)) + public DtlsSrtpClient(TlsCrypto crypto, string[] certificatesPath, string keyPath) : + this(crypto, DtlsUtils.LoadCertificateChain(crypto, certificatesPath), DtlsUtils.LoadPrivateKeyResource(keyPath)) { } - public DtlsSrtpClient(Certificate certificateChain, AsymmetricKeyParameter privateKey) : - this(certificateChain, privateKey, null) + public DtlsSrtpClient(TlsCrypto crypto, Certificate certificateChain, Org.BouncyCastle.Crypto.AsymmetricKeyParameter privateKey) : + this(crypto, certificateChain, privateKey, null) { } - public DtlsSrtpClient(Certificate certificateChain, AsymmetricKeyParameter privateKey, UseSrtpData clientSrtpData) + public DtlsSrtpClient(TlsCrypto crypto, Certificate certificateChain, Org.BouncyCastle.Crypto.AsymmetricKeyParameter privateKey, UseSrtpData clientSrtpData) : base(crypto) { + if (certificateChain == null && privateKey == null) { - (certificateChain, privateKey) = DtlsUtils.CreateSelfSignedTlsCert(); + (certificateChain, privateKey) = DtlsUtils.CreateSelfSignedTlsCert(crypto); } if (clientSrtpData == null) @@ -158,31 +156,37 @@ public DtlsSrtpClient(Certificate certificateChain, AsymmetricKeyParameter priva Fingerprint = certificate != null ? DtlsUtils.Fingerprint(certificate) : null; } - public DtlsSrtpClient(UseSrtpData clientSrtpData) : this(null, null, clientSrtpData) + public DtlsSrtpClient(TlsCrypto crypto, UseSrtpData clientSrtpData) : this(crypto, null, null, clientSrtpData) { } - public override IDictionary GetClientExtensions() + public override bool ShouldUseExtendedPadding() + { + return base.ShouldUseExtendedPadding(); + } + + public override IDictionary GetClientExtensions() { var clientExtensions = base.GetClientExtensions(); - if (TlsSRTPUtils.GetUseSrtpExtension(clientExtensions) == null) + if (TlsSrpUtilities.GetSrpExtension(clientExtensions) == null) { if (clientExtensions == null) { - clientExtensions = new Hashtable(); + clientExtensions = new Hashtable() as IDictionary; } - TlsSRTPUtils.AddUseSrtpExtension(clientExtensions, clientSrtpData); + TlsSrtpUtilities.AddUseSrtpExtension(clientExtensions, clientSrtpData); } return clientExtensions; } - public override void ProcessServerExtensions(IDictionary clientExtensions) + + public override void ProcessServerExtensions(IDictionary serverExtensions) { - base.ProcessServerExtensions(clientExtensions); + base.ProcessServerExtensions(serverExtensions); // set to some reasonable default value int chosenProfile = SrtpProtectionProfile.SRTP_AES128_CM_HMAC_SHA1_80; - UseSrtpData clientSrtpData = TlsSRTPUtils.GetUseSrtpExtension(clientExtensions); + clientSrtpData = TlsSrtpUtilities.GetUseSrtpExtension(serverExtensions); foreach (int profile in clientSrtpData.ProtectionProfiles) { @@ -244,12 +248,12 @@ public override void NotifyHandshakeComplete() { base.NotifyHandshakeComplete(); - //Copy master Secret (will be inaccessible after this call) - masterSecret = new byte[mContext.SecurityParameters.MasterSecret != null ? mContext.SecurityParameters.MasterSecret.Length : 0]; - Buffer.BlockCopy(mContext.SecurityParameters.MasterSecret, 0, masterSecret, 0, masterSecret.Length); - //Prepare Srtp Keys (we must to it here because master key will be cleared after that) PrepareSrtpSharedSecret(); + + //Copy master Secret (will be inaccessible after this call) + masterSecret = new byte[m_context.SecurityParameters.MasterSecret != null ? m_context.SecurityParameters.MasterSecret.Length : 0]; + Buffer.BlockCopy(m_context.SecurityParameters.MasterSecret.Extract(), 0, masterSecret, 0, masterSecret.Length); } public bool IsClient() @@ -269,7 +273,7 @@ protected virtual byte[] GetKeyingMaterial(string asciiLabel, byte[] context_val throw new ArgumentException("must have length less than 2^16 (or be null)", "context_value"); } - SecurityParameters sp = mContext.SecurityParameters; + SecurityParameters sp = m_context.SecurityParameters; if (!sp.IsExtendedMasterSecret && RequiresExtendedMasterSecret()) { /* @@ -309,7 +313,7 @@ protected virtual byte[] GetKeyingMaterial(string asciiLabel, byte[] context_val throw new InvalidOperationException("error in calculation of seed for export"); } - return TlsUtilities.PRF(mContext, sp.MasterSecret, asciiLabel, seed, length); + return TlsUtilities.Prf(sp, sp.MasterSecret, asciiLabel, seed, length).Extract(); } public override bool RequiresExtendedMasterSecret() @@ -371,22 +375,12 @@ protected virtual void PrepareSrtpSharedSecret() Buffer.BlockCopy(sharedSecret, (2 * keyLen + saltLen), srtpMasterServerSalt, 0, saltLen); } - public override ProtocolVersion ClientVersion - { - get { return ProtocolVersion.DTLSv12; } - } - - public override ProtocolVersion MinimumVersion - { - get { return ProtocolVersion.DTLSv10; } - } - public override TlsSession GetSessionToResume() { return this.mSession; } - public override void NotifyAlertRaised(byte alertLevel, byte alertDescription, string message, Exception cause) + public override void NotifyAlertRaised(short alertLevel, short alertDescription, string message, Exception cause) { string description = null; if (message != null) @@ -418,10 +412,19 @@ public override void NotifyServerVersion(ProtocolVersion serverVersion) public Certificate GetRemoteCertificate() { - return ServerCertificate; + return ServerCertificate.Certificate; + } + + protected override ProtocolVersion[] GetSupportedVersions() + { + return new ProtocolVersion[] + { + ProtocolVersion.DTLSv10, + ProtocolVersion.DTLSv12 + }; } - public override void NotifyAlertReceived(byte alertLevel, byte alertDescription) + public override void NotifyAlertReceived(short alertLevel, short alertDescription) { string description = AlertDescription.GetText(alertDescription); diff --git a/src/net/DtlsSrtp/DtlsSrtpServer.cs b/src/net/DtlsSrtp/DtlsSrtpServer.cs index 1af347285..b3785cfbd 100644 --- a/src/net/DtlsSrtp/DtlsSrtpServer.cs +++ b/src/net/DtlsSrtp/DtlsSrtpServer.cs @@ -17,16 +17,18 @@ // Original Source: AGPL-3.0 License //----------------------------------------------------------------------------- -using System; +using System; using System.Collections; -using System.Collections.Generic; +using System.Collections.Generic; using Microsoft.Extensions.Logging; -using Org.BouncyCastle.Crypto; -using Org.BouncyCastle.Crypto.Tls; -using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Tls; +using Org.BouncyCastle.Tls.Crypto; +using Org.BouncyCastle.Tls.Crypto.Impl.BC; +using Org.BouncyCastle.Utilities; using SIPSorcery.Sys; - -namespace SIPSorcery.Net + +namespace SIPSorcery.Net { public enum AlertLevelsEnum : byte { @@ -69,28 +71,28 @@ public enum AlertTypesEnum : byte unknown_psk_identity = 115, unknown = 255 } - - public interface IDtlsSrtpPeer + + public interface IDtlsSrtpPeer { - event Action OnAlert; - bool ForceUseExtendedMasterSecret { get; set; } - SrtpPolicy GetSrtpPolicy(); - SrtpPolicy GetSrtcpPolicy(); - byte[] GetSrtpMasterServerKey(); - byte[] GetSrtpMasterServerSalt(); - byte[] GetSrtpMasterClientKey(); - byte[] GetSrtpMasterClientSalt(); + event Action OnAlert; + bool ForceUseExtendedMasterSecret { get; set; } + SrtpPolicy GetSrtpPolicy(); + SrtpPolicy GetSrtcpPolicy(); + byte[] GetSrtpMasterServerKey(); + byte[] GetSrtpMasterServerSalt(); + byte[] GetSrtpMasterClientKey(); + byte[] GetSrtpMasterClientSalt(); bool IsClient(); - Certificate GetRemoteCertificate(); + Certificate GetRemoteCertificate(); } - - public class DtlsSrtpServer : DefaultTlsServer, IDtlsSrtpPeer + + public class DtlsSrtpServer : DefaultTlsServer, IDtlsSrtpPeer { private static readonly ILogger logger = Log.Logger; - - Certificate mCertificateChain = null; - AsymmetricKeyParameter mPrivateKey = null; - + + Certificate mCertificateChain = null; + AsymmetricKeyParameter mPrivateKey = null; + private RTCDtlsFingerprint mFingerPrint; //private AlgorithmCertificate algorithmCertificate; @@ -98,23 +100,21 @@ public class DtlsSrtpServer : DefaultTlsServer, IDtlsSrtpPeer public bool ForceUseExtendedMasterSecret { get; set; } = true; public Certificate ClientCertificate { get; private set; } - - // the server response to the client handshake request - // http://tools.ietf.org/html/rfc5764#section-4.1.1 - private UseSrtpData serverSrtpData; - - // Asymmetric shared keys derived from the DTLS handshake and used for the SRTP encryption/ - private byte[] srtpMasterClientKey; - private byte[] srtpMasterServerKey; - private byte[] srtpMasterClientSalt; - private byte[] srtpMasterServerSalt; - byte[] masterSecret = null; - - // Policies - private SrtpPolicy srtpPolicy; - private SrtpPolicy srtcpPolicy; - - private int[] cipherSuites; + + // the server response to the client handshake request + // http://tools.ietf.org/html/rfc5764#section-4.1.1 + private UseSrtpData serverSrtpData; + + // Asymmetric shared keys derived from the DTLS handshake and used for the SRTP encryption/ + private byte[] srtpMasterClientKey; + private byte[] srtpMasterServerKey; + private byte[] srtpMasterClientSalt; + private byte[] srtpMasterServerSalt; + byte[] masterSecret = null; + + // Policies + private SrtpPolicy srtpPolicy; + private SrtpPolicy srtcpPolicy; /// /// Parameters: @@ -122,122 +122,121 @@ public class DtlsSrtpServer : DefaultTlsServer, IDtlsSrtpPeer /// - alert type, /// - alert description. /// - public event Action OnAlert; - - public DtlsSrtpServer() : this((Certificate)null, null) - { - } - - public DtlsSrtpServer(System.Security.Cryptography.X509Certificates.X509Certificate2 certificate) : this(DtlsUtils.LoadCertificateChain(certificate), DtlsUtils.LoadPrivateKeyResource(certificate)) - { - } - - public DtlsSrtpServer(string certificatePath, string keyPath) : this(new string[] { certificatePath }, keyPath) - { - } - - public DtlsSrtpServer(string[] certificatesPath, string keyPath) : - this(DtlsUtils.LoadCertificateChain(certificatesPath), DtlsUtils.LoadPrivateKeyResource(keyPath)) - { - } - - public DtlsSrtpServer(Certificate certificateChain, AsymmetricKeyParameter privateKey) + public event Action OnAlert; + + public DtlsSrtpServer(TlsCrypto crypto) : this(crypto, (Certificate)null, null) + { + } + + public DtlsSrtpServer(TlsCrypto crypto, System.Security.Cryptography.X509Certificates.X509Certificate2 certificate) : this(crypto,DtlsUtils.LoadCertificateChain(crypto,certificate), DtlsUtils.LoadPrivateKeyResource(certificate)) + { + } + + public DtlsSrtpServer(TlsCrypto crypto, string certificatePath, string keyPath) : this(crypto, new string[] { certificatePath }, keyPath) + { + } + + public DtlsSrtpServer(TlsCrypto crypto, string[] certificatesPath, string keyPath) : + this(crypto, DtlsUtils.LoadCertificateChain(crypto, certificatesPath), DtlsUtils.LoadPrivateKeyResource(keyPath)) + { + } + + public DtlsSrtpServer(TlsCrypto crypto, Certificate certificateChain, AsymmetricKeyParameter privateKey) : base(crypto) { if (certificateChain == null && privateKey == null) { - (certificateChain, privateKey) = DtlsUtils.CreateSelfSignedTlsCert(); + (certificateChain, privateKey) = DtlsUtils.CreateSelfSignedTlsCert(crypto); } - - this.cipherSuites = base.GetCipherSuites(); - - this.mPrivateKey = privateKey; - mCertificateChain = certificateChain; - - //Generate FingerPrint + + this.mPrivateKey = privateKey; + mCertificateChain = certificateChain; + + //Generate FingerPrint var certificate = mCertificateChain.GetCertificateAt(0); - - this.mFingerPrint = certificate != null ? DtlsUtils.Fingerprint(certificate) : null; - } - - public RTCDtlsFingerprint Fingerprint - { - get - { - return mFingerPrint; - } - } - - public AsymmetricKeyParameter PrivateKey - { - get - { - return mPrivateKey; - } - } - - public Certificate CertificateChain - { - get - { - return mCertificateChain; - } - } - - protected override ProtocolVersion MaximumVersion - { - get - { - return ProtocolVersion.DTLSv12; - } - } - - protected override ProtocolVersion MinimumVersion - { - get - { - return ProtocolVersion.DTLSv10; - } - } - - public override int GetSelectedCipherSuite() - { - /* - * TODO RFC 5246 7.4.3. In order to negotiate correctly, the server MUST check any candidate cipher suites against the - * "signature_algorithms" extension before selecting them. This is somewhat inelegant but is a compromise designed to - * minimize changes to the original cipher suite design. - */ - - /* - * RFC 4429 5.1. A server that receives a ClientHello containing one or both of these extensions MUST use the client's - * enumerated capabilities to guide its selection of an appropriate cipher suite. One of the proposed ECC cipher suites - * must be negotiated only if the server can successfully complete the handshake while using the curves and point - * formats supported by the client [...]. - */ - bool eccCipherSuitesEnabled = SupportsClientEccCapabilities(this.mNamedCurves, this.mClientECPointFormats); - - int[] cipherSuites = GetCipherSuites(); - for (int i = 0; i < cipherSuites.Length; ++i) - { - int cipherSuite = cipherSuites[i]; - - if (Arrays.Contains(this.mOfferedCipherSuites, cipherSuite) - && (eccCipherSuitesEnabled || !TlsEccUtilities.IsEccCipherSuite(cipherSuite)) - && TlsUtilities.IsValidCipherSuiteForVersion(cipherSuite, mServerVersion)) - { - return this.mSelectedCipherSuite = cipherSuite; - } - } - throw new TlsFatalAlert(AlertDescription.handshake_failure); + + this.mFingerPrint = certificate != null ? DtlsUtils.Fingerprint(certificate) : null; + } + + public RTCDtlsFingerprint Fingerprint + { + get + { + return mFingerPrint; + } + } + + public AsymmetricKeyParameter PrivateKey + { + get + { + return mPrivateKey; + } + } + + public Certificate CertificateChain + { + get + { + return mCertificateChain; + } + } + + protected ProtocolVersion MaximumVersion + { + get + { + return ProtocolVersion.DTLSv13; + } + } + + protected ProtocolVersion MinimumVersion + { + get + { + return ProtocolVersion.DTLSv10; + } + } + + public override int GetSelectedCipherSuite() + { + /* + * TODO RFC 5246 7.4.3. In order to negotiate correctly, the server MUST check any candidate cipher suites against the + * "signature_algorithms" extension before selecting them. This is somewhat inelegant but is a compromise designed to + * minimize changes to the original cipher suite design. + */ + + /* + * RFC 4429 5.1. A server that receives a ClientHello containing one or both of these extensions MUST use the client's + * enumerated capabilities to guide its selection of an appropriate cipher suite. One of the proposed ECC cipher suites + * must be negotiated only if the server can successfully complete the handshake while using the curves and point + * formats supported by the client [...]. + */ + + bool eccCipherSuitesEnabled = false;// SupportsClientEccCapabilities(this.mNamedCurves, this.mClientECPointFormats); + + int[] cipherSuites = GetCipherSuites(); + for (int i = 0; i < cipherSuites.Length; ++i) + { + int cipherSuite = cipherSuites[i]; + + if (Arrays.Contains(this.m_offeredCipherSuites, cipherSuite) + && (eccCipherSuitesEnabled || !TlsEccUtilities.IsEccCipherSuite(cipherSuite)) + && TlsUtilities.IsValidVersionForCipherSuite(cipherSuite, GetServerVersion())) + { + return this.m_selectedCipherSuite = cipherSuite; + } + } + throw new TlsFatalAlert(AlertDescription.handshake_failure); } public override CertificateRequest GetCertificateRequest() { List serverSigAlgs = new List(); - if (TlsUtilities.IsSignatureAlgorithmsExtensionAllowed(mServerVersion)) + if (TlsUtilities.IsSignatureAlgorithmsExtensionAllowed(GetServerVersion())) { - byte[] hashAlgorithms = new byte[] { HashAlgorithm.sha512, HashAlgorithm.sha384, HashAlgorithm.sha256, HashAlgorithm.sha224, HashAlgorithm.sha1 }; - byte[] signatureAlgorithms = new byte[] { SignatureAlgorithm.rsa, SignatureAlgorithm.ecdsa }; + short[] hashAlgorithms = new short[] { HashAlgorithm.sha512, HashAlgorithm.sha384, HashAlgorithm.sha256, HashAlgorithm.sha224, HashAlgorithm.sha1 }; + short[] signatureAlgorithms = new short[] { SignatureAlgorithm.rsa, SignatureAlgorithm.ecdsa }; serverSigAlgs = new List(); for (int i = 0; i < hashAlgorithms.Length; ++i) @@ -248,194 +247,171 @@ public override CertificateRequest GetCertificateRequest() } } } - return new CertificateRequest(new byte[] { ClientCertificateType.rsa_sign }, serverSigAlgs, null); + return new CertificateRequest(new short[] { ClientCertificateType.rsa_sign }, serverSigAlgs, null); } public override void NotifyClientCertificate(Certificate clientCertificate) { ClientCertificate = clientCertificate; } - - public override IDictionary GetServerExtensions() - { - Hashtable serverExtensions = (Hashtable)base.GetServerExtensions(); - if (TlsSRTPUtils.GetUseSrtpExtension(serverExtensions) == null) - { - if (serverExtensions == null) - { - serverExtensions = new Hashtable(); - } - TlsSRTPUtils.AddUseSrtpExtension(serverExtensions, serverSrtpData); - } - return serverExtensions; - } - - public override void ProcessClientExtensions(IDictionary clientExtensions) - { - base.ProcessClientExtensions(clientExtensions); - - // set to some reasonable default value - int chosenProfile = SrtpProtectionProfile.SRTP_AES128_CM_HMAC_SHA1_80; - UseSrtpData clientSrtpData = TlsSRTPUtils.GetUseSrtpExtension(clientExtensions); - - foreach (int profile in clientSrtpData.ProtectionProfiles) - { - switch (profile) - { - case SrtpProtectionProfile.SRTP_AES128_CM_HMAC_SHA1_32: - case SrtpProtectionProfile.SRTP_AES128_CM_HMAC_SHA1_80: - case SrtpProtectionProfile.SRTP_NULL_HMAC_SHA1_32: - case SrtpProtectionProfile.SRTP_NULL_HMAC_SHA1_80: - chosenProfile = profile; - break; - } - } - - // server chooses a mutually supported SRTP protection profile - // http://tools.ietf.org/html/draft-ietf-avt-dtls-srtp-07#section-4.1.2 - int[] protectionProfiles = { chosenProfile }; - - // server agrees to use the MKI offered by the client - serverSrtpData = new UseSrtpData(protectionProfiles, clientSrtpData.Mki); - } - - public SrtpPolicy GetSrtpPolicy() - { - return srtpPolicy; - } - - public SrtpPolicy GetSrtcpPolicy() - { - return srtcpPolicy; - } - - public byte[] GetSrtpMasterServerKey() - { - return srtpMasterServerKey; - } - - public byte[] GetSrtpMasterServerSalt() - { - return srtpMasterServerSalt; - } - - public byte[] GetSrtpMasterClientKey() - { - return srtpMasterClientKey; - } - - public byte[] GetSrtpMasterClientSalt() - { - return srtpMasterClientSalt; - } - - public override void NotifyHandshakeComplete() - { - //Copy master Secret (will be inaccessible after this call) - masterSecret = new byte[mContext.SecurityParameters.MasterSecret != null ? mContext.SecurityParameters.MasterSecret.Length : 0]; - Buffer.BlockCopy(mContext.SecurityParameters.MasterSecret, 0, masterSecret, 0, masterSecret.Length); - - //Prepare Srtp Keys (we must to it here because master key will be cleared after that) - PrepareSrtpSharedSecret(); - } - - public bool IsClient() - { - return false; - } - - protected override TlsSignerCredentials GetECDsaSignerCredentials() - { - return DtlsUtils.LoadSignerCredentials(mContext, mCertificateChain, mPrivateKey, new SignatureAndHashAlgorithm(HashAlgorithm.sha256, SignatureAlgorithm.ecdsa)); - } - - protected override TlsEncryptionCredentials GetRsaEncryptionCredentials() - { - return DtlsUtils.LoadEncryptionCredentials(mContext, mCertificateChain, mPrivateKey); - } - - protected override TlsSignerCredentials GetRsaSignerCredentials() - { - /* - * TODO Note that this code fails to provide default value for the client supported - * algorithms if it wasn't sent. - */ - SignatureAndHashAlgorithm signatureAndHashAlgorithm = null; - IList sigAlgs = mSupportedSignatureAlgorithms; - if (sigAlgs != null) - { - foreach (var sigAlgUncasted in sigAlgs) - { - SignatureAndHashAlgorithm sigAlg = sigAlgUncasted as SignatureAndHashAlgorithm; - if (sigAlg != null && sigAlg.Signature == SignatureAlgorithm.rsa) - { - signatureAndHashAlgorithm = sigAlg; - break; - } - } - - if (signatureAndHashAlgorithm == null) - { - return null; - } - } - return DtlsUtils.LoadSignerCredentials(mContext, mCertificateChain, mPrivateKey, signatureAndHashAlgorithm); - } - - protected virtual void PrepareSrtpSharedSecret() - { - //Set master secret back to security parameters (only works in old bouncy castle versions) - //mContext.SecurityParameters.masterSecret = masterSecret; - - SrtpParameters srtpParams = SrtpParameters.GetSrtpParametersForProfile(serverSrtpData.ProtectionProfiles[0]); - int keyLen = srtpParams.GetCipherKeyLength(); - int saltLen = srtpParams.GetCipherSaltLength(); - - srtpPolicy = srtpParams.GetSrtpPolicy(); - srtcpPolicy = srtpParams.GetSrtcpPolicy(); - - srtpMasterClientKey = new byte[keyLen]; - srtpMasterServerKey = new byte[keyLen]; - srtpMasterClientSalt = new byte[saltLen]; - srtpMasterServerSalt = new byte[saltLen]; - - // 2* (key + salt length) / 8. From http://tools.ietf.org/html/rfc5764#section-4-2 - // No need to divide by 8 here since lengths are already in bits - byte[] sharedSecret = GetKeyingMaterial(2 * (keyLen + saltLen)); - - /* - * - * See: http://tools.ietf.org/html/rfc5764#section-4.2 - * - * sharedSecret is an equivalent of : - * - * struct { - * client_write_SRTP_master_key[SRTPSecurityParams.master_key_len]; - * server_write_SRTP_master_key[SRTPSecurityParams.master_key_len]; - * client_write_SRTP_master_salt[SRTPSecurityParams.master_salt_len]; - * server_write_SRTP_master_salt[SRTPSecurityParams.master_salt_len]; - * } ; - * - * Here, client = local configuration, server = remote. - * NOTE [ivelin]: 'local' makes sense if this code is used from a DTLS SRTP client. - * Here we run as a server, so 'local' referring to the client is actually confusing. - * - * l(k) = KEY length - * s(k) = salt length - * - * So we have the following repartition : - * l(k) 2*l(k)+s(k) - * 2*l(k) 2*(l(k)+s(k)) - * +------------------------+------------------------+---------------+-------------------+ - * + local key | remote key | local salt | remote salt | - * +------------------------+------------------------+---------------+-------------------+ - */ - Buffer.BlockCopy(sharedSecret, 0, srtpMasterClientKey, 0, keyLen); - Buffer.BlockCopy(sharedSecret, keyLen, srtpMasterServerKey, 0, keyLen); - Buffer.BlockCopy(sharedSecret, 2 * keyLen, srtpMasterClientSalt, 0, saltLen); - Buffer.BlockCopy(sharedSecret, (2 * keyLen + saltLen), srtpMasterServerSalt, 0, saltLen); - } - + + public override IDictionary GetServerExtensions() + { + var serverExtensions = base.GetServerExtensions(); + if (TlsSrtpUtilities.GetUseSrtpExtension(serverExtensions) == null) + { + if (serverExtensions == null) + { + serverExtensions = (IDictionary)new Hashtable(); + } + TlsSrtpUtilities.AddUseSrtpExtension(serverExtensions, serverSrtpData); + } + return serverExtensions; + } + + public override void ProcessClientExtensions(IDictionary clientExtensions) + { + base.ProcessClientExtensions(clientExtensions); + + // set to some reasonable default value + int chosenProfile = SrtpProtectionProfile.SRTP_AES128_CM_HMAC_SHA1_80; + UseSrtpData clientSrtpData = TlsSrtpUtilities.GetUseSrtpExtension(clientExtensions); + + foreach (int profile in clientSrtpData.ProtectionProfiles) + { + switch (profile) + { + case SrtpProtectionProfile.SRTP_AES128_CM_HMAC_SHA1_32: + case SrtpProtectionProfile.SRTP_AES128_CM_HMAC_SHA1_80: + case SrtpProtectionProfile.SRTP_NULL_HMAC_SHA1_32: + case SrtpProtectionProfile.SRTP_NULL_HMAC_SHA1_80: + chosenProfile = profile; + break; + } + } + + // server chooses a mutually supported SRTP protection profile + // http://tools.ietf.org/html/draft-ietf-avt-dtls-srtp-07#section-4.1.2 + int[] protectionProfiles = { chosenProfile }; + + // server agrees to use the MKI offered by the client + serverSrtpData = new UseSrtpData(protectionProfiles, clientSrtpData.Mki); + } + + public SrtpPolicy GetSrtpPolicy() + { + return srtpPolicy; + } + + public SrtpPolicy GetSrtcpPolicy() + { + return srtcpPolicy; + } + + public byte[] GetSrtpMasterServerKey() + { + return srtpMasterServerKey; + } + + public byte[] GetSrtpMasterServerSalt() + { + return srtpMasterServerSalt; + } + + public byte[] GetSrtpMasterClientKey() + { + return srtpMasterClientKey; + } + + public byte[] GetSrtpMasterClientSalt() + { + return srtpMasterClientSalt; + } + + public override void NotifyHandshakeComplete() + { + //Prepare Srtp Keys (we must to it here because master key will be cleared after that) + PrepareSrtpSharedSecret(); + //Copy master Secret (will be inaccessible after this call) + masterSecret = new byte[m_context.SecurityParameters.MasterSecret != null ? m_context.SecurityParameters.MasterSecret.Length : 0]; + Buffer.BlockCopy(m_context.SecurityParameters.MasterSecret.Extract(), 0, masterSecret, 0, masterSecret.Length); + + } + + public bool IsClient() + { + return false; + } + + protected override TlsCredentialedSigner GetECDsaSignerCredentials() + { + return new BcDefaultTlsCredentialedSigner(new TlsCryptoParameters(m_context), this.Crypto as BcTlsCrypto, mPrivateKey, mCertificateChain, new SignatureAndHashAlgorithm(HashAlgorithm.sha256, SignatureAlgorithm.ecdsa)); + } + + protected override TlsCredentialedDecryptor GetRsaEncryptionCredentials() + { + return new BcDefaultTlsCredentialedDecryptor(Crypto as BcTlsCrypto, mCertificateChain, mPrivateKey); + } + + protected override TlsCredentialedSigner GetRsaSignerCredentials() + { + return new BcDefaultTlsCredentialedSigner(new TlsCryptoParameters(m_context), this.Crypto as BcTlsCrypto, mPrivateKey, mCertificateChain, new SignatureAndHashAlgorithm(HashAlgorithm.sha256, SignatureAlgorithm.rsa)); + } + + protected virtual void PrepareSrtpSharedSecret() + { + //Set master secret back to security parameters (only works in old bouncy castle versions) + //mContext.SecurityParameters.masterSecret = masterSecret; + + SrtpParameters srtpParams = SrtpParameters.GetSrtpParametersForProfile(serverSrtpData.ProtectionProfiles[0]); + int keyLen = srtpParams.GetCipherKeyLength(); + int saltLen = srtpParams.GetCipherSaltLength(); + + srtpPolicy = srtpParams.GetSrtpPolicy(); + srtcpPolicy = srtpParams.GetSrtcpPolicy(); + + srtpMasterClientKey = new byte[keyLen]; + srtpMasterServerKey = new byte[keyLen]; + srtpMasterClientSalt = new byte[saltLen]; + srtpMasterServerSalt = new byte[saltLen]; + + // 2* (key + salt length) / 8. From http://tools.ietf.org/html/rfc5764#section-4-2 + // No need to divide by 8 here since lengths are already in bits + byte[] sharedSecret = GetKeyingMaterial(2 * (keyLen + saltLen)); + + /* + * + * See: http://tools.ietf.org/html/rfc5764#section-4.2 + * + * sharedSecret is an equivalent of : + * + * struct { + * client_write_SRTP_master_key[SRTPSecurityParams.master_key_len]; + * server_write_SRTP_master_key[SRTPSecurityParams.master_key_len]; + * client_write_SRTP_master_salt[SRTPSecurityParams.master_salt_len]; + * server_write_SRTP_master_salt[SRTPSecurityParams.master_salt_len]; + * } ; + * + * Here, client = local configuration, server = remote. + * NOTE [ivelin]: 'local' makes sense if this code is used from a DTLS SRTP client. + * Here we run as a server, so 'local' referring to the client is actually confusing. + * + * l(k) = KEY length + * s(k) = salt length + * + * So we have the following repartition : + * l(k) 2*l(k)+s(k) + * 2*l(k) 2*(l(k)+s(k)) + * +------------------------+------------------------+---------------+-------------------+ + * + local key | remote key | local salt | remote salt | + * +------------------------+------------------------+---------------+-------------------+ + */ + Buffer.BlockCopy(sharedSecret, 0, srtpMasterClientKey, 0, keyLen); + Buffer.BlockCopy(sharedSecret, keyLen, srtpMasterServerKey, 0, keyLen); + Buffer.BlockCopy(sharedSecret, 2 * keyLen, srtpMasterClientSalt, 0, saltLen); + Buffer.BlockCopy(sharedSecret, (2 * keyLen + saltLen), srtpMasterServerSalt, 0, saltLen); + } + protected byte[] GetKeyingMaterial(int length) { return GetKeyingMaterial(ExporterLabel.dtls_srtp, null, length); @@ -448,7 +424,7 @@ protected virtual byte[] GetKeyingMaterial(string asciiLabel, byte[] context_val throw new ArgumentException("must have length less than 2^16 (or be null)", "context_value"); } - SecurityParameters sp = mContext.SecurityParameters; + SecurityParameters sp = m_context.SecurityParameters; if (!sp.IsExtendedMasterSecret && RequiresExtendedMasterSecret()) { /* @@ -488,22 +464,12 @@ protected virtual byte[] GetKeyingMaterial(string asciiLabel, byte[] context_val throw new InvalidOperationException("error in calculation of seed for export"); } - return TlsUtilities.PRF(mContext, sp.MasterSecret, asciiLabel, seed, length); + return TlsUtilities.Prf(sp, sp.MasterSecret, asciiLabel, seed, length).Extract(); } public override bool RequiresExtendedMasterSecret() { return ForceUseExtendedMasterSecret; - } - - protected override int[] GetCipherSuites() - { - int[] cipherSuites = new int[this.cipherSuites.Length]; - for (int i = 0; i < this.cipherSuites.Length; i++) - { - cipherSuites[i] = this.cipherSuites[i]; - } - return cipherSuites; } public Certificate GetRemoteCertificate() @@ -511,16 +477,25 @@ public Certificate GetRemoteCertificate() return ClientCertificate; } - public override void NotifyAlertRaised(byte alertLevel, byte alertDescription, string message, Exception cause) + protected override ProtocolVersion[] GetSupportedVersions() + { + return new ProtocolVersion[] + { + ProtocolVersion.DTLSv10, + ProtocolVersion.DTLSv12 + }; + } + + public override void NotifyAlertRaised(short alertLevel, short alertDescription, string message, Exception cause) { string description = null; - if (message != null) + if (message != null) { - description += message; - } - if (cause != null) + description += message; + } + if (cause != null) { - description += cause; + description += cause; } string alertMsg = $"{AlertLevel.GetText(alertLevel)}, {AlertDescription.GetText(alertDescription)}"; @@ -534,9 +509,9 @@ public override void NotifyAlertRaised(byte alertLevel, byte alertDescription, s { logger.LogWarning($"DTLS server raised unexpected alert: {alertMsg}"); } - } - - public override void NotifyAlertReceived(byte alertLevel, byte alertDescription) + } + + public override void NotifyAlertReceived(short alertLevel, short alertDescription) { string description = AlertDescription.GetText(alertDescription); @@ -565,7 +540,7 @@ public override void NotifyAlertReceived(byte alertLevel, byte alertDescription) logger.LogWarning($"DTLS server received unexpected alert: {alertMsg}"); } - OnAlert?.Invoke(level, alertType, description); + OnAlert?.Invoke(level, alertType, description); } /// @@ -580,6 +555,6 @@ public override void NotifySecureRenegotiation(bool secureRenegotiation) { logger.LogWarning($"DTLS server received a client handshake without renegotiation support."); } - } - } + } + } } \ No newline at end of file diff --git a/src/net/DtlsSrtp/DtlsSrtpTransport.cs b/src/net/DtlsSrtp/DtlsSrtpTransport.cs index 0741df535..72f476948 100644 --- a/src/net/DtlsSrtp/DtlsSrtpTransport.cs +++ b/src/net/DtlsSrtp/DtlsSrtpTransport.cs @@ -19,7 +19,7 @@ using System; using System.Collections.Concurrent; using Microsoft.Extensions.Logging; -using Org.BouncyCastle.Crypto.Tls; +using Org.BouncyCastle.Tls; using Org.BouncyCastle.Security; using SIPSorcery.Sys; @@ -171,8 +171,7 @@ private bool DoHandshakeAsClient(out string handshakeError) this._waitMillis = RetransmissionMilliseconds; this._startTime = System.DateTime.Now; this._handshaking = true; - SecureRandom secureRandom = new SecureRandom(); - DtlsClientProtocol clientProtocol = new DtlsClientProtocol(secureRandom); + DtlsClientProtocol clientProtocol = new DtlsClientProtocol(); try { var client = (DtlsSrtpClient)connection; @@ -208,9 +207,9 @@ private bool DoHandshakeAsClient(out string handshakeError) else { handshakeError = "unknown"; - if (excp is Org.BouncyCastle.Crypto.Tls.TlsFatalAlert) + if (excp is Org.BouncyCastle.Tls.TlsFatalAlert) { - handshakeError = (excp as Org.BouncyCastle.Crypto.Tls.TlsFatalAlert).Message; + handshakeError = (excp as Org.BouncyCastle.Tls.TlsFatalAlert).Message; } logger.LogWarning(excp, $"DTLS handshake as client failed. {excp.Message}"); @@ -238,8 +237,7 @@ private bool DoHandshakeAsServer(out string handshakeError) this._waitMillis = RetransmissionMilliseconds; this._startTime = System.DateTime.Now; this._handshaking = true; - SecureRandom secureRandom = new SecureRandom(); - DtlsServerProtocol serverProtocol = new DtlsServerProtocol(secureRandom); + DtlsServerProtocol serverProtocol = new DtlsServerProtocol(); try { var server = (DtlsSrtpServer)connection; @@ -274,9 +272,9 @@ private bool DoHandshakeAsServer(out string handshakeError) else { handshakeError = "unknown"; - if (excp is Org.BouncyCastle.Crypto.Tls.TlsFatalAlert) + if (excp is Org.BouncyCastle.Tls.TlsFatalAlert) { - handshakeError = (excp as Org.BouncyCastle.Crypto.Tls.TlsFatalAlert).Message; + handshakeError = (excp as Org.BouncyCastle.Tls.TlsFatalAlert).Message; } logger.LogWarning(excp, $"DTLS handshake as server failed. {excp.Message}"); @@ -514,6 +512,14 @@ private int Read(byte[] buffer, int offset, int count, int timeout) return DTLS_RETRANSMISSION_CODE; } +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || NET6_0_OR_GREATER + public int Receive(Span buf, int waitMillis) + { + // TODO + return Receive(buf.ToArray(), 0, buf.Length, waitMillis); + } +#endif + public int Receive(byte[] buf, int off, int len, int waitMillis) { if (!_handshakeComplete) @@ -580,6 +586,13 @@ public void Send(byte[] buf, int off, int len) OnDataReady?.Invoke(buf); } +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + public void Send(ReadOnlySpan buf) + { + OnDataReady?.Invoke(buf.ToArray()); + } +#endif + public virtual void Close() { diff --git a/src/net/DtlsSrtp/DtlsUtils.cs b/src/net/DtlsSrtp/DtlsUtils.cs index ef2337c93..9dfb6f2dd 100644 --- a/src/net/DtlsSrtp/DtlsUtils.cs +++ b/src/net/DtlsSrtp/DtlsUtils.cs @@ -56,7 +56,7 @@ using Org.BouncyCastle.Crypto.Operators; using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.Crypto.Prng; -using Org.BouncyCastle.Crypto.Tls; +using Org.BouncyCastle.Tls; using Org.BouncyCastle.Math; using Org.BouncyCastle.Pkcs; using Org.BouncyCastle.Security; @@ -64,6 +64,9 @@ using Org.BouncyCastle.Utilities.IO.Pem; using Org.BouncyCastle.X509; using SIPSorcery.Sys; +using System.Runtime.CompilerServices; +using Org.BouncyCastle.Tls.Crypto.Impl.BC; +using Org.BouncyCastle.Tls.Crypto; namespace SIPSorcery.Net { @@ -76,12 +79,12 @@ public class DtlsUtils private static ILogger logger = SIPSorcery.Sys.Log.Logger; - public static RTCDtlsFingerprint Fingerprint(string hashAlgorithm, X509Certificate2 certificate) + public static RTCDtlsFingerprint Fingerprint(TlsCrypto crypto, string hashAlgorithm, X509Certificate2 certificate) { - return Fingerprint(hashAlgorithm, LoadCertificateResource(certificate)); + return Fingerprint(hashAlgorithm, LoadCertificateResource(crypto, certificate)); } - public static RTCDtlsFingerprint Fingerprint(string hashAlgorithm, Org.BouncyCastle.Asn1.X509.X509CertificateStructure c) + public static RTCDtlsFingerprint Fingerprint(string hashAlgorithm, TlsCertificate c) { if (!IsHashSupported(hashAlgorithm)) { @@ -105,9 +108,9 @@ public static RTCDtlsFingerprint Fingerprint(Certificate certificateChain) return Fingerprint(certificate); } - public static RTCDtlsFingerprint Fingerprint(X509Certificate2 certificate) + public static RTCDtlsFingerprint Fingerprint(TlsCrypto crypto, X509Certificate2 certificate) { - return Fingerprint(LoadCertificateResource(certificate)); + return Fingerprint(LoadCertificateResource(crypto, certificate)); } public static RTCDtlsFingerprint Fingerprint(Org.BouncyCastle.X509.X509Certificate certificate) @@ -128,6 +131,18 @@ public static RTCDtlsFingerprint Fingerprint(X509CertificateStructure c) value = sha256Hash.HexStr(':') }; } + public static RTCDtlsFingerprint Fingerprint(TlsCertificate c) + { + IDigest sha256 = DigestUtilities.GetDigest(HashAlgorithmTag.Sha256.ToString()); + byte[] der = c.GetEncoded(); + byte[] sha256Hash = DigestOf(sha256, der); + + return new RTCDtlsFingerprint + { + algorithm = sha256.AlgorithmName.ToLower(), + value = sha256Hash.HexStr(':') + }; + } public static byte[] DigestOf(IDigest dAlg, byte[] input) { @@ -137,70 +152,56 @@ public static byte[] DigestOf(IDigest dAlg, byte[] input) return result; } - public static TlsAgreementCredentials LoadAgreementCredentials(TlsContext context, + public static TlsCredentialedAgreement LoadAgreementCredentials(TlsContext context, Certificate certificate, AsymmetricKeyParameter privateKey) { - return new DefaultTlsAgreementCredentials(certificate, privateKey); + return new BcDefaultTlsCredentialedAgreement(context.Crypto as BcTlsCrypto, certificate, privateKey); } - public static TlsAgreementCredentials LoadAgreementCredentials(TlsContext context, + public static TlsCredentialedAgreement LoadAgreementCredentials(TlsContext context, string[] certResources, string keyResource) { - Certificate certificate = LoadCertificateChain(certResources); + Certificate certificate = LoadCertificateChain(context.Crypto, certResources); AsymmetricKeyParameter privateKey = LoadPrivateKeyResource(keyResource); return LoadAgreementCredentials(context, certificate, privateKey); } - public static TlsEncryptionCredentials LoadEncryptionCredentials( + public static TlsCredentialedDecryptor LoadEncryptionCredentials( TlsContext context, Certificate certificate, AsymmetricKeyParameter privateKey) { - return new DefaultTlsEncryptionCredentials(context, certificate, + + return new BcDefaultTlsCredentialedDecryptor(context.Crypto as BcTlsCrypto, certificate, privateKey); } - public static TlsEncryptionCredentials LoadEncryptionCredentials( + public static TlsCredentialedDecryptor LoadEncryptionCredentials( TlsContext context, string[] certResources, string keyResource) { - Certificate certificate = LoadCertificateChain(certResources); + Certificate certificate = LoadCertificateChain(context.Crypto, certResources); AsymmetricKeyParameter privateKey = LoadPrivateKeyResource(keyResource); return LoadEncryptionCredentials(context, certificate, privateKey); } - public static TlsSignerCredentials LoadSignerCredentials(TlsContext context, - Certificate certificate, AsymmetricKeyParameter privateKey) - { - return new DefaultTlsSignerCredentials(context, certificate, privateKey); - } - - public static TlsSignerCredentials LoadSignerCredentials(TlsContext context, - string[] certResources, string keyResource) - { - Certificate certificate = LoadCertificateChain(certResources); - AsymmetricKeyParameter privateKey = LoadPrivateKeyResource(keyResource); - return LoadSignerCredentials(context, certificate, privateKey); - } - - public static TlsSignerCredentials LoadSignerCredentials(TlsContext context, + public static TlsCredentialedSigner LoadSignerCredentials(TlsContext context, Certificate certificate, AsymmetricKeyParameter privateKey, SignatureAndHashAlgorithm signatureAndHashAlgorithm) { - return new DefaultTlsSignerCredentials(context, certificate, - privateKey, signatureAndHashAlgorithm); + return new BcDefaultTlsCredentialedSigner(new TlsCryptoParameters(context), context.Crypto as BcTlsCrypto, privateKey, certificate, signatureAndHashAlgorithm); } - public static TlsSignerCredentials LoadSignerCredentials(TlsContext context, + public static TlsCredentialedSigner LoadSignerCredentials(TlsContext context, string[] certResources, string keyResource, SignatureAndHashAlgorithm signatureAndHashAlgorithm) { - Certificate certificate = LoadCertificateChain(certResources); + Certificate certificate = LoadCertificateChain(context.Crypto as BcTlsCrypto, certResources); Org.BouncyCastle.Crypto.AsymmetricKeyParameter privateKey = LoadPrivateKeyResource(keyResource); return LoadSignerCredentials(context, certificate, privateKey, signatureAndHashAlgorithm); } - public static TlsSignerCredentials LoadSignerCredentials(TlsContext context, IList supportedSignatureAlgorithms, - byte signatureAlgorithm, Certificate certificate, AsymmetricKeyParameter privateKey) + public static TlsCredentialedSigner LoadSignerCredentials(TlsContext context, IList supportedSignatureAlgorithms, + short signatureAlgorithm, Certificate certificate, AsymmetricKeyParameter privateKey) { /* * TODO Note that this code fails to provide default value for the client supported @@ -228,59 +229,59 @@ public static TlsSignerCredentials LoadSignerCredentials(TlsContext context, ILi return LoadSignerCredentials(context, certificate, privateKey, signatureAndHashAlgorithm); } - public static TlsSignerCredentials LoadSignerCredentials(TlsContext context, IList supportedSignatureAlgorithms, + public static TlsCredentialedSigner LoadSignerCredentials(TlsContext context, IList supportedSignatureAlgorithms, byte signatureAlgorithm, string certResource, string keyResource) { - Certificate certificate = LoadCertificateChain(new string[] { certResource, "x509-ca.pem" }); + Certificate certificate = LoadCertificateChain(context.Crypto as BcTlsCrypto, new string[] { certResource, "x509-ca.pem" }); AsymmetricKeyParameter privateKey = LoadPrivateKeyResource(keyResource); return LoadSignerCredentials(context, supportedSignatureAlgorithms, signatureAlgorithm, certificate, privateKey); } - public static Certificate LoadCertificateChain(X509Certificate2[] certificates) + public static Certificate LoadCertificateChain(TlsCrypto crypto, X509Certificate2[] certificates) { - var chain = new Org.BouncyCastle.Asn1.X509.X509CertificateStructure[certificates.Length]; + var chain = new TlsCertificate[certificates.Length]; for (int i = 0; i < certificates.Length; i++) { - chain[i] = LoadCertificateResource(certificates[i]); + chain[i] = LoadCertificateResource(crypto, certificates[i]); } return new Certificate(chain); } - public static Certificate LoadCertificateChain(X509Certificate2 certificate) + public static Certificate LoadCertificateChain(TlsCrypto crypto, X509Certificate2 certificate) { - return LoadCertificateChain(new X509Certificate2[] { certificate }); + return LoadCertificateChain(crypto, new X509Certificate2[] { certificate }); } - public static Certificate LoadCertificateChain(string[] resources) + public static Certificate LoadCertificateChain(TlsCrypto crypto, string[] resources) { - Org.BouncyCastle.Asn1.X509.X509CertificateStructure[] - chain = new Org.BouncyCastle.Asn1.X509.X509CertificateStructure[resources.Length]; + TlsCertificate[] + chain = new TlsCertificate[resources.Length]; for (int i = 0; i < resources.Length; ++i) { - chain[i] = LoadCertificateResource(resources[i]); + chain[i] = LoadCertificateResource(crypto, resources[i]); } return new Certificate(chain); } - public static X509CertificateStructure LoadCertificateResource(X509Certificate2 certificate) + public static TlsCertificate LoadCertificateResource(TlsCrypto crypto, X509Certificate2 certificate) { if (certificate != null) { var bouncyCertificate = DotNetUtilities.FromX509Certificate(certificate); - return X509CertificateStructure.GetInstance(bouncyCertificate.GetEncoded()); + return new BcTlsCertificate(crypto as BcTlsCrypto, X509CertificateStructure.GetInstance(bouncyCertificate.GetEncoded())); } throw new Exception("'resource' doesn't specify a valid certificate"); } - public static X509CertificateStructure LoadCertificateResource(string resource) + public static TlsCertificate LoadCertificateResource(TlsCrypto crypto, string resource) { PemObject pem = LoadPemResource(resource); if (pem.Type.EndsWith("CERTIFICATE")) { - return X509CertificateStructure.GetInstance(pem.Content); + return new BcTlsCertificate(crypto as BcTlsCrypto, X509CertificateStructure.GetInstance(pem.Content)); } throw new Exception("'resource' doesn't specify a valid certificate"); } @@ -484,18 +485,18 @@ public static (Org.BouncyCastle.X509.X509Certificate certificate, AsymmetricKeyP return (certificate, subjectKeyPair.Private); } - public static (Org.BouncyCastle.Crypto.Tls.Certificate certificate, AsymmetricKeyParameter privateKey) CreateSelfSignedTlsCert() + public static (Org.BouncyCastle.Tls.Certificate certificate, AsymmetricKeyParameter privateKey) CreateSelfSignedTlsCert(TlsCrypto crypto) { - return CreateSelfSignedTlsCert("CN=localhost", "CN=root", null); + return CreateSelfSignedTlsCert(crypto, "CN=localhost", "CN=root", null); } - public static (Org.BouncyCastle.Crypto.Tls.Certificate certificate, AsymmetricKeyParameter privateKey) CreateSelfSignedTlsCert(string subjectName, string issuerName, AsymmetricKeyParameter issuerPrivateKey) + public static (Org.BouncyCastle.Tls.Certificate certificate, AsymmetricKeyParameter privateKey) CreateSelfSignedTlsCert(TlsCrypto crypto, string subjectName, string issuerName, AsymmetricKeyParameter issuerPrivateKey) { var tuple = CreateSelfSignedBouncyCastleCert(subjectName, issuerName, issuerPrivateKey); var certificate = tuple.certificate; var privateKey = tuple.privateKey; - var chain = new Org.BouncyCastle.Asn1.X509.X509CertificateStructure[] { X509CertificateStructure.GetInstance(certificate.GetEncoded()) }; - var tlsCertificate = new Org.BouncyCastle.Crypto.Tls.Certificate(chain); + var chain = new TlsCertificate[] { new BcTlsCertificate(crypto as BcTlsCrypto, X509CertificateStructure.GetInstance(certificate.GetEncoded())) }; + var tlsCertificate = new Org.BouncyCastle.Tls.Certificate(chain); return (tlsCertificate, privateKey); } @@ -505,63 +506,48 @@ public static (Org.BouncyCastle.Crypto.Tls.Certificate certificate, AsymmetricKe /// use the serialize/deserialize from pfx to get from bouncy castle to .NET Core X509 certificates. public static X509Certificate2 ConvertBouncyCert(Org.BouncyCastle.X509.X509Certificate bouncyCert, AsymmetricCipherKeyPair keyPair) { - var pkcs12Store = new Pkcs12Store(); - var certEntry = new X509CertificateEntry(bouncyCert); - - pkcs12Store.SetCertificateEntry(bouncyCert.SerialNumber.ToString(), certEntry); - pkcs12Store.SetKeyEntry(bouncyCert.SerialNumber.ToString(), - new AsymmetricKeyEntry(keyPair.Private), new[] { certEntry }); +#if !NET461 && !NETSTANDARD2_0 + var info = Org.BouncyCastle.Pkcs.PrivateKeyInfoFactory.CreatePrivateKeyInfo(keyPair.Private); - X509Certificate2 keyedCert; + //// merge into X509Certificate2 + var x509 = new X509Certificate2(bouncyCert.GetEncoded()); - using (MemoryStream pfxStream = new MemoryStream()) + var seq = (Asn1Sequence)Asn1Object.FromByteArray(info.ParsePrivateKey().GetDerEncoded()); + if (seq.Count != 9) { - pkcs12Store.Save(pfxStream, new char[] { }, new SecureRandom()); - pfxStream.Seek(0, SeekOrigin.Begin); - keyedCert = new X509Certificate2(pfxStream.ToArray(), string.Empty, X509KeyStorageFlags.Exportable); + throw new Org.BouncyCastle.OpenSsl.PemException("malformed sequence in RSA private key"); } - return keyedCert; - - //var info = Org.BouncyCastle.Pkcs.PrivateKeyInfoFactory.CreatePrivateKeyInfo(keyPair.Private); + var rsa = RsaPrivateKeyStructure.GetInstance(seq); //new RsaPrivateKeyStructure(seq); + var rsaparams = new RsaPrivateCrtKeyParameters( + rsa.Modulus, rsa.PublicExponent, rsa.PrivateExponent, rsa.Prime1, rsa.Prime2, rsa.Exponent1, rsa.Exponent2, rsa.Coefficient); - //// merge into X509Certificate2 - //var x509 = new X509Certificate2(bouncyCert.GetEncoded()); - - //var seq = (Asn1Sequence)Asn1Object.FromByteArray(info.ParsePrivateKey().GetDerEncoded()); - //if (seq.Count != 9) - //{ - // throw new Org.BouncyCastle.OpenSsl.PemException("malformed sequence in RSA private key"); - //} - - //var rsa = RsaPrivateKeyStructure.GetInstance(seq); //new RsaPrivateKeyStructure(seq); - //var rsaparams = new RsaPrivateCrtKeyParameters( - // rsa.Modulus, rsa.PublicExponent, rsa.PrivateExponent, rsa.Prime1, rsa.Prime2, rsa.Exponent1, rsa.Exponent2, rsa.Coefficient); - - //return x509.CopyWithPrivateKey(ToRSA(rsaparams)); + return x509.CopyWithPrivateKey(ToRSA(rsaparams)); - //X509Certificate2 x509 = null; +#else + X509Certificate2 x509 = null; - //using (MemoryStream ms = new MemoryStream()) - //{ - // using (StreamWriter tw = new StreamWriter(ms)) - // { - // PemWriter pw = new PemWriter(tw); - // //PemObject po = new PemObject("CERTIFICATE", bouncyCert.GetEncoded()); - // PemObject po = new PemObject("CERTIFICATE", bouncyCert.GetEncoded()); - // pw.WriteObject(po); + using (MemoryStream ms = new MemoryStream()) + { + using (StreamWriter tw = new StreamWriter(ms)) + { + PemWriter pw = new PemWriter(tw); + //PemObject po = new PemObject("CERTIFICATE", bouncyCert.GetEncoded()); + PemObject po = new PemObject("CERTIFICATE", bouncyCert.GetEncoded()); + pw.WriteObject(po); - // logger.LogDebug(Encoding.UTF8.GetString(ms.GetBuffer())); + logger.LogDebug(System.Text.Encoding.UTF8.GetString(ms.GetBuffer())); - // StreamWriter sw2 = new StreamWriter("test.cer"); - // sw2.Write(ms.GetBuffer()); - // sw2.Close(); + StreamWriter sw2 = new StreamWriter("test.cer"); + sw2.Write(ms.GetBuffer()); + sw2.Close(); - // x509 = new X509Certificate2(bouncyCert.GetEncoded()); - // } - //} + x509 = new X509Certificate2(bouncyCert.GetEncoded()); + } + } - //return x509; + return x509; +#endif } @@ -602,7 +588,7 @@ public static AsymmetricKeyParameter CreatePrivateKeyResource(string subjectName return subjectKeyPair.Private; } - #endregion +#endregion /// /// This method and the related ones have been copied from the BouncyCode DotNetUtilities diff --git a/src/net/DtlsSrtp/SrtpParameters.cs b/src/net/DtlsSrtp/SrtpParameters.cs index 8d6ed5582..b70d78244 100644 --- a/src/net/DtlsSrtp/SrtpParameters.cs +++ b/src/net/DtlsSrtp/SrtpParameters.cs @@ -18,7 +18,7 @@ //----------------------------------------------------------------------------- using System; -using Org.BouncyCastle.Crypto.Tls; +using Org.BouncyCastle.Tls; namespace SIPSorcery.Net { diff --git a/src/net/WebRTC/IRTCPeerConnection.cs b/src/net/WebRTC/IRTCPeerConnection.cs index 1919f67f9..6516e0d16 100644 --- a/src/net/WebRTC/IRTCPeerConnection.cs +++ b/src/net/WebRTC/IRTCPeerConnection.cs @@ -24,6 +24,8 @@ using System.Net; using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; +using Org.BouncyCastle.Tls; +using Org.BouncyCastle.Tls.Crypto; using SIPSorcery.Sys; namespace SIPSorcery.Net @@ -231,7 +233,7 @@ public long expires public List getFingerprints() { - return new List { DtlsUtils.Fingerprint(Certificate) }; + return new List { DtlsUtils.Fingerprint(Org.BouncyCastle.Security.DotNetUtilities.FromX509Certificate(Certificate)) }; } } @@ -287,10 +289,7 @@ public class RTCConfiguration public RTCIceTransportPolicy iceTransportPolicy; public RTCBundlePolicy bundlePolicy; public RTCRtcpMuxPolicy rtcpMuxPolicy; -#pragma warning disable CS0618 // Type or member is obsolete - public List certificates; -#pragma warning restore CS0618 // Type or member is obsolete - public List certificates2; + public List certificates2; /// /// The Bouncy Castle DTLS logic enforces the use of Extended Master diff --git a/src/net/WebRTC/RTCPeerConnection.cs b/src/net/WebRTC/RTCPeerConnection.cs index 3253746b5..938e20164 100644 --- a/src/net/WebRTC/RTCPeerConnection.cs +++ b/src/net/WebRTC/RTCPeerConnection.cs @@ -43,9 +43,10 @@ using System.Threading.Tasks; using Microsoft.Extensions.Logging; using SIPSorcery.net.RTP; -using Org.BouncyCastle.Crypto.Tls; using SIPSorcery.SIP.App; using SIPSorcery.Sys; +using Org.BouncyCastle.Tls; +using Org.BouncyCastle.Tls.Crypto.Impl.BC; namespace SIPSorcery.Net { @@ -182,8 +183,9 @@ public class RTCPeerConnection : RTPSession, IRTCPeerConnection readonly RTCDataChannelCollection dataChannels; public IReadOnlyCollection DataChannels => dataChannels; - private Org.BouncyCastle.Crypto.Tls.Certificate _dtlsCertificate; + private Org.BouncyCastle.Tls.Certificate _dtlsCertificate; private Org.BouncyCastle.Crypto.AsymmetricKeyParameter _dtlsPrivateKey; + private BcTlsCrypto _crypto; private DtlsSrtpTransport _dtlsHandle; private Task _iceGatheringTask; @@ -384,6 +386,7 @@ public RTCPeerConnection() : public RTCPeerConnection(RTCConfiguration configuration, int bindPort = 0, PortRange portRange = null, Boolean videoAsPrimary = false) : base(true, true, true, configuration?.X_BindAddress, bindPort, portRange) { + _crypto = new BcTlsCrypto(); dataChannels = new RTCDataChannelCollection(useEvenIds: () => _dtlsHandle.IsClient); if (_configuration != null && @@ -397,7 +400,7 @@ public RTCPeerConnection(RTCConfiguration configuration, int bindPort = 0, PortR { _configuration = configuration; - if (!InitializeCertificates(configuration) && !InitializeCertificates2(configuration)) + if (!InitializeCertificates2(configuration)) { logger.LogWarning("No DTLS certificate is provided in the configuration"); } @@ -415,7 +418,7 @@ public RTCPeerConnection(RTCConfiguration configuration, int bindPort = 0, PortR if (_dtlsCertificate == null) { // No certificate was provided so create a new self signed one. - (_dtlsCertificate, _dtlsPrivateKey) = DtlsUtils.CreateSelfSignedTlsCert(); + (_dtlsCertificate, _dtlsPrivateKey) = DtlsUtils.CreateSelfSignedTlsCert(_crypto); } DtlsCertificateFingerprint = DtlsUtils.Fingerprint(_dtlsCertificate); @@ -451,55 +454,6 @@ public RTCPeerConnection(RTCConfiguration configuration, int bindPort = 0, PortR _iceGatheringTask = Task.Run(_rtpIceChannel.StartGathering); } - private bool InitializeCertificates(RTCConfiguration configuration) - { - if (configuration.certificates == null || configuration.certificates.Count == 0) - { - return false; - } - - // Find the first certificate that has a usable private key. -#pragma warning disable CS0618 // Type or member is obsolete - RTCCertificate usableCert = null; -#pragma warning restore CS0618 // Type or member is obsolete - foreach (var cert in _configuration.certificates) - { - // Attempting to check that a certificate has an exportable private key. - // TODO: Does not seem to be a particularly reliable way of checking private key exportability. - if (cert.Certificate.HasPrivateKey) - { - //if (cert.Certificate.PrivateKey is RSACryptoServiceProvider) - //{ - // var rsa = cert.Certificate.PrivateKey as RSACryptoServiceProvider; - // if (!rsa.CspKeyContainerInfo.Exportable) - // { - // logger.LogWarning($"RTCPeerConnection was passed a certificate for {cert.Certificate.FriendlyName} with a non-exportable RSA private key."); - // } - // else - // { - // usableCert = cert; - // break; - // } - //} - //else - //{ - usableCert = cert; - break; - //} - } - } - - if (usableCert == null) - { - throw new ApplicationException("RTCPeerConnection was not able to find a certificate from the input configuration list with a usable private key."); - } - - _dtlsCertificate = DtlsUtils.LoadCertificateChain(usableCert.Certificate); - _dtlsPrivateKey = DtlsUtils.LoadPrivateKeyResource(usableCert.Certificate); - - return true; - } - private bool InitializeCertificates2(RTCConfiguration configuration) { if (configuration.certificates2 == null || configuration.certificates2.Count == 0) @@ -507,7 +461,7 @@ private bool InitializeCertificates2(RTCConfiguration configuration) return false; } - _dtlsCertificate = new Certificate(new [] { configuration.certificates2[0].Certificate.CertificateStructure }); + _dtlsCertificate = new Certificate(new [] { new BcTlsCertificate(_crypto, configuration.certificates2[0].Certificate.CertificateStructure) }); _dtlsPrivateKey = configuration.certificates2[0].PrivateKey; return true; @@ -554,11 +508,14 @@ private async void IceConnectionStateChange(RTCIceConnectionState iceState) logger.LogInformation($"ICE connected to remote end point {connectedEP}."); bool disableDtlsExtendedMasterSecret = _configuration != null && _configuration.X_DisableExtendedMasterSecretKey; + + + _dtlsHandle = new DtlsSrtpTransport( IceRole == IceRolesEnum.active ? - new DtlsSrtpClient(_dtlsCertificate, _dtlsPrivateKey) + new DtlsSrtpClient(_crypto, _dtlsCertificate, _dtlsPrivateKey) { ForceUseExtendedMasterSecret = !disableDtlsExtendedMasterSecret } : - (IDtlsSrtpPeer)new DtlsSrtpServer(_dtlsCertificate, _dtlsPrivateKey) + (IDtlsSrtpPeer)new DtlsSrtpServer(_crypto, _dtlsCertificate, _dtlsPrivateKey) { ForceUseExtendedMasterSecret = !disableDtlsExtendedMasterSecret } ); diff --git a/src/net/WebRTC/RTCSctpTransport.cs b/src/net/WebRTC/RTCSctpTransport.cs index cc5f113c4..61b9d1ff8 100644 --- a/src/net/WebRTC/RTCSctpTransport.cs +++ b/src/net/WebRTC/RTCSctpTransport.cs @@ -20,7 +20,7 @@ using System.Net.Sockets; using System.Threading; using Microsoft.Extensions.Logging; -using Org.BouncyCastle.Crypto.Tls; +using Org.BouncyCastle.Tls; using SIPSorcery.Sys; namespace SIPSorcery.Net diff --git a/test/integration/net/DtlsSrtp/DtlsSrtpTransportUnitTest.cs b/test/integration/net/DtlsSrtp/DtlsSrtpTransportUnitTest.cs index c575162a7..176b0df82 100644 --- a/test/integration/net/DtlsSrtp/DtlsSrtpTransportUnitTest.cs +++ b/test/integration/net/DtlsSrtp/DtlsSrtpTransportUnitTest.cs @@ -14,6 +14,7 @@ using System.Threading.Tasks; using Microsoft.Extensions.Logging; +using Org.BouncyCastle.Tls.Crypto.Impl.BC; using Xunit; namespace SIPSorcery.Net.IntegrationTests @@ -37,8 +38,9 @@ public void CreateClientInstanceUnitTest() logger.LogDebug("--> " + System.Reflection.MethodBase.GetCurrentMethod().Name); logger.BeginScope(System.Reflection.MethodBase.GetCurrentMethod().Name); - (var tlsCert, var pvtKey) = DtlsUtils.CreateSelfSignedTlsCert(); - DtlsSrtpTransport dtlsTransport = new DtlsSrtpTransport(new DtlsSrtpClient(tlsCert, pvtKey)); + var crypto = new BcTlsCrypto(); + (var tlsCert, var pvtKey) = DtlsUtils.CreateSelfSignedTlsCert(crypto); + DtlsSrtpTransport dtlsTransport = new DtlsSrtpTransport(new DtlsSrtpClient(crypto, tlsCert, pvtKey)); Assert.NotNull(dtlsTransport); } @@ -52,7 +54,7 @@ public void CreateServerInstanceUnitTest() logger.LogDebug("--> " + System.Reflection.MethodBase.GetCurrentMethod().Name); logger.BeginScope(System.Reflection.MethodBase.GetCurrentMethod().Name); - DtlsSrtpTransport dtlsTransport = new DtlsSrtpTransport(new DtlsSrtpServer()); + DtlsSrtpTransport dtlsTransport = new DtlsSrtpTransport(new DtlsSrtpServer(new BcTlsCrypto())); Assert.NotNull(dtlsTransport); } @@ -67,8 +69,8 @@ public void DoHandshakeUnitTest() logger.LogDebug("--> " + System.Reflection.MethodBase.GetCurrentMethod().Name); logger.BeginScope(System.Reflection.MethodBase.GetCurrentMethod().Name); - var dtlsClient = new DtlsSrtpClient(); - var dtlsServer = new DtlsSrtpServer(); + var dtlsClient = new DtlsSrtpClient(new BcTlsCrypto()); + var dtlsServer = new DtlsSrtpServer(new BcTlsCrypto()); DtlsSrtpTransport dtlsClientTransport = new DtlsSrtpTransport(dtlsClient); dtlsClientTransport.TimeoutMilliseconds = 5000; @@ -117,7 +119,7 @@ public async void DoHandshakeClientTimeoutUnitTest() logger.LogDebug("--> " + System.Reflection.MethodBase.GetCurrentMethod().Name); logger.BeginScope(System.Reflection.MethodBase.GetCurrentMethod().Name); - DtlsSrtpTransport dtlsClientTransport = new DtlsSrtpTransport(new DtlsSrtpClient()); + DtlsSrtpTransport dtlsClientTransport = new DtlsSrtpTransport(new DtlsSrtpClient(new BcTlsCrypto())); dtlsClientTransport.TimeoutMilliseconds = 2000; var result = await Task.Run(() => dtlsClientTransport.DoHandshake(out _)); @@ -134,7 +136,7 @@ public async void DoHandshakeServerTimeoutUnitTest() logger.LogDebug("--> " + System.Reflection.MethodBase.GetCurrentMethod().Name); logger.BeginScope(System.Reflection.MethodBase.GetCurrentMethod().Name); - DtlsSrtpTransport dtlsServerTransport = new DtlsSrtpTransport(new DtlsSrtpServer()); + DtlsSrtpTransport dtlsServerTransport = new DtlsSrtpTransport(new DtlsSrtpServer(new BcTlsCrypto())); dtlsServerTransport.TimeoutMilliseconds = 2000; var result = await Task.Run(() => dtlsServerTransport.DoHandshake(out _)); diff --git a/test/integration/net/DtlsSrtp/DtlsUtilsUnitTest.cs b/test/integration/net/DtlsSrtp/DtlsUtilsUnitTest.cs index b6ff14e3d..92e2090d6 100644 --- a/test/integration/net/DtlsSrtp/DtlsUtilsUnitTest.cs +++ b/test/integration/net/DtlsSrtp/DtlsUtilsUnitTest.cs @@ -14,6 +14,7 @@ using System.Runtime.InteropServices; using System.Security.Cryptography.X509Certificates; using Microsoft.Extensions.Logging; +using Org.BouncyCastle.Tls.Crypto.Impl.BC; using Xunit; namespace SIPSorcery.Net.IntegrationTests @@ -25,10 +26,12 @@ namespace SIPSorcery.Net.IntegrationTests public class DtlsUtilsUnitTest { private Microsoft.Extensions.Logging.ILogger logger = null; + private BcTlsCrypto crypto = null; public DtlsUtilsUnitTest(Xunit.Abstractions.ITestOutputHelper output) { logger = SIPSorcery.UnitTests.TestLogHelper.InitTestLogger(output); + crypto = new BcTlsCrypto(); } /// @@ -40,7 +43,7 @@ public void CreateSelfSignedCertifcateUnitTest() logger.LogDebug("--> " + System.Reflection.MethodBase.GetCurrentMethod().Name); logger.BeginScope(System.Reflection.MethodBase.GetCurrentMethod().Name); - (var tlsCert, var pvtKey) = DtlsUtils.CreateSelfSignedTlsCert(); + (var tlsCert, var pvtKey) = DtlsUtils.CreateSelfSignedTlsCert(crypto); logger.LogDebug(tlsCert.ToString()); @@ -57,7 +60,7 @@ public void GetCertifcateFingerprintUnitTest() logger.LogDebug("--> " + System.Reflection.MethodBase.GetCurrentMethod().Name); logger.BeginScope(System.Reflection.MethodBase.GetCurrentMethod().Name); - (var tlsCert, var pvtKey) = DtlsUtils.CreateSelfSignedTlsCert(); + (var tlsCert, var pvtKey) = DtlsUtils.CreateSelfSignedTlsCert(crypto); Assert.NotNull(tlsCert); var fingerprint = DtlsUtils.Fingerprint(tlsCert); @@ -115,7 +118,7 @@ public void BouncyCertFromCoreFxCert() Assert.NotNull(coreFxCert); Assert.NotNull(coreFxCert.PrivateKey); - string coreFxFingerprint = DtlsUtils.Fingerprint(coreFxCert).ToString(); + string coreFxFingerprint = DtlsUtils.Fingerprint(crypto, coreFxCert).ToString(); logger.LogDebug($"Core FX certificate fingerprint {coreFxFingerprint}."); var bcCert = Org.BouncyCastle.Security.DotNetUtilities.FromX509Certificate(coreFxCert); From 715075d8cfd4f9ef53e8aa6a3b8c1f6951131e0c Mon Sep 17 00:00:00 2001 From: cam Date: Wed, 28 Jun 2023 10:07:36 +1000 Subject: [PATCH 02/88] clean --- src/net/DtlsSrtp/DtlsSrtpClient.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/net/DtlsSrtp/DtlsSrtpClient.cs b/src/net/DtlsSrtp/DtlsSrtpClient.cs index e5a6504f7..f9bfbdd6f 100644 --- a/src/net/DtlsSrtp/DtlsSrtpClient.cs +++ b/src/net/DtlsSrtp/DtlsSrtpClient.cs @@ -159,10 +159,6 @@ public DtlsSrtpClient(TlsCrypto crypto, Certificate certificateChain, Org.Bouncy public DtlsSrtpClient(TlsCrypto crypto, UseSrtpData clientSrtpData) : this(crypto, null, null, clientSrtpData) { } - public override bool ShouldUseExtendedPadding() - { - return base.ShouldUseExtendedPadding(); - } public override IDictionary GetClientExtensions() { From 2efbb12ba1146364fbb5c1b8d6c321fd7568fd43 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Thu, 19 Oct 2023 20:48:14 -0700 Subject: [PATCH 03/88] bumped minimum protocol to DTLSv12 --- src/net/DtlsSrtp/DtlsSrtpServer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/net/DtlsSrtp/DtlsSrtpServer.cs b/src/net/DtlsSrtp/DtlsSrtpServer.cs index b3785cfbd..dc012cc98 100644 --- a/src/net/DtlsSrtp/DtlsSrtpServer.cs +++ b/src/net/DtlsSrtp/DtlsSrtpServer.cs @@ -193,7 +193,7 @@ protected ProtocolVersion MinimumVersion { get { - return ProtocolVersion.DTLSv10; + return ProtocolVersion.DTLSv12; } } From 26964c978b8d9335c953072bf710b1d25e13b917 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Thu, 19 Oct 2023 20:52:33 -0700 Subject: [PATCH 04/88] updated version to v7.0.0-bc2-23.10.19 --- src/SIPSorcery.csproj | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/SIPSorcery.csproj b/src/SIPSorcery.csproj index b0fb2fb5f..624306da6 100755 --- a/src/SIPSorcery.csproj +++ b/src/SIPSorcery.csproj @@ -44,7 +44,8 @@ git master SIP WebRTC VoIP RTP SDP STUN ICE SIPSorcery - -v6.0.12: Bug fixes. + -v7.0.0: upgraded BouncyCastle to v2. Set minimal supported DTLS to 1.2, and enabled 1.3. +-v6.0.12: Bug fixes. -v6.0.11: Bug fixes. -v6.0.9: Supporting custom SIP TLS certificate validation callback. SCTP improvements. -v6.0.8: Multiple audio and video stream support and other fixes. @@ -55,9 +56,9 @@ -v6.0.2: Set .net6 targetted version as stable. -v6.0.1-pre: Added .net6 target. en - 6.0.12 - 6.0.12 - 6.0.12 + 7.0.0-bc2-23.10.19 + 7.0.0 + 7.0.0 From f70ba52a53672de2c2d7b6c299a645ee492dc875 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Thu, 19 Oct 2023 21:00:14 -0700 Subject: [PATCH 05/88] dropped .NET Core 3.1 and .NET 5.0 which are out of support --- src/SIPSorcery.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SIPSorcery.csproj b/src/SIPSorcery.csproj index 624306da6..92619574a 100755 --- a/src/SIPSorcery.csproj +++ b/src/SIPSorcery.csproj @@ -25,7 +25,7 @@ - netstandard2.0;netstandard2.1;netcoreapp3.1;net461;net5.0;net6.0; + netstandard2.0;netstandard2.1;net461;net6.0; Aaron Clauson, Christophe Irles, Rafael Soares & Contributors Copyright © 2010-2023 Aaron Clauson BSD-3-Clause From a105659ab9778435d8b04dfe50caec5167c57280 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Sat, 21 Oct 2023 20:00:10 -0700 Subject: [PATCH 06/88] handle STUN messages that end abruptly --- src/SIPSorcery.csproj | 2 +- src/net/STUN/STUNAttributes/STUNAttribute.cs | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/SIPSorcery.csproj b/src/SIPSorcery.csproj index 92619574a..bea540867 100755 --- a/src/SIPSorcery.csproj +++ b/src/SIPSorcery.csproj @@ -56,7 +56,7 @@ -v6.0.2: Set .net6 targetted version as stable. -v6.0.1-pre: Added .net6 target. en - 7.0.0-bc2-23.10.19 + 7.0.0-bc2-23.10.21 7.0.0 7.0.0 diff --git a/src/net/STUN/STUNAttributes/STUNAttribute.cs b/src/net/STUN/STUNAttributes/STUNAttribute.cs index fc23979c9..255fc28f6 100644 --- a/src/net/STUN/STUNAttributes/STUNAttribute.cs +++ b/src/net/STUN/STUNAttributes/STUNAttribute.cs @@ -156,6 +156,13 @@ public static List ParseMessageAttributes(byte[] buffer, int star while (startAttIndex < endIndex) { + int remainingBytes = endIndex - startAttIndex; + if (remainingBytes < 4) + { + logger.LogWarning("The remaining number of bytes in the STUN message was less than the minimum attribute length 4. Remaining bytes: {RemainingBytes}.", remainingBytes); + break; + } + UInt16 stunAttributeType = NetConvert.ParseUInt16(buffer, startAttIndex); UInt16 stunAttributeLength = NetConvert.ParseUInt16(buffer, startAttIndex + 2); byte[] stunAttributeValue = null; From f1be4e7b85563f4aa0053aa7b56c456f21c3fc14 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Sat, 21 Oct 2023 20:26:17 -0700 Subject: [PATCH 07/88] improve handling of abruptly ending STUN messages --- src/SIPSorcery.csproj | 2 +- src/net/STUN/STUNAttributes/STUNAttribute.cs | 13 +++++++++---- src/net/STUN/STUNMessage.cs | 2 +- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/SIPSorcery.csproj b/src/SIPSorcery.csproj index bea540867..58eb63c38 100755 --- a/src/SIPSorcery.csproj +++ b/src/SIPSorcery.csproj @@ -56,7 +56,7 @@ -v6.0.2: Set .net6 targetted version as stable. -v6.0.1-pre: Added .net6 target. en - 7.0.0-bc2-23.10.21 + 7.0.0-bc2-23.10.21.2 7.0.0 7.0.0 diff --git a/src/net/STUN/STUNAttributes/STUNAttribute.cs b/src/net/STUN/STUNAttributes/STUNAttribute.cs index 255fc28f6..d1159169e 100644 --- a/src/net/STUN/STUNAttributes/STUNAttribute.cs +++ b/src/net/STUN/STUNAttributes/STUNAttribute.cs @@ -147,11 +147,15 @@ public STUNAttribute(STUNAttributeTypesEnum attributeType, ulong value) Value = NetConvert.GetBytes(value); } - public static List ParseMessageAttributes(byte[] buffer, int startIndex, int endIndex) + public static bool TryParseMessageAttributes(List attributes, byte[] buffer, int startIndex, int endIndex) { + if (buffer is null) + { + throw new ArgumentNullException(nameof(buffer)); + } + if (buffer != null && buffer.Length > startIndex && buffer.Length >= endIndex) { - List attributes = new List(); int startAttIndex = startIndex; while (startAttIndex < endIndex) @@ -220,11 +224,12 @@ public static List ParseMessageAttributes(byte[] buffer, int star startAttIndex = startAttIndex + 4 + stunAttributeLength + padding; } - return attributes; + return true; } else { - return null; + logger.LogWarning("Bad STUN attribute parse request. Start: {Start}; End: {End}; Length: {Length}.", startIndex, endIndex, buffer.Length); + return false; } } diff --git a/src/net/STUN/STUNMessage.cs b/src/net/STUN/STUNMessage.cs index 28e6aa840..cfd712d3e 100644 --- a/src/net/STUN/STUNMessage.cs +++ b/src/net/STUN/STUNMessage.cs @@ -100,7 +100,7 @@ public static STUNMessage ParseSTUNMessage(byte[] buffer, int bufferLength) if (stunMessage.Header.MessageLength > 0) { - stunMessage.Attributes = STUNAttribute.ParseMessageAttributes(buffer, STUNHeader.STUN_HEADER_LENGTH, bufferLength); + STUNAttribute.TryParseMessageAttributes(stunMessage.Attributes, buffer, STUNHeader.STUN_HEADER_LENGTH, bufferLength); } if (stunMessage.Attributes.Count > 0 && stunMessage.Attributes.Last().AttributeType == STUNAttributeTypesEnum.FingerPrint) From ac69ecbb7f0724f4100b125e7afda5f3a9624a35 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Sat, 21 Oct 2023 23:29:37 -0700 Subject: [PATCH 08/88] fixed DTLS alert handling --- src/net/DtlsSrtp/DtlsSrtpClient.cs | 8 ++++---- src/net/DtlsSrtp/DtlsSrtpServer.cs | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/net/DtlsSrtp/DtlsSrtpClient.cs b/src/net/DtlsSrtp/DtlsSrtpClient.cs index f9bfbdd6f..8e7d00d74 100644 --- a/src/net/DtlsSrtp/DtlsSrtpClient.cs +++ b/src/net/DtlsSrtp/DtlsSrtpClient.cs @@ -391,7 +391,7 @@ public override void NotifyAlertRaised(short alertLevel, short alertDescription, string alertMessage = $"{AlertLevel.GetText(alertLevel)}, {AlertDescription.GetText(alertDescription)}"; alertMessage += !string.IsNullOrEmpty(description) ? $", {description}." : "."; - if (alertDescription == AlertTypesEnum.close_notify.GetHashCode()) + if (alertDescription == (byte)AlertTypesEnum.close_notify) { logger.LogDebug($"DTLS client raised close notification: {alertMessage}"); } @@ -416,7 +416,7 @@ protected override ProtocolVersion[] GetSupportedVersions() return new ProtocolVersion[] { ProtocolVersion.DTLSv10, - ProtocolVersion.DTLSv12 + ProtocolVersion.DTLSv12, }; } @@ -427,12 +427,12 @@ public override void NotifyAlertReceived(short alertLevel, short alertDescriptio AlertLevelsEnum level = AlertLevelsEnum.Warning; AlertTypesEnum alertType = AlertTypesEnum.unknown; - if (Enum.IsDefined(typeof(AlertLevelsEnum), alertLevel)) + if (Enum.IsDefined(typeof(AlertLevelsEnum), checked((byte)alertLevel))) { level = (AlertLevelsEnum)alertLevel; } - if (Enum.IsDefined(typeof(AlertTypesEnum), alertDescription)) + if (Enum.IsDefined(typeof(AlertTypesEnum), checked((byte)alertDescription))) { alertType = (AlertTypesEnum)alertDescription; } diff --git a/src/net/DtlsSrtp/DtlsSrtpServer.cs b/src/net/DtlsSrtp/DtlsSrtpServer.cs index dc012cc98..53c33ee3b 100644 --- a/src/net/DtlsSrtp/DtlsSrtpServer.cs +++ b/src/net/DtlsSrtp/DtlsSrtpServer.cs @@ -518,12 +518,12 @@ public override void NotifyAlertReceived(short alertLevel, short alertDescriptio AlertLevelsEnum level = AlertLevelsEnum.Warning; AlertTypesEnum alertType = AlertTypesEnum.unknown; - if (Enum.IsDefined(typeof(AlertLevelsEnum), alertLevel)) + if (Enum.IsDefined(typeof(AlertLevelsEnum), checked((byte)alertLevel))) { level = (AlertLevelsEnum)alertLevel; } - if (Enum.IsDefined(typeof(AlertTypesEnum), alertDescription)) + if (Enum.IsDefined(typeof(AlertTypesEnum), checked((byte)alertDescription))) { alertType = (AlertTypesEnum)alertDescription; } From 0c6aff560cd5af0ae64b3ff50392c59eb48a2055 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Sat, 21 Oct 2023 23:31:43 -0700 Subject: [PATCH 09/88] Receive to Span was not really implemented --- src/net/DtlsSrtp/DtlsSrtpTransport.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/net/DtlsSrtp/DtlsSrtpTransport.cs b/src/net/DtlsSrtp/DtlsSrtpTransport.cs index 72f476948..94e593246 100644 --- a/src/net/DtlsSrtp/DtlsSrtpTransport.cs +++ b/src/net/DtlsSrtp/DtlsSrtpTransport.cs @@ -515,7 +515,7 @@ private int Read(byte[] buffer, int offset, int count, int timeout) #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || NET6_0_OR_GREATER public int Receive(Span buf, int waitMillis) { - // TODO + throw new NotImplementedException(); return Receive(buf.ToArray(), 0, buf.Length, waitMillis); } #endif From 04b9faf0cdd5b32bd64892f1de070a3bfa3e9a6c Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Sat, 21 Oct 2023 23:31:56 -0700 Subject: [PATCH 10/88] 23.10.21.4 --- src/SIPSorcery.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SIPSorcery.csproj b/src/SIPSorcery.csproj index 58eb63c38..04baada44 100755 --- a/src/SIPSorcery.csproj +++ b/src/SIPSorcery.csproj @@ -56,7 +56,7 @@ -v6.0.2: Set .net6 targetted version as stable. -v6.0.1-pre: Added .net6 target. en - 7.0.0-bc2-23.10.21.2 + 7.0.0-bc2-23.10.21.4 7.0.0 7.0.0 From 6608c1debf09b48960a9b744b429ed489295d650 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Sat, 21 Oct 2023 23:48:49 -0700 Subject: [PATCH 11/88] handle chunks larger than destination buffer --- src/SIPSorcery.csproj | 2 +- src/net/DtlsSrtp/DtlsSrtpTransport.cs | 28 ++++++++++++++++++++++++--- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/src/SIPSorcery.csproj b/src/SIPSorcery.csproj index 04baada44..23e86f5f9 100755 --- a/src/SIPSorcery.csproj +++ b/src/SIPSorcery.csproj @@ -56,7 +56,7 @@ -v6.0.2: Set .net6 targetted version as stable. -v6.0.1-pre: Added .net6 target. en - 7.0.0-bc2-23.10.21.4 + 7.0.0-bc2-23.10.21.5 7.0.0 7.0.0 diff --git a/src/net/DtlsSrtp/DtlsSrtpTransport.cs b/src/net/DtlsSrtp/DtlsSrtpTransport.cs index 94e593246..de9b68c87 100644 --- a/src/net/DtlsSrtp/DtlsSrtpTransport.cs +++ b/src/net/DtlsSrtp/DtlsSrtpTransport.cs @@ -491,19 +491,41 @@ public void WriteToRecvStream(byte[] buf) } } + private byte[] _partialChunk = null; + private int _partialChunkOffset = 0; private int Read(byte[] buffer, int offset, int count, int timeout) { try { - if(_isClosed) + if (_isClosed) { throw new System.Net.Sockets.SocketException((int)System.Net.Sockets.SocketError.NotConnected); //return DTLS_RECEIVE_ERROR_CODE; } + else if (_partialChunk != null) + { + int bytesToCopy = Math.Min(count, _partialChunk.Length - _partialChunkOffset); + Buffer.BlockCopy(_partialChunk, _partialChunkOffset, buffer, offset, bytesToCopy); + _partialChunkOffset += bytesToCopy; + + if (_partialChunkOffset == _partialChunk.Length) + { + _partialChunk = null; + _partialChunkOffset = 0; + } + + return bytesToCopy; + } else if (_chunks.TryTake(out var item, timeout)) { - Buffer.BlockCopy(item, 0, buffer, 0, item.Length); - return item.Length; + int bytesToCopy = Math.Min(count, item.Length); + Buffer.BlockCopy(item, 0, buffer, offset, bytesToCopy); + if (bytesToCopy < item.Length) + { + _partialChunk = item; + _partialChunkOffset = bytesToCopy; + } + return bytesToCopy; } } catch (ObjectDisposedException) { } From 0f9f9313de2a103f91b865e5fcedcebd2e559114 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Sun, 22 Oct 2023 15:23:55 -0700 Subject: [PATCH 12/88] added data channel bandwidth test --- .../DataChannelBandwidth.csproj | 20 ++ .../DataChannelBandwidth.sln | 31 ++ .../DataChannelBandwidth/DataChannelStream.cs | 315 ++++++++++++++++++ .../DataChannelBandwidth/Program.cs | 79 +++++ 4 files changed, 445 insertions(+) create mode 100644 examples/WebRTCScenarios/DataChannelBandwidth/DataChannelBandwidth.csproj create mode 100644 examples/WebRTCScenarios/DataChannelBandwidth/DataChannelBandwidth.sln create mode 100644 examples/WebRTCScenarios/DataChannelBandwidth/DataChannelStream.cs create mode 100644 examples/WebRTCScenarios/DataChannelBandwidth/Program.cs diff --git a/examples/WebRTCScenarios/DataChannelBandwidth/DataChannelBandwidth.csproj b/examples/WebRTCScenarios/DataChannelBandwidth/DataChannelBandwidth.csproj new file mode 100644 index 000000000..2a8b338e1 --- /dev/null +++ b/examples/WebRTCScenarios/DataChannelBandwidth/DataChannelBandwidth.csproj @@ -0,0 +1,20 @@ + + + + Exe + net6.0 + 10 + enable + enable + + + + + + + + + + + + diff --git a/examples/WebRTCScenarios/DataChannelBandwidth/DataChannelBandwidth.sln b/examples/WebRTCScenarios/DataChannelBandwidth/DataChannelBandwidth.sln new file mode 100644 index 000000000..624d48cb6 --- /dev/null +++ b/examples/WebRTCScenarios/DataChannelBandwidth/DataChannelBandwidth.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.7.34202.233 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DataChannelBandwidth", "DataChannelBandwidth.csproj", "{4D400CB1-0889-4774-A676-66F6BFEE1764}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SIPSorcery", "..\..\..\src\SIPSorcery.csproj", "{B22DEBFD-1013-45D2-A8B9-6624898D33FC}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {4D400CB1-0889-4774-A676-66F6BFEE1764}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4D400CB1-0889-4774-A676-66F6BFEE1764}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4D400CB1-0889-4774-A676-66F6BFEE1764}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4D400CB1-0889-4774-A676-66F6BFEE1764}.Release|Any CPU.Build.0 = Release|Any CPU + {B22DEBFD-1013-45D2-A8B9-6624898D33FC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B22DEBFD-1013-45D2-A8B9-6624898D33FC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B22DEBFD-1013-45D2-A8B9-6624898D33FC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B22DEBFD-1013-45D2-A8B9-6624898D33FC}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {3661FD7C-0C07-4840-AAC1-AB386B5FAD96} + EndGlobalSection +EndGlobal diff --git a/examples/WebRTCScenarios/DataChannelBandwidth/DataChannelStream.cs b/examples/WebRTCScenarios/DataChannelBandwidth/DataChannelStream.cs new file mode 100644 index 000000000..7e423ba90 --- /dev/null +++ b/examples/WebRTCScenarios/DataChannelBandwidth/DataChannelStream.cs @@ -0,0 +1,315 @@ +namespace DataChannelBandwidth; + +using Microsoft.Extensions.Logging; + +using SIPSorcery.Net; + +#pragma warning disable IDE0011 // Add braces +class DataChannelStream : Stream +{ + readonly RTCDataChannel channel; + int currentMessageOffset; + byte[] message = Array.Empty(); + readonly CancellationTokenSource closed = new(); + readonly SemaphoreSlim messageNeeded = new(0, 1); + readonly SemaphoreSlim messageAvailable = new(0, maxCount: 1); + readonly SemaphoreSlim writeLock = new(1, 1); + readonly SemaphoreSlim readLock = new(1, 1); + readonly ILogger? log; + readonly object sync = new(); + + readonly IRTCPeerConnection? ownedConnection; + public IRTCPeerConnection? OwnedConnection + { + get => ownedConnection; + init + { + if (ownedConnection is not null) + ownedConnection.onconnectionstatechange -= ConnectionStateChanged; + ownedConnection = value; + if (value is not null) + value.onconnectionstatechange += ConnectionStateChanged; + } + } + + public int MaxSendBytes { get; init; } = 260_000; + public long TotalSent => totalSent; + public long TotalReceived => totalRead; + + public DataChannelStream(RTCDataChannel channel, ILogger? log = null) + { + this.log = log; + this.channel = channel ?? throw new ArgumentNullException(nameof(channel)); + this.channel.onmessage += OnMessage; + this.channel.onerror += OnChannelError; + this.channel.onclose += OnChannelClosed; + } + + void OnChannelClosed() + { + log?.LogInformation("channel closed"); + Unsubscribe(); + closed.Cancel(); + } + + void OnChannelError(string error) + { + log?.LogError("channel error: {Error}", error); + Unsubscribe(); + closed.Cancel(); + } + + void ConnectionStateChanged(RTCPeerConnectionState state) + { + log?.LogInformation("connection state changed to {State}", state); + switch (state) + { + case RTCPeerConnectionState.closed + or RTCPeerConnectionState.disconnected + or RTCPeerConnectionState.failed: + Unsubscribe(); + closed.Cancel(); + break; + } + } + + int messages; + void OnMessage(RTCDataChannel _, DataChannelPayloadProtocols protocol, byte[] data) + { + int seq = Interlocked.Increment(ref messages); + log?.LogDebug("{Seq} received", seq); + try + { + messageNeeded.Wait(); + lock (sync) + { + message = data; + currentMessageOffset = 0; + } + messageAvailable.Release(); + } + catch (ObjectDisposedException) { } + } + + public override int Read(byte[] buffer, int offset, int count) + { + if (buffer is null) + throw new ArgumentNullException(nameof(buffer)); + if (offset < 0) + throw new ArgumentOutOfRangeException(nameof(offset)); + if (count < 0) + throw new ArgumentOutOfRangeException(nameof(count)); + if (checked(offset + count) > buffer.Length) + throw new ArgumentException( + "The sum of offset and count is larger than the buffer length."); + + return Read(buffer.AsSpan().Slice(offset, count)); + } + + public override int Read(Span buffer) + { + readLock.Wait(closed.Token); + try + { + if (buffer.Length == 0) return 0; + + bool wait; + do + { + lock (sync) + { + wait = MessageNeeded(); + if (wait) + messageNeeded.Release(); + } + + if (wait) + { + try + { + messageAvailable.Wait(closed.Token); + } + catch (OperationCanceledException) + { + return 0; + } + } + } while (wait); + + lock (sync) + { + int remaining = message.Length - currentMessageOffset; + int toCopy = Math.Min(remaining, buffer.Length); + message.AsSpan(currentMessageOffset, toCopy).CopyTo(buffer); + currentMessageOffset += toCopy; + Interlocked.Add(ref totalRead, toCopy); + return toCopy; + } + } + finally + { + readLock.Release(); + } + } + + public override async ValueTask ReadAsync(Memory buffer, + CancellationToken cancellationToken = default) + { + await readLock.WaitAsync(cancellationToken).ConfigureAwait(false); + + try + { + if (buffer.Length == 0) return 0; + + using var cancel = + CancellationTokenSource.CreateLinkedTokenSource( + cancellationToken, closed.Token); + + bool wait; + do + { + lock (sync) + { + wait = MessageNeeded(); + + if (wait) + messageNeeded.Release(); + } + + if (wait) + { + try + { + await messageAvailable.WaitAsync(cancel.Token).ConfigureAwait(false); + } + catch (OperationCanceledException) when (!cancellationToken + .IsCancellationRequested) + { + return 0; + } + } + } while (wait); + + lock (sync) + { + int remaining = message.Length - currentMessageOffset; + int toCopy = Math.Min(remaining, buffer.Length); + message.AsSpan(currentMessageOffset, toCopy).CopyTo(buffer.Span); + currentMessageOffset += toCopy; + Interlocked.Add(ref totalRead, toCopy); + return toCopy; + } + } + finally + { + readLock.Release(); + } + } + long totalRead; + + bool MessageNeeded() => message.Length - currentMessageOffset == 0; + + public override void Write(byte[] buffer, int offset, int count) + { + if (buffer is null) + throw new ArgumentNullException(nameof(buffer)); + if (offset < 0) + throw new ArgumentOutOfRangeException(nameof(offset)); + if (count < 0) + throw new ArgumentOutOfRangeException(nameof(count)); + if (checked(offset + count) > buffer.Length) + throw new ArgumentException( + "The sum of offset and count is larger than the buffer length."); + + Write(buffer.AsSpan().Slice(offset, count)); + } + + public override async ValueTask WriteAsync(ReadOnlyMemory buffer, + CancellationToken cancellationToken = default) + { + await writeLock.WaitAsync(cancellationToken).ConfigureAwait(false); + try + { + while (buffer.Length > MaxSendBytes) + { + cancellationToken.ThrowIfCancellationRequested(); + var packet = buffer[..MaxSendBytes]; + await Task.Run(() => Send(packet.Span), cancellationToken) + .ConfigureAwait(false); + buffer = buffer[MaxSendBytes..]; + } + cancellationToken.ThrowIfCancellationRequested(); + if (buffer.Length > 0) + Send(buffer.Span); + } + finally + { + writeLock.Release(); + } + } + + long totalSent; + void Send(ReadOnlySpan buffer) + { + channel.send(buffer.ToArray()); + Interlocked.Add(ref totalSent, buffer.Length); + } + + public override void Write(ReadOnlySpan buffer) + { + writeLock.Wait(closed.Token); + try + { + while (buffer.Length > MaxSendBytes) + { + var packet = buffer[..MaxSendBytes]; + Send(packet); + buffer = buffer[MaxSendBytes..]; + } + if (buffer.Length > 0) + Send(buffer); + } + finally + { + writeLock.Release(); + } + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + if (!disposing) return; + + Unsubscribe(); + channel.close(); + (OwnedConnection as IDisposable)?.Dispose(); + messageNeeded.Dispose(); + messageAvailable.Dispose(); + } + + void Unsubscribe() + { + channel.onmessage -= OnMessage; + channel.onclose -= OnChannelClosed; + if (ownedConnection is { } connection) + connection.onconnectionstatechange -= ConnectionStateChanged; + } + + public override void Flush() { } + + public override bool CanRead => true; + public override bool CanSeek => false; + public override bool CanWrite => true; + + public override long Length => throw new NotSupportedException(); + + public override long Position + { + get => throw new NotSupportedException(); + set => throw new NotSupportedException(); + } + + public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); + public override void SetLength(long value) => throw new NotSupportedException(); +} \ No newline at end of file diff --git a/examples/WebRTCScenarios/DataChannelBandwidth/Program.cs b/examples/WebRTCScenarios/DataChannelBandwidth/Program.cs new file mode 100644 index 000000000..7f9a4784c --- /dev/null +++ b/examples/WebRTCScenarios/DataChannelBandwidth/Program.cs @@ -0,0 +1,79 @@ +using System.Diagnostics; +using DataChannelBandwidth; +using Microsoft.Extensions.Logging; +using SIPSorcery; +using SIPSorcery.Net; + +ILoggerFactory logs = LoggerFactory.Create( + builder => builder.AddFilter(level => level >= LogLevel.Error).AddConsole()); + +LogFactory.Set(logs); + +var server = new RTCPeerConnection(); +var client = new RTCPeerConnection(); + +long clientReceived = 0; +long serverReceived = 0; +double clientRate = 0; +double serverRate = 0; + +client.ondatachannel += ch => +{ + new Thread(() => SendRecv(ch, ref clientReceived, ref clientRate)) { + IsBackground = true, + Name = "client", + }.Start(); +}; + + +var serverCH = await server.createDataChannel("test"); +serverCH.onopen += () => +{ + new Thread(() => SendRecv(serverCH, ref serverReceived, ref serverRate)) + { + IsBackground = true, + Name = "server", + }.Start(); +}; +var offer = server.createOffer(); +await server.setLocalDescription(offer); +client.setRemoteDescription(offer); +var answer = client.createAnswer(); +server.setRemoteDescription(answer); +await client.setLocalDescription(answer); + +while (true) +{ + long recvC = Interlocked.Read(ref clientReceived); + long recvS = Interlocked.Read(ref serverReceived); + double rateC = Volatile.Read(ref clientRate); + double rateS = Volatile.Read(ref serverRate); + Console.WriteLine($"client: {rateC:F1}MB/s server: {rateS:F1}MB/s", recvC, recvS); + Thread.Sleep(1000); +} + +void Send(RTCDataChannel channel) +{ + byte[] sample = new byte[180_000]; + while (true) + { + channel.send(sample); + } +} + +void SendRecv(RTCDataChannel channel, ref long received, ref double rate) +{ + var stream = new DataChannelStream(channel); + var sender = new Thread(() => Send(channel)) + { + IsBackground = true, + }; + sender.Start(); + var stopwatch = Stopwatch.StartNew(); + byte[] buffer = new byte[200_000]; + while (true) + { + Interlocked.Add(ref received, stream.Read(buffer)); + Interlocked.Exchange(ref rate, received / 1024 / 1024 / stopwatch.Elapsed.TotalSeconds); + } +} \ No newline at end of file From 210fc7169a27695498ef6755f2999602326070c4 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Mon, 23 Oct 2023 17:00:00 -0700 Subject: [PATCH 13/88] limit the size of SctpDataSender send queue --- src/SIPSorcery.csproj | 2 +- src/net/SCTP/SctpDataSender.cs | 23 ++++++++++++++++++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/SIPSorcery.csproj b/src/SIPSorcery.csproj index 23e86f5f9..5a503d34f 100755 --- a/src/SIPSorcery.csproj +++ b/src/SIPSorcery.csproj @@ -56,7 +56,7 @@ -v6.0.2: Set .net6 targetted version as stable. -v6.0.1-pre: Added .net6 target. en - 7.0.0-bc2-23.10.21.5 + 7.0.0-bc2-23.10.23.1 7.0.0 7.0.0 diff --git a/src/net/SCTP/SctpDataSender.cs b/src/net/SCTP/SctpDataSender.cs index 37ce5e823..47dc0d573 100644 --- a/src/net/SCTP/SctpDataSender.cs +++ b/src/net/SCTP/SctpDataSender.cs @@ -80,6 +80,7 @@ public class SctpDataSender private bool _inFastRecoveryMode; private uint _fastRecoveryExitPoint; private ManualResetEventSlim _senderMre = new ManualResetEventSlim(); + private readonly ManualResetEventSlim _queueSpaceAvailable = new ManualResetEventSlim(initialState: true); /// /// Congestion control window (cwnd, in bytes), which is adjusted by @@ -141,6 +142,7 @@ public class SctpDataSender /// private Dictionary _streamSeqnums = new Dictionary(); + public int MaxSendQueueCount => 128; /// /// Queue to hold SCTP frames that are waiting to be sent to the remote peer. /// @@ -308,6 +310,12 @@ public void GotSack(SctpSackChunk sack) /// The byte data to send. public void SendData(ushort streamID, uint ppid, byte[] data) { + // combined spin/lock wait + while (!_queueSpaceAvailable.Wait(TimeSpan.FromMilliseconds(10)) && _sendQueue.Count > MaxSendQueueCount) + { + + } + lock (_sendQueue) { ushort seqnum = 0; @@ -349,6 +357,11 @@ public void SendData(ushort streamID, uint ppid, byte[] data) _sendQueue.Enqueue(dataChunk); + if (_sendQueue.Count > MaxSendQueueCount) + { + _queueSpaceAvailable.Reset(); + } + TSN = (TSN == UInt32.MaxValue) ? 0 : TSN + 1; } @@ -365,7 +378,11 @@ public void StartSending() if (!_isStarted) { _isStarted = true; - var sendThread = new Thread(DoSend); + var sendThread = new Thread(DoSend) + { + IsBackground = true, + Name = $"{nameof(SctpDataSender)}-{_associationID}", + }; sendThread.IsBackground = true; sendThread.Start(); } @@ -617,6 +634,10 @@ private void DoSend(object state) _unconfirmedChunks.TryAdd(dataChunk.TSN, dataChunk); _sendDataChunk(dataChunk); + if (_sendQueue.Count < MaxSendQueueCount) + { + _queueSpaceAvailable.Set(); + } chunksSent++; } } From 7dd537518f5b2f160e97039cd014d61328e295a9 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Thu, 28 Dec 2023 17:07:36 -0800 Subject: [PATCH 14/88] assume server supports client ECC workaround for https://github.com/sipsorcery-org/sipsorcery/issues/1036 --- src/SIPSorcery.csproj | 2 +- src/net/DtlsSrtp/DtlsSrtpServer.cs | 17 ++++++++++++++--- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/SIPSorcery.csproj b/src/SIPSorcery.csproj index 5a503d34f..9e5ee20b2 100755 --- a/src/SIPSorcery.csproj +++ b/src/SIPSorcery.csproj @@ -56,7 +56,7 @@ -v6.0.2: Set .net6 targetted version as stable. -v6.0.1-pre: Added .net6 target. en - 7.0.0-bc2-23.10.23.1 + 7.0.0-bc2-23.12.28.1 7.0.0 7.0.0 diff --git a/src/net/DtlsSrtp/DtlsSrtpServer.cs b/src/net/DtlsSrtp/DtlsSrtpServer.cs index 53c33ee3b..8ff76b18a 100644 --- a/src/net/DtlsSrtp/DtlsSrtpServer.cs +++ b/src/net/DtlsSrtp/DtlsSrtpServer.cs @@ -212,20 +212,31 @@ public override int GetSelectedCipherSuite() * formats supported by the client [...]. */ - bool eccCipherSuitesEnabled = false;// SupportsClientEccCapabilities(this.mNamedCurves, this.mClientECPointFormats); - int[] cipherSuites = GetCipherSuites(); for (int i = 0; i < cipherSuites.Length; ++i) { int cipherSuite = cipherSuites[i]; if (Arrays.Contains(this.m_offeredCipherSuites, cipherSuite) - && (eccCipherSuitesEnabled || !TlsEccUtilities.IsEccCipherSuite(cipherSuite)) + && !TlsEccUtilities.IsEccCipherSuite(cipherSuite) && TlsUtilities.IsValidVersionForCipherSuite(cipherSuite, GetServerVersion())) { return this.m_selectedCipherSuite = cipherSuite; } } + + for (int i = 0; i < cipherSuites.Length; ++i) + { + int cipherSuite = cipherSuites[i]; + + if (Arrays.Contains(this.m_offeredCipherSuites, cipherSuite) + && TlsEccUtilities.IsEccCipherSuite(cipherSuite) + && TlsUtilities.IsValidVersionForCipherSuite(cipherSuite, GetServerVersion())) + { + return this.m_selectedCipherSuite = cipherSuite; + } + } + throw new TlsFatalAlert(AlertDescription.handshake_failure); } From 1c416fbde85d9d9edd52d0c98ac0cc3ba34a7a2c Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Thu, 28 Dec 2023 17:31:17 -0800 Subject: [PATCH 15/88] 23.12.28.2 for some reason .1 did not include the changes o-O --- src/SIPSorcery.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SIPSorcery.csproj b/src/SIPSorcery.csproj index 9e5ee20b2..03f315e2c 100755 --- a/src/SIPSorcery.csproj +++ b/src/SIPSorcery.csproj @@ -56,7 +56,7 @@ -v6.0.2: Set .net6 targetted version as stable. -v6.0.1-pre: Added .net6 target. en - 7.0.0-bc2-23.12.28.1 + 7.0.0-bc2-23.12.28.2 7.0.0 7.0.0 From 49140a892c8979ae5149b5ce4ed6b7e1b25b212a Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Tue, 9 Jan 2024 11:25:28 -0800 Subject: [PATCH 16/88] improved bandwidth test logging --- .../DataChannelBandwidth/Program.cs | 36 ++++++++++++------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/examples/WebRTCScenarios/DataChannelBandwidth/Program.cs b/examples/WebRTCScenarios/DataChannelBandwidth/Program.cs index 7f9a4784c..66c09c616 100644 --- a/examples/WebRTCScenarios/DataChannelBandwidth/Program.cs +++ b/examples/WebRTCScenarios/DataChannelBandwidth/Program.cs @@ -1,25 +1,34 @@ using System.Diagnostics; + using DataChannelBandwidth; + using Microsoft.Extensions.Logging; + using SIPSorcery; using SIPSorcery.Net; ILoggerFactory logs = LoggerFactory.Create( - builder => builder.AddFilter(level => level >= LogLevel.Error).AddConsole()); + builder => builder.AddFilter(level => level >= LogLevel.Warning).AddConsole()); LogFactory.Set(logs); -var server = new RTCPeerConnection(); -var client = new RTCPeerConnection(); +var rtcConfig = new RTCConfiguration +{ + iceServers = new List { + new() { urls = "stun:stun.l.google.com:19302" }, + }, +}; + +var server = new RTCPeerConnection(rtcConfig); +var client = new RTCPeerConnection(rtcConfig); long clientReceived = 0; long serverReceived = 0; -double clientRate = 0; -double serverRate = 0; client.ondatachannel += ch => { - new Thread(() => SendRecv(ch, ref clientReceived, ref clientRate)) { + new Thread(() => SendRecv(ch, ref clientReceived)) + { IsBackground = true, Name = "client", }.Start(); @@ -29,7 +38,7 @@ var serverCH = await server.createDataChannel("test"); serverCH.onopen += () => { - new Thread(() => SendRecv(serverCH, ref serverReceived, ref serverRate)) + new Thread(() => SendRecv(serverCH, ref serverReceived)) { IsBackground = true, Name = "server", @@ -42,13 +51,15 @@ server.setRemoteDescription(answer); await client.setLocalDescription(answer); +var stopwatch = Stopwatch.StartNew(); + while (true) { long recvC = Interlocked.Read(ref clientReceived); long recvS = Interlocked.Read(ref serverReceived); - double rateC = Volatile.Read(ref clientRate); - double rateS = Volatile.Read(ref serverRate); - Console.WriteLine($"client: {rateC:F1}MB/s server: {rateS:F1}MB/s", recvC, recvS); + double rateC = recvC / 1024 / 1024 / stopwatch.Elapsed.TotalSeconds; + double rateS = recvS / 1024 / 1024 / stopwatch.Elapsed.TotalSeconds; + Console.Title = $"client: {rateC:F1}MB/s server: {rateS:F1}MB/s"; Thread.Sleep(1000); } @@ -61,7 +72,7 @@ void Send(RTCDataChannel channel) } } -void SendRecv(RTCDataChannel channel, ref long received, ref double rate) +void SendRecv(RTCDataChannel channel, ref long received) { var stream = new DataChannelStream(channel); var sender = new Thread(() => Send(channel)) @@ -69,11 +80,10 @@ void SendRecv(RTCDataChannel channel, ref long received, ref double rate) IsBackground = true, }; sender.Start(); - var stopwatch = Stopwatch.StartNew(); + byte[] buffer = new byte[200_000]; while (true) { Interlocked.Add(ref received, stream.Read(buffer)); - Interlocked.Exchange(ref rate, received / 1024 / 1024 / stopwatch.Elapsed.TotalSeconds); } } \ No newline at end of file From a4664f55c8bf5f439401d672ca988d37b0f6b3b3 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Wed, 10 Jan 2024 13:40:27 -0800 Subject: [PATCH 17/88] rewrote most of the processing to use spans --- src/SIPSorcery.csproj | 4 +- src/net/DtlsSrtp/DtlsSrtpTransport.cs | 64 +++++++++------ src/net/DtlsSrtp/SrtpHandler.cs | 24 +++--- .../DtlsSrtp/Transform/IPackerTransformer.cs | 10 ++- src/net/DtlsSrtp/Transform/RawPacket.cs | 9 ++- .../DtlsSrtp/Transform/SrtcpTransformer.cs | 13 +++- src/net/DtlsSrtp/Transform/SrtpTransformer.cs | 13 +++- src/net/ICE/RtpIceChannel.cs | 6 +- src/net/RTCP/RTCPBye.cs | 15 +--- src/net/RTCP/RTCPCompoundPacket.cs | 4 +- src/net/RTCP/RTCPFeedback.cs | 38 ++------- src/net/RTCP/RTCPHeader.cs | 16 +--- src/net/RTCP/RTCPReceiverReport.cs | 15 +--- src/net/RTCP/RTCPSdesReport.cs | 14 +--- src/net/RTCP/RTCPSenderReport.cs | 26 ++----- src/net/RTCP/ReceptionReport.cs | 31 +++----- src/net/RTP/MediaStream.cs | 18 +++-- src/net/RTP/RTPChannel.cs | 28 ++++--- src/net/RTP/RTPHeader.cs | 43 +++-------- src/net/RTP/RTPPacket.cs | 4 +- src/net/RTP/RTPSession.cs | 31 ++++---- src/net/SCTP/Chunks/SctpChunk.cs | 32 ++++---- src/net/SCTP/Chunks/SctpDataChunk.cs | 12 ++- src/net/SCTP/Chunks/SctpErrorCauses.cs | 49 +++++------- src/net/SCTP/Chunks/SctpErrorChunk.cs | 11 +-- src/net/SCTP/Chunks/SctpInitChunk.cs | 11 +-- src/net/SCTP/Chunks/SctpSackChunk.cs | 5 +- src/net/SCTP/Chunks/SctpShutdownChunk.cs | 5 +- src/net/SCTP/Chunks/SctpTlvChunkParameter.cs | 13 ++-- src/net/SCTP/SctpAssociation.cs | 10 +-- src/net/SCTP/SctpDataSender.cs | 7 +- src/net/SCTP/SctpHeader.cs | 22 ++++-- src/net/SCTP/SctpPacket.cs | 77 ++++++++++++++----- src/net/SCTP/SctpTransport.cs | 27 +++++-- src/net/SCTP/SctpUdpTransport.cs | 20 +++-- src/net/STUN/STUNAttributes/STUNAttribute.cs | 11 +-- src/net/STUN/STUNHeader.cs | 21 ++--- src/net/STUN/STUNMessage.cs | 6 +- src/net/WebRTC/RTCPeerConnection.cs | 6 +- src/net/WebRTC/RTCSctpTransport.cs | 29 ++++--- src/sys/CRC32.cs | 14 ++-- src/sys/MemoryExtensions.cs | 15 ++++ src/sys/Net/NetConvert.cs | 65 ++++++++++++++++ src/sys/Once.cs | 36 +++++++++ src/sys/TypeExtensions.cs | 9 ++- 45 files changed, 535 insertions(+), 404 deletions(-) create mode 100644 src/sys/MemoryExtensions.cs create mode 100644 src/sys/Once.cs diff --git a/src/SIPSorcery.csproj b/src/SIPSorcery.csproj index 03f315e2c..54b0a0145 100755 --- a/src/SIPSorcery.csproj +++ b/src/SIPSorcery.csproj @@ -25,7 +25,9 @@ - netstandard2.0;netstandard2.1;net461;net6.0; + netstandard2.0;net461;net6.0; + 12 + true Aaron Clauson, Christophe Irles, Rafael Soares & Contributors Copyright © 2010-2023 Aaron Clauson BSD-3-Clause diff --git a/src/net/DtlsSrtp/DtlsSrtpTransport.cs b/src/net/DtlsSrtp/DtlsSrtpTransport.cs index de9b68c87..a0ba42233 100644 --- a/src/net/DtlsSrtp/DtlsSrtpTransport.cs +++ b/src/net/DtlsSrtp/DtlsSrtpTransport.cs @@ -22,6 +22,7 @@ using Org.BouncyCastle.Tls; using Org.BouncyCastle.Security; using SIPSorcery.Sys; +using System.Buffers; namespace SIPSorcery.Net { @@ -47,7 +48,7 @@ public class DtlsSrtpTransport : DatagramTransport, IDisposable IDtlsSrtpPeer connection = null; /// The collection of chunks to be written. - private BlockingCollection _chunks = new BlockingCollection(new ConcurrentQueue()); + private BlockingCollection> _chunks = new(new ConcurrentQueue>()); public DtlsTransport Transport { get; private set; } @@ -371,7 +372,7 @@ protected IPacketTransformer GenerateTransformer(bool isClient, bool isRtp) } } - public byte[] UnprotectRTP(byte[] packet, int offset, int length) + public byte[] UnprotectRTP(Span packet, int offset, int length) { lock (this.srtpDecoder) { @@ -379,7 +380,7 @@ public byte[] UnprotectRTP(byte[] packet, int offset, int length) } } - public int UnprotectRTP(byte[] payload, int length, out int outLength) + public int UnprotectRTP(Span payload, int length, out int outLength) { var result = UnprotectRTP(payload, 0, length); @@ -389,13 +390,13 @@ public int UnprotectRTP(byte[] payload, int length, out int outLength) return -1; } - System.Buffer.BlockCopy(result, 0, payload, 0, result.Length); + result.AsSpan().CopyTo(payload); outLength = result.Length; return 0; //No Errors } - public byte[] ProtectRTP(byte[] packet, int offset, int length) + public byte[] ProtectRTP(Span packet, int offset, int length) { lock (this.srtpEncoder) { @@ -403,7 +404,7 @@ public byte[] ProtectRTP(byte[] packet, int offset, int length) } } - public int ProtectRTP(byte[] payload, int length, out int outLength) + public int ProtectRTP(Span payload, int length, out int outLength) { var result = ProtectRTP(payload, 0, length); @@ -413,13 +414,13 @@ public int ProtectRTP(byte[] payload, int length, out int outLength) return -1; } - System.Buffer.BlockCopy(result, 0, payload, 0, result.Length); + result.AsSpan().CopyTo(payload); outLength = result.Length; return 0; //No Errors } - public byte[] UnprotectRTCP(byte[] packet, int offset, int length) + public byte[] UnprotectRTCP(Span packet, int offset, int length) { lock (this.srtcpDecoder) { @@ -427,7 +428,7 @@ public byte[] UnprotectRTCP(byte[] packet, int offset, int length) } } - public int UnprotectRTCP(byte[] payload, int length, out int outLength) + public int UnprotectRTCP(Span payload, int length, out int outLength) { var result = UnprotectRTCP(payload, 0, length); if (result == null) @@ -436,13 +437,13 @@ public int UnprotectRTCP(byte[] payload, int length, out int outLength) return -1; } - System.Buffer.BlockCopy(result, 0, payload, 0, result.Length); + result.AsSpan().CopyTo(payload); outLength = result.Length; return 0; //No Errors } - public byte[] ProtectRTCP(byte[] packet, int offset, int length) + public byte[] ProtectRTCP(Span packet, int offset, int length) { lock (this.srtcpEncoder) { @@ -450,7 +451,7 @@ public byte[] ProtectRTCP(byte[] packet, int offset, int length) } } - public int ProtectRTCP(byte[] payload, int length, out int outLength) + public int ProtectRTCP(Span payload, int length, out int outLength) { var result = ProtectRTCP(payload, 0, length); if (result == null) @@ -459,7 +460,7 @@ public int ProtectRTCP(byte[] payload, int length, out int outLength) return -1; } - System.Buffer.BlockCopy(result, 0, payload, 0, result.Length); + result.AsSpan().CopyTo(payload); outLength = result.Length; return 0; //No Errors @@ -483,15 +484,17 @@ public int GetSendLimit() return this._sendLimit; } - public void WriteToRecvStream(byte[] buf) + public void WriteToRecvStream(ReadOnlySpan buf) { if (!_isClosed) { - _chunks.Add(buf); + var chunk = ArrayPool.Shared.Rent(buf.Length); + buf.CopyTo(chunk); + _chunks.Add(new(chunk, 0, buf.Length)); } } - private byte[] _partialChunk = null; + private ArraySegment _partialChunk = default; private int _partialChunkOffset = 0; private int Read(byte[] buffer, int offset, int count, int timeout) { @@ -502,15 +505,16 @@ private int Read(byte[] buffer, int offset, int count, int timeout) throw new System.Net.Sockets.SocketException((int)System.Net.Sockets.SocketError.NotConnected); //return DTLS_RECEIVE_ERROR_CODE; } - else if (_partialChunk != null) + else if (_partialChunk.Array != null) { - int bytesToCopy = Math.Min(count, _partialChunk.Length - _partialChunkOffset); - Buffer.BlockCopy(_partialChunk, _partialChunkOffset, buffer, offset, bytesToCopy); + int bytesToCopy = Math.Min(count, _partialChunk.Count - _partialChunkOffset); + Buffer.BlockCopy(_partialChunk.Array, _partialChunkOffset, buffer, offset, bytesToCopy); _partialChunkOffset += bytesToCopy; - if (_partialChunkOffset == _partialChunk.Length) + if (_partialChunkOffset == _partialChunk.Count) { - _partialChunk = null; + ArrayPool.Shared.Return(_partialChunk.Array); + _partialChunk = default; _partialChunkOffset = 0; } @@ -518,13 +522,17 @@ private int Read(byte[] buffer, int offset, int count, int timeout) } else if (_chunks.TryTake(out var item, timeout)) { - int bytesToCopy = Math.Min(count, item.Length); - Buffer.BlockCopy(item, 0, buffer, offset, bytesToCopy); - if (bytesToCopy < item.Length) + int bytesToCopy = Math.Min(count, item.Count); + Buffer.BlockCopy(item.Array, 0, buffer, offset, bytesToCopy); + if (bytesToCopy < item.Count) { _partialChunk = item; _partialChunkOffset = bytesToCopy; } + else + { + ArrayPool.Shared.Return(item.Array); + } return bytesToCopy; } } @@ -620,6 +628,14 @@ public virtual void Close() { _isClosed = true; this._startTime = System.DateTime.MinValue; + foreach(var chunk in _chunks.GetConsumingEnumerable()) + { + ArrayPool.Shared.Return(chunk.Array); + } + if (_partialChunk.Array is { } partialChunk) + { + ArrayPool.Shared.Return(partialChunk); + } this._chunks?.Dispose(); } diff --git a/src/net/DtlsSrtp/SrtpHandler.cs b/src/net/DtlsSrtp/SrtpHandler.cs index 2fed6ecf6..509a86947 100644 --- a/src/net/DtlsSrtp/SrtpHandler.cs +++ b/src/net/DtlsSrtp/SrtpHandler.cs @@ -166,7 +166,7 @@ private IPacketTransformer GenerateTransformer(SDPSecurityDescription securityDe } } - public byte[] UnprotectRTP(byte[] packet, int offset, int length) + public byte[] UnprotectRTP(Span packet, int offset, int length) { lock (SrtpDecoder) { @@ -174,7 +174,7 @@ public byte[] UnprotectRTP(byte[] packet, int offset, int length) } } - public int UnprotectRTP(byte[] payload, int length, out int outLength) + public int UnprotectRTP(Span payload, int length, out int outLength) { var result = UnprotectRTP(payload, 0, length); @@ -184,13 +184,13 @@ public int UnprotectRTP(byte[] payload, int length, out int outLength) return -1; } - System.Buffer.BlockCopy(result, 0, payload, 0, result.Length); + result.AsSpan().CopyTo(payload); outLength = result.Length; return 0; //No Errors } - public byte[] ProtectRTP(byte[] packet, int offset, int length) + public byte[] ProtectRTP(Span packet, int offset, int length) { lock (SrtpEncoder) { @@ -198,7 +198,7 @@ public byte[] ProtectRTP(byte[] packet, int offset, int length) } } - public int ProtectRTP(byte[] payload, int length, out int outLength) + public int ProtectRTP(Span payload, int length, out int outLength) { var result = ProtectRTP(payload, 0, length); @@ -208,13 +208,13 @@ public int ProtectRTP(byte[] payload, int length, out int outLength) return -1; } - System.Buffer.BlockCopy(result, 0, payload, 0, result.Length); + result.AsSpan().CopyTo(payload); outLength = result.Length; return 0; //No Errors } - public byte[] UnprotectRTCP(byte[] packet, int offset, int length) + public byte[] UnprotectRTCP(Span packet, int offset, int length) { lock (SrtcpDecoder) { @@ -222,7 +222,7 @@ public byte[] UnprotectRTCP(byte[] packet, int offset, int length) } } - public int UnprotectRTCP(byte[] payload, int length, out int outLength) + public int UnprotectRTCP(Span payload, int length, out int outLength) { var result = UnprotectRTCP(payload, 0, length); if (result == null) @@ -231,13 +231,13 @@ public int UnprotectRTCP(byte[] payload, int length, out int outLength) return -1; } - System.Buffer.BlockCopy(result, 0, payload, 0, result.Length); + result.AsSpan().CopyTo(payload); outLength = result.Length; return 0; //No Errors } - public byte[] ProtectRTCP(byte[] packet, int offset, int length) + public byte[] ProtectRTCP(Span packet, int offset, int length) { lock (SrtcpEncoder) { @@ -245,7 +245,7 @@ public byte[] ProtectRTCP(byte[] packet, int offset, int length) } } - public int ProtectRTCP(byte[] payload, int length, out int outLength) + public int ProtectRTCP(Span payload, int length, out int outLength) { var result = ProtectRTCP(payload, 0, length); if (result == null) @@ -254,7 +254,7 @@ public int ProtectRTCP(byte[] payload, int length, out int outLength) return -1; } - System.Buffer.BlockCopy(result, 0, payload, 0, result.Length); + result.AsSpan().CopyTo(payload); outLength = result.Length; return 0; //No Errors diff --git a/src/net/DtlsSrtp/Transform/IPackerTransformer.cs b/src/net/DtlsSrtp/Transform/IPackerTransformer.cs index 4f570f782..ef4af9b10 100644 --- a/src/net/DtlsSrtp/Transform/IPackerTransformer.cs +++ b/src/net/DtlsSrtp/Transform/IPackerTransformer.cs @@ -19,6 +19,8 @@ // Original Source: AGPL-3.0 License //----------------------------------------------------------------------------- +using System; + namespace SIPSorcery.Net { public interface IPacketTransformer @@ -30,7 +32,7 @@ public interface IPacketTransformer * the packet to be transformed * @return The transformed packet. Returns null if the packet cannot be transformed. */ - byte[] Transform(byte[] pkt); + byte[] Transform(ReadOnlySpan pkt); /** * Transforms a specific non-secure packet. @@ -44,7 +46,7 @@ public interface IPacketTransformer * @return The transformed packet. Returns null if the packet cannot be * transformed. */ - byte[] Transform(byte[] pkt, int offset, int length); + byte[] Transform(ReadOnlySpan pkt, int offset, int length); /** * Reverse-transforms a specific packet (i.e. transforms a transformed @@ -54,7 +56,7 @@ public interface IPacketTransformer * the transformed packet to be restored * @return Whether the packet was successfully restored */ - byte[] ReverseTransform(byte[] pkt); + byte[] ReverseTransform(ReadOnlySpan pkt); /** * Reverse-transforms a specific packet (i.e. transforms a transformed @@ -68,7 +70,7 @@ public interface IPacketTransformer * the length of data in the packet * @return The restored packet. Returns null if packet cannot be restored. */ - byte[] ReverseTransform(byte[] pkt, int offset, int length); + byte[] ReverseTransform(ReadOnlySpan pkt, int offset, int length); /** * Close the transformer and underlying transform engine. diff --git a/src/net/DtlsSrtp/Transform/RawPacket.cs b/src/net/DtlsSrtp/Transform/RawPacket.cs index 7da6fb3a7..ee3e38804 100644 --- a/src/net/DtlsSrtp/Transform/RawPacket.cs +++ b/src/net/DtlsSrtp/Transform/RawPacket.cs @@ -40,6 +40,7 @@ * */ +using System; using System.IO; namespace SIPSorcery.Net @@ -82,16 +83,16 @@ public RawPacket() * @param length the number of bytes in buffer which * constitute the actual data to be represented by the new instance */ - public RawPacket(byte[] data, int offset, int length) + public RawPacket(ReadOnlySpan data, int offset, int length) { this.buffer = new MemoryStream(RTP_PACKET_MAX_SIZE); Wrap(data, offset, length); } - public void Wrap(byte[] data, int offset, int length) + public void Wrap(ReadOnlySpan data, int offset, int length) { - this.buffer.Position = 0; - this.buffer.Write(data, offset, length); + this.buffer.Position = 0; + this.buffer.Write(data.ToArray(), offset, length); this.buffer.SetLength(length - offset); this.buffer.Position = 0; } diff --git a/src/net/DtlsSrtp/Transform/SrtcpTransformer.cs b/src/net/DtlsSrtp/Transform/SrtcpTransformer.cs index 8efb73adf..513f04039 100644 --- a/src/net/DtlsSrtp/Transform/SrtcpTransformer.cs +++ b/src/net/DtlsSrtp/Transform/SrtcpTransformer.cs @@ -17,6 +17,7 @@ // Original Source: AGPL-3.0 License //----------------------------------------------------------------------------- +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Threading; @@ -59,12 +60,12 @@ public SrtcpTransformer(SrtpTransformEngine forwardEngine, SrtpTransformEngine r /// /// plain SRTCP packet to be encrypted. /// encrypted SRTCP packet. - public byte[] Transform(byte[] pkt) + public byte[] Transform(ReadOnlySpan pkt) { return Transform(pkt, 0, pkt.Length); } - public byte[] Transform(byte[] pkt, int offset, int length) + public byte[] Transform(ReadOnlySpan pkt, int offset, int length) { var isLocked = Interlocked.CompareExchange(ref _isLocked, 1, 0) != 0; try @@ -95,16 +96,18 @@ public byte[] Transform(byte[] pkt, int offset, int length) { //Unlock if (!isLocked) + { Interlocked.CompareExchange(ref _isLocked, 0, 1); + } } } - public byte[] ReverseTransform(byte[] pkt) + public byte[] ReverseTransform(ReadOnlySpan pkt) { return ReverseTransform(pkt, 0, pkt.Length); } - public byte[] ReverseTransform(byte[] pkt, int offset, int length) + public byte[] ReverseTransform(ReadOnlySpan pkt, int offset, int length) { var isLocked = Interlocked.CompareExchange(ref _isLocked, 1, 0) != 0; try @@ -138,7 +141,9 @@ public byte[] ReverseTransform(byte[] pkt, int offset, int length) { //Unlock if (!isLocked) + { Interlocked.CompareExchange(ref _isLocked, 0, 1); + } } } diff --git a/src/net/DtlsSrtp/Transform/SrtpTransformer.cs b/src/net/DtlsSrtp/Transform/SrtpTransformer.cs index e72816c98..0f800c50b 100644 --- a/src/net/DtlsSrtp/Transform/SrtpTransformer.cs +++ b/src/net/DtlsSrtp/Transform/SrtpTransformer.cs @@ -39,6 +39,7 @@ * */ +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Threading; @@ -70,12 +71,12 @@ public SrtpTransformer(SrtpTransformEngine forwardEngine, SrtpTransformEngine re this.rawPacket = new RawPacket(); } - public byte[] Transform(byte[] pkt) + public byte[] Transform(ReadOnlySpan pkt) { return Transform(pkt, 0, pkt.Length); } - public byte[] Transform(byte[] pkt, int offset, int length) + public byte[] Transform(ReadOnlySpan pkt, int offset, int length) { var isLocked = Interlocked.CompareExchange(ref _isLocked, 1, 0) != 0; @@ -107,7 +108,9 @@ public byte[] Transform(byte[] pkt, int offset, int length) { //Unlock if (!isLocked) + { Interlocked.CompareExchange(ref _isLocked, 0, 1); + } } } @@ -119,12 +122,12 @@ public byte[] Transform(byte[] pkt, int offset, int length) * the transformed packet to be restored * @return the restored packet */ - public byte[] ReverseTransform(byte[] pkt) + public byte[] ReverseTransform(ReadOnlySpan pkt) { return ReverseTransform(pkt, 0, pkt.Length); } - public byte[] ReverseTransform(byte[] pkt, int offset, int length) + public byte[] ReverseTransform(ReadOnlySpan pkt, int offset, int length) { var isLocked = Interlocked.CompareExchange(ref _isLocked, 1, 0) != 0; try @@ -157,7 +160,9 @@ public byte[] ReverseTransform(byte[] pkt, int offset, int length) { //Unlock if (!isLocked) + { Interlocked.CompareExchange(ref _isLocked, 0, 1); + } } } diff --git a/src/net/ICE/RtpIceChannel.cs b/src/net/ICE/RtpIceChannel.cs index 0fb321de6..94b636de8 100644 --- a/src/net/ICE/RtpIceChannel.cs +++ b/src/net/ICE/RtpIceChannel.cs @@ -569,7 +569,7 @@ internal int RTO /// public event Action OnStunMessageSent; - public new event Action OnRTPDataReceived; + public new event DataReceivedDelegate OnRTPDataReceived; /// /// An optional callback function to resolve remote ICE candidates with MDNS hostnames. @@ -2547,9 +2547,9 @@ private byte[] GetAuthenticatedStunRequest(STUNMessage stunRequest, string usern /// The local port it was received on. /// The remote end point of the sender. /// The raw packet received (note this may not be RTP if other protocols are being multiplexed). - protected override void OnRTPPacketReceived(UdpReceiver receiver, int localPort, IPEndPoint remoteEndPoint, byte[] packet) + protected override void OnRTPPacketReceived(UdpReceiver receiver, int localPort, IPEndPoint remoteEndPoint, ReadOnlySpan packet) { - if (packet?.Length > 0) + if (packet.Length > 0) { bool wasRelayed = false; diff --git a/src/net/RTCP/RTCPBye.cs b/src/net/RTCP/RTCPBye.cs index 56158e640..c43e1cd98 100644 --- a/src/net/RTCP/RTCPBye.cs +++ b/src/net/RTCP/RTCPBye.cs @@ -27,6 +27,7 @@ //----------------------------------------------------------------------------- using System; +using System.Buffers.Binary; using System.Text; using SIPSorcery.Sys; @@ -73,7 +74,7 @@ public RTCPBye(uint ssrc, string reason) /// Create a new RTCP Goodbye packet from a serialised byte array. /// /// The byte array holding the Goodbye packet. - public RTCPBye(byte[] packet) + public RTCPBye(ReadOnlySpan packet) { if (packet.Length < MIN_PACKET_SIZE) { @@ -81,15 +82,7 @@ public RTCPBye(byte[] packet) } Header = new RTCPHeader(packet); - - if (BitConverter.IsLittleEndian) - { - SSRC = NetConvert.DoReverseEndian(BitConverter.ToUInt32(packet, 4)); - } - else - { - SSRC = BitConverter.ToUInt32(packet, 4); - } + SSRC = BinaryPrimitives.ReadUInt32BigEndian(packet.Slice(4)); if (packet.Length > MIN_PACKET_SIZE) { @@ -97,7 +90,7 @@ public RTCPBye(byte[] packet) if (packet.Length - MIN_PACKET_SIZE - 1 >= reasonLength) { - Reason = Encoding.UTF8.GetString(packet, 9, reasonLength); + Reason = packet.Slice(9, reasonLength).ToString(Encoding.UTF8); } } } diff --git a/src/net/RTCP/RTCPCompoundPacket.cs b/src/net/RTCP/RTCPCompoundPacket.cs index 1ec7be7f1..8ae161c9f 100644 --- a/src/net/RTCP/RTCPCompoundPacket.cs +++ b/src/net/RTCP/RTCPCompoundPacket.cs @@ -60,7 +60,7 @@ public RTCPCompoundPacket(RTCPReceiverReport receiverReport, RTCPSDesReport sdes /// Creates a new RTCP compound packet from a serialised buffer. /// /// The serialised RTCP compound packet to parse. - public RTCPCompoundPacket(byte[] packet) + public RTCPCompoundPacket(ReadOnlySpan packet) { int offset = 0; while (offset < packet.Length) @@ -72,7 +72,7 @@ public RTCPCompoundPacket(byte[] packet) } else { - var buffer = packet.Skip(offset).ToArray(); + var buffer = packet.Slice(offset); // The payload type field is the second byte in the RTCP header. byte packetTypeID = buffer[1]; diff --git a/src/net/RTCP/RTCPFeedback.cs b/src/net/RTCP/RTCPFeedback.cs index b3dc9a214..0c6bc4e6e 100644 --- a/src/net/RTCP/RTCPFeedback.cs +++ b/src/net/RTCP/RTCPFeedback.cs @@ -27,6 +27,7 @@ //----------------------------------------------------------------------------- using System; +using System.Buffers.Binary; using Microsoft.Extensions.Logging; using SIPSorcery.Sys; @@ -146,21 +147,13 @@ public RTCPFeedback(uint senderSsrc, uint mediaSsrc, PSFBFeedbackTypesEnum feedb /// Create a new RTCP Report from a serialised byte array. /// /// The byte array holding the serialised feedback report. - public RTCPFeedback(byte[] packet) + public RTCPFeedback(ReadOnlySpan packet) { Header = new RTCPHeader(packet); int payloadIndex = RTCPHeader.HEADER_BYTES_LENGTH; - if (BitConverter.IsLittleEndian) - { - SenderSSRC = NetConvert.DoReverseEndian(BitConverter.ToUInt32(packet, payloadIndex)); - MediaSSRC = NetConvert.DoReverseEndian(BitConverter.ToUInt32(packet, payloadIndex + 4)); - } - else - { - SenderSSRC = BitConverter.ToUInt32(packet, payloadIndex); - MediaSSRC = BitConverter.ToUInt32(packet, payloadIndex + 4); - } + SenderSSRC = BinaryPrimitives.ReadUInt32BigEndian(packet.Slice(payloadIndex)); + MediaSSRC = BinaryPrimitives.ReadUInt32BigEndian(packet.Slice(payloadIndex + 4)); switch (Header) { @@ -170,16 +163,8 @@ public RTCPFeedback(byte[] packet) break; case var x when x.PacketType == RTCPReportTypesEnum.RTPFB: SENDER_PAYLOAD_SIZE = 12; - if (BitConverter.IsLittleEndian) - { - PID = NetConvert.DoReverseEndian(BitConverter.ToUInt16(packet, payloadIndex + 8)); - BLP = NetConvert.DoReverseEndian(BitConverter.ToUInt16(packet, payloadIndex + 10)); - } - else - { - PID = BitConverter.ToUInt16(packet, payloadIndex + 8); - BLP = BitConverter.ToUInt16(packet, payloadIndex + 10); - } + PID = BinaryPrimitives.ReadUInt16BigEndian(packet.Slice(payloadIndex + 8)); + BLP = BinaryPrimitives.ReadUInt16BigEndian(packet.Slice(payloadIndex + 10)); break; case var x when x.PacketType == RTCPReportTypesEnum.PSFB && x.PayloadFeedbackMessageType == PSFBFeedbackTypesEnum.PLI: @@ -202,7 +187,7 @@ public RTCPFeedback(byte[] packet) SENDER_PAYLOAD_SIZE = 8 + 12; // 8 bytes from (SenderSSRC + MediaSSRC) + extra 12 bytes from REMB Definition var currentCounter = payloadIndex + 8; - UniqueID = System.Text.ASCIIEncoding.ASCII.GetString(packet, currentCounter, 4); + UniqueID = packet.Slice(currentCounter, 4).ToString(System.Text.Encoding.ASCII); currentCounter += 4; if (string.Equals(UniqueID,"REMB", StringComparison.CurrentCultureIgnoreCase)) @@ -227,14 +212,7 @@ public RTCPFeedback(byte[] packet) currentCounter += 3; - if (BitConverter.IsLittleEndian) - { - FeedbackSSRC = NetConvert.DoReverseEndian(BitConverter.ToUInt32(packet, currentCounter)); - } - else - { - FeedbackSSRC = BitConverter.ToUInt32(packet, currentCounter); - } + FeedbackSSRC = BinaryPrimitives.ReadUInt32BigEndian(packet.Slice(currentCounter)); } break; diff --git a/src/net/RTCP/RTCPHeader.cs b/src/net/RTCP/RTCPHeader.cs index a6ccf244d..81aab080e 100644 --- a/src/net/RTCP/RTCPHeader.cs +++ b/src/net/RTCP/RTCPHeader.cs @@ -33,6 +33,7 @@ //----------------------------------------------------------------------------- using System; +using System.Buffers.Binary; using SIPSorcery.Sys; namespace SIPSorcery.Net @@ -132,24 +133,15 @@ public bool IsFeedbackReport() /// Extract and load the RTCP header from an RTCP packet. /// /// - public RTCPHeader(byte[] packet) + public RTCPHeader(ReadOnlySpan packet) { if (packet.Length < HEADER_BYTES_LENGTH) { throw new ApplicationException("The packet did not contain the minimum number of bytes for an RTCP header packet."); } - UInt16 firstWord = BitConverter.ToUInt16(packet, 0); - - if (BitConverter.IsLittleEndian) - { - firstWord = NetConvert.DoReverseEndian(firstWord); - Length = NetConvert.DoReverseEndian(BitConverter.ToUInt16(packet, 2)); - } - else - { - Length = BitConverter.ToUInt16(packet, 2); - } + UInt16 firstWord = BinaryPrimitives.ReadUInt16BigEndian(packet); + Length = BinaryPrimitives.ReadUInt16BigEndian(packet.Slice(2)); Version = Convert.ToInt32(firstWord >> 14); PaddingFlag = Convert.ToInt32((firstWord >> 13) & 0x1); diff --git a/src/net/RTCP/RTCPReceiverReport.cs b/src/net/RTCP/RTCPReceiverReport.cs index 2819c4dee..cdf23a3c3 100644 --- a/src/net/RTCP/RTCPReceiverReport.cs +++ b/src/net/RTCP/RTCPReceiverReport.cs @@ -45,6 +45,7 @@ //----------------------------------------------------------------------------- using System; +using System.Buffers.Binary; using System.Collections.Generic; using System.Linq; using SIPSorcery.Sys; @@ -76,7 +77,7 @@ public RTCPReceiverReport(uint ssrc, List receptionReport /// Create a new RTCP Receiver Report from a serialised byte array. /// /// The byte array holding the serialised receiver report. - public RTCPReceiverReport(byte[] packet) + public RTCPReceiverReport(ReadOnlySpan packet) { if (packet.Length < MIN_PACKET_SIZE) { @@ -85,20 +86,12 @@ public RTCPReceiverReport(byte[] packet) Header = new RTCPHeader(packet); ReceptionReports = new List(); - - if (BitConverter.IsLittleEndian) - { - SSRC = NetConvert.DoReverseEndian(BitConverter.ToUInt32(packet, 4)); - } - else - { - SSRC = BitConverter.ToUInt32(packet, 4); - } + SSRC = BinaryPrimitives.ReadUInt32BigEndian(packet.Slice(4)); int rrIndex = 8; for (int i = 0; i < Header.ReceptionReportCount; i++) { - var rr = new ReceptionReportSample(packet.Skip(rrIndex + i * ReceptionReportSample.PAYLOAD_SIZE).ToArray()); + var rr = new ReceptionReportSample(packet.Slice(rrIndex + i * ReceptionReportSample.PAYLOAD_SIZE)); ReceptionReports.Add(rr); } } diff --git a/src/net/RTCP/RTCPSdesReport.cs b/src/net/RTCP/RTCPSdesReport.cs index c90a7f6f1..b30e68ec0 100644 --- a/src/net/RTCP/RTCPSdesReport.cs +++ b/src/net/RTCP/RTCPSdesReport.cs @@ -51,6 +51,7 @@ //----------------------------------------------------------------------------- using System; +using System.Buffers.Binary; using System.Text; using SIPSorcery.Sys; @@ -100,7 +101,7 @@ public RTCPSDesReport(uint ssrc, string cname) /// Create a new RTCP SDES item from a serialised byte array. /// /// The byte array holding the SDES report. - public RTCPSDesReport(byte[] packet) + public RTCPSDesReport(ReadOnlySpan packet) { // if (packet.Length < MIN_PACKET_SIZE) // { @@ -119,14 +120,7 @@ public RTCPSDesReport(byte[] packet) if (packet.Length >= RTCPHeader.HEADER_BYTES_LENGTH+4) { - if (BitConverter.IsLittleEndian) - { - SSRC = NetConvert.DoReverseEndian(BitConverter.ToUInt32(packet, RTCPHeader.HEADER_BYTES_LENGTH)); - } - else - { - SSRC = BitConverter.ToUInt32(packet, RTCPHeader.HEADER_BYTES_LENGTH); - } + SSRC = BinaryPrimitives.ReadUInt32BigEndian(packet.Slice(RTCPHeader.HEADER_BYTES_LENGTH)); } if (packet.Length >= MIN_PACKET_SIZE) @@ -137,7 +131,7 @@ public RTCPSDesReport(byte[] packet) CNAME = string.Empty; return; } - CNAME = Encoding.UTF8.GetString(packet, 10, cnameLength); + CNAME = packet.Slice(10, cnameLength).ToString(Encoding.UTF8); } } diff --git a/src/net/RTCP/RTCPSenderReport.cs b/src/net/RTCP/RTCPSenderReport.cs index 0e5d8cb22..590eabd5f 100644 --- a/src/net/RTCP/RTCPSenderReport.cs +++ b/src/net/RTCP/RTCPSenderReport.cs @@ -45,6 +45,7 @@ //----------------------------------------------------------------------------- using System; +using System.Buffers.Binary; using System.Collections.Generic; using System.Linq; using SIPSorcery.Sys; @@ -91,7 +92,7 @@ public RTCPSenderReport(uint ssrc, ulong ntpTimestamp, uint rtpTimestamp, uint p /// Create a new RTCP Sender Report from a serialised byte array. /// /// The byte array holding the serialised sender report. - public RTCPSenderReport(byte[] packet) + public RTCPSenderReport(ReadOnlySpan packet) { if (packet.Length < MIN_PACKET_SIZE) { @@ -101,27 +102,16 @@ public RTCPSenderReport(byte[] packet) Header = new RTCPHeader(packet); ReceptionReports = new List(); - if (BitConverter.IsLittleEndian) - { - SSRC = NetConvert.DoReverseEndian(BitConverter.ToUInt32(packet, 4)); - NtpTimestamp = NetConvert.DoReverseEndian(BitConverter.ToUInt64(packet, 8)); - RtpTimestamp = NetConvert.DoReverseEndian(BitConverter.ToUInt32(packet, 16)); - PacketCount = NetConvert.DoReverseEndian(BitConverter.ToUInt32(packet, 20)); - OctetCount = NetConvert.DoReverseEndian(BitConverter.ToUInt32(packet, 24)); - } - else - { - SSRC = BitConverter.ToUInt32(packet, 4); - NtpTimestamp = BitConverter.ToUInt64(packet, 8); - RtpTimestamp = BitConverter.ToUInt32(packet, 16); - PacketCount = BitConverter.ToUInt32(packet, 20); - OctetCount = BitConverter.ToUInt32(packet, 24); - } + SSRC = BinaryPrimitives.ReadUInt32BigEndian(packet.Slice(4)); + NtpTimestamp = BinaryPrimitives.ReadUInt64BigEndian(packet.Slice(8)); + RtpTimestamp = BinaryPrimitives.ReadUInt32BigEndian(packet.Slice(16)); + PacketCount = BinaryPrimitives.ReadUInt32BigEndian(packet.Slice(20)); + OctetCount = BinaryPrimitives.ReadUInt32BigEndian(packet.Slice(24)); int rrIndex = 28; for (int i = 0; i < Header.ReceptionReportCount; i++) { - var rr = new ReceptionReportSample(packet.Skip(rrIndex + i * ReceptionReportSample.PAYLOAD_SIZE).ToArray()); + var rr = new ReceptionReportSample(packet.Slice(rrIndex + i * ReceptionReportSample.PAYLOAD_SIZE)); ReceptionReports.Add(rr); } } diff --git a/src/net/RTCP/ReceptionReport.cs b/src/net/RTCP/ReceptionReport.cs index 0fbef3779..66d422a90 100644 --- a/src/net/RTCP/ReceptionReport.cs +++ b/src/net/RTCP/ReceptionReport.cs @@ -33,6 +33,7 @@ //----------------------------------------------------------------------------- using System; +using System.Buffers.Binary; using SIPSorcery.Sys; namespace SIPSorcery.Net @@ -111,28 +112,16 @@ public ReceptionReportSample( DelaySinceLastSenderReport = delaySinceLastSR; } - public ReceptionReportSample(byte[] packet) + public ReceptionReportSample(ReadOnlySpan packet) { - if (BitConverter.IsLittleEndian) - { - SSRC = NetConvert.DoReverseEndian(BitConverter.ToUInt32(packet, 0)); - FractionLost = packet[4]; - PacketsLost = NetConvert.DoReverseEndian(BitConverter.ToInt32(new byte[] { 0x00, packet[5], packet[6], packet[7] }, 0)); - ExtendedHighestSequenceNumber = NetConvert.DoReverseEndian(BitConverter.ToUInt32(packet, 8)); - Jitter = NetConvert.DoReverseEndian(BitConverter.ToUInt32(packet, 12)); - LastSenderReportTimestamp = NetConvert.DoReverseEndian(BitConverter.ToUInt32(packet, 16)); - DelaySinceLastSenderReport = NetConvert.DoReverseEndian(BitConverter.ToUInt32(packet, 20)); - } - else - { - SSRC = BitConverter.ToUInt32(packet, 4); - FractionLost = packet[4]; - PacketsLost = BitConverter.ToInt32(new byte[] { 0x00, packet[5], packet[6], packet[7] }, 0); - ExtendedHighestSequenceNumber = BitConverter.ToUInt32(packet, 8); - Jitter = BitConverter.ToUInt32(packet, 12); - LastSenderReportTimestamp = BitConverter.ToUInt32(packet, 16); - DelaySinceLastSenderReport = BitConverter.ToUInt32(packet, 20); - } + SSRC = BinaryPrimitives.ReadUInt32BigEndian(packet.Slice(4)); + FractionLost = packet[4]; + Span packetsLost = stackalloc byte[] { 0x00, packet[5], packet[6], packet[7] }; + PacketsLost = BinaryPrimitives.ReadInt32BigEndian(packetsLost); + ExtendedHighestSequenceNumber = BinaryPrimitives.ReadUInt32BigEndian(packet.Slice(8)); + Jitter = BinaryPrimitives.ReadUInt32BigEndian(packet.Slice(12)); + LastSenderReportTimestamp = BinaryPrimitives.ReadUInt32BigEndian(packet.Slice(16)); + DelaySinceLastSenderReport = BinaryPrimitives.ReadUInt32BigEndian(packet.Slice(20)); } /// diff --git a/src/net/RTP/MediaStream.cs b/src/net/RTP/MediaStream.cs index e23e3ff49..c0f2052cb 100644 --- a/src/net/RTP/MediaStream.cs +++ b/src/net/RTP/MediaStream.cs @@ -253,7 +253,7 @@ public Boolean IsSecurityContextReady() return (SecureContext != null); } - private (bool, byte[]) UnprotectBuffer(byte[] buffer) + private bool UnprotectBuffer(Span buffer, out ReadOnlySpan result) { if (SecureContext != null) { @@ -261,21 +261,23 @@ public Boolean IsSecurityContextReady() if (res == 0) { - return (true, buffer.Take(outBufLen).ToArray()); + result = buffer.Slice(0, outBufLen); + return true; } else { logger.LogWarning($"SRTP unprotect failed for {MediaType}, result {res}."); } } - return (false, buffer); + result = buffer; + return false; } - public bool EnsureBufferUnprotected(byte[] buf, RTPHeader header, out RTPPacket packet) + public bool EnsureBufferUnprotected(Span buf, RTPHeader header, out RTPPacket packet) { if (RtpSessionConfig.IsSecure || RtpSessionConfig.UseSdpCryptoNegotiation) { - var (succeeded, newBuffer) = UnprotectBuffer(buf); + var succeeded = UnprotectBuffer(buf, out var newBuffer); if (!succeeded) { packet = null; @@ -525,7 +527,7 @@ public void SendRtcpFeedback(RTCPFeedback feedback) #region RECEIVE PACKET - public void OnReceiveRTPPacket(RTPHeader hdr, int localPort, IPEndPoint remoteEndPoint, byte[] buffer, VideoStream videoStream = null) + public void OnReceiveRTPPacket(RTPHeader hdr, int localPort, IPEndPoint remoteEndPoint, Span buffer, VideoStream videoStream = null) { RTPPacket rtpPacket = null; if (RemoteRtpEventPayloadID != 0 && hdr.PayloadType == RemoteRtpEventPayloadID) @@ -677,7 +679,7 @@ protected virtual void ClearPendingPackages() // Cache pending packages to use it later to prevent missing frames // when DTLS was not completed yet as a Server but already completed as a client - protected virtual bool AddPendingPackage(RTPHeader hdr, int localPort, IPEndPoint remoteEndPoint, byte[] buffer, VideoStream videoStream = null) + protected virtual bool AddPendingPackage(RTPHeader hdr, int localPort, IPEndPoint remoteEndPoint, ReadOnlySpan buffer, VideoStream videoStream = null) { const int MAX_PENDING_PACKAGES_BUFFER_SIZE = 32; @@ -690,7 +692,7 @@ protected virtual bool AddPendingPackage(RTPHeader hdr, int localPort, IPEndPoin { _pendingPackagesBuffer.RemoveAt(0); } - _pendingPackagesBuffer.Add(new PendingPackages(hdr, localPort, remoteEndPoint, buffer, videoStream)); + _pendingPackagesBuffer.Add(new PendingPackages(hdr, localPort, remoteEndPoint, buffer.ToArray(), videoStream)); } return true; } diff --git a/src/net/RTP/RTPChannel.cs b/src/net/RTP/RTPChannel.cs index bb6e5abfa..a442e92c8 100644 --- a/src/net/RTP/RTPChannel.cs +++ b/src/net/RTP/RTPChannel.cs @@ -25,7 +25,7 @@ namespace SIPSorcery.Net { - public delegate void PacketReceivedDelegate(UdpReceiver receiver, int localPort, IPEndPoint remoteEndPoint, byte[] packet); + public delegate void PacketReceivedDelegate(UdpReceiver receiver, int localPort, IPEndPoint remoteEndPoint, ReadOnlySpan packet); /// /// A basic UDP socket manager. The RTP channel may need both an RTP and Control socket. This class encapsulates @@ -177,10 +177,14 @@ protected virtual void EndReceiveFrom(IAsyncResult ar) // localEndPoint = new IPEndPoint(packetInfo.Address, localEndPoint.Port); //} - byte[] packetBuffer = new byte[bytesRead]; - // TODO: When .NET Framework support is dropped switch to using a slice instead of a copy. - Buffer.BlockCopy(m_recvBuffer, 0, packetBuffer, 0, bytesRead); - CallOnPacketReceivedCallback(m_localEndPoint.Port, remoteEP as IPEndPoint, packetBuffer); + if (bytesRead < 256 * 1024) + { + Span packetBuffer = stackalloc byte[bytesRead]; + CallOnPacketReceivedCallback(m_localEndPoint.Port, remoteEP as IPEndPoint, m_recvBuffer.AsSpan().Slice(0, bytesRead)); + } else + { + logger.LogCritical("UDP packet received was larger than 256KB and was ignored."); + } } } @@ -258,7 +262,7 @@ public virtual void Close(string reason) } } - protected virtual void CallOnPacketReceivedCallback(int localPort, IPEndPoint remoteEndPoint, byte[] packet) + protected virtual void CallOnPacketReceivedCallback(int localPort, IPEndPoint remoteEndPoint, ReadOnlySpan packet) { OnPacketReceived?.Invoke(this, localPort, remoteEndPoint, packet); } @@ -343,8 +347,10 @@ public bool IsClosed get { return m_isClosed; } } - public event Action OnRTPDataReceived; - public event Action OnControlDataReceived; + public delegate void DataReceivedDelegate(int localPort, IPEndPoint remoteEndPoint, ReadOnlySpan buffer); + + public event DataReceivedDelegate OnRTPDataReceived; + public event DataReceivedDelegate OnControlDataReceived; public event Action OnClosed; /// @@ -569,9 +575,9 @@ private void EndSendTo(IAsyncResult ar) /// The local port it was received on. /// The remote end point of the sender. /// The raw packet received (note this may not be RTP if other protocols are being multiplexed). - protected virtual void OnRTPPacketReceived(UdpReceiver receiver, int localPort, IPEndPoint remoteEndPoint, byte[] packet) + protected virtual void OnRTPPacketReceived(UdpReceiver receiver, int localPort, IPEndPoint remoteEndPoint, ReadOnlySpan packet) { - if (packet?.Length > 0) + if (packet.Length > 0) { LastRtpDestination = remoteEndPoint; OnRTPDataReceived?.Invoke(localPort, remoteEndPoint, packet); @@ -585,7 +591,7 @@ protected virtual void OnRTPPacketReceived(UdpReceiver receiver, int localPort, /// The local port it was received on. /// The remote end point of the sender. /// The raw packet received which should always be an RTCP packet. - private void OnControlPacketReceived(UdpReceiver receiver, int localPort, IPEndPoint remoteEndPoint, byte[] packet) + private void OnControlPacketReceived(UdpReceiver receiver, int localPort, IPEndPoint remoteEndPoint, ReadOnlySpan packet) { LastControlDestination = remoteEndPoint; OnControlDataReceived?.Invoke(localPort, remoteEndPoint, packet); diff --git a/src/net/RTP/RTPHeader.cs b/src/net/RTP/RTPHeader.cs index 9ac9698ff..6f7b8c656 100644 --- a/src/net/RTP/RTPHeader.cs +++ b/src/net/RTP/RTPHeader.cs @@ -62,29 +62,17 @@ public RTPHeader() /// Extract and load the RTP header from an RTP packet. /// /// - public RTPHeader(byte[] packet) + public RTPHeader(ReadOnlySpan packet) { if (packet.Length < MIN_HEADER_LEN) { throw new ApplicationException("The packet did not contain the minimum number of bytes for an RTP header packet."); } - UInt16 firstWord = BitConverter.ToUInt16(packet, 0); - - if (BitConverter.IsLittleEndian) - { - firstWord = NetConvert.DoReverseEndian(firstWord); - SequenceNumber = NetConvert.DoReverseEndian(BitConverter.ToUInt16(packet, 2)); - Timestamp = NetConvert.DoReverseEndian(BitConverter.ToUInt32(packet, 4)); - SyncSource = NetConvert.DoReverseEndian(BitConverter.ToUInt32(packet, 8)); - } - else - { - SequenceNumber = BitConverter.ToUInt16(packet, 2); - Timestamp = BitConverter.ToUInt32(packet, 4); - SyncSource = BitConverter.ToUInt32(packet, 8); - } - + UInt16 firstWord = BinaryPrimitives.ReadUInt16BigEndian(packet); + SequenceNumber = BinaryPrimitives.ReadUInt16BigEndian(packet.Slice(2)); + Timestamp = BinaryPrimitives.ReadUInt32BigEndian(packet.Slice(4)); + SyncSource = BinaryPrimitives.ReadUInt32BigEndian(packet.Slice(8)); Version = firstWord >> 14; PaddingFlag = (firstWord >> 13) & 0x1; @@ -99,25 +87,16 @@ public RTPHeader(byte[] packet) if (HeaderExtensionFlag == 1 && (packet.Length >= (headerAndCSRCLength + 4))) { - if (BitConverter.IsLittleEndian) - { - ExtensionProfile = NetConvert.DoReverseEndian(BitConverter.ToUInt16(packet, 12 + 4 * CSRCCount)); - headerExtensionLength += 2; - ExtensionLength = NetConvert.DoReverseEndian(BitConverter.ToUInt16(packet, 14 + 4 * CSRCCount)); - headerExtensionLength += 2 + ExtensionLength * 4; - } - else - { - ExtensionProfile = BitConverter.ToUInt16(packet, 12 + 4 * CSRCCount); - headerExtensionLength += 2; - ExtensionLength = BitConverter.ToUInt16(packet, 14 + 4 * CSRCCount); - headerExtensionLength += 2 + ExtensionLength * 4; - } + + ExtensionProfile = BinaryPrimitives.ReadUInt16BigEndian(packet.Slice(12 + 4 * CSRCCount)); + headerExtensionLength += 2; + ExtensionLength = BinaryPrimitives.ReadUInt16BigEndian(packet.Slice(14 + 4 * CSRCCount)); + headerExtensionLength += 2 + ExtensionLength * 4; if (ExtensionLength > 0 && packet.Length >= (headerAndCSRCLength + 4 + ExtensionLength * 4)) { ExtensionPayload = new byte[ExtensionLength * 4]; - Buffer.BlockCopy(packet, headerAndCSRCLength + 4, ExtensionPayload, 0, ExtensionLength * 4); + packet.Slice(headerAndCSRCLength + 4, ExtensionPayload.Length).CopyTo(ExtensionPayload); } } diff --git a/src/net/RTP/RTPPacket.cs b/src/net/RTP/RTPPacket.cs index 4da11bf74..5cdd3caac 100644 --- a/src/net/RTP/RTPPacket.cs +++ b/src/net/RTP/RTPPacket.cs @@ -34,11 +34,11 @@ public RTPPacket(int payloadSize) Payload = new byte[payloadSize]; } - public RTPPacket(byte[] packet) + public RTPPacket(ReadOnlySpan packet) { Header = new RTPHeader(packet); Payload = new byte[Header.PayloadSize]; - Array.Copy(packet, Header.Length, Payload, 0, Payload.Length); + packet.Slice(Header.Length, Header.PayloadSize).CopyTo(Payload); } public byte[] GetBytes() diff --git a/src/net/RTP/RTPSession.cs b/src/net/RTP/RTPSession.cs index 01aca0dd0..b42b5773b 100644 --- a/src/net/RTP/RTPSession.cs +++ b/src/net/RTP/RTPSession.cs @@ -20,6 +20,7 @@ //----------------------------------------------------------------------------- using System; +using System.Buffers.Binary; using System.Collections.Generic; using System.Linq; using System.Net; @@ -34,7 +35,7 @@ namespace SIPSorcery.Net { - public delegate int ProtectRtpPacket(byte[] payload, int length, out int outputBufferLength); + public delegate int ProtectRtpPacket(Span payload, int length, out int outputBufferLength); public enum SetDescriptionResultEnum { @@ -2000,7 +2001,7 @@ public virtual void Close(string reason) } } - protected void OnReceive(int localPort, IPEndPoint remoteEndPoint, byte[] buffer) + protected void OnReceive(int localPort, IPEndPoint remoteEndPoint, ReadOnlySpan buffer) { if (remoteEndPoint.Address.IsIPv4MappedToIPv6) { @@ -2010,7 +2011,7 @@ protected void OnReceive(int localPort, IPEndPoint remoteEndPoint, byte[] buffer } // Quick sanity check on whether this is not an RTP or RTCP packet. - if (buffer?.Length > RTPHeader.MIN_HEADER_LEN && buffer[0] >= 128 && buffer[0] <= 191) + if (buffer.Length > RTPHeader.MIN_HEADER_LEN && buffer[0] >= 128 && buffer[0] <= 191) { if ((rtpSessionConfig.IsSecure || rtpSessionConfig.UseSdpCryptoNegotiation) && !IsSecureContextReady()) { @@ -2039,23 +2040,17 @@ protected void OnReceive(int localPort, IPEndPoint remoteEndPoint, byte[] buffer } } - private void OnReceiveRTCPPacket(int localPort, IPEndPoint remoteEndPoint, byte[] buffer) + private void OnReceiveRTCPPacket(int localPort, IPEndPoint remoteEndPoint, ReadOnlySpan bufferRO) { //logger.LogDebug($"RTCP packet received from {remoteEndPoint} {buffer.HexStr()}"); #region RTCP packet. + Span buffer = stackalloc byte[bufferRO.Length]; + bufferRO.CopyTo(buffer); // Get the SSRC in order to be able to figure out which media type // This will let us choose the apropriate unprotect methods - uint ssrc; - if (BitConverter.IsLittleEndian) - { - ssrc = NetConvert.DoReverseEndian(BitConverter.ToUInt32(buffer, 4)); - } - else - { - ssrc = BitConverter.ToUInt32(buffer, 4); - } + uint ssrc = BinaryPrimitives.ReadUInt32BigEndian(buffer.Slice(4)); MediaStream mediaStream = GetMediaStream(ssrc); if (mediaStream != null) @@ -2071,7 +2066,7 @@ private void OnReceiveRTCPPacket(int localPort, IPEndPoint remoteEndPoint, byte[ } else { - buffer = buffer.Take(outBufLen).ToArray(); + buffer = buffer.Slice(0, outBufLen); } } } @@ -2147,11 +2142,11 @@ private void OnReceiveRTCPPacket(int localPort, IPEndPoint remoteEndPoint, byte[ #endregion } - private void OnReceiveRTPPacket(int localPort, IPEndPoint remoteEndPoint, byte[] buffer) + private void OnReceiveRTPPacket(int localPort, IPEndPoint remoteEndPoint, ReadOnlySpan bufferRO) { if (!IsClosed) { - var hdr = new RTPHeader(buffer); + var hdr = new RTPHeader(bufferRO); MediaStream mediaStream = GetMediaStream(hdr.SyncSource); @@ -2169,10 +2164,14 @@ private void OnReceiveRTPPacket(int localPort, IPEndPoint remoteEndPoint, byte[] hdr.ReceivedTime = DateTime.Now; if (mediaStream.MediaType == SDPMediaTypesEnum.audio) { + Span buffer = stackalloc byte[bufferRO.Length]; + bufferRO.CopyTo(buffer); mediaStream.OnReceiveRTPPacket(hdr, localPort, remoteEndPoint, buffer, null); } else if (mediaStream.MediaType == SDPMediaTypesEnum.video) { + Span buffer = stackalloc byte[bufferRO.Length]; + bufferRO.CopyTo(buffer); mediaStream.OnReceiveRTPPacket(hdr, localPort, remoteEndPoint, buffer, mediaStream as VideoStream); } } diff --git a/src/net/SCTP/Chunks/SctpChunk.cs b/src/net/SCTP/Chunks/SctpChunk.cs index 6f324a6c4..fc51a8e89 100644 --- a/src/net/SCTP/Chunks/SctpChunk.cs +++ b/src/net/SCTP/Chunks/SctpChunk.cs @@ -185,11 +185,11 @@ public virtual ushort GetChunkLength(bool padded) /// The buffer holding the serialised chunk. /// The position in the buffer that indicates the start of the chunk. /// The chunk length value. - public ushort ParseFirstWord(byte[] buffer, int posn) + public ushort ParseFirstWord(ReadOnlySpan buffer, int posn) { ChunkType = buffer[posn]; ChunkFlags = buffer[posn + 1]; - ushort chunkLength = NetConvert.ParseUInt16(buffer, posn + 2); + ushort chunkLength = NetConvert.ParseUInt16(buffer.Slice(posn + 2)); if (chunkLength > 0 && buffer.Length < posn + chunkLength) { @@ -209,7 +209,7 @@ public ushort ParseFirstWord(byte[] buffer, int posn) /// The buffer to write the chunk header to. /// The position in the buffer to write at. /// The padded length of this chunk. - protected void WriteChunkHeader(byte[] buffer, int posn) + protected void WriteChunkHeader(Span buffer, int posn) { buffer[posn] = ChunkType; buffer[posn + 1] = ChunkFlags; @@ -225,13 +225,13 @@ protected void WriteChunkHeader(byte[] buffer, int posn) /// must have the required space already allocated. /// The position in the buffer to write to. /// The number of bytes, including padding, written to the buffer. - public virtual ushort WriteTo(byte[] buffer, int posn) + public virtual ushort WriteTo(Span buffer, int posn) { WriteChunkHeader(buffer, posn); if (ChunkValue?.Length > 0) { - Buffer.BlockCopy(ChunkValue, 0, buffer, posn + SCTP_CHUNK_HEADER_LENGTH, ChunkValue.Length); + ChunkValue.CopyTo(buffer.Slice(posn + SCTP_CHUNK_HEADER_LENGTH)); } return GetChunkLength(true); @@ -276,14 +276,14 @@ public bool GotUnrecognisedParameter(SctpTlvChunkParameter chunkParameter) /// The buffer holding the serialised chunk. /// The position to start parsing at. /// An SCTP chunk instance. - public static SctpChunk ParseBaseChunk(byte[] buffer, int posn) + public static SctpChunk ParseBaseChunk(ReadOnlySpan buffer, int posn) { var chunk = new SctpChunk(); ushort chunkLength = chunk.ParseFirstWord(buffer, posn); if (chunkLength > SCTP_CHUNK_HEADER_LENGTH) { chunk.ChunkValue = new byte[chunkLength - SCTP_CHUNK_HEADER_LENGTH]; - Buffer.BlockCopy(buffer, posn + SCTP_CHUNK_HEADER_LENGTH, chunk.ChunkValue, 0, chunk.ChunkValue.Length); + buffer.Slice(posn + SCTP_CHUNK_HEADER_LENGTH, chunk.ChunkValue.Length).CopyTo(chunk.ChunkValue); } return chunk; @@ -298,7 +298,7 @@ public static SctpChunk ParseBaseChunk(byte[] buffer, int posn) /// parameters from. /// The length of the TLV chunk parameters in the buffer. /// A list of chunk parameters. Can be empty. - public static IEnumerable GetParameters(byte[] buffer, int posn, int length) + public static void GetParameters(ReadOnlySpan buffer, int posn, int length, Func onParam) { int paramPosn = posn; @@ -306,7 +306,10 @@ public static IEnumerable GetParameters(byte[] buffer, in { var chunkParam = SctpTlvChunkParameter.ParseTlvParameter(buffer, paramPosn); - yield return chunkParam; + if (!onParam(chunkParam)) + { + break; + } paramPosn += chunkParam.GetParameterLength(true); } @@ -318,7 +321,7 @@ public static IEnumerable GetParameters(byte[] buffer, in /// The buffer holding the serialised chunk. /// The position to start parsing at. /// An SCTP chunk instance. - public static SctpChunk Parse(byte[] buffer, int posn) + public static SctpChunk Parse(ReadOnlySpan buffer, int posn) { if (buffer.Length < posn + SCTP_CHUNK_HEADER_LENGTH) { @@ -369,7 +372,7 @@ public static SctpChunk Parse(byte[] buffer, int posn) /// The start position of the serialised chunk. /// If true the length field will be padded to a 4 byte boundary. /// The padded length of the serialised chunk. - public static uint GetChunkLengthFromHeader(byte[] buffer, int posn, bool padded) + public static uint GetChunkLengthFromHeader(ReadOnlySpan buffer, int posn, bool padded) { ushort len = NetConvert.ParseUInt16(buffer, posn + 2); return (padded) ? SctpPadding.PadTo4ByteBoundary(len) : len; @@ -389,11 +392,10 @@ public static SctpUnrecognisedChunkActions GetUnrecognisedChunkAction(ushort chu /// The buffer containing the chunk. /// The position in the buffer that the unrecognised chunk starts. /// A new buffer containing a copy of the chunk. - public static byte[] CopyUnrecognisedChunk(byte[] buffer, int posn) + public static ReadOnlySpan CopyUnrecognisedChunk(ReadOnlySpan buffer, int posn) { - byte[] unrecognised = new byte[SctpChunk.GetChunkLengthFromHeader(buffer, posn, true)]; - Buffer.BlockCopy(buffer, posn, unrecognised, 0, unrecognised.Length); - return unrecognised; + uint length = GetChunkLengthFromHeader(buffer, posn, true); + return buffer.Slice(posn, checked((int)length)); } } } diff --git a/src/net/SCTP/Chunks/SctpDataChunk.cs b/src/net/SCTP/Chunks/SctpDataChunk.cs index 2d32a07b5..67eaf5a88 100644 --- a/src/net/SCTP/Chunks/SctpDataChunk.cs +++ b/src/net/SCTP/Chunks/SctpDataChunk.cs @@ -18,6 +18,7 @@ //----------------------------------------------------------------------------- using System; +using System.Buffers.Binary; using SIPSorcery.Sys; namespace SIPSorcery.Net @@ -154,7 +155,7 @@ public override ushort GetChunkLength(bool padded) /// must have the required space already allocated. /// The position in the buffer to write to. /// The number of bytes, including padding, written to the buffer. - public override ushort WriteTo(byte[] buffer, int posn) + public override ushort WriteTo(Span buffer, int posn) { WriteChunkHeader(buffer, posn); @@ -168,10 +169,7 @@ public override ushort WriteTo(byte[] buffer, int posn) int userDataPosn = startPosn + FIXED_PARAMETERS_LENGTH; - if (UserData != null) - { - Buffer.BlockCopy(UserData, 0, buffer, userDataPosn, UserData.Length); - } + UserData?.CopyTo(buffer.Slice(userDataPosn)); return GetChunkLength(true); } @@ -186,7 +184,7 @@ public bool IsEmpty() /// /// The buffer holding the serialised chunk. /// The position to start parsing at. - public static SctpDataChunk ParseChunk(byte[] buffer, int posn) + public static SctpDataChunk ParseChunk(ReadOnlySpan buffer, int posn) { var dataChunk = new SctpDataChunk(); ushort chunkLen = dataChunk.ParseFirstWord(buffer, posn); @@ -213,7 +211,7 @@ public static SctpDataChunk ParseChunk(byte[] buffer, int posn) if (userDataLen > 0) { dataChunk.UserData = new byte[userDataLen]; - Buffer.BlockCopy(buffer, userDataPosn, dataChunk.UserData, 0, dataChunk.UserData.Length); + buffer.Slice(userDataPosn, userDataLen).CopyTo(dataChunk.UserData); } return dataChunk; diff --git a/src/net/SCTP/Chunks/SctpErrorCauses.cs b/src/net/SCTP/Chunks/SctpErrorCauses.cs index 8159d1984..54c86ba2c 100644 --- a/src/net/SCTP/Chunks/SctpErrorCauses.cs +++ b/src/net/SCTP/Chunks/SctpErrorCauses.cs @@ -19,6 +19,7 @@ //----------------------------------------------------------------------------- using System; +using System.Buffers.Binary; using System.Collections.Generic; using System.Text; using SIPSorcery.Sys; @@ -49,7 +50,7 @@ public interface ISctpErrorCause { SctpErrorCauseCode CauseCode { get; } ushort GetErrorCauseLength(bool padded); - int WriteTo(byte[] buffer, int posn); + int WriteTo(Span buffer, int posn); } /// @@ -87,7 +88,7 @@ public SctpCauseOnlyError(SctpErrorCauseCode causeCode) public ushort GetErrorCauseLength(bool padded) => ERROR_CAUSE_LENGTH; - public int WriteTo(byte[] buffer, int posn) + public int WriteTo(Span buffer, int posn) { NetConvert.ToBuffer((ushort)CauseCode, buffer, posn); NetConvert.ToBuffer(ERROR_CAUSE_LENGTH, buffer, posn + 2); @@ -115,7 +116,7 @@ public struct SctpErrorInvalidStreamIdentifier : ISctpErrorCause public ushort GetErrorCauseLength(bool padded) => ERROR_CAUSE_LENGTH; - public int WriteTo(byte[] buffer, int posn) + public int WriteTo(Span buffer, int posn) { NetConvert.ToBuffer((ushort)CauseCode, buffer, posn); NetConvert.ToBuffer(ERROR_CAUSE_LENGTH, buffer, posn + 2); @@ -143,7 +144,7 @@ public ushort GetErrorCauseLength(bool padded) return padded ? SctpPadding.PadTo4ByteBoundary(len) : len; } - public int WriteTo(byte[] buffer, int posn) + public int WriteTo(Span buffer, int posn) { var len = GetErrorCauseLength(true); NetConvert.ToBuffer((ushort)CauseCode, buffer, posn); @@ -180,7 +181,7 @@ public struct SctpErrorStaleCookieError : ISctpErrorCause public ushort GetErrorCauseLength(bool padded) => ERROR_CAUSE_LENGTH; - public int WriteTo(byte[] buffer, int posn) + public int WriteTo(Span buffer, int posn) { NetConvert.ToBuffer((ushort)CauseCode, buffer, posn); NetConvert.ToBuffer(ERROR_CAUSE_LENGTH, buffer, posn + 2); @@ -214,15 +215,12 @@ public ushort GetErrorCauseLength(bool padded) return padded ? SctpPadding.PadTo4ByteBoundary(len) : len; } - public int WriteTo(byte[] buffer, int posn) + public int WriteTo(Span buffer, int posn) { var len = GetErrorCauseLength(true); NetConvert.ToBuffer((ushort)CauseCode, buffer, posn); NetConvert.ToBuffer(len, buffer, posn + 2); - if (UnresolvableAddress != null) - { - Buffer.BlockCopy(UnresolvableAddress, 0, buffer, posn + 4, UnresolvableAddress.Length); - } + UnresolvableAddress?.CopyTo(buffer.Slice(posn + 4)); return len; } } @@ -251,15 +249,12 @@ public ushort GetErrorCauseLength(bool padded) return padded ? SctpPadding.PadTo4ByteBoundary(len) : len; } - public int WriteTo(byte[] buffer, int posn) + public int WriteTo(Span buffer, int posn) { var len = GetErrorCauseLength(true); NetConvert.ToBuffer((ushort)CauseCode, buffer, posn); NetConvert.ToBuffer(len, buffer, posn + 2); - if (UnrecognizedChunk != null) - { - Buffer.BlockCopy(UnrecognizedChunk, 0, buffer, posn + 4, UnrecognizedChunk.Length); - } + UnrecognizedChunk?.CopyTo(buffer.Slice(posn + 4)); return len; } } @@ -292,15 +287,12 @@ public ushort GetErrorCauseLength(bool padded) return padded ? SctpPadding.PadTo4ByteBoundary(len) : len; } - public int WriteTo(byte[] buffer, int posn) + public int WriteTo(Span buffer, int posn) { var len = GetErrorCauseLength(true); NetConvert.ToBuffer((ushort)CauseCode, buffer, posn); NetConvert.ToBuffer(len, buffer, posn + 2); - if (UnrecognizedParameters != null) - { - Buffer.BlockCopy(UnrecognizedParameters, 0, buffer, posn + 4, UnrecognizedParameters.Length); - } + UnrecognizedParameters?.CopyTo(buffer.Slice(posn + 4)); return len; } } @@ -326,7 +318,7 @@ public struct SctpErrorNoUserData : ISctpErrorCause public ushort GetErrorCauseLength(bool padded) => ERROR_CAUSE_LENGTH; - public int WriteTo(byte[] buffer, int posn) + public int WriteTo(Span buffer, int posn) { NetConvert.ToBuffer((ushort)CauseCode, buffer, posn); NetConvert.ToBuffer(ERROR_CAUSE_LENGTH, buffer, posn + 2); @@ -360,15 +352,12 @@ public ushort GetErrorCauseLength(bool padded) return padded ? SctpPadding.PadTo4ByteBoundary(len) : len; } - public int WriteTo(byte[] buffer, int posn) + public int WriteTo(Span buffer, int posn) { var len = GetErrorCauseLength(true); NetConvert.ToBuffer((ushort)CauseCode, buffer, posn); NetConvert.ToBuffer(len, buffer, posn + 2); - if (NewAddressTLVs != null) - { - Buffer.BlockCopy(NewAddressTLVs, 0, buffer, posn + 4, NewAddressTLVs.Length); - } + NewAddressTLVs?.CopyTo(buffer.Slice(posn + 4)); return len; } } @@ -395,7 +384,7 @@ public ushort GetErrorCauseLength(bool padded) return padded ? SctpPadding.PadTo4ByteBoundary(len) : len; } - public int WriteTo(byte[] buffer, int posn) + public int WriteTo(Span buffer, int posn) { var len = GetErrorCauseLength(true); NetConvert.ToBuffer((ushort)CauseCode, buffer, posn); @@ -403,7 +392,7 @@ public int WriteTo(byte[] buffer, int posn) if (!string.IsNullOrEmpty(AbortReason)) { var reasonBuffer = Encoding.UTF8.GetBytes(AbortReason); - Buffer.BlockCopy(reasonBuffer, 0, buffer, posn + 4, reasonBuffer.Length); + reasonBuffer.CopyTo(buffer.Slice(posn + 4)); } return len; } @@ -432,7 +421,7 @@ public ushort GetErrorCauseLength(bool padded) return padded ? SctpPadding.PadTo4ByteBoundary(len) : len; } - public int WriteTo(byte[] buffer, int posn) + public int WriteTo(Span buffer, int posn) { var len = GetErrorCauseLength(true); NetConvert.ToBuffer((ushort)CauseCode, buffer, posn); @@ -440,7 +429,7 @@ public int WriteTo(byte[] buffer, int posn) if (!string.IsNullOrEmpty(AdditionalInformation)) { var reasonBuffer = Encoding.UTF8.GetBytes(AdditionalInformation); - Buffer.BlockCopy(reasonBuffer, 0, buffer, posn + 4, reasonBuffer.Length); + reasonBuffer.CopyTo(buffer.Slice(posn + 4)); } return len; } diff --git a/src/net/SCTP/Chunks/SctpErrorChunk.cs b/src/net/SCTP/Chunks/SctpErrorChunk.cs index c79a5ae62..819b92439 100644 --- a/src/net/SCTP/Chunks/SctpErrorChunk.cs +++ b/src/net/SCTP/Chunks/SctpErrorChunk.cs @@ -107,7 +107,7 @@ public override ushort GetChunkLength(bool padded) /// must have the required space already allocated. /// The position in the buffer to write to. /// The number of bytes, including padding, written to the buffer. - public override ushort WriteTo(byte[] buffer, int posn) + public override ushort WriteTo(Span buffer, int posn) { WriteChunkHeader(buffer, posn); if (ErrorCauses != null && ErrorCauses.Count > 0) @@ -126,7 +126,7 @@ public override ushort WriteTo(byte[] buffer, int posn) /// /// The buffer holding the serialised chunk. /// The position to start parsing at. - public static SctpErrorChunk ParseChunk(byte[] buffer, int posn, bool isAbort) + public static SctpErrorChunk ParseChunk(ReadOnlySpan buffer, int posn, bool isAbort) { var errorChunk = (isAbort) ? new SctpAbortChunk(false) : new SctpErrorChunk(); ushort chunkLen = errorChunk.ParseFirstWord(buffer, posn); @@ -138,7 +138,7 @@ public static SctpErrorChunk ParseChunk(byte[] buffer, int posn, bool isAbort) { bool stopProcessing = false; - foreach (var varParam in GetParameters(buffer, paramPosn, paramsBufferLength)) + GetParameters(buffer, paramPosn, paramsBufferLength, varParam => { switch (varParam.ParameterType) { @@ -220,9 +220,10 @@ public static SctpErrorChunk ParseChunk(byte[] buffer, int posn, bool isAbort) { logger.LogWarning($"SCTP unrecognised parameter {varParam.ParameterType} for chunk type {SctpChunkType.ERROR} " + "indicated no further chunks should be processed."); - break; + return false; } - } + return true; + }); } return errorChunk; diff --git a/src/net/SCTP/Chunks/SctpInitChunk.cs b/src/net/SCTP/Chunks/SctpInitChunk.cs index 9c6983e05..219bfb90a 100644 --- a/src/net/SCTP/Chunks/SctpInitChunk.cs +++ b/src/net/SCTP/Chunks/SctpInitChunk.cs @@ -286,7 +286,7 @@ public override ushort GetChunkLength(bool padded) /// must have the required space already allocated. /// The position in the buffer to write to. /// The number of bytes, including padding, written to the buffer. - public override ushort WriteTo(byte[] buffer, int posn) + public override ushort WriteTo(Span buffer, int posn) { WriteChunkHeader(buffer, posn); @@ -319,7 +319,7 @@ public override ushort WriteTo(byte[] buffer, int posn) /// /// The buffer holding the serialised chunk. /// The position to start parsing at. - public static SctpInitChunk ParseChunk(byte[] buffer, int posn) + public static SctpInitChunk ParseChunk(ReadOnlySpan buffer, int posn) { var initChunk = new SctpInitChunk(); ushort chunkLen = initChunk.ParseFirstWord(buffer, posn); @@ -339,7 +339,7 @@ public static SctpInitChunk ParseChunk(byte[] buffer, int posn) { bool stopProcessing = false; - foreach (var varParam in GetParameters(buffer, paramPosn, paramsBufferLength)) + GetParameters(buffer, paramPosn, paramsBufferLength, varParam => { switch (varParam.ParameterType) { @@ -399,9 +399,10 @@ public static SctpInitChunk ParseChunk(byte[] buffer, int posn) { logger.LogWarning($"SCTP unrecognised parameter {varParam.ParameterType} for chunk type {initChunk.KnownType} " + "indicated no further chunks should be processed."); - break; + return false; } - } + return true; + }); } return initChunk; diff --git a/src/net/SCTP/Chunks/SctpSackChunk.cs b/src/net/SCTP/Chunks/SctpSackChunk.cs index 313170db6..cb28c0111 100644 --- a/src/net/SCTP/Chunks/SctpSackChunk.cs +++ b/src/net/SCTP/Chunks/SctpSackChunk.cs @@ -17,6 +17,7 @@ // BSD 3-Clause "New" or "Revised" License, see included LICENSE.md file. //----------------------------------------------------------------------------- +using System; using System.Collections.Generic; using SIPSorcery.Sys; @@ -95,7 +96,7 @@ public override ushort GetChunkLength(bool padded) /// must have the required space already allocated. /// The position in the buffer to write to. /// The number of bytes, including padding, written to the buffer. - public override ushort WriteTo(byte[] buffer, int posn) + public override ushort WriteTo(Span buffer, int posn) { WriteChunkHeader(buffer, posn); @@ -129,7 +130,7 @@ public override ushort WriteTo(byte[] buffer, int posn) /// /// The buffer holding the serialised chunk. /// The position to start parsing at. - public static SctpSackChunk ParseChunk(byte[] buffer, int posn) + public static SctpSackChunk ParseChunk(ReadOnlySpan buffer, int posn) { var sackChunk = new SctpSackChunk(); ushort chunkLen = sackChunk.ParseFirstWord(buffer, posn); diff --git a/src/net/SCTP/Chunks/SctpShutdownChunk.cs b/src/net/SCTP/Chunks/SctpShutdownChunk.cs index 61cdb0a31..954cc3980 100644 --- a/src/net/SCTP/Chunks/SctpShutdownChunk.cs +++ b/src/net/SCTP/Chunks/SctpShutdownChunk.cs @@ -17,6 +17,7 @@ // BSD 3-Clause "New" or "Revised" License, see included LICENSE.md file. //----------------------------------------------------------------------------- +using System; using SIPSorcery.Sys; namespace SIPSorcery.Net @@ -67,7 +68,7 @@ public override ushort GetChunkLength(bool padded) /// must have the required space already allocated. /// The position in the buffer to write to. /// The number of bytes, including padding, written to the buffer. - public override ushort WriteTo(byte[] buffer, int posn) + public override ushort WriteTo(Span buffer, int posn) { WriteChunkHeader(buffer, posn); NetConvert.ToBuffer(CumulativeTsnAck.GetValueOrDefault(), buffer, posn + SCTP_CHUNK_HEADER_LENGTH); @@ -79,7 +80,7 @@ public override ushort WriteTo(byte[] buffer, int posn) /// /// The buffer holding the serialised chunk. /// The position to start parsing at. - public static SctpShutdownChunk ParseChunk(byte[] buffer, int posn) + public static SctpShutdownChunk ParseChunk(ReadOnlySpan buffer, int posn) { var shutdownChunk = new SctpShutdownChunk(); shutdownChunk.CumulativeTsnAck = NetConvert.ParseUInt32(buffer, posn + SCTP_CHUNK_HEADER_LENGTH); diff --git a/src/net/SCTP/Chunks/SctpTlvChunkParameter.cs b/src/net/SCTP/Chunks/SctpTlvChunkParameter.cs index 12f5e8f3c..8cf2a9310 100644 --- a/src/net/SCTP/Chunks/SctpTlvChunkParameter.cs +++ b/src/net/SCTP/Chunks/SctpTlvChunkParameter.cs @@ -157,7 +157,7 @@ public virtual ushort GetParameterLength(bool padded) /// /// The buffer to write the chunk parameter header to. /// The position in the buffer to write at. - protected void WriteParameterHeader(byte[] buffer, int posn) + protected void WriteParameterHeader(Span buffer, int posn) { NetConvert.ToBuffer(ParameterType, buffer, posn); NetConvert.ToBuffer(GetParameterLength(false), buffer, posn + 2); @@ -172,13 +172,13 @@ protected void WriteParameterHeader(byte[] buffer, int posn) /// must have the required space already allocated. /// The position in the buffer to write to. /// The number of bytes, including padding, written to the buffer. - public virtual int WriteTo(byte[] buffer, int posn) + public virtual int WriteTo(Span buffer, int posn) { WriteParameterHeader(buffer, posn); if (ParameterValue?.Length > 0) { - Buffer.BlockCopy(ParameterValue, 0, buffer, posn + SCTP_PARAMETER_HEADER_LENGTH, ParameterValue.Length); + ParameterValue.CopyTo(buffer.Slice(posn + SCTP_PARAMETER_HEADER_LENGTH)); } return GetParameterLength(true); @@ -201,7 +201,7 @@ public byte[] GetBytes() /// /// The buffer holding the serialised chunk parameter. /// The position in the buffer that indicates the start of the chunk parameter. - public ushort ParseFirstWord(byte[] buffer, int posn) + public ushort ParseFirstWord(ReadOnlySpan buffer, int posn) { ParameterType = NetConvert.ParseUInt16(buffer, posn); ushort paramLen = NetConvert.ParseUInt16(buffer, posn + 2); @@ -224,7 +224,7 @@ public ushort ParseFirstWord(byte[] buffer, int posn) /// The buffer holding the serialised TLV chunk parameter. /// The position to start parsing at. /// An SCTP TLV chunk parameter instance. - public static SctpTlvChunkParameter ParseTlvParameter(byte[] buffer, int posn) + public static SctpTlvChunkParameter ParseTlvParameter(ReadOnlySpan buffer, int posn) { if (buffer.Length < posn + SCTP_PARAMETER_HEADER_LENGTH) { @@ -236,8 +236,7 @@ public static SctpTlvChunkParameter ParseTlvParameter(byte[] buffer, int posn) if (paramLen > SCTP_PARAMETER_HEADER_LENGTH) { tlvParam.ParameterValue = new byte[paramLen - SCTP_PARAMETER_HEADER_LENGTH]; - Buffer.BlockCopy(buffer, posn + SCTP_PARAMETER_HEADER_LENGTH, tlvParam.ParameterValue, - 0, tlvParam.ParameterValue.Length); + buffer.Slice(posn + SCTP_PARAMETER_HEADER_LENGTH, tlvParam.ParameterValue.Length).CopyTo(tlvParam.ParameterValue); } return tlvParam; } diff --git a/src/net/SCTP/SctpAssociation.cs b/src/net/SCTP/SctpAssociation.cs index 4f43216fb..550d70d3a 100644 --- a/src/net/SCTP/SctpAssociation.cs +++ b/src/net/SCTP/SctpAssociation.cs @@ -707,8 +707,7 @@ private void SendInit() SetState(SctpAssociationState.CookieWait); - byte[] buffer = init.GetBytes(); - _sctpTransport.Send(ID, buffer, 0, buffer.Length); + SendPacket(init); _t1Init = new Timer(T1InitTimerExpired, init, T1_INIT_TIMER_MILLISECONDS, T1_INIT_TIMER_MILLISECONDS); } @@ -729,9 +728,7 @@ internal void SendChunk(SctpChunk chunk) pkt.AddChunk(chunk); - byte[] buffer = pkt.GetBytes(); - - _sctpTransport.Send(ID, buffer, 0, buffer.Length); + SendPacket(pkt); } } @@ -743,8 +740,7 @@ private void SendPacket(SctpPacket pkt) { if (!_wasAborted) { - byte[] buffer = pkt.GetBytes(); - _sctpTransport.Send(ID, buffer, 0, buffer.Length); + _sctpTransport.Send(ID, pkt); } } diff --git a/src/net/SCTP/SctpDataSender.cs b/src/net/SCTP/SctpDataSender.cs index 47dc0d573..b99877633 100644 --- a/src/net/SCTP/SctpDataSender.cs +++ b/src/net/SCTP/SctpDataSender.cs @@ -74,7 +74,7 @@ public class SctpDataSender private uint _initialTSN; private bool _gotFirstSACK; private bool _isStarted; - private bool _isClosed; + private Once _closed; private int _lastAckedDataChunkSize; private bool _inRetransmitMode; private bool _inFastRecoveryMode; @@ -143,6 +143,7 @@ public class SctpDataSender private Dictionary _streamSeqnums = new Dictionary(); public int MaxSendQueueCount => 128; +#warning this must be rewritten to use BlockingQueue /// /// Queue to hold SCTP frames that are waiting to be sent to the remote peer. /// @@ -393,7 +394,7 @@ public void StartSending() /// public void Close() { - _isClosed = true; + _closed.TryMarkOccurred(); } /// @@ -544,7 +545,7 @@ private void DoSend(object state) { logger.LogDebug($"SCTP association data send thread started for association {_associationID}."); - while (!_isClosed) + while (!_closed.HasOccurred) { var outstandingBytes = _outstandingBytes; // DateTime.Now calls have been a tiny bit expensive in the past so get a small saving by only diff --git a/src/net/SCTP/SctpHeader.cs b/src/net/SCTP/SctpHeader.cs index 569778d12..8c5611ae8 100644 --- a/src/net/SCTP/SctpHeader.cs +++ b/src/net/SCTP/SctpHeader.cs @@ -61,13 +61,25 @@ public void WriteToBuffer(byte[] buffer, int posn) NetConvert.ToBuffer(VerificationTag, buffer, posn + 4); } + /// + /// Serialises the header to a pre-allocated buffer. + /// + /// The buffer to write the SCTP header bytes to. It + /// must have the required space already allocated. + public readonly void WriteToBuffer(Span buffer) + { + NetConvert.ToBuffer(SourcePort, buffer, 0); + NetConvert.ToBuffer(DestinationPort, buffer, 2); + NetConvert.ToBuffer(VerificationTag, buffer, 4); + } + /// /// Parses the an SCTP header from a buffer. /// /// The buffer to parse the SCTP header from. /// The position in the buffer to start parsing the header from. /// A new SCTPHeaer instance. - public static SctpHeader Parse(byte[] buffer, int posn) + public static SctpHeader Parse(ReadOnlySpan buffer) { if (buffer.Length < SCTP_HEADER_LENGTH) { @@ -76,10 +88,10 @@ public static SctpHeader Parse(byte[] buffer, int posn) SctpHeader header = new SctpHeader(); - header.SourcePort = NetConvert.ParseUInt16(buffer, posn); - header.DestinationPort = NetConvert.ParseUInt16(buffer, posn + 2); - header.VerificationTag = NetConvert.ParseUInt32(buffer, posn + 4); - header.Checksum = NetConvert.ParseUInt32(buffer, posn + 8); + header.SourcePort = NetConvert.ParseUInt16(buffer); + header.DestinationPort = NetConvert.ParseUInt16(buffer.Slice(2)); + header.VerificationTag = NetConvert.ParseUInt32(buffer.Slice(4)); + header.Checksum = NetConvert.ParseUInt32(buffer.Slice(8)); return header; } diff --git a/src/net/SCTP/SctpPacket.cs b/src/net/SCTP/SctpPacket.cs index 9f68bb5cf..447246e55 100644 --- a/src/net/SCTP/SctpPacket.cs +++ b/src/net/SCTP/SctpPacket.cs @@ -54,6 +54,17 @@ public static uint Calculate(byte[] buffer, int offset, int length) } return crc ^ 0xffffffff; } + + public static uint Calculate(ReadOnlySpan span) + { + uint crc = ~0u; + while (span.Length > 0) + { + crc = _table[(crc ^ span[0]) & 0xff] ^ crc >> 8; + span = span.Slice(1); + } + return crc ^ 0xffffffff; + } } /// @@ -142,6 +153,32 @@ public byte[] GetBytes() return buffer; } + public int GetBytes(Span buffer) + { + int chunksLength = Chunks.Sum(x => x.GetChunkLength(true)); + int totalSize = SctpHeader.SCTP_HEADER_LENGTH + chunksLength; + if (buffer.Length < totalSize) + { + return -totalSize; + } + + buffer = buffer.Slice(0, totalSize); + + Header.WriteToBuffer(buffer); + + int writePosn = SctpHeader.SCTP_HEADER_LENGTH; + foreach (var chunk in Chunks) + { + writePosn += chunk.WriteTo(buffer, writePosn); + } + + NetConvert.ToBuffer(0U, buffer, CHECKSUM_BUFFER_POSITION); + uint checksum = CRC32C.Calculate(buffer); + NetConvert.ToBuffer(NetConvert.EndianFlip(checksum), buffer, CHECKSUM_BUFFER_POSITION); + + return totalSize; + } + /// /// Adds a new chunk to send with an outgoing packet. /// @@ -157,11 +194,11 @@ public void AddChunk(SctpChunk chunk) /// The buffer holding the serialised packet. /// The position in the buffer of the packet. /// The length of the serialised packet in the buffer. - public static SctpPacket Parse(byte[] buffer, int offset, int length) + public static SctpPacket Parse(ReadOnlySpan buffer) { var pkt = new SctpPacket(); - pkt.Header = SctpHeader.Parse(buffer, offset); - (pkt.Chunks, pkt.UnrecognisedChunks) = ParseChunks(buffer, offset, length); + pkt.Header = SctpHeader.Parse(buffer); + (pkt.Chunks, pkt.UnrecognisedChunks) = ParseChunks(buffer); return pkt; } @@ -173,12 +210,13 @@ public static SctpPacket Parse(byte[] buffer, int offset, int length) /// The position in the buffer of the packet. /// The length of the serialised packet in the buffer. /// The lsit of parsed chunks and a list of unrecognised chunks that were not de-serialised. - private static (List chunks, List unrecognisedChunks) ParseChunks(byte[] buffer, int offset, int length) + private static (List chunks, List unrecognisedChunks) ParseChunks(ReadOnlySpan buffer) { List chunks = new List(); List unrecognisedChunks = new List(); - int posn = offset + SctpHeader.SCTP_HEADER_LENGTH; + int posn = SctpHeader.SCTP_HEADER_LENGTH; + int length = buffer.Length; bool stop = false; @@ -200,12 +238,12 @@ private static (List chunks, List unrecognisedChunks) ParseCh break; case SctpUnrecognisedChunkActions.StopAndReport: stop = true; - unrecognisedChunks.Add(SctpChunk.CopyUnrecognisedChunk(buffer, posn)); + unrecognisedChunks.Add(SctpChunk.CopyUnrecognisedChunk(buffer, posn).ToArray()); break; case SctpUnrecognisedChunkActions.Skip: break; case SctpUnrecognisedChunkActions.SkipAndReport: - unrecognisedChunks.Add(SctpChunk.CopyUnrecognisedChunk(buffer, posn)); + unrecognisedChunks.Add(SctpChunk.CopyUnrecognisedChunk(buffer, posn).ToArray()); break; } } @@ -229,14 +267,16 @@ private static (List chunks, List unrecognisedChunks) ParseCh /// The start position in the buffer. /// The length of the packet in the buffer. /// True if the checksum was valid, false if not. - public static bool VerifyChecksum(byte[] buffer, int posn, int length) + public static bool VerifyChecksum(ReadOnlySpan bufferRO) { - uint origChecksum = NetConvert.ParseUInt32(buffer, posn + CHECKSUM_BUFFER_POSITION); - NetConvert.ToBuffer(0U, buffer, posn + CHECKSUM_BUFFER_POSITION); - uint calcChecksum = CRC32C.Calculate(buffer, posn, length); + uint origChecksum = NetConvert.ParseUInt32(bufferRO, CHECKSUM_BUFFER_POSITION); + Span buffer = stackalloc byte[bufferRO.Length]; + bufferRO.CopyTo(buffer); + NetConvert.ToBuffer(0U, buffer, CHECKSUM_BUFFER_POSITION); + uint calcChecksum = CRC32C.Calculate(buffer); // Put the original checksum back. - NetConvert.ToBuffer(origChecksum, buffer, posn + CHECKSUM_BUFFER_POSITION); + NetConvert.ToBuffer(origChecksum, buffer, CHECKSUM_BUFFER_POSITION); return origChecksum == NetConvert.EndianFlip(calcChecksum); } @@ -246,27 +286,22 @@ public static bool VerifyChecksum(byte[] buffer, int posn, int length) /// a pre-flight check to be carried out before de-serialising the whole buffer. /// /// The buffer holding the serialised packet. - /// The start position in the buffer. - /// The length of the packet in the buffer. /// The verification tag for the serialised SCTP packet. - public static uint GetVerificationTag(byte[] buffer, int posn, int length) + public static uint GetVerificationTag(ReadOnlySpan buffer) { - return NetConvert.ParseUInt32(buffer, posn + VERIFICATIONTAG_BUFFER_POSITION); + return NetConvert.ParseUInt32(buffer, VERIFICATIONTAG_BUFFER_POSITION); } /// /// Performs verification checks on a serialised SCTP packet. /// /// The buffer holding the serialised packet. - /// The start position in the buffer. - /// The length of the packet in the buffer. /// The required verification tag for the serialised /// packet. This should match the verification tag supplied by the remote party. /// True if the packet is valid, false if not. - public static bool IsValid(byte[] buffer, int posn, int length, uint requiredTag) + public static bool IsValid(ReadOnlySpan buffer, uint requiredTag) { - return GetVerificationTag(buffer, posn, length) == requiredTag && - VerifyChecksum(buffer, posn, length); + return GetVerificationTag(buffer) == requiredTag && VerifyChecksum(buffer); } } } diff --git a/src/net/SCTP/SctpTransport.cs b/src/net/SCTP/SctpTransport.cs index 06f227761..698063d57 100644 --- a/src/net/SCTP/SctpTransport.cs +++ b/src/net/SCTP/SctpTransport.cs @@ -93,7 +93,7 @@ public abstract class SctpTransport /// public virtual bool IsPortAgnostic => false; - public abstract void Send(string associationID, byte[] buffer, int offset, int length); + public abstract void Send(string associationID, ReadOnlySpan buffer); static SctpTransport() { @@ -131,8 +131,26 @@ protected void GotInit(SctpPacket initPacket, IPEndPoint remoteEndPoint) else { var initAckPacket = GetInitAck(initPacket, remoteEndPoint); - var buffer = initAckPacket.GetBytes(); - Send(null, buffer, 0, buffer.Length); + Send(null, initAckPacket); + } + } + + /// + /// Sends an SCTP packet to the remote peer. + /// + /// The packet to send. + internal void Send(string? ID, SctpPacket pkt) + { + Span span = stackalloc byte[4 * 1024]; + if (pkt.GetBytes(span) is { } size and >= 0) + { + Send(ID, span.Slice(0, size)); + } + else + { + System.Diagnostics.Debug.WriteLine("SCTP packet too large to send without allocation."); + byte[] buffer = pkt.GetBytes(); + Send(ID, buffer.AsSpan()); } } @@ -318,8 +336,7 @@ private void SendError( errorChunk.AddErrorCause(error); errorPacket.AddChunk(errorChunk); - var buffer = errorPacket.GetBytes(); - Send(null, buffer, 0, buffer.Length); + Send(null, errorPacket); } /// diff --git a/src/net/SCTP/SctpUdpTransport.cs b/src/net/SCTP/SctpUdpTransport.cs index 9daaa19fa..5d198e6b7 100644 --- a/src/net/SCTP/SctpUdpTransport.cs +++ b/src/net/SCTP/SctpUdpTransport.cs @@ -18,6 +18,7 @@ //----------------------------------------------------------------------------- using System; +using System.Buffers; using System.Collections.Concurrent; using System.Linq; using System.Net; @@ -65,17 +66,17 @@ public SctpUdpTransport(int udpEncapPort = 0, PortRange portRange = null) /// The local port the packet was received on. /// The remote end point the packet was received from. /// A buffer containing the packet. - private void OnEncapsulationSocketPacketReceived(UdpReceiver receiver, int localPort, IPEndPoint remoteEndPoint, byte[] packet) + private void OnEncapsulationSocketPacketReceived(UdpReceiver receiver, int localPort, IPEndPoint remoteEndPoint, ReadOnlySpan packet) { try { - if (!SctpPacket.VerifyChecksum(packet, 0, packet.Length)) + if (!SctpPacket.VerifyChecksum(packet)) { logger.LogWarning($"SCTP packet from UDP {remoteEndPoint} dropped due to invalid checksum."); } else { - var sctpPacket = SctpPacket.Parse(packet, 0, packet.Length); + var sctpPacket = SctpPacket.Parse(packet); // Process packet. if (sctpPacket.Header.VerificationTag == 0) @@ -134,11 +135,20 @@ private void OnEncapsulationSocketClosed(string reason) logger.LogInformation($"SCTP transport encapsulation receiver closed with reason: {reason}."); } - public override void Send(string associationID, byte[] buffer, int offset, int length) + public override void Send(string associationID, ReadOnlySpan span) { if (_associations.TryGetValue(associationID, out var assoc)) { - _udpEncapSocket.SendTo(buffer, offset, length, SocketFlags.None, assoc.Destination); + byte[] buffer = ArrayPool.Shared.Rent(span.Length); + span.CopyTo(buffer); + try + { + _udpEncapSocket.SendTo(buffer, 0, span.Length, SocketFlags.None, assoc.Destination); + } + finally + { + ArrayPool.Shared.Return(buffer); + } } } diff --git a/src/net/STUN/STUNAttributes/STUNAttribute.cs b/src/net/STUN/STUNAttributes/STUNAttribute.cs index d1159169e..e7f1c78e4 100644 --- a/src/net/STUN/STUNAttributes/STUNAttribute.cs +++ b/src/net/STUN/STUNAttributes/STUNAttribute.cs @@ -147,14 +147,9 @@ public STUNAttribute(STUNAttributeTypesEnum attributeType, ulong value) Value = NetConvert.GetBytes(value); } - public static bool TryParseMessageAttributes(List attributes, byte[] buffer, int startIndex, int endIndex) + public static bool TryParseMessageAttributes(List attributes, ReadOnlySpan buffer, int startIndex, int endIndex) { - if (buffer is null) - { - throw new ArgumentNullException(nameof(buffer)); - } - - if (buffer != null && buffer.Length > startIndex && buffer.Length >= endIndex) + if (buffer.Length > startIndex && buffer.Length >= endIndex) { int startAttIndex = startIndex; @@ -182,7 +177,7 @@ public static bool TryParseMessageAttributes(List attributes, byt else { stunAttributeValue = new byte[stunAttributeLength]; - Buffer.BlockCopy(buffer, startAttIndex + 4, stunAttributeValue, 0, stunAttributeLength); + buffer.Slice(startAttIndex + 4, stunAttributeLength).CopyTo(stunAttributeValue); } } diff --git a/src/net/STUN/STUNHeader.cs b/src/net/STUN/STUNHeader.cs index 3633b532a..9ea66ba1a 100644 --- a/src/net/STUN/STUNHeader.cs +++ b/src/net/STUN/STUNHeader.cs @@ -72,6 +72,7 @@ //----------------------------------------------------------------------------- using System; +using System.Buffers.Binary; using System.Text; using SIPSorcery.Sys; @@ -164,30 +165,24 @@ public static STUNHeader ParseSTUNHeader(byte[] buffer) return ParseSTUNHeader(new ArraySegment(buffer, 0, buffer.Length)); } - public static STUNHeader ParseSTUNHeader(ArraySegment bufferSegment) + public static STUNHeader ParseSTUNHeader(ReadOnlySpan bufferSegment) { - var startIndex = bufferSegment.Offset; - if ((bufferSegment.Array[startIndex] & STUN_INITIAL_BYTE_MASK) != 0) + var startIndex = 0; + if ((bufferSegment[startIndex] & STUN_INITIAL_BYTE_MASK) != 0) { throw new ApplicationException("The STUN header did not begin with 0x00."); } - if (bufferSegment != null && bufferSegment.Count > 0 && bufferSegment.Count >= STUN_HEADER_LENGTH) + if (bufferSegment != null && bufferSegment.Length > 0 && bufferSegment.Length >= STUN_HEADER_LENGTH) { STUNHeader stunHeader = new STUNHeader(); - UInt16 stunTypeValue = BitConverter.ToUInt16(bufferSegment.Array, startIndex); - UInt16 stunMessageLength = BitConverter.ToUInt16(bufferSegment.Array, startIndex + 2);; - - if (BitConverter.IsLittleEndian) - { - stunTypeValue = NetConvert.DoReverseEndian(stunTypeValue); - stunMessageLength = NetConvert.DoReverseEndian(stunMessageLength); - } + UInt16 stunTypeValue = BinaryPrimitives.ReadUInt16BigEndian(bufferSegment.Slice(startIndex)); + UInt16 stunMessageLength = BinaryPrimitives.ReadUInt16BigEndian(bufferSegment.Slice(startIndex + 2)); stunHeader.MessageType = STUNMessageTypes.GetSTUNMessageTypeForId(stunTypeValue); stunHeader.MessageLength = stunMessageLength; - Buffer.BlockCopy(bufferSegment.Array, startIndex + 8, stunHeader.TransactionId, 0, TRANSACTION_ID_LENGTH); + bufferSegment.Slice(startIndex + 8, TRANSACTION_ID_LENGTH).CopyTo(stunHeader.TransactionId); return stunHeader; } diff --git a/src/net/STUN/STUNMessage.cs b/src/net/STUN/STUNMessage.cs index cfd712d3e..f245eaf17 100644 --- a/src/net/STUN/STUNMessage.cs +++ b/src/net/STUN/STUNMessage.cs @@ -90,12 +90,12 @@ public void AddXORAddressAttribute(STUNAttributeTypesEnum addressType, IPAddress Attributes.Add(xorAddressAttribute); } - public static STUNMessage ParseSTUNMessage(byte[] buffer, int bufferLength) + public static STUNMessage ParseSTUNMessage(ReadOnlySpan buffer, int bufferLength) { if (buffer != null && buffer.Length > 0 && buffer.Length >= bufferLength) { STUNMessage stunMessage = new STUNMessage(); - stunMessage._receivedBuffer = buffer.Take(bufferLength).ToArray(); + stunMessage._receivedBuffer = buffer.Slice(0, bufferLength).ToArray(); stunMessage.Header = STUNHeader.ParseSTUNHeader(buffer); if (stunMessage.Header.MessageLength > 0) @@ -108,7 +108,7 @@ public static STUNMessage ParseSTUNMessage(byte[] buffer, int bufferLength) // Check fingerprint. var fingerprintAttribute = stunMessage.Attributes.Last(); - var input = buffer.Take(buffer.Length - STUNAttribute.STUNATTRIBUTE_HEADER_LENGTH - FINGERPRINT_ATTRIBUTE_CRC32_LENGTH).ToArray(); + var input = buffer.Slice(0, buffer.Length - STUNAttribute.STUNATTRIBUTE_HEADER_LENGTH - FINGERPRINT_ATTRIBUTE_CRC32_LENGTH); uint crc = Crc32.Compute(input) ^ FINGERPRINT_XOR; byte[] fingerPrint = (BitConverter.IsLittleEndian) ? BitConverter.GetBytes(NetConvert.DoReverseEndian(crc)) : BitConverter.GetBytes(crc); diff --git a/src/net/WebRTC/RTCPeerConnection.cs b/src/net/WebRTC/RTCPeerConnection.cs index 938e20164..726e020b2 100644 --- a/src/net/WebRTC/RTCPeerConnection.cs +++ b/src/net/WebRTC/RTCPeerConnection.cs @@ -1191,7 +1191,7 @@ void AddIceCandidates(SDPMediaAnnouncement announcement) /// The local port on the RTP socket that received the packet. /// The remote end point the packet was received from. /// The data received. - private void OnRTPDataReceived(int localPort, IPEndPoint remoteEP, byte[] buffer) + private void OnRTPDataReceived(int localPort, IPEndPoint remoteEP, ReadOnlySpan buffer) { //logger.LogDebug($"RTP channel received a packet from {remoteEP}, {buffer?.Length} bytes."); @@ -1200,11 +1200,11 @@ private void OnRTPDataReceived(int localPort, IPEndPoint remoteEP, byte[] buffer // Because DTLS packets can be fragmented and RTP/RTCP should never be use the RTP/RTCP // prefix to distinguish. - if (buffer?.Length > 0) + if (buffer.Length > 0) { try { - if (buffer?.Length > RTPHeader.MIN_HEADER_LEN && buffer[0] >= 128 && buffer[0] <= 191) + if (buffer.Length > RTPHeader.MIN_HEADER_LEN && buffer[0] >= 128 && buffer[0] <= 191) { // RTP/RTCP packet. base.OnReceive(localPort, remoteEP, buffer); diff --git a/src/net/WebRTC/RTCSctpTransport.cs b/src/net/WebRTC/RTCSctpTransport.cs index 61b9d1ff8..c81aff7fe 100644 --- a/src/net/WebRTC/RTCSctpTransport.cs +++ b/src/net/WebRTC/RTCSctpTransport.cs @@ -16,6 +16,7 @@ //----------------------------------------------------------------------------- using System; +using System.Buffers; using System.Linq; using System.Net.Sockets; using System.Threading; @@ -282,13 +283,13 @@ private void DoReceive(object state) } else if (bytesRead > 0) { - if (!SctpPacket.VerifyChecksum(recvBuffer, 0, bytesRead)) + if (!SctpPacket.VerifyChecksum(recvBuffer.AsSpan().Slice(0, bytesRead))) { logger.LogWarning($"SCTP packet received on DTLS transport dropped due to invalid checksum."); } else { - var pkt = SctpPacket.Parse(recvBuffer, 0, bytesRead); + var pkt = SctpPacket.Parse(recvBuffer.AsSpan(0, bytesRead)); if (pkt.Chunks.Any(x => x.KnownType == SctpChunkType.INIT)) { @@ -362,14 +363,11 @@ private void DoReceive(object state) /// to the remote party. /// /// Not used for the DTLS transport. - /// The buffer containing the data to send. - /// The position in the buffer to send from. - /// The number of bytes to send. - public override void Send(string associationID, byte[] buffer, int offset, int length) + public override void Send(string associationID, ReadOnlySpan data) { - if (length > maxMessageSize) + if (data.Length > maxMessageSize) { - throw new ApplicationException($"RTCSctpTransport was requested to send data of length {length} " + + throw new ApplicationException($"RTCSctpTransport was requested to send data of length {data.Length} " + $" that exceeded the maximum allowed message size of {maxMessageSize}."); } @@ -377,7 +375,20 @@ public override void Send(string associationID, byte[] buffer, int offset, int l { lock (transport) { - transport.Send(buffer, offset, length); +#if NET6_0_OR_GREATER + transport.Send(data); +#else + byte[] tmp = ArrayPool.Shared.Rent(data.Length); + try + { + data.CopyTo(tmp); + transport.Send(tmp, 0, data.Length); + } + finally + { + ArrayPool.Shared.Return(tmp); + } +#endif } } } diff --git a/src/sys/CRC32.cs b/src/sys/CRC32.cs index 52df73bd5..861a13822 100644 --- a/src/sys/CRC32.cs +++ b/src/sys/CRC32.cs @@ -36,7 +36,7 @@ public override void Initialize() protected override void HashCore(byte[] buffer, int start, int length) { - hash = CalculateHash(table, hash, buffer, start, length); + hash = CalculateHash(table, hash, buffer.AsSpan().Slice(start, length)); } protected override byte[] HashFinal() @@ -51,19 +51,19 @@ public override int HashSize get { return 32; } } - public static UInt32 Compute(byte[] buffer) + public static UInt32 Compute(ReadOnlySpan buffer) { - return ~CalculateHash(InitializeTable(DefaultPolynomial), DefaultSeed, buffer, 0, buffer.Length); + return ~CalculateHash(InitializeTable(DefaultPolynomial), DefaultSeed, buffer); } public static UInt32 Compute(UInt32 seed, byte[] buffer) { - return ~CalculateHash(InitializeTable(DefaultPolynomial), seed, buffer, 0, buffer.Length); + return ~CalculateHash(InitializeTable(DefaultPolynomial), seed, buffer); } public static UInt32 Compute(UInt32 polynomial, UInt32 seed, byte[] buffer) { - return ~CalculateHash(InitializeTable(polynomial), seed, buffer, 0, buffer.Length); + return ~CalculateHash(InitializeTable(polynomial), seed, buffer); } private static UInt32[] InitializeTable(UInt32 polynomial) @@ -99,10 +99,10 @@ private static UInt32[] InitializeTable(UInt32 polynomial) return createTable; } - private static UInt32 CalculateHash(UInt32[] table, UInt32 seed, byte[] buffer, int start, int size) + private static UInt32 CalculateHash(UInt32[] table, UInt32 seed, ReadOnlySpan buffer) { UInt32 crc = seed; - for (int i = start; i < size; i++) + for (int i = 0; i < buffer.Length; i++) { unchecked { diff --git a/src/sys/MemoryExtensions.cs b/src/sys/MemoryExtensions.cs new file mode 100644 index 000000000..c246b93fd --- /dev/null +++ b/src/sys/MemoryExtensions.cs @@ -0,0 +1,15 @@ +using System; + +namespace SIPSorcery.Sys +{ + static class MemoryExtensions + { + public static unsafe string ToString(this ReadOnlySpan buffer, System.Text.Encoding encoding) + { + fixed (byte* ptr = buffer) + { + return encoding.GetString(ptr, buffer.Length); + } + } + } +} diff --git a/src/sys/Net/NetConvert.cs b/src/sys/Net/NetConvert.cs index 8c223f9d4..9907b41c6 100644 --- a/src/sys/Net/NetConvert.cs +++ b/src/sys/Net/NetConvert.cs @@ -14,6 +14,7 @@ //----------------------------------------------------------------------------- using System; +using System.Buffers.Binary; using System.Linq; namespace SIPSorcery.Sys @@ -54,6 +55,26 @@ public static ushort ParseUInt16(byte[] buffer, int posn) return (ushort)(buffer[posn] << 8 | buffer[posn + 1]); } + /// + /// Parse a UInt16 from a network buffer using network byte order. + /// + /// The buffer to parse the value from. + /// The position in the buffer to start the parse from. + /// A UInt16 value. + public static ushort ParseUInt16(ReadOnlySpan buffer, int posn) + { + return (ushort)(buffer[posn] << 8 | buffer[posn + 1]); + } + + /// + /// Parse a UInt16 from a network buffer using network byte order. + /// + /// The buffer to parse the value from. + public static ushort ParseUInt16(ReadOnlySpan buffer) + { + return (ushort)(buffer[0] << 8 | buffer[1]); + } + /// /// Parse a UInt32 from a network buffer using network byte order. /// @@ -65,6 +86,28 @@ public static uint ParseUInt32(byte[] buffer, int posn) return (uint)(buffer[posn] << 24 | buffer[posn + 1] << 16 | buffer[posn + 2] << 8 | buffer[posn + 3]); } + /// + /// Parse a UInt32 from a network buffer using network byte order. + /// + /// The buffer to parse the value from. + /// The position in the buffer to start the parse from. + /// A UInt32 value. + public static uint ParseUInt32(ReadOnlySpan buffer, int posn) + { + return (uint)(buffer[posn] << 24 | buffer[posn + 1] << 16 | buffer[posn + 2] << 8 | buffer[posn + 3]); + } + + /// + /// Parse a UInt32 from a network buffer using network byte order. + /// + /// The buffer to parse the value from. + /// The position in the buffer to start the parse from. + /// A UInt32 value. + public static uint ParseUInt32(ReadOnlySpan buffer) + { + return (uint)(buffer[0] << 24 | buffer[1] << 16 | buffer[2] << 8 | buffer[3]); + } + /// /// Parse a UInt64 from a network buffer using network byte order. /// @@ -101,6 +144,17 @@ public static void ToBuffer(ushort val, byte[] buffer, int posn) buffer[posn + 1] = (byte)val; } + /// + /// Writes a UInt16 value to a network buffer using network byte order. + /// + /// The value to write to the buffer. + /// The buffer to write the value to. + /// The start position in the buffer to write the value at. + public static void ToBuffer(ushort val, Span buffer, int posn) + { + BinaryPrimitives.WriteUInt16BigEndian(buffer.Slice(posn), val); + } + /// /// Get a buffer representing the unsigned 16 bit integer in network /// byte (big endian) order. @@ -133,6 +187,17 @@ public static void ToBuffer(uint val, byte[] buffer, int posn) buffer[posn + 3] = (byte)val; } + /// + /// Writes a UInt16 value to a network buffer using network byte order. + /// + /// The value to write to the buffer. + /// The buffer to write the value to. + /// The start position in the buffer to write the value at. + public static void ToBuffer(uint val, Span buffer, int posn) + { + BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(posn), val); + } + /// /// Get a buffer representing the 32 bit unsigned integer in network /// byte (big endian) order. diff --git a/src/sys/Once.cs b/src/sys/Once.cs new file mode 100644 index 000000000..b10ef7320 --- /dev/null +++ b/src/sys/Once.cs @@ -0,0 +1,36 @@ +using System; +using System.Threading; + +namespace SIPSorcery.Sys +{ + /// + /// A thread-safe struct that represents a one-time event. + /// + struct Once + { + int occured; + + /// + /// Gets a value indicating whether the event has occurred or not. + /// + public bool HasOccurred => Interlocked.CompareExchange(ref this.occured, 0, 0) != 0; + + /// + /// Tries to mark the event as occurred and returns true if successful. + /// Returns false if the even has already been marked. + /// + public bool TryMarkOccurred() => Interlocked.CompareExchange(ref this.occured, 1, comparand: 0) == 0; + + /// + /// Marks the event as occurred and throws if it has already occurred. + /// + /// If the event has already occurred. + public void MarkOccurred() + { + if (!this.TryMarkOccurred()) + { + throw new InvalidOperationException("Can only be called once"); + } + } + } +} diff --git a/src/sys/TypeExtensions.cs b/src/sys/TypeExtensions.cs index b78c422e7..dbbad2f91 100644 --- a/src/sys/TypeExtensions.cs +++ b/src/sys/TypeExtensions.cs @@ -115,12 +115,17 @@ public static string Slice(this string s, char startDelimiter, char endDelimeter } } + public static string HexStr(this ReadOnlySpan buffer, char? separator = null) + { + return HexStr(buffer, buffer.Length, separator); + } + public static string HexStr(this byte[] buffer, char? separator = null) { - return buffer.HexStr(buffer.Length, separator); + return HexStr(buffer, buffer.Length, separator); } - public static string HexStr(this byte[] buffer, int length, char? separator = null) + public static string HexStr(this ReadOnlySpan buffer, int length, char? separator = null) { string rv = string.Empty; From 90e09e4cd5e3afc3c12f2698d9b41f7829986030 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Wed, 10 Jan 2024 13:55:12 -0800 Subject: [PATCH 18/88] use Environment.TickCount instead of DateTime for timestamping --- src/net/SCTP/Chunks/SctpDataChunk.cs | 11 +++++++++-- src/net/SCTP/SctpDataSender.cs | 10 +++++----- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/net/SCTP/Chunks/SctpDataChunk.cs b/src/net/SCTP/Chunks/SctpDataChunk.cs index 67eaf5a88..f47f73f3e 100644 --- a/src/net/SCTP/Chunks/SctpDataChunk.cs +++ b/src/net/SCTP/Chunks/SctpDataChunk.cs @@ -18,7 +18,7 @@ //----------------------------------------------------------------------------- using System; -using System.Buffers.Binary; +using System.Diagnostics; using SIPSorcery.Sys; namespace SIPSorcery.Net @@ -84,8 +84,15 @@ public class SctpDataChunk : SctpChunk /// public byte[] UserData; + internal struct Timestamp + { + readonly long ticks; + Timestamp(long ticks) => this.ticks = ticks; + public readonly double Milliseconds => ticks / (Stopwatch.Frequency / 1000.0); + public static Timestamp Now => new(Stopwatch.GetTimestamp()); + } // These properties are used by the data sender. - internal DateTime LastSentAt; + internal Timestamp LastSentAt; internal int SendCount; private SctpDataChunk() diff --git a/src/net/SCTP/SctpDataSender.cs b/src/net/SCTP/SctpDataSender.cs index b99877633..212c8df59 100644 --- a/src/net/SCTP/SctpDataSender.cs +++ b/src/net/SCTP/SctpDataSender.cs @@ -550,7 +550,7 @@ private void DoSend(object state) var outstandingBytes = _outstandingBytes; // DateTime.Now calls have been a tiny bit expensive in the past so get a small saving by only // calling once per loop. - DateTime now = DateTime.Now; + var now = SctpDataChunk.Timestamp.Now; int burstSize = (_inRetransmitMode || _inFastRecoveryMode || _congestionWindow < outstandingBytes || _receiverWindow == 0) ? 1 : MAX_BURST; int chunksSent = 0; @@ -588,10 +588,10 @@ private void DoSend(object state) if (chunksSent < burstSize && _unconfirmedChunks.Count > 0) { foreach (var chunk in _unconfirmedChunks.Values - .Where(x => now.Subtract(x.LastSentAt).TotalMilliseconds > (_hasRoundTripTime ? _rto : _rtoInitialMilliseconds)) + .Where(x => now.Milliseconds - x.LastSentAt.Milliseconds > (_hasRoundTripTime ? _rto : _rtoInitialMilliseconds)) .Take(burstSize - chunksSent)) { - chunk.LastSentAt = DateTime.Now; + chunk.LastSentAt = SctpDataChunk.Timestamp.Now; chunk.SendCount += 1; logger.LogTrace($"SCTP retransmitting data chunk for TSN {chunk.TSN}, data length {chunk.UserData.Length}, " + @@ -627,7 +627,7 @@ private void DoSend(object state) { while (chunksSent < burstSize && _sendQueue.TryDequeue(out var dataChunk)) { - dataChunk.LastSentAt = DateTime.Now; + dataChunk.LastSentAt = SctpDataChunk.Timestamp.Now; dataChunk.SendCount = 1; logger.LogTrace($"SCTP sending data chunk for TSN {dataChunk.TSN}, data length {dataChunk.UserData.Length}, " + @@ -696,7 +696,7 @@ private void UpdateRoundTripTime(SctpDataChunk acknowledgedChunk) return; } - var rttMilliseconds = (DateTime.Now - acknowledgedChunk.LastSentAt).TotalMilliseconds; + var rttMilliseconds = SctpDataChunk.Timestamp.Now.Milliseconds - acknowledgedChunk.LastSentAt.Milliseconds; if (!_hasRoundTripTime) { From 582b3a22ff9a4507ed74d97d4be0f1e389e9f062 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Thu, 11 Jan 2024 16:10:29 -0800 Subject: [PATCH 19/88] minor performance improvements --- src/net/ICE/RtpIceChannel.cs | 4 ++-- src/net/SCTP/SctpDataSender.cs | 11 ++++++----- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/net/ICE/RtpIceChannel.cs b/src/net/ICE/RtpIceChannel.cs index 94b636de8..ca7c17806 100644 --- a/src/net/ICE/RtpIceChannel.cs +++ b/src/net/ICE/RtpIceChannel.cs @@ -742,7 +742,7 @@ public void StartGathering() logger.LogDebug($"RTP ICE Channel discovered {_candidates.Count} local candidates."); - if (_iceServerConnections?.Count > 0) + if (_iceServerConnections?.IsEmpty == false) { InitialiseIceServers(_iceServers); _processIceServersTimer = new Timer(CheckIceServers, null, 0, Ta); @@ -2215,7 +2215,7 @@ private ChecklistEntry GetChecklistEntryForStunResponse(byte[] transactionID) /// If found a matching state object or null if not. private IceServer GetIceServerForTransactionID(byte[] transactionID) { - if (_iceServerConnections == null || _iceServerConnections.Count == 0) + if (_iceServerConnections == null || _iceServerConnections.IsEmpty) { return null; } diff --git a/src/net/SCTP/SctpDataSender.cs b/src/net/SCTP/SctpDataSender.cs index 212c8df59..6b4b94678 100644 --- a/src/net/SCTP/SctpDataSender.cs +++ b/src/net/SCTP/SctpDataSender.cs @@ -558,7 +558,7 @@ private void DoSend(object state) //logger.LogTrace($"SCTP sender burst size {burstSize}, in retransmit mode {_inRetransmitMode}, cwnd {_congestionWindow}, arwnd {_receiverWindow}."); // Missing chunks from a SACK gap report take priority. - if (_missingChunks.Count > 0) + if (!_missingChunks.IsEmpty) { foreach (var missing in _missingChunks) { @@ -585,9 +585,10 @@ private void DoSend(object state) } // Check if there are any unconfirmed transactions that are due for a retransmit. - if (chunksSent < burstSize && _unconfirmedChunks.Count > 0) + if (chunksSent < burstSize && !_unconfirmedChunks.IsEmpty) { - foreach (var chunk in _unconfirmedChunks.Values + foreach (var chunk in _unconfirmedChunks + .Select(kv => kv.Value) .Where(x => now.Milliseconds - x.LastSentAt.Milliseconds > (_hasRoundTripTime ? _rto : _rtoInitialMilliseconds)) .Take(burstSize - chunksSent)) { @@ -661,7 +662,7 @@ private void DoSend(object state) /// private int GetSendWaitMilliseconds() { - if (_sendQueue.Count > 0 || _missingChunks.Count > 0) + if (!_sendQueue.IsEmpty || !_missingChunks.IsEmpty) { if (_receiverWindow > 0 && _congestionWindow > _outstandingBytes) { @@ -672,7 +673,7 @@ private int GetSendWaitMilliseconds() return _rtoMinimumMilliseconds; } } - else if (_unconfirmedChunks.Count > 0) + else if (!_unconfirmedChunks.IsEmpty) { return (int)(_hasRoundTripTime ? _rto : _rtoInitialMilliseconds); } From d4a2e8889518158dfc5b574e10e1c51c2d412ced Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Thu, 11 Jan 2024 16:12:30 -0800 Subject: [PATCH 20/88] reduce allocations across the project and use SSE for CRC32C when available --- .../DataChannelBandwidth/Program.cs | 3 +- src/net/RTP/RTPChannel.cs | 52 +++++++------- src/net/SCTP/Chunks/SctpChunk.cs | 12 ++-- src/net/SCTP/SctpPacket.cs | 68 +++++++++++-------- src/sys/EnumExtensions.cs | 17 +++++ 5 files changed, 93 insertions(+), 59 deletions(-) create mode 100644 src/sys/EnumExtensions.cs diff --git a/examples/WebRTCScenarios/DataChannelBandwidth/Program.cs b/examples/WebRTCScenarios/DataChannelBandwidth/Program.cs index 66c09c616..096c64244 100644 --- a/examples/WebRTCScenarios/DataChannelBandwidth/Program.cs +++ b/examples/WebRTCScenarios/DataChannelBandwidth/Program.cs @@ -59,7 +59,8 @@ long recvS = Interlocked.Read(ref serverReceived); double rateC = recvC / 1024 / 1024 / stopwatch.Elapsed.TotalSeconds; double rateS = recvS / 1024 / 1024 / stopwatch.Elapsed.TotalSeconds; - Console.Title = $"client: {rateC:F1}MB/s server: {rateS:F1}MB/s"; + // almost 2000 MB/s with TCP over loopback + Console.Title = $"client: {rateC:F1}MB/s server: {rateS:F1}MB/s total: {rateC + rateS:F1}MB/s"; Thread.Sleep(1000); } diff --git a/src/net/RTP/RTPChannel.cs b/src/net/RTP/RTPChannel.cs index a442e92c8..fdcb1f9fc 100644 --- a/src/net/RTP/RTPChannel.cs +++ b/src/net/RTP/RTPChannel.cs @@ -50,11 +50,11 @@ public class UdpReceiver protected static ILogger logger = Log.Logger; protected readonly Socket m_socket; - protected byte[] m_recvBuffer; + protected readonly byte[] m_recvBuffer; protected bool m_isClosed; protected bool m_isRunningReceive; - protected IPEndPoint m_localEndPoint; - protected AddressFamily m_addressFamily; + protected readonly IPEndPoint m_localEndPoint; + protected readonly AddressFamily m_addressFamily; public virtual bool IsClosed { @@ -104,8 +104,11 @@ public UdpReceiver(Socket socket, int mtu = RECEIVE_BUFFER_SIZE) m_localEndPoint = m_socket.LocalEndPoint as IPEndPoint; m_recvBuffer = new byte[mtu]; m_addressFamily = m_socket.LocalEndPoint.AddressFamily; + endReceiveFrom = EndReceiveFrom; } + static readonly IPEndPoint IPv4AnyEndPoint = new(IPAddress.Any, 0); + static readonly IPEndPoint IPv6AnyEndPoint = new(IPAddress.IPv6Any, 0); /// /// Starts the receive. This method returns immediately. An event will be fired in the corresponding "End" event to /// return any data received. @@ -125,8 +128,8 @@ public virtual void BeginReceiveFrom() try { m_isRunningReceive = true; - EndPoint recvEndPoint = m_addressFamily == AddressFamily.InterNetwork ? new IPEndPoint(IPAddress.Any, 0) : new IPEndPoint(IPAddress.IPv6Any, 0); - m_socket.BeginReceiveFrom(m_recvBuffer, 0, m_recvBuffer.Length, SocketFlags.None, ref recvEndPoint, EndReceiveFrom, null); + EndPoint recvEndPoint = m_addressFamily == AddressFamily.InterNetwork ? IPv4AnyEndPoint : IPv6AnyEndPoint; + m_socket.BeginReceiveFrom(m_recvBuffer, 0, m_recvBuffer.Length, SocketFlags.None, ref recvEndPoint, endReceiveFrom, null); } // Thrown when socket is closed. Can be safely ignored. // This exception can be thrown in response to an ICMP packet. The problem is the ICMP packet can be a false positive. @@ -153,6 +156,7 @@ public virtual void BeginReceiveFrom() } } + readonly AsyncCallback endReceiveFrom; /// /// Handler for end of the begin receive call. /// @@ -164,7 +168,7 @@ protected virtual void EndReceiveFrom(IAsyncResult ar) // When socket is closed the object will be disposed of in the middle of a receive. if (!m_isClosed) { - EndPoint remoteEP = m_addressFamily == AddressFamily.InterNetwork ? new IPEndPoint(IPAddress.Any, 0) : new IPEndPoint(IPAddress.IPv6Any, 0); + EndPoint remoteEP = m_addressFamily == AddressFamily.InterNetwork ? IPv4AnyEndPoint : IPv6AnyEndPoint; int bytesRead = m_socket.EndReceiveFrom(ar, ref remoteEP); if (bytesRead > 0) @@ -193,24 +197,21 @@ protected virtual void EndReceiveFrom(IAsyncResult ar) // It also avoids the situation where if the application cannot keep up with the network then BeginReceiveFrom // will be called synchronously (if data is available it calls the callback method immediately) which can // create a very nasty stack. - if (!m_isClosed && m_socket.Available > 0) + while (!m_isClosed && m_socket.Available > 0) { - while (!m_isClosed && m_socket.Available > 0) - { - EndPoint remoteEP = m_addressFamily == AddressFamily.InterNetwork ? new IPEndPoint(IPAddress.Any, 0) : new IPEndPoint(IPAddress.IPv6Any, 0); - int bytesReadSync = m_socket.ReceiveFrom(m_recvBuffer, 0, m_recvBuffer.Length, SocketFlags.None, ref remoteEP); + EndPoint remoteEP = m_addressFamily == AddressFamily.InterNetwork ? IPv4AnyEndPoint : IPv6AnyEndPoint; + int bytesReadSync = m_socket.ReceiveFrom(m_recvBuffer, 0, m_recvBuffer.Length, SocketFlags.None, ref remoteEP); - if (bytesReadSync > 0) - { - byte[] packetBufferSync = new byte[bytesReadSync]; - // TODO: When .NET Framework support is dropped switch to using a slice instead of a copy. - Buffer.BlockCopy(m_recvBuffer, 0, packetBufferSync, 0, bytesReadSync); - CallOnPacketReceivedCallback(m_localEndPoint.Port, remoteEP as IPEndPoint, packetBufferSync); - } - else - { - break; - } + if (bytesReadSync > 0) + { + byte[] packetBufferSync = new byte[bytesReadSync]; + // TODO: When .NET Framework support is dropped switch to using a slice instead of a copy. + Buffer.BlockCopy(m_recvBuffer, 0, packetBufferSync, 0, bytesReadSync); + CallOnPacketReceivedCallback(m_localEndPoint.Port, remoteEP as IPEndPoint, packetBufferSync); + } + else + { + break; } } } @@ -281,6 +282,7 @@ public enum RTPChannelSocketsEnum /// public class RTPChannel : IDisposable { + private static readonly bool isMono = Type.GetType("Mono.Runtime") != null; private static ILogger logger = Log.Logger; protected UdpReceiver m_rtpReceiver; private Socket m_controlSocket; @@ -381,6 +383,7 @@ public RTPChannel(bool createControlSocket, IPAddress bindAddress, int bindPort RTPPort = RTPLocalEndPoint.Port; ControlLocalEndPoint = (m_controlSocket != null) ? m_controlSocket.LocalEndPoint as IPEndPoint : null; ControlPort = (m_controlSocket != null) ? ControlLocalEndPoint.Port : 0; + endSendTo = EndSendTo; } /// @@ -510,7 +513,7 @@ public virtual SocketError Send(RTPChannelSocketsEnum sendOn, IPEndPoint dstEndP } //Prevent Send to IPV4 while socket is IPV6 (Mono Error) - if (dstEndPoint.AddressFamily == AddressFamily.InterNetwork && sendSocket.AddressFamily != dstEndPoint.AddressFamily) + if (isMono && dstEndPoint.AddressFamily == AddressFamily.InterNetwork && sendSocket.AddressFamily != dstEndPoint.AddressFamily) { dstEndPoint = new IPEndPoint(dstEndPoint.Address.MapToIPv6(), dstEndPoint.Port); } @@ -521,7 +524,7 @@ public virtual SocketError Send(RTPChannelSocketsEnum sendOn, IPEndPoint dstEndP m_rtpReceiver.BeginReceiveFrom(); } - sendSocket.BeginSendTo(buffer, 0, buffer.Length, SocketFlags.None, dstEndPoint, EndSendTo, sendSocket); + sendSocket.BeginSendTo(buffer, 0, buffer.Length, SocketFlags.None, dstEndPoint, endSendTo, sendSocket); return SocketError.Success; } catch (ObjectDisposedException) // Thrown when socket is closed. Can be safely ignored. @@ -540,6 +543,7 @@ public virtual SocketError Send(RTPChannelSocketsEnum sendOn, IPEndPoint dstEndP } } + readonly AsyncCallback endSendTo; /// /// Ends an async send on one of the channel's sockets. /// diff --git a/src/net/SCTP/Chunks/SctpChunk.cs b/src/net/SCTP/Chunks/SctpChunk.cs index fc51a8e89..bfb5d1c2d 100644 --- a/src/net/SCTP/Chunks/SctpChunk.cs +++ b/src/net/SCTP/Chunks/SctpChunk.cs @@ -132,7 +132,7 @@ public SctpChunkType? KnownType { get { - if (Enum.IsDefined(typeof(SctpChunkType), ChunkType)) + if (((SctpChunkType)ChunkType).IsDefined()) { return (SctpChunkType)ChunkType; } @@ -147,7 +147,7 @@ public SctpChunkType? KnownType /// Records any unrecognised parameters received from the remote peer and are classified /// as needing to be reported. These can be sent back to the remote peer if needed. /// - public List UnrecognizedPeerParameters = new List(); + public IReadOnlyList UnrecognizedPeerParameters = Array.Empty(); public SctpChunk(SctpChunkType chunkType, byte chunkFlags = 0x00) { @@ -255,12 +255,14 @@ public bool GotUnrecognisedParameter(SctpTlvChunkParameter chunkParameter) break; case SctpUnrecognisedParameterActions.StopAndReport: stop = true; - UnrecognizedPeerParameters.Add(chunkParameter); + UnrecognizedPeerParameters = UnrecognizedPeerParameters as List ?? []; + ((List)UnrecognizedPeerParameters).Add(chunkParameter); break; case SctpUnrecognisedParameterActions.Skip: break; case SctpUnrecognisedParameterActions.SkipAndReport: - UnrecognizedPeerParameters.Add(chunkParameter); + UnrecognizedPeerParameters = UnrecognizedPeerParameters as List ?? []; + ((List)UnrecognizedPeerParameters).Add(chunkParameter); break; } @@ -330,7 +332,7 @@ public static SctpChunk Parse(ReadOnlySpan buffer, int posn) byte chunkType = buffer[posn]; - if (Enum.IsDefined(typeof(SctpChunkType), chunkType)) + if (((SctpChunkType)chunkType).IsDefined()) { switch ((SctpChunkType)chunkType) { diff --git a/src/net/SCTP/SctpPacket.cs b/src/net/SCTP/SctpPacket.cs index 447246e55..b5a38ef01 100644 --- a/src/net/SCTP/SctpPacket.cs +++ b/src/net/SCTP/SctpPacket.cs @@ -20,6 +20,11 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +#if NET6_0_OR_GREATER +using System.Runtime.Intrinsics.X86; +#endif using Microsoft.Extensions.Logging; using SIPSorcery.Sys; @@ -55,13 +60,26 @@ public static uint Calculate(byte[] buffer, int offset, int length) return crc ^ 0xffffffff; } - public static uint Calculate(ReadOnlySpan span) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static uint Calculate(ReadOnlySpan span) { uint crc = ~0u; - while (span.Length > 0) +#if NET6_0_OR_GREATER + if (Sse42.X64.IsSupported) { - crc = _table[(crc ^ span[0]) & 0xff] ^ crc >> 8; - span = span.Slice(1); + int strides = span.Length / 8; + int stridedBytes = strides * 8; + ReadOnlySpan ulongs = MemoryMarshal.Cast(span[..stridedBytes]); + for (int i = 0; i < strides; i++) + { + crc = (uint)Sse42.X64.Crc32(crc, ulongs[i]); + } + span = span[stridedBytes..]; + } +#endif + for (int i = 0; i < span.Length; i++) + { + crc = _table[(crc ^ span[i]) & 0xff] ^ crc >> 8; } return crc ^ 0xffffffff; } @@ -102,10 +120,14 @@ public class SctpPacket /// A list of the blobs for chunks that weren't recognised when parsing /// a received packet. /// - public List UnrecognisedChunks; + public IReadOnlyList UnrecognisedChunks; - private SctpPacket() - { } + private SctpPacket(SctpHeader header, List chunks, IReadOnlyList unrecognizedChunks) + { + Header = header; + Chunks = chunks; + UnrecognisedChunks = unrecognizedChunks; + } /// /// Creates a new SCTP packet instance. @@ -126,7 +148,7 @@ public SctpPacket( }; Chunks = new List(); - UnrecognisedChunks = new List(); + UnrecognisedChunks = Array.Empty(); } /// @@ -196,24 +218,10 @@ public void AddChunk(SctpChunk chunk) /// The length of the serialised packet in the buffer. public static SctpPacket Parse(ReadOnlySpan buffer) { - var pkt = new SctpPacket(); - pkt.Header = SctpHeader.Parse(buffer); - (pkt.Chunks, pkt.UnrecognisedChunks) = ParseChunks(buffer); - - return pkt; - } - - /// - /// Parses the chunks from a serialised SCTP packet. - /// - /// The buffer holding the serialised packet. - /// The position in the buffer of the packet. - /// The length of the serialised packet in the buffer. - /// The lsit of parsed chunks and a list of unrecognised chunks that were not de-serialised. - private static (List chunks, List unrecognisedChunks) ParseChunks(ReadOnlySpan buffer) - { + var header = SctpHeader.Parse(buffer); List chunks = new List(); - List unrecognisedChunks = new List(); + // avoid allocation + IReadOnlyList unrecognisedChunks = Array.Empty(); int posn = SctpHeader.SCTP_HEADER_LENGTH; int length = buffer.Length; @@ -224,7 +232,7 @@ private static (List chunks, List unrecognisedChunks) ParseCh { byte chunkType = buffer[posn]; - if (Enum.IsDefined(typeof(SctpChunkType), chunkType)) + if (((SctpChunkType)chunkType).IsDefined()) { var chunk = SctpChunk.Parse(buffer, posn); chunks.Add(chunk); @@ -238,12 +246,14 @@ private static (List chunks, List unrecognisedChunks) ParseCh break; case SctpUnrecognisedChunkActions.StopAndReport: stop = true; - unrecognisedChunks.Add(SctpChunk.CopyUnrecognisedChunk(buffer, posn).ToArray()); + unrecognisedChunks = unrecognisedChunks as List ?? []; + ((List)unrecognisedChunks).Add(SctpChunk.CopyUnrecognisedChunk(buffer, posn).ToArray()); break; case SctpUnrecognisedChunkActions.Skip: break; case SctpUnrecognisedChunkActions.SkipAndReport: - unrecognisedChunks.Add(SctpChunk.CopyUnrecognisedChunk(buffer, posn).ToArray()); + unrecognisedChunks = unrecognisedChunks as List ?? []; + ((List)unrecognisedChunks).Add(SctpChunk.CopyUnrecognisedChunk(buffer, posn).ToArray()); break; } } @@ -257,7 +267,7 @@ private static (List chunks, List unrecognisedChunks) ParseCh posn += (int)SctpChunk.GetChunkLengthFromHeader(buffer, posn, true); } - return (chunks, unrecognisedChunks); + return new(header, chunks, unrecognisedChunks); } /// diff --git a/src/sys/EnumExtensions.cs b/src/sys/EnumExtensions.cs new file mode 100644 index 000000000..745f7d7ae --- /dev/null +++ b/src/sys/EnumExtensions.cs @@ -0,0 +1,17 @@ +using System; + +namespace SIPSorcery.Sys +{ + static class EnumExtensions + { + public static bool IsDefined(this T value) where T : struct, Enum + { + return Array.IndexOf(EnumDefined.Values, value) >= 0; + } + + class EnumDefined where T : struct, Enum + { + public static readonly T[] Values = (T[])Enum.GetValues(typeof(T)); + } + } +} From a63097becc511eee33cdee1f658f1dc32b8a9049 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Fri, 12 Jan 2024 14:21:40 -0800 Subject: [PATCH 21/88] partially replaced SctpPacket with SctpPacketView on receive --- src/SIPSorcery.csproj | 1 + src/net/SCTP/Chunks/SctpDataChunk.cs | 9 + src/net/SCTP/Chunks/SctpSackChunk.cs | 4 +- src/net/SCTP/Chunks/SctpTlvChunkParameter.cs | 21 ++- src/net/SCTP/SctpAssociation.cs | 24 +-- src/net/SCTP/SctpChunkView.cs | 177 +++++++++++++++++++ src/net/SCTP/SctpDataReceiver.cs | 15 +- src/net/SCTP/SctpDataSender.cs | 20 +-- src/net/SCTP/SctpPacket.cs | 2 +- src/net/SCTP/SctpPacketView.cs | 123 +++++++++++++ src/net/SCTP/SctpTransport.cs | 20 +-- src/net/SCTP/SctpUdpTransport.cs | 6 +- src/net/WebRTC/RTCSctpTransport.cs | 10 +- 13 files changed, 380 insertions(+), 52 deletions(-) create mode 100644 src/net/SCTP/SctpChunkView.cs create mode 100644 src/net/SCTP/SctpPacketView.cs diff --git a/src/SIPSorcery.csproj b/src/SIPSorcery.csproj index 54b0a0145..f20361c13 100755 --- a/src/SIPSorcery.csproj +++ b/src/SIPSorcery.csproj @@ -21,6 +21,7 @@ + diff --git a/src/net/SCTP/Chunks/SctpDataChunk.cs b/src/net/SCTP/Chunks/SctpDataChunk.cs index f47f73f3e..bbfd12bc7 100644 --- a/src/net/SCTP/Chunks/SctpDataChunk.cs +++ b/src/net/SCTP/Chunks/SctpDataChunk.cs @@ -223,5 +223,14 @@ public static SctpDataChunk ParseChunk(ReadOnlySpan buffer, int posn) return dataChunk; } + + [Flags] + public enum Flags: byte + { + None = 0x00, + Unordered = 0x04, + Beginning = 0x02, + Ending = 0x01 + } } } diff --git a/src/net/SCTP/Chunks/SctpSackChunk.cs b/src/net/SCTP/Chunks/SctpSackChunk.cs index cb28c0111..6cf9c4eb9 100644 --- a/src/net/SCTP/Chunks/SctpSackChunk.cs +++ b/src/net/SCTP/Chunks/SctpSackChunk.cs @@ -32,8 +32,8 @@ namespace SIPSorcery.Net public class SctpSackChunk : SctpChunk { public const int FIXED_PARAMETERS_LENGTH = 12; - private const int GAP_REPORT_LENGTH = 4; - private const int DUPLICATE_TSN_LENGTH = 4; + internal const int GAP_REPORT_LENGTH = 4; + internal const int DUPLICATE_TSN_LENGTH = 4; /// /// This parameter contains the TSN of the last chunk received in diff --git a/src/net/SCTP/Chunks/SctpTlvChunkParameter.cs b/src/net/SCTP/Chunks/SctpTlvChunkParameter.cs index 8cf2a9310..7e0d1c46f 100644 --- a/src/net/SCTP/Chunks/SctpTlvChunkParameter.cs +++ b/src/net/SCTP/Chunks/SctpTlvChunkParameter.cs @@ -203,14 +203,27 @@ public byte[] GetBytes() /// The position in the buffer that indicates the start of the chunk parameter. public ushort ParseFirstWord(ReadOnlySpan buffer, int posn) { - ParameterType = NetConvert.ParseUInt16(buffer, posn); - ushort paramLen = NetConvert.ParseUInt16(buffer, posn + 2); + ushort len = ParseFirstWord(buffer.Slice(posn), out ushort type); + ParameterType = type; + return len; + } + + /// + /// The first 32 bits of all chunk parameters represent the type and length. This method + /// parses those fields and sets them on the current instance. + /// + /// The buffer holding the serialised chunk parameter. + /// The position in the buffer that indicates the start of the chunk parameter. + public static ushort ParseFirstWord(ReadOnlySpan buffer, out ushort type) + { + type = NetConvert.ParseUInt16(buffer, 0); + ushort paramLen = NetConvert.ParseUInt16(buffer, 2); - if (paramLen > 0 && buffer.Length < posn + paramLen) + if (paramLen > 0 && buffer.Length < paramLen) { // The buffer was not big enough to supply the specified chunk parameter. int bytesRequired = paramLen; - int bytesAvailable = buffer.Length - posn; + int bytesAvailable = buffer.Length; throw new ApplicationException($"The SCTP chunk parameter buffer was too short. " + $"Required {bytesRequired} bytes but only {bytesAvailable} available."); } diff --git a/src/net/SCTP/SctpAssociation.cs b/src/net/SCTP/SctpAssociation.cs index 550d70d3a..deb685269 100644 --- a/src/net/SCTP/SctpAssociation.cs +++ b/src/net/SCTP/SctpAssociation.cs @@ -366,7 +366,7 @@ internal void InitRemoteProperties( /// SCTP Association State Diagram: /// https://tools.ietf.org/html/rfc4960#section-4 /// - internal void OnPacketReceived(SctpPacket packet) + internal void OnPacketReceived(SctpPacketView packet) { if (_wasAborted) { @@ -389,9 +389,10 @@ internal void OnPacketReceived(SctpPacket packet) } else { - foreach (var chunk in packet.Chunks) + for (int chunkIndex = 0; chunkIndex < packet.ChunkCount; chunkIndex++) { - var chunkType = (SctpChunkType)chunk.ChunkType; + var chunk = packet[chunkIndex]; + var chunkType = chunk.Type; switch (chunkType) { @@ -423,19 +424,19 @@ internal void OnPacketReceived(SctpPacket packet) break; case SctpChunkType.DATA: - var dataChunk = chunk as SctpDataChunk; + var dataChunk = chunk; - if (dataChunk.UserData == null || dataChunk.UserData.Length == 0) + if (dataChunk.UserData.Length == 0) { // Fatal condition: // - If an endpoint receives a DATA chunk with no user data (i.e., the // Length field is set to 16), it MUST send an ABORT with error cause // set to "No User Data". (RFC4960 pg. 80) - Abort(new SctpErrorNoUserData { TSN = (chunk as SctpDataChunk).TSN }); + Abort(new SctpErrorNoUserData { TSN = dataChunk.TSN }); } else { - logger.LogTrace($"SCTP data chunk received on ID {ID} with TSN {dataChunk.TSN}, payload length {dataChunk.UserData.Length}, flags {dataChunk.ChunkFlags:X2}."); + logger.LogTrace($"SCTP data chunk received on ID {ID} with TSN {dataChunk.TSN}, payload length {dataChunk.UserData.Length}, flags {dataChunk.Flags:X2}."); // A received data chunk can result in multiple data frames becoming available. // For example if a stream has out of order frames already received and the next @@ -457,10 +458,9 @@ internal void OnPacketReceived(SctpPacket packet) break; case SctpChunkType.ERROR: - var errorChunk = chunk as SctpErrorChunk; - foreach (var err in errorChunk.ErrorCauses) + foreach (var code in chunk.GetErrorCodes()) { - logger.LogWarning($"SCTP error {err.CauseCode}."); + logger.LogWarning("SCTP error {Code}.", code); } break; @@ -484,7 +484,7 @@ internal void OnPacketReceived(SctpPacket packet) _t1Init = null; } - var initAckChunk = chunk as SctpInitChunk; + var initAckChunk = chunk; if (initAckChunk.InitiateTag == 0 || initAckChunk.NumberInboundStreams == 0 || @@ -534,7 +534,7 @@ internal void OnPacketReceived(SctpPacket packet) break; case SctpChunkType.SACK: - _dataSender.GotSack(chunk as SctpSackChunk); + _dataSender.GotSack(chunk); break; case var ct when ct == SctpChunkType.SHUTDOWN && State == SctpAssociationState.Established: diff --git a/src/net/SCTP/SctpChunkView.cs b/src/net/SCTP/SctpChunkView.cs new file mode 100644 index 000000000..5d8f0f30a --- /dev/null +++ b/src/net/SCTP/SctpChunkView.cs @@ -0,0 +1,177 @@ +using System; +using System.Buffers.Binary; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using Microsoft.Extensions.Logging; + +using SIPSorcery.Sys; + +using static SIPSorcery.Net.SctpChunkType; + +namespace SIPSorcery.Net; + +public readonly ref struct SctpChunkView +{ + static readonly ILogger logger = LogFactory.CreateLogger(); + + readonly ReadOnlySpan buffer; + + public SctpChunkType Type => (SctpChunkType)buffer[0]; + public SctpDataChunk.Flags Flags => (SctpDataChunk.Flags)buffer[1]; + public bool Unordered => (Flags & SctpDataChunk.Flags.Unordered) != default; + public bool Beginning => (Flags & SctpDataChunk.Flags.Beginning) != default; + public bool Ending => (Flags & SctpDataChunk.Flags.Ending) != default; + public ushort Length => BinaryPrimitives.ReadUInt16BigEndian(buffer.Slice(2, 2)); + public ReadOnlySpan Value => buffer.Slice(SctpChunk.SCTP_CHUNK_HEADER_LENGTH, Length); + + #region Data Chunk + public uint TSN => BinaryPrimitives.ReadUInt32BigEndian(Value); + public ushort StreamID => BinaryPrimitives.ReadUInt16BigEndian(Value.Slice(4, 2)); + public ushort StreamSeqNum => BinaryPrimitives.ReadUInt16BigEndian(Value.Slice(6, 2)); + public uint PPID => BinaryPrimitives.ReadUInt32BigEndian(Value.Slice(8, 4)); + public ReadOnlySpan UserData + { + get + { + int pos = SctpChunk.SCTP_CHUNK_HEADER_LENGTH + SctpDataChunk.FIXED_PARAMETERS_LENGTH; + int len = Length - pos; + return buffer.Slice(pos, len); + } + } + #endregion Data Chunk + + #region SACK Chunk + public uint CumulativeTsnAck => BinaryPrimitives.ReadUInt32BigEndian(Value); + public uint ARwnd => BinaryPrimitives.ReadUInt32BigEndian(Value.Slice(4, 4)); + public ushort NumGapAckBlocks => BinaryPrimitives.ReadUInt16BigEndian(Value.Slice(8, 2)); + public ushort NumDuplicateTSNs => BinaryPrimitives.ReadUInt16BigEndian(Value.Slice(10, 2)); + public SctpTsnGapBlock GetTsnGapBlock(int index) + { + int posn = SctpChunk.SCTP_CHUNK_HEADER_LENGTH + + SctpSackChunk.FIXED_PARAMETERS_LENGTH + + (index * SctpSackChunk.GAP_REPORT_LENGTH); + return new SctpTsnGapBlock + { + Start = BinaryPrimitives.ReadUInt16BigEndian(Value.Slice(posn, 2)), + End = BinaryPrimitives.ReadUInt16BigEndian(Value.Slice(posn + 2, 2)) + }; + } + public ReadOnlySpan GapAckBlocks + => MemoryMarshal.Cast(Value.Slice(SctpChunk.SCTP_CHUNK_HEADER_LENGTH + SctpSackChunk.FIXED_PARAMETERS_LENGTH, NumGapAckBlocks * SctpSackChunk.GAP_REPORT_LENGTH)); + public uint GetDuplicateTSN(int index) + { + int posn = SctpChunk.SCTP_CHUNK_HEADER_LENGTH + + SctpSackChunk.FIXED_PARAMETERS_LENGTH + + (NumGapAckBlocks * SctpSackChunk.GAP_REPORT_LENGTH) + + (index * SctpSackChunk.DUPLICATE_TSN_LENGTH); + return BinaryPrimitives.ReadUInt32BigEndian(Value.Slice(posn)); + } + #endregion SACK Chunk + + #region Init Chunk + public uint InitiateTag => BinaryPrimitives.ReadUInt32BigEndian(Value); + public uint ARwndInit => BinaryPrimitives.ReadUInt32BigEndian(Value.Slice(4, 4)); + public ushort NumberInboundStreams => BinaryPrimitives.ReadUInt16BigEndian(Value.Slice(8, 2)); + public ushort NumberOutboundStreams => BinaryPrimitives.ReadUInt16BigEndian(Value.Slice(10, 2)); + public uint InitialTSN => BinaryPrimitives.ReadUInt32BigEndian(Value.Slice(12, 4)); + #endregion Init Chunk + + public IEnumerable GetErrorCodes() + { + int paramsBufferLength = Length - SctpChunk.SCTP_CHUNK_HEADER_LENGTH; + if (paramsBufferLength == 0) + { + yield break; + } + + int paramPosn = SctpChunk.SCTP_CHUNK_HEADER_LENGTH; + + var paramsBuffer = Value.Slice(paramPosn, paramsBufferLength); + while (!paramsBuffer.IsEmpty) + { + int length = SctpTlvChunkParameter.ParseFirstWord(paramsBuffer, out var type); + var causeCode = (SctpErrorCauseCode)type; + yield return causeCode; + paramsBuffer = paramsBuffer.Slice(length); + } + } + + public SctpChunkView(ReadOnlySpan buffer) + { + if (buffer.Length < 4) + { + throw new ArgumentException("Buffer too short to be a valid SCTP chunk."); + } + this.buffer = buffer; + if (!Type.IsDefined()) + { + throw new ArgumentException($"Unknown chunk type {Type}"); + } + + _ = Type switch + { + ABORT => ValidateError(isAbort: true), + ERROR => ValidateError(isAbort: false), + DATA => ValidateData(), + SACK => ValidateSack(), + COOKIE_ACK or COOKIE_ECHO + or HEARTBEAT or HEARTBEAT_ACK + or SHUTDOWN_ACK or SHUTDOWN_COMPLETE + => ValidateBase(), + INIT or INIT_ACK => ValidateInit(), + SHUTDOWN => ValidateShutdown(), + _ => ValidateUnknownBase(), + }; + } + + bool ValidateShutdown() + { + throw new NotImplementedException(); + } + + bool ValidateInit() + { + throw new NotImplementedException(); + } + + bool ValidateSack() + { + int gapAckSize = NumGapAckBlocks * SctpSackChunk.GAP_REPORT_LENGTH; + int dupTsnSize = NumDuplicateTSNs * SctpSackChunk.DUPLICATE_TSN_LENGTH; + int expectedLength = SctpSackChunk.FIXED_PARAMETERS_LENGTH + gapAckSize + dupTsnSize; + if (Length < expectedLength) + { + throw new ApplicationException($"SCTP SACK chunk length {Length} does not match expected length {expectedLength}."); + } + return true; + } + + bool ValidateBase() + { + if (Length + SctpChunk.SCTP_CHUNK_HEADER_LENGTH > buffer.Length) + { + throw new ArgumentException("Buffer too short to be a valid SCTP chunk."); + } + return true; + } + + bool ValidateData() + { + if (Length < SctpDataChunk.FIXED_PARAMETERS_LENGTH) + { + throw new ApplicationException($"SCTP data chunk cannot be parsed as buffer too short for fixed parameter fields."); + } + return true; + } + + bool ValidateError(bool isAbort) + { + throw new NotImplementedException(); + } + + bool ValidateUnknownBase() + { + logger.LogDebug("TODO: Implement parsing logic for well known chunk type {Type}.", Type); + return ValidateBase(); + } +} diff --git a/src/net/SCTP/SctpDataReceiver.cs b/src/net/SCTP/SctpDataReceiver.cs index 6842ee6fb..d8361c652 100644 --- a/src/net/SCTP/SctpDataReceiver.cs +++ b/src/net/SCTP/SctpDataReceiver.cs @@ -25,7 +25,7 @@ namespace SIPSorcery.Net { public struct SctpDataFrame { - public static SctpDataFrame Empty = new SctpDataFrame(); + public static SctpDataFrame Empty => default; public bool Unordered; public ushort StreamID; @@ -199,8 +199,13 @@ public void SetInitialTSN(uint tsn) /// or more new frames will be returned otherwise an empty frame is returned. Multiple /// frames may be returned if this chunk is part of a stream and was received out /// or order. For unordered chunks the list will always have a single entry. - public List OnDataChunk(SctpDataChunk dataChunk) + public List OnDataChunk(SctpChunkView dataChunk) { + if (dataChunk.Type != SctpChunkType.DATA) + { + throw new ArgumentException($"An attempt was made to process a {dataChunk.Type} chunk as a DATA chunk."); + } + var sortedFrames = new List(); var frame = SctpDataFrame.Empty; @@ -272,7 +277,7 @@ public List OnDataChunk(SctpDataChunk dataChunk) if (processFrame) { // Now go about processing the data chunk. - if (dataChunk.Begining && dataChunk.Ending) + if (dataChunk.Beginning && dataChunk.Ending) { // Single packet chunk. frame = new SctpDataFrame( @@ -290,7 +295,7 @@ public List OnDataChunk(SctpDataChunk dataChunk) if (begin != null && end != null) { - frame = GetFragmentedChunk(_fragmentedChunks, begin.Value, end.Value); + frame = ExtractFragmentedChunk(_fragmentedChunks, begin.Value, end.Value); } } } @@ -504,7 +509,7 @@ private List ProcessStreamFrame(SctpDataFrame frame) /// The dictionary containing the chunk fragments. /// The beginning TSN for the fragment. /// The end TSN for the fragment. - private SctpDataFrame GetFragmentedChunk(Dictionary fragments, uint beginTSN, uint endTSN) + private SctpDataFrame ExtractFragmentedChunk(Dictionary fragments, uint beginTSN, uint endTSN) { unchecked { diff --git a/src/net/SCTP/SctpDataSender.cs b/src/net/SCTP/SctpDataSender.cs index 6b4b94678..5e4df544c 100644 --- a/src/net/SCTP/SctpDataSender.cs +++ b/src/net/SCTP/SctpDataSender.cs @@ -201,9 +201,8 @@ public void SetReceiverWindow(uint remoteARwnd) /// Handler for SACK chunks received from the remote peer. /// /// The SACK chunk from the remote peer. - public void GotSack(SctpSackChunk sack) + public void GotSack(SctpChunkView sack) { - if (sack != null) { if (_inRetransmitMode) { @@ -233,7 +232,7 @@ public void GotSack(SctpSackChunk sack) if (SctpDataReceiver.GetDistance(_initialTSN, sack.CumulativeTsnAck) < maxTSNDistance && SctpDataReceiver.IsNewerOrEqual(_initialTSN, sack.CumulativeTsnAck)) { - logger.LogTrace($"SCTP first SACK remote peer TSN ACK {sack.CumulativeTsnAck} next sender TSN {TSN}, arwnd {sack.ARwnd} (gap reports {sack.GapAckBlocks.Count})."); + logger.LogTrace($"SCTP first SACK remote peer TSN ACK {sack.CumulativeTsnAck} next sender TSN {TSN}, arwnd {sack.ARwnd} (gap reports {sack.NumGapAckBlocks})."); _gotFirstSACK = true; _cumulativeAckTSN = _initialTSN; RemoveAckedUnconfirmedChunks(sack.CumulativeTsnAck); @@ -255,24 +254,25 @@ public void GotSack(SctpSackChunk sack) } else { - logger.LogTrace($"SCTP SACK remote peer TSN ACK {sack.CumulativeTsnAck}, next sender TSN {TSN}, arwnd {sack.ARwnd} (gap reports {sack.GapAckBlocks.Count})."); + logger.LogTrace($"SCTP SACK remote peer TSN ACK {sack.CumulativeTsnAck}, next sender TSN {TSN}, arwnd {sack.ARwnd} (gap reports {sack.NumGapAckBlocks})."); RemoveAckedUnconfirmedChunks(sack.CumulativeTsnAck); } } else { - logger.LogTrace($"SCTP SACK remote peer TSN ACK no change {_cumulativeAckTSN}, next sender TSN {TSN}, arwnd {sack.ARwnd} (gap reports {sack.GapAckBlocks.Count})."); + logger.LogTrace($"SCTP SACK remote peer TSN ACK no change {_cumulativeAckTSN}, next sender TSN {TSN}, arwnd {sack.ARwnd} (gap reports {sack.NumGapAckBlocks})."); RemoveAckedUnconfirmedChunks(sack.CumulativeTsnAck); } } - if (sack.DuplicateTSN.Count > 0) + if (sack.NumDuplicateTSNs > 0) { // The remote is reporting that we have sent a duplicate TSN. // This is probably because a SACK chunk was dropped. // Ensure that we stop sending the duplicate. - foreach (uint duplicateTSN in sack.DuplicateTSN) + for (int tsnIndex = 0; tsnIndex < sack.NumDuplicateTSNs; tsnIndex++) { + uint duplicateTSN = sack.GetDuplicateTSN(tsnIndex); _unconfirmedChunks.TryRemove(duplicateTSN, out _); _missingChunks.TryRemove(duplicateTSN, out _); } @@ -280,7 +280,7 @@ public void GotSack(SctpSackChunk sack) // Check gap reports. Only process them if the cumulative ACK TSN was acceptable. - if (processGapReports && sack.GapAckBlocks.Count > 0) + if (processGapReports && sack.NumGapAckBlocks > 0) { bool didIncrementCumAckTSN = SctpDataReceiver.IsNewer(cumAckTSNBeforeSackProcessing, _cumulativeAckTSN); ProcessGapReports(sack.GapAckBlocks, maxTSNDistance, didIncrementCumAckTSN); @@ -406,7 +406,7 @@ public void Close() /// ACK'ed TSN. If this distance gets exceeded by a gap report then it's likely something has been /// miscalculated. /// If true, processing of the SACK incremented the - private void ProcessGapReports(List sackGapBlocks, uint maxTSNDistance, bool didSackIncrementTSN) + private void ProcessGapReports(ReadOnlySpan sackGapBlocks, uint maxTSNDistance, bool didSackIncrementTSN) { uint lastAckTSN = _cumulativeAckTSN; @@ -489,7 +489,7 @@ private void ProcessGapReports(List sackGapBlocks, uint maxTSND { _inFastRecoveryMode = true; // mark the highest outstanding TSN as the Fast Recovery exit point - _fastRecoveryExitPoint = _cumulativeAckTSN + sackGapBlocks.Last().End; + _fastRecoveryExitPoint = _cumulativeAckTSN + sackGapBlocks[sackGapBlocks.Length - 1].End; logger.LogTrace($"SCTP sender entering fast recovery mode due to missing TSN {missingTSN}. Fast recovery exit point {_fastRecoveryExitPoint}."); // RFC4960 7.2.3 diff --git a/src/net/SCTP/SctpPacket.cs b/src/net/SCTP/SctpPacket.cs index b5a38ef01..7ccdf6496 100644 --- a/src/net/SCTP/SctpPacket.cs +++ b/src/net/SCTP/SctpPacket.cs @@ -267,7 +267,7 @@ public static SctpPacket Parse(ReadOnlySpan buffer) posn += (int)SctpChunk.GetChunkLengthFromHeader(buffer, posn, true); } - return new(header, chunks, unrecognisedChunks); + return new SctpPacket(header, chunks, unrecognisedChunks); } /// diff --git a/src/net/SCTP/SctpPacketView.cs b/src/net/SCTP/SctpPacketView.cs new file mode 100644 index 000000000..056febb91 --- /dev/null +++ b/src/net/SCTP/SctpPacketView.cs @@ -0,0 +1,123 @@ +using System; +using System.Collections.Generic; + +using Microsoft.Extensions.Logging; + +using SIPSorcery.Sys; + +using Small.Collections; + +using TypeNum; + +namespace SIPSorcery.Net; + +public readonly ref struct SctpPacketView +{ + static readonly ILogger logger = LogFactory.CreateLogger(); + readonly ReadOnlySpan buffer; + readonly SmallList, Chunk> chunks; + readonly SmallList, Chunk> unrecognized; + + public readonly SctpHeader Header => SctpHeader.Parse(buffer); + public int ChunkCount => chunks.Count; + public SctpChunkView this[int index] => chunks[index].View(buffer); + public SctpChunkView GetChunk(SctpChunkType type) + { + bool found = false; + SctpChunkView result = default; + for (int i = 0; i < chunks.Count; i++) + { + var chunk = chunks[i]; + var view = chunk.View(buffer); + if (view.Type == type) + { + if (found) + { + throw new InvalidOperationException($"Multiple {type} chunks found."); + } + + result = view; + found = true; + } + } + return found ? result : throw new KeyNotFoundException(); + } + public bool Has(SctpChunkType type) + { + for (int i = 0; i < chunks.Count; i++) + { + var chunk = chunks[i]; + var view = chunk.View(buffer); + if (view.Type == type) + { + return true; + } + } + return false; + } + public int UnrecognizedChunkCount => unrecognized.Count; + + public static SctpPacketView Parse(ReadOnlySpan buffer) + { + var chunks = new SmallList, Chunk>(); + var unrecognized = new SmallList, Chunk>(); + int posn = SctpHeader.SCTP_HEADER_LENGTH; + + bool stop = false; + + while (posn < buffer.Length) + { + byte chunkType = buffer[posn]; + int chunkLength = (int)SctpChunk.GetChunkLengthFromHeader(buffer, posn, true); + var chunk = new Chunk() { Offset = posn, Length = chunkLength }; + + if (((SctpChunkType)chunkType).IsDefined()) + { + chunk.View(buffer); + chunks.Add(chunk); + } + else + { + switch (SctpChunk.GetUnrecognisedChunkAction(chunkType)) + { + case SctpUnrecognisedChunkActions.Stop: + stop = true; + break; + case SctpUnrecognisedChunkActions.StopAndReport: + stop = true; + unrecognized.Add(chunk); + break; + case SctpUnrecognisedChunkActions.Skip: + break; + case SctpUnrecognisedChunkActions.SkipAndReport: + unrecognized.Add(chunk); + break; + } + } + + if (stop) + { + logger.LogWarning("SCTP unrecognised chunk type {Type} indicated no further chunks should be processed.", chunkType); + break; + } + + posn += chunkLength; + } + return new(buffer, chunks, unrecognized); + } + + SctpPacketView(ReadOnlySpan buffer, SmallList, Chunk> chunks, SmallList, Chunk> unrecognized) + { + this.buffer = buffer; + this.chunks = chunks; + this.unrecognized = unrecognized; + } + + struct Chunk + { + public int Offset { get; set; } + public int Length { get; set; } + public SctpChunkView View(ReadOnlySpan buffer) + => new(buffer.Slice(Offset, Length)); + } +} diff --git a/src/net/SCTP/SctpTransport.cs b/src/net/SCTP/SctpTransport.cs index 698063d57..4e83436fa 100644 --- a/src/net/SCTP/SctpTransport.cs +++ b/src/net/SCTP/SctpTransport.cs @@ -100,12 +100,12 @@ static SctpTransport() Crypto.GetRandomBytes(_hmacKey); } - protected void GotInit(SctpPacket initPacket, IPEndPoint remoteEndPoint) + protected void GotInit(SctpPacketView initPacket, IPEndPoint remoteEndPoint) { // INIT packets have specific processing rules in order to prevent resource exhaustion. // See Section 5 of RFC 4960 https://tools.ietf.org/html/rfc4960#section-5 "Association Initialization". - SctpInitChunk initChunk = initPacket.Chunks.Single(x => x.KnownType == SctpChunkType.INIT) as SctpInitChunk; + var initChunk = initPacket.GetChunk(SctpChunkType.INIT); if (initChunk.InitiateTag == 0 || initChunk.NumberInboundStreams == 0 || @@ -198,9 +198,9 @@ protected virtual SctpTransportCookie GetInitAckCookie( /// received on. For transports that don't use an IP transport directly this parameter /// can be set to null and it will not form part of the COOKIE ECHO checks. /// An SCTP packet with a single INIT ACK chunk. - protected SctpPacket GetInitAck(SctpPacket initPacket, IPEndPoint remoteEP) + protected SctpPacket GetInitAck(SctpPacketView initPacket, IPEndPoint remoteEP) { - SctpInitChunk initChunk = initPacket.Chunks.Single(x => x.KnownType == SctpChunkType.INIT) as SctpInitChunk; + var initChunk = initPacket.GetChunk(SctpChunkType.INIT); SctpPacket initAckPacket = new SctpPacket( initPacket.Header.DestinationPort, @@ -251,11 +251,11 @@ protected SctpPacket GetInitAck(SctpPacket initPacket, IPEndPoint remoteEP) /// The packet containing the COOKIE ECHO chunk received from the remote party. /// If the state cookie in the chunk is valid a new SCTP association will be returned. IF /// it's not valid an empty cookie will be returned and an error response gets sent to the peer. - protected SctpTransportCookie GetCookie(SctpPacket sctpPacket) + protected SctpTransportCookie GetCookie(SctpPacketView sctpPacket) { - var cookieEcho = sctpPacket.Chunks.Single(x => x.KnownType == SctpChunkType.COOKIE_ECHO); - var cookieBuffer = cookieEcho.ChunkValue; - var cookie = JSONParser.FromJson(Encoding.UTF8.GetString(cookieBuffer)); + var cookieEcho = sctpPacket.GetChunk(SctpChunkType.COOKIE_ECHO); + var cookieBuffer = cookieEcho.Value; + var cookie = JSONParser.FromJson(cookieBuffer.ToString(Encoding.UTF8)); logger.LogDebug($"Cookie: {cookie.ToJson()}"); @@ -295,9 +295,9 @@ protected SctpTransportCookie GetCookie(SctpPacket sctpPacket) /// /// The buffer holding the state cookie. /// True if the cookie is determined as valid, false if not. - protected string GetCookieHMAC(byte[] buffer) + protected string GetCookieHMAC(ReadOnlySpan buffer) { - var cookie = JSONParser.FromJson(Encoding.UTF8.GetString(buffer)); + var cookie = JSONParser.FromJson(buffer.ToString(Encoding.UTF8)); string hmacCalculated = null; cookie.HMAC = string.Empty; diff --git a/src/net/SCTP/SctpUdpTransport.cs b/src/net/SCTP/SctpUdpTransport.cs index 5d198e6b7..7b0594790 100644 --- a/src/net/SCTP/SctpUdpTransport.cs +++ b/src/net/SCTP/SctpUdpTransport.cs @@ -76,14 +76,14 @@ private void OnEncapsulationSocketPacketReceived(UdpReceiver receiver, int local } else { - var sctpPacket = SctpPacket.Parse(packet); + var sctpPacket = SctpPacketView.Parse(packet); // Process packet. if (sctpPacket.Header.VerificationTag == 0) { GotInit(sctpPacket, remoteEndPoint); } - else if (sctpPacket.Chunks.Any(x => x.KnownType == SctpChunkType.COOKIE_ECHO)) + else if (sctpPacket.Has(SctpChunkType.COOKIE_ECHO)) { // The COOKIE ECHO chunk is the 3rd step in the SCTP handshake when the remote party has // requested a new association be created. @@ -101,7 +101,7 @@ private void OnEncapsulationSocketPacketReceived(UdpReceiver receiver, int local if (_associations.TryAdd(association.ID, association)) { - if (sctpPacket.Chunks.Count > 1) + if (sctpPacket.ChunkCount > 1) { // There could be DATA chunks after the COOKIE ECHO chunk. association.OnPacketReceived(sctpPacket); diff --git a/src/net/WebRTC/RTCSctpTransport.cs b/src/net/WebRTC/RTCSctpTransport.cs index c81aff7fe..9983db728 100644 --- a/src/net/WebRTC/RTCSctpTransport.cs +++ b/src/net/WebRTC/RTCSctpTransport.cs @@ -289,16 +289,16 @@ private void DoReceive(object state) } else { - var pkt = SctpPacket.Parse(recvBuffer.AsSpan(0, bytesRead)); + var pkt = SctpPacketView.Parse(recvBuffer.AsSpan(0, bytesRead)); - if (pkt.Chunks.Any(x => x.KnownType == SctpChunkType.INIT)) + if (pkt.Has(SctpChunkType.INIT)) { - var initChunk = pkt.Chunks.First(x => x.KnownType == SctpChunkType.INIT) as SctpInitChunk; + var initChunk = pkt.GetChunk(SctpChunkType.INIT); logger.LogDebug($"SCTP INIT packet received, initial tag {initChunk.InitiateTag}, initial TSN {initChunk.InitialTSN}."); GotInit(pkt, null); } - else if (pkt.Chunks.Any(x => x.KnownType == SctpChunkType.COOKIE_ECHO)) + else if (pkt.Has(SctpChunkType.COOKIE_ECHO)) { // The COOKIE ECHO chunk is the 3rd step in the SCTP handshake when the remote party has // requested a new association be created. @@ -312,7 +312,7 @@ private void DoReceive(object state) { RTCSctpAssociation.GotCookie(cookie); - if (pkt.Chunks.Count() > 1) + if (pkt.ChunkCount > 1) { // There could be DATA chunks after the COOKIE ECHO chunk. RTCSctpAssociation.OnPacketReceived(pkt); From 4b08420e01fe64cba3287dfbf7cc5f11fdbc7bc9 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Fri, 12 Jan 2024 14:37:28 -0800 Subject: [PATCH 22/88] use allocation for rare complex chunks --- .../DataChannelBandwidth/DataChannelStream.cs | 4 +- src/net/SCTP/Chunks/SctpDataChunk.cs | 36 ++++++++--- src/net/SCTP/SctpAssociation.cs | 11 ++-- src/net/SCTP/SctpChunkView.cs | 60 +++++++++++++++---- src/net/SCTP/SctpDataReceiver.cs | 43 +++++++------ src/net/SCTP/SctpDataSender.cs | 2 +- src/net/SCTP/SctpPacket.cs | 5 ++ src/net/SCTP/SctpPacketView.cs | 2 + src/net/SCTP/SctpTransport.cs | 6 +- src/net/WebRTC/DCEP.cs | 6 +- src/net/WebRTC/IRTCDataChannel.cs | 2 +- src/net/WebRTC/RTCDataChannel.cs | 2 +- src/sys/BorrowedArray.cs | 51 ++++++++++++++++ 13 files changed, 176 insertions(+), 54 deletions(-) create mode 100644 src/sys/BorrowedArray.cs diff --git a/examples/WebRTCScenarios/DataChannelBandwidth/DataChannelStream.cs b/examples/WebRTCScenarios/DataChannelBandwidth/DataChannelStream.cs index 7e423ba90..58f4c23c9 100644 --- a/examples/WebRTCScenarios/DataChannelBandwidth/DataChannelStream.cs +++ b/examples/WebRTCScenarios/DataChannelBandwidth/DataChannelStream.cs @@ -74,7 +74,7 @@ or RTCPeerConnectionState.disconnected } int messages; - void OnMessage(RTCDataChannel _, DataChannelPayloadProtocols protocol, byte[] data) + void OnMessage(RTCDataChannel _, DataChannelPayloadProtocols protocol, ReadOnlySpan data) { int seq = Interlocked.Increment(ref messages); log?.LogDebug("{Seq} received", seq); @@ -83,7 +83,7 @@ void OnMessage(RTCDataChannel _, DataChannelPayloadProtocols protocol, byte[] da messageNeeded.Wait(); lock (sync) { - message = data; + message = data.ToArray(); currentMessageOffset = 0; } messageAvailable.Release(); diff --git a/src/net/SCTP/Chunks/SctpDataChunk.cs b/src/net/SCTP/Chunks/SctpDataChunk.cs index bbfd12bc7..466b6f5b8 100644 --- a/src/net/SCTP/Chunks/SctpDataChunk.cs +++ b/src/net/SCTP/Chunks/SctpDataChunk.cs @@ -18,12 +18,13 @@ //----------------------------------------------------------------------------- using System; +using System.Buffers; using System.Diagnostics; using SIPSorcery.Sys; namespace SIPSorcery.Net { - public class SctpDataChunk : SctpChunk + public class SctpDataChunk : SctpChunk, IDisposable { /// /// An empty data chunk. The main use is to indicate a DATA chunk has @@ -79,10 +80,12 @@ public class SctpDataChunk : SctpChunk /// public uint PPID; + BorrowedArray userData; /// /// This is the payload user data. /// - public byte[] UserData; + public Span UserData => userData; + public bool HasUserData => !userData.IsNull(); internal struct Timestamp { @@ -135,7 +138,7 @@ public SctpDataChunk( StreamID = streamID; StreamSeqNum = seqnum; PPID = ppid; - UserData = data; + userData.Set(data); ChunkFlags = (byte)( (Unordered ? 0x04 : 0x0) + @@ -151,7 +154,7 @@ public SctpDataChunk( public override ushort GetChunkLength(bool padded) { ushort len = SCTP_CHUNK_HEADER_LENGTH + FIXED_PARAMETERS_LENGTH; - len += (ushort)(UserData != null ? UserData.Length : 0); + len += (ushort)(!userData.IsNull() ? UserData.Length : 0); return (padded) ? SctpPadding.PadTo4ByteBoundary(len) : len; } @@ -176,14 +179,17 @@ public override ushort WriteTo(Span buffer, int posn) int userDataPosn = startPosn + FIXED_PARAMETERS_LENGTH; - UserData?.CopyTo(buffer.Slice(userDataPosn)); + if (!userData.IsNull()) + { + UserData.CopyTo(buffer.Slice(userDataPosn)); + } return GetChunkLength(true); } public bool IsEmpty() { - return UserData == null; + return userData.IsNull(); } /// @@ -191,7 +197,7 @@ public bool IsEmpty() /// /// The buffer holding the serialised chunk. /// The position to start parsing at. - public static SctpDataChunk ParseChunk(ReadOnlySpan buffer, int posn) + public static SctpDataChunk ParseChunk(ReadOnlySpan buffer, int posn, ArrayPool pool = null) { var dataChunk = new SctpDataChunk(); ushort chunkLen = dataChunk.ParseFirstWord(buffer, posn); @@ -217,13 +223,25 @@ public static SctpDataChunk ParseChunk(ReadOnlySpan buffer, int posn) if (userDataLen > 0) { - dataChunk.UserData = new byte[userDataLen]; - buffer.Slice(userDataPosn, userDataLen).CopyTo(dataChunk.UserData); + if (pool != null) + { + dataChunk.userData.Set(buffer.Slice(userDataPosn, userDataLen), pool); + } + else + { + dataChunk.userData.Set(new byte[userDataLen]); + buffer.Slice(userDataPosn, userDataLen).CopyTo(dataChunk.UserData); + } } return dataChunk; } + public void Dispose() + { + userData.Dispose(); + } + [Flags] public enum Flags: byte { diff --git a/src/net/SCTP/SctpAssociation.cs b/src/net/SCTP/SctpAssociation.cs index deb685269..6df71ce7b 100644 --- a/src/net/SCTP/SctpAssociation.cs +++ b/src/net/SCTP/SctpAssociation.cs @@ -397,7 +397,8 @@ internal void OnPacketReceived(SctpPacketView packet) switch (chunkType) { case SctpChunkType.ABORT: - string abortReason = (chunk as SctpAbortChunk).GetAbortReason(); + var abortChunk = (SctpAbortChunk)chunk.AsChunk(); + string abortReason = abortChunk.GetAbortReason(); logger.LogWarning($"SCTP packet ABORT chunk received from remote party, reason {abortReason}."); _wasAborted = true; OnAbortReceived?.Invoke(abortReason); @@ -452,6 +453,7 @@ internal void OnPacketReceived(SctpPacketView packet) foreach (var frame in sortedFrames) { OnData?.Invoke(frame); + frame.Dispose(); } } @@ -466,8 +468,9 @@ internal void OnPacketReceived(SctpPacketView packet) case SctpChunkType.HEARTBEAT: // The HEARTBEAT ACK sends back the same chunk but with the type changed. - chunk.ChunkType = (byte)SctpChunkType.HEARTBEAT_ACK; - SendChunk(chunk); + var ack = chunk.AsChunk(); + ack.ChunkType = (byte)SctpChunkType.HEARTBEAT_ACK; + SendChunk(ack); break; case var ct when ct == SctpChunkType.INIT_ACK && State != SctpAssociationState.CookieWait: @@ -484,7 +487,7 @@ internal void OnPacketReceived(SctpPacketView packet) _t1Init = null; } - var initAckChunk = chunk; + var initAckChunk = (SctpInitChunk)chunk.AsChunk(); if (initAckChunk.InitiateTag == 0 || initAckChunk.NumberInboundStreams == 0 || diff --git a/src/net/SCTP/SctpChunkView.cs b/src/net/SCTP/SctpChunkView.cs index 5d8f0f30a..518fb6e43 100644 --- a/src/net/SCTP/SctpChunkView.cs +++ b/src/net/SCTP/SctpChunkView.cs @@ -1,6 +1,5 @@ using System; using System.Buffers.Binary; -using System.Collections.Generic; using System.Runtime.InteropServices; using Microsoft.Extensions.Logging; @@ -16,6 +15,8 @@ public readonly ref struct SctpChunkView readonly ReadOnlySpan buffer; + public ReadOnlySpan Buffer => buffer; + public SctpChunkType Type => (SctpChunkType)buffer[0]; public SctpDataChunk.Flags Flags => (SctpDataChunk.Flags)buffer[1]; public bool Unordered => (Flags & SctpDataChunk.Flags.Unordered) != default; @@ -76,23 +77,54 @@ public uint GetDuplicateTSN(int index) public uint InitialTSN => BinaryPrimitives.ReadUInt32BigEndian(Value.Slice(12, 4)); #endregion Init Chunk - public IEnumerable GetErrorCodes() + public ErrorCodeEnumerable GetErrorCodes() { int paramsBufferLength = Length - SctpChunk.SCTP_CHUNK_HEADER_LENGTH; - if (paramsBufferLength == 0) + int paramPosn = SctpChunk.SCTP_CHUNK_HEADER_LENGTH; + var paramsBuffer = Value.Slice(paramPosn, paramsBufferLength); + return new ErrorCodeEnumerable(paramsBuffer); + } + + public readonly ref struct ErrorCodeEnumerable(ReadOnlySpan paramsBuffer) + { + readonly ReadOnlySpan originalParamsBuffer = paramsBuffer; + public ErrorCodeEnumerator GetEnumerator() => new (originalParamsBuffer); + } + + public ref struct ErrorCodeEnumerator(ReadOnlySpan paramsBuffer) + { + readonly ReadOnlySpan originalParamsBuffer = paramsBuffer; + ReadOnlySpan paramsBuffer; + bool started; + + public readonly SctpErrorCauseCode Current { - yield break; + get + { + SctpTlvChunkParameter.ParseFirstWord(paramsBuffer, out var type); + return (SctpErrorCauseCode)type; + } } - int paramPosn = SctpChunk.SCTP_CHUNK_HEADER_LENGTH; + public bool MoveNext() + { + if (started) + { + int length = SctpTlvChunkParameter.ParseFirstWord(paramsBuffer, out var type); + paramsBuffer = paramsBuffer.Slice(length); + } + else + { + started = true; + paramsBuffer = originalParamsBuffer; + } + return !paramsBuffer.IsEmpty; + } + public void Dispose() { } - var paramsBuffer = Value.Slice(paramPosn, paramsBufferLength); - while (!paramsBuffer.IsEmpty) + public void Reset() { - int length = SctpTlvChunkParameter.ParseFirstWord(paramsBuffer, out var type); - var causeCode = (SctpErrorCauseCode)type; - yield return causeCode; - paramsBuffer = paramsBuffer.Slice(length); + throw new NotSupportedException(); } } @@ -124,14 +156,16 @@ or SHUTDOWN_ACK or SHUTDOWN_COMPLETE }; } + public SctpChunk AsChunk() => SctpChunk.Parse(buffer, 0); + bool ValidateShutdown() { - throw new NotImplementedException(); + return true; } bool ValidateInit() { - throw new NotImplementedException(); + return true; } bool ValidateSack() diff --git a/src/net/SCTP/SctpDataReceiver.cs b/src/net/SCTP/SctpDataReceiver.cs index d8361c652..283b91410 100644 --- a/src/net/SCTP/SctpDataReceiver.cs +++ b/src/net/SCTP/SctpDataReceiver.cs @@ -15,15 +15,16 @@ //----------------------------------------------------------------------------- using System; -using System.Collections.Concurrent; +using System.Buffers; using System.Collections.Generic; using System.Linq; -using System.Text; + using Microsoft.Extensions.Logging; +using SIPSorcery.Sys; namespace SIPSorcery.Net { - public struct SctpDataFrame + public struct SctpDataFrame: IDisposable { public static SctpDataFrame Empty => default; @@ -31,21 +32,25 @@ public struct SctpDataFrame public ushort StreamID; public ushort StreamSeqNum; public uint PPID; - public byte[] UserData; + BorrowedArray userData; + public readonly ReadOnlySpan UserData => userData.Data; - public SctpDataFrame(bool unordered, ushort streamID, ushort streamSeqNum, uint ppid, byte[] userData) + public SctpDataFrame(bool unordered, ushort streamID, ushort streamSeqNum, uint ppid) { Unordered = unordered; StreamID = streamID; StreamSeqNum = streamSeqNum; PPID = ppid; - UserData = userData; } - public bool IsEmpty() + public void SetUserData(ReadOnlySpan userData) { - return UserData == null; + this.userData.Set(userData); } + + public void Dispose() => userData.Dispose(); + + public readonly bool IsEmpty() => userData.IsNull(); } public struct SctpTsnGapBlock @@ -284,13 +289,15 @@ public List OnDataChunk(SctpChunkView dataChunk) dataChunk.Unordered, dataChunk.StreamID, dataChunk.StreamSeqNum, - dataChunk.PPID, - dataChunk.UserData); + dataChunk.PPID); + + frame.SetUserData(dataChunk.UserData); } else { + var tmp = SctpDataChunk.ParseChunk(dataChunk.Buffer, 0, ArrayPool.Shared); // This is a data chunk fragment. - _fragmentedChunks.Add(dataChunk.TSN, dataChunk); + _fragmentedChunks.Add(dataChunk.TSN, tmp); (var begin, var end) = GetChunkBeginAndEnd(_fragmentedChunks, dataChunk.TSN); if (begin != null && end != null) @@ -513,24 +520,26 @@ private SctpDataFrame ExtractFragmentedChunk(Dictionary fra { unchecked { - byte[] full = new byte[MAX_FRAME_SIZE]; + Span full = stackalloc byte[MAX_FRAME_SIZE]; int posn = 0; var beginChunk = fragments[beginTSN]; - var frame = new SctpDataFrame(beginChunk.Unordered, beginChunk.StreamID, beginChunk.StreamSeqNum, beginChunk.PPID, full); uint afterEndTSN = endTSN + 1; uint tsn = beginTSN; while (tsn != afterEndTSN) { - var fragment = fragments[tsn].UserData; - Buffer.BlockCopy(fragment, 0, full, posn, fragment.Length); - posn += fragment.Length; + var fragment = fragments[tsn]; + var fragmentData = fragment.UserData; + fragmentData.CopyTo(full.Slice(posn)); + posn += fragmentData.Length; fragments.Remove(tsn); + fragment.Dispose(); tsn++; } - frame.UserData = frame.UserData.Take(posn).ToArray(); + var frame = new SctpDataFrame(beginChunk.Unordered, beginChunk.StreamID, beginChunk.StreamSeqNum, beginChunk.PPID); + frame.SetUserData(full.Slice(0, posn)); return frame; } diff --git a/src/net/SCTP/SctpDataSender.cs b/src/net/SCTP/SctpDataSender.cs index 5e4df544c..60dd71164 100644 --- a/src/net/SCTP/SctpDataSender.cs +++ b/src/net/SCTP/SctpDataSender.cs @@ -163,7 +163,7 @@ public class SctpDataSender /// /// The total size (in bytes) of queued user data that will be sent to the peer. /// - public ulong BufferedAmount => (ulong)_sendQueue.Sum(x => x.UserData?.Length ?? 0); + public ulong BufferedAmount => (ulong)_sendQueue.Sum(x => x.HasUserData ? x.UserData.Length : 0); /// /// The Transaction Sequence Number (TSN) that will be used in the next DATA chunk sent. diff --git a/src/net/SCTP/SctpPacket.cs b/src/net/SCTP/SctpPacket.cs index 7ccdf6496..a6205736f 100644 --- a/src/net/SCTP/SctpPacket.cs +++ b/src/net/SCTP/SctpPacket.cs @@ -313,5 +313,10 @@ public static bool IsValid(ReadOnlySpan buffer, uint requiredTag) { return GetVerificationTag(buffer) == requiredTag && VerifyChecksum(buffer); } + + public SctpChunk GetChunk(SctpChunkType chunkType) + { + return Chunks.Single(x => x.ChunkType == (byte)chunkType); + } } } diff --git a/src/net/SCTP/SctpPacketView.cs b/src/net/SCTP/SctpPacketView.cs index 056febb91..0fd4b4b0e 100644 --- a/src/net/SCTP/SctpPacketView.cs +++ b/src/net/SCTP/SctpPacketView.cs @@ -106,6 +106,8 @@ public static SctpPacketView Parse(ReadOnlySpan buffer) return new(buffer, chunks, unrecognized); } + public SctpPacket AsPacket() => SctpPacket.Parse(buffer); + SctpPacketView(ReadOnlySpan buffer, SmallList, Chunk> chunks, SmallList, Chunk> unrecognized) { this.buffer = buffer; diff --git a/src/net/SCTP/SctpTransport.cs b/src/net/SCTP/SctpTransport.cs index 4e83436fa..ce8554472 100644 --- a/src/net/SCTP/SctpTransport.cs +++ b/src/net/SCTP/SctpTransport.cs @@ -130,7 +130,7 @@ protected void GotInit(SctpPacketView initPacket, IPEndPoint remoteEndPoint) } else { - var initAckPacket = GetInitAck(initPacket, remoteEndPoint); + var initAckPacket = GetInitAck(initPacket.AsPacket(), remoteEndPoint); Send(null, initAckPacket); } } @@ -198,9 +198,9 @@ protected virtual SctpTransportCookie GetInitAckCookie( /// received on. For transports that don't use an IP transport directly this parameter /// can be set to null and it will not form part of the COOKIE ECHO checks. /// An SCTP packet with a single INIT ACK chunk. - protected SctpPacket GetInitAck(SctpPacketView initPacket, IPEndPoint remoteEP) + protected SctpPacket GetInitAck(SctpPacket initPacket, IPEndPoint remoteEP) { - var initChunk = initPacket.GetChunk(SctpChunkType.INIT); + var initChunk = (SctpInitChunk)initPacket.GetChunk(SctpChunkType.INIT); SctpPacket initAckPacket = new SctpPacket( initPacket.Header.DestinationPort, diff --git a/src/net/WebRTC/DCEP.cs b/src/net/WebRTC/DCEP.cs index 952cc093c..593d8a63e 100644 --- a/src/net/WebRTC/DCEP.cs +++ b/src/net/WebRTC/DCEP.cs @@ -137,7 +137,7 @@ public struct DataChannelOpenMessage /// The buffer to parse the message from. /// The position in the buffer to start parsing from. /// A new DCEP open message instance. - public static DataChannelOpenMessage Parse(byte[] buffer, int posn) + public static DataChannelOpenMessage Parse(ReadOnlySpan buffer, int posn) { if (buffer.Length < DCEP_OPEN_FIXED_PARAMETERS_LENGTH) { @@ -156,12 +156,12 @@ public static DataChannelOpenMessage Parse(byte[] buffer, int posn) if (labelLength > 0) { - dcepOpen.Label = Encoding.UTF8.GetString(buffer, 12, labelLength); + dcepOpen.Label = buffer.Slice(12, labelLength).ToString(Encoding.UTF8); } if (protocolLength > 0) { - dcepOpen.Protocol = Encoding.UTF8.GetString(buffer, 12 + labelLength, protocolLength); + dcepOpen.Protocol = buffer.Slice(12 + labelLength, protocolLength).ToString(Encoding.UTF8); } return dcepOpen; diff --git a/src/net/WebRTC/IRTCDataChannel.cs b/src/net/WebRTC/IRTCDataChannel.cs index b2c2e3618..2222fd6ba 100644 --- a/src/net/WebRTC/IRTCDataChannel.cs +++ b/src/net/WebRTC/IRTCDataChannel.cs @@ -36,7 +36,7 @@ namespace SIPSorcery.Net { - public delegate void OnDataChannelMessageDelegate(RTCDataChannel dc, DataChannelPayloadProtocols protocol, byte[] data); + public delegate void OnDataChannelMessageDelegate(RTCDataChannel dc, DataChannelPayloadProtocols protocol, ReadOnlySpan data); public enum RTCDataChannelState { diff --git a/src/net/WebRTC/RTCDataChannel.cs b/src/net/WebRTC/RTCDataChannel.cs index e9e253287..dc4dbd643 100644 --- a/src/net/WebRTC/RTCDataChannel.cs +++ b/src/net/WebRTC/RTCDataChannel.cs @@ -249,7 +249,7 @@ internal void SendDcepAck() /// /// Event handler for an SCTP data chunk being received for this data channel. /// - internal void GotData(ushort streamID, ushort streamSeqNum, uint ppID, byte[] data) + internal void GotData(ushort streamID, ushort streamSeqNum, uint ppID, ReadOnlySpan data) { //logger.LogTrace($"WebRTC data channel GotData stream ID {streamID}, stream seqnum {streamSeqNum}, ppid {ppID}, label {label}."); diff --git a/src/sys/BorrowedArray.cs b/src/sys/BorrowedArray.cs new file mode 100644 index 000000000..e2d6373a4 --- /dev/null +++ b/src/sys/BorrowedArray.cs @@ -0,0 +1,51 @@ +#nullable enable +using System; +using System.Buffers; + +namespace SIPSorcery.Sys +{ + internal struct BorrowedArray: IDisposable + { + byte[]? data; + int length; + ArrayPool? dataOwner; + + public readonly bool IsNull() => data == null; + public readonly Span Data => data.AsSpan(0, length); + + public static implicit operator Span(BorrowedArray borrowed) => borrowed.Data; + + public void Set(ReadOnlySpan data, ArrayPool pool) + { + if (this.data?.Length >= data.Length) + { + data.CopyTo(this.data); + length = data.Length; + return; + } + + Empty(); + dataOwner = pool; + this.data = pool.Rent(data.Length); + data.CopyTo(this.data); + length = data.Length; + } + + public void Set(ReadOnlySpan data) => Set(data, ArrayPool.Shared); + + public void Set(byte[] data) + { + Empty(); + this.data = data; + } + + void Empty() + { + dataOwner?.Return(data); + data = null; + dataOwner = null; + } + + public void Dispose() => Empty(); + } +} From 267e82d6a82d8653d1f24a27b0460816c32309a4 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Fri, 12 Jan 2024 17:04:35 -0800 Subject: [PATCH 23/88] fixed bad format string for enum --- src/net/SCTP/SctpAssociation.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/net/SCTP/SctpAssociation.cs b/src/net/SCTP/SctpAssociation.cs index 6df71ce7b..273a8cc4a 100644 --- a/src/net/SCTP/SctpAssociation.cs +++ b/src/net/SCTP/SctpAssociation.cs @@ -437,7 +437,7 @@ internal void OnPacketReceived(SctpPacketView packet) } else { - logger.LogTrace($"SCTP data chunk received on ID {ID} with TSN {dataChunk.TSN}, payload length {dataChunk.UserData.Length}, flags {dataChunk.Flags:X2}."); + logger.LogTrace($"SCTP data chunk received on ID {ID} with TSN {dataChunk.TSN}, payload length {dataChunk.UserData.Length}, flags {dataChunk.Flags}."); // A received data chunk can result in multiple data frames becoming available. // For example if a stream has out of order frames already received and the next From 0123c676b779e3879a405a6d015fb75f1c48cd75 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Fri, 12 Jan 2024 17:05:09 -0800 Subject: [PATCH 24/88] fixed chunk value offset and validation --- src/net/SCTP/SctpChunkView.cs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/net/SCTP/SctpChunkView.cs b/src/net/SCTP/SctpChunkView.cs index 518fb6e43..3b1461025 100644 --- a/src/net/SCTP/SctpChunkView.cs +++ b/src/net/SCTP/SctpChunkView.cs @@ -23,7 +23,7 @@ public readonly ref struct SctpChunkView public bool Beginning => (Flags & SctpDataChunk.Flags.Beginning) != default; public bool Ending => (Flags & SctpDataChunk.Flags.Ending) != default; public ushort Length => BinaryPrimitives.ReadUInt16BigEndian(buffer.Slice(2, 2)); - public ReadOnlySpan Value => buffer.Slice(SctpChunk.SCTP_CHUNK_HEADER_LENGTH, Length); + public ReadOnlySpan Value => buffer.Slice(SctpChunk.SCTP_CHUNK_HEADER_LENGTH, Length - SctpChunk.SCTP_CHUNK_HEADER_LENGTH); #region Data Chunk public uint TSN => BinaryPrimitives.ReadUInt32BigEndian(Value); @@ -48,8 +48,7 @@ public ReadOnlySpan UserData public ushort NumDuplicateTSNs => BinaryPrimitives.ReadUInt16BigEndian(Value.Slice(10, 2)); public SctpTsnGapBlock GetTsnGapBlock(int index) { - int posn = SctpChunk.SCTP_CHUNK_HEADER_LENGTH - + SctpSackChunk.FIXED_PARAMETERS_LENGTH + int posn = SctpSackChunk.FIXED_PARAMETERS_LENGTH + (index * SctpSackChunk.GAP_REPORT_LENGTH); return new SctpTsnGapBlock { @@ -58,11 +57,10 @@ public SctpTsnGapBlock GetTsnGapBlock(int index) }; } public ReadOnlySpan GapAckBlocks - => MemoryMarshal.Cast(Value.Slice(SctpChunk.SCTP_CHUNK_HEADER_LENGTH + SctpSackChunk.FIXED_PARAMETERS_LENGTH, NumGapAckBlocks * SctpSackChunk.GAP_REPORT_LENGTH)); + => MemoryMarshal.Cast(Value.Slice(SctpSackChunk.FIXED_PARAMETERS_LENGTH, NumGapAckBlocks * SctpSackChunk.GAP_REPORT_LENGTH)); public uint GetDuplicateTSN(int index) { - int posn = SctpChunk.SCTP_CHUNK_HEADER_LENGTH - + SctpSackChunk.FIXED_PARAMETERS_LENGTH + int posn = SctpSackChunk.FIXED_PARAMETERS_LENGTH + (NumGapAckBlocks * SctpSackChunk.GAP_REPORT_LENGTH) + (index * SctpSackChunk.DUPLICATE_TSN_LENGTH); return BinaryPrimitives.ReadUInt32BigEndian(Value.Slice(posn)); @@ -80,7 +78,7 @@ public uint GetDuplicateTSN(int index) public ErrorCodeEnumerable GetErrorCodes() { int paramsBufferLength = Length - SctpChunk.SCTP_CHUNK_HEADER_LENGTH; - int paramPosn = SctpChunk.SCTP_CHUNK_HEADER_LENGTH; + int paramPosn = 0; var paramsBuffer = Value.Slice(paramPosn, paramsBufferLength); return new ErrorCodeEnumerable(paramsBuffer); } @@ -160,11 +158,13 @@ or SHUTDOWN_ACK or SHUTDOWN_COMPLETE bool ValidateShutdown() { + ValidateBase(); return true; } bool ValidateInit() { + ValidateBase(); return true; } @@ -182,7 +182,7 @@ bool ValidateSack() bool ValidateBase() { - if (Length + SctpChunk.SCTP_CHUNK_HEADER_LENGTH > buffer.Length) + if (Length > buffer.Length) { throw new ArgumentException("Buffer too short to be a valid SCTP chunk."); } @@ -191,6 +191,7 @@ bool ValidateBase() bool ValidateData() { + ValidateBase(); if (Length < SctpDataChunk.FIXED_PARAMETERS_LENGTH) { throw new ApplicationException($"SCTP data chunk cannot be parsed as buffer too short for fixed parameter fields."); @@ -200,6 +201,7 @@ bool ValidateData() bool ValidateError(bool isAbort) { + ValidateBase(); throw new NotImplementedException(); } From d8bc3a1fc39c3aa085884207abd0f1d3a240cffb Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Fri, 12 Jan 2024 17:05:36 -0800 Subject: [PATCH 25/88] fixed BorrowedArray.Set for preallocated array --- src/sys/BorrowedArray.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sys/BorrowedArray.cs b/src/sys/BorrowedArray.cs index e2d6373a4..a702d43f6 100644 --- a/src/sys/BorrowedArray.cs +++ b/src/sys/BorrowedArray.cs @@ -37,6 +37,7 @@ public void Set(byte[] data) { Empty(); this.data = data; + length = data.Length; } void Empty() From 082d476601add843218a97ec3cbdf650281c7667 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Fri, 12 Jan 2024 17:06:00 -0800 Subject: [PATCH 26/88] updated Small package to fix critical issue with stack-allocated lists --- src/SIPSorcery.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SIPSorcery.csproj b/src/SIPSorcery.csproj index f20361c13..cb80476e7 100755 --- a/src/SIPSorcery.csproj +++ b/src/SIPSorcery.csproj @@ -21,7 +21,7 @@ - + From 81f31c8ef3a8f1d60fd23c7cece9b309ec327dc6 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Fri, 12 Jan 2024 17:23:26 -0800 Subject: [PATCH 27/88] fixed SctpTsnGapBlock processing --- src/net/SCTP/SctpChunkView.cs | 4 ++-- src/net/SCTP/SctpDataReceiver.cs | 10 +++++++++- src/net/SCTP/SctpDataSender.cs | 11 +++++++---- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/net/SCTP/SctpChunkView.cs b/src/net/SCTP/SctpChunkView.cs index 3b1461025..7eb227b98 100644 --- a/src/net/SCTP/SctpChunkView.cs +++ b/src/net/SCTP/SctpChunkView.cs @@ -56,8 +56,8 @@ public SctpTsnGapBlock GetTsnGapBlock(int index) End = BinaryPrimitives.ReadUInt16BigEndian(Value.Slice(posn + 2, 2)) }; } - public ReadOnlySpan GapAckBlocks - => MemoryMarshal.Cast(Value.Slice(SctpSackChunk.FIXED_PARAMETERS_LENGTH, NumGapAckBlocks * SctpSackChunk.GAP_REPORT_LENGTH)); + public ReadOnlySpan GapAckBlocks + => Value.Slice(SctpSackChunk.FIXED_PARAMETERS_LENGTH, NumGapAckBlocks * SctpSackChunk.GAP_REPORT_LENGTH); public uint GetDuplicateTSN(int index) { int posn = SctpSackChunk.FIXED_PARAMETERS_LENGTH diff --git a/src/net/SCTP/SctpDataReceiver.cs b/src/net/SCTP/SctpDataReceiver.cs index 283b91410..8183f172a 100644 --- a/src/net/SCTP/SctpDataReceiver.cs +++ b/src/net/SCTP/SctpDataReceiver.cs @@ -16,15 +16,17 @@ using System; using System.Buffers; +using System.Buffers.Binary; using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.Logging; + using SIPSorcery.Sys; namespace SIPSorcery.Net { - public struct SctpDataFrame: IDisposable + public struct SctpDataFrame : IDisposable { public static SctpDataFrame Empty => default; @@ -70,6 +72,12 @@ public struct SctpTsnGapBlock /// DATA chunk received in this Gap Ack Block. /// public ushort End; + + public static SctpTsnGapBlock Read(ReadOnlySpan bytes) => new() + { + Start = BinaryPrimitives.ReadUInt16BigEndian(bytes), + End = BinaryPrimitives.ReadUInt16BigEndian(bytes.Slice(2)), + }; } /// diff --git a/src/net/SCTP/SctpDataSender.cs b/src/net/SCTP/SctpDataSender.cs index 60dd71164..2744fa31d 100644 --- a/src/net/SCTP/SctpDataSender.cs +++ b/src/net/SCTP/SctpDataSender.cs @@ -406,7 +406,7 @@ public void Close() /// ACK'ed TSN. If this distance gets exceeded by a gap report then it's likely something has been /// miscalculated. /// If true, processing of the SACK incremented the - private void ProcessGapReports(ReadOnlySpan sackGapBlocks, uint maxTSNDistance, bool didSackIncrementTSN) + private void ProcessGapReports(ReadOnlySpan sackGapBlocks, uint maxTSNDistance, bool didSackIncrementTSN) { uint lastAckTSN = _cumulativeAckTSN; @@ -417,8 +417,9 @@ private void ProcessGapReports(ReadOnlySpan sackGapBlocks, uint unchecked { // Parse the gap report to identify missing chunks that have now been acknowledged in the gap report - foreach (var block in sackGapBlocks) + for(int index = 0; index < sackGapBlocks.Length; index += SctpSackChunk.GAP_REPORT_LENGTH) { + var block = SctpTsnGapBlock.Read(sackGapBlocks.Slice(index)); for (ushort offset = block.Start; offset <= block.End; offset++) { uint goodTSN = _cumulativeAckTSN + offset; @@ -432,8 +433,9 @@ private void ProcessGapReports(ReadOnlySpan sackGapBlocks, uint } } - foreach (var gapBlock in sackGapBlocks) + for (int index = 0; index < sackGapBlocks.Length; index += SctpSackChunk.GAP_REPORT_LENGTH) { + var gapBlock = SctpTsnGapBlock.Read(sackGapBlocks.Slice(index)); uint goodTSNStart = _cumulativeAckTSN + gapBlock.Start; if (SctpDataReceiver.GetDistance(lastAckTSN, goodTSNStart) > maxTSNDistance) @@ -489,7 +491,8 @@ private void ProcessGapReports(ReadOnlySpan sackGapBlocks, uint { _inFastRecoveryMode = true; // mark the highest outstanding TSN as the Fast Recovery exit point - _fastRecoveryExitPoint = _cumulativeAckTSN + sackGapBlocks[sackGapBlocks.Length - 1].End; + var last = SctpTsnGapBlock.Read(sackGapBlocks.Slice(sackGapBlocks.Length - SctpSackChunk.GAP_REPORT_LENGTH)); + _fastRecoveryExitPoint = _cumulativeAckTSN + last.End; logger.LogTrace($"SCTP sender entering fast recovery mode due to missing TSN {missingTSN}. Fast recovery exit point {_fastRecoveryExitPoint}."); // RFC4960 7.2.3 From ea5a2f7a4add79b8dc2b9ba7c407bc2c7fa182dd Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Fri, 12 Jan 2024 19:30:05 -0800 Subject: [PATCH 28/88] avoid micro allocations on receive in DatagramReceiver --- src/net/WebRTC/RTCSctpTransport.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/net/WebRTC/RTCSctpTransport.cs b/src/net/WebRTC/RTCSctpTransport.cs index 9983db728..86861ed97 100644 --- a/src/net/WebRTC/RTCSctpTransport.cs +++ b/src/net/WebRTC/RTCSctpTransport.cs @@ -273,7 +273,11 @@ private void DoReceive(object state) { try { +#if NET6_0_OR_GREATER + int bytesRead = transport.Receive(recvBuffer.AsSpan(), RECEIVE_TIMEOUT_MILLISECONDS); +#else int bytesRead = transport.Receive(recvBuffer, 0, recvBuffer.Length, RECEIVE_TIMEOUT_MILLISECONDS); +#endif if (bytesRead == DtlsSrtpTransport.DTLS_RETRANSMISSION_CODE) { From a7a7694358455c447cf185ea74b2e2e9cc10f7af Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Fri, 12 Jan 2024 19:46:00 -0800 Subject: [PATCH 29/88] data channel send takes ReadOnlySpan instead of array --- .../DataChannelBandwidth/DataChannelStream.cs | 2 +- examples/WebRTCScenarios/DataChannelBandwidth/Program.cs | 5 ++--- src/net/SCTP/SctpAssociation.cs | 2 +- src/net/SCTP/SctpDataSender.cs | 4 ++-- src/net/WebRTC/IRTCDataChannel.cs | 2 +- src/net/WebRTC/RTCDataChannel.cs | 4 ++-- 6 files changed, 9 insertions(+), 10 deletions(-) diff --git a/examples/WebRTCScenarios/DataChannelBandwidth/DataChannelStream.cs b/examples/WebRTCScenarios/DataChannelBandwidth/DataChannelStream.cs index 58f4c23c9..f3152915b 100644 --- a/examples/WebRTCScenarios/DataChannelBandwidth/DataChannelStream.cs +++ b/examples/WebRTCScenarios/DataChannelBandwidth/DataChannelStream.cs @@ -251,7 +251,7 @@ await Task.Run(() => Send(packet.Span), cancellationToken) long totalSent; void Send(ReadOnlySpan buffer) { - channel.send(buffer.ToArray()); + channel.send(buffer); Interlocked.Add(ref totalSent, buffer.Length); } diff --git a/examples/WebRTCScenarios/DataChannelBandwidth/Program.cs b/examples/WebRTCScenarios/DataChannelBandwidth/Program.cs index 096c64244..7d4a426f2 100644 --- a/examples/WebRTCScenarios/DataChannelBandwidth/Program.cs +++ b/examples/WebRTCScenarios/DataChannelBandwidth/Program.cs @@ -4,13 +4,12 @@ using Microsoft.Extensions.Logging; -using SIPSorcery; using SIPSorcery.Net; ILoggerFactory logs = LoggerFactory.Create( - builder => builder.AddFilter(level => level >= LogLevel.Warning).AddConsole()); + builder => builder.AddFilter(level => level >= LogLevel.Debug).AddConsole()); -LogFactory.Set(logs); +// SIPSorcery.LogFactory.Set(logs); var rtcConfig = new RTCConfiguration { diff --git a/src/net/SCTP/SctpAssociation.cs b/src/net/SCTP/SctpAssociation.cs index 273a8cc4a..5ed5e7664 100644 --- a/src/net/SCTP/SctpAssociation.cs +++ b/src/net/SCTP/SctpAssociation.cs @@ -592,7 +592,7 @@ public void SendData(ushort streamID, uint ppid, string message) /// The stream ID to sent the data on. /// The payload protocol ID for the data. /// The byte data to send. - public void SendData(ushort streamID, uint ppid, byte[] data) + public void SendData(ushort streamID, uint ppid, ReadOnlySpan data) { if (_wasAborted) { diff --git a/src/net/SCTP/SctpDataSender.cs b/src/net/SCTP/SctpDataSender.cs index 2744fa31d..a70a59c1e 100644 --- a/src/net/SCTP/SctpDataSender.cs +++ b/src/net/SCTP/SctpDataSender.cs @@ -309,7 +309,7 @@ public void GotSack(SctpChunkView sack) /// The stream ID to sent the data on. /// The payload protocol ID for the data. /// The byte data to send. - public void SendData(ushort streamID, uint ppid, byte[] data) + public void SendData(ushort streamID, uint ppid, ReadOnlySpan data) { // combined spin/lock wait while (!_queueSpaceAvailable.Wait(TimeSpan.FromMilliseconds(10)) && _sendQueue.Count > MaxSendQueueCount) @@ -341,7 +341,7 @@ public void SendData(ushort streamID, uint ppid, byte[] data) // Future TODO: Replace with slice when System.Memory is introduced as a dependency. byte[] payload = new byte[payloadLength]; - Buffer.BlockCopy(data, offset, payload, 0, payloadLength); + data.Slice(offset, payloadLength).CopyTo(payload); bool isBegining = index == 0; bool isEnd = ((offset + payloadLength) >= data.Length) ? true : false; diff --git a/src/net/WebRTC/IRTCDataChannel.cs b/src/net/WebRTC/IRTCDataChannel.cs index 2222fd6ba..d26526add 100644 --- a/src/net/WebRTC/IRTCDataChannel.cs +++ b/src/net/WebRTC/IRTCDataChannel.cs @@ -161,7 +161,7 @@ interface IRTCDataChannel string binaryType { get; set; } void send(string data); - void send(byte[] data); + void send(ReadOnlySpan data); }; public class RTCDataChannelInit diff --git a/src/net/WebRTC/RTCDataChannel.cs b/src/net/WebRTC/RTCDataChannel.cs index dc4dbd643..dd811126b 100644 --- a/src/net/WebRTC/RTCDataChannel.cs +++ b/src/net/WebRTC/RTCDataChannel.cs @@ -165,7 +165,7 @@ public void send(string message) /// Sends a binary data payload on the data channel. /// /// The data to send. - public void send(byte[] data) + public void send(ReadOnlySpan data) { if (data.Length > _transport.maxMessageSize) { @@ -180,7 +180,7 @@ public void send(byte[] data) { lock (this) { - if (data?.Length == 0) + if (data.Length == 0) { _transport.RTCSctpAssociation.SendData(id.GetValueOrDefault(), (uint)DataChannelPayloadProtocols.WebRTC_Binary_Empty, From 282915076442d662320d475983142f6192a1a3eb Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Fri, 12 Jan 2024 21:37:10 -0800 Subject: [PATCH 30/88] label data channel bandwidth test threads --- examples/WebRTCScenarios/DataChannelBandwidth/Program.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/examples/WebRTCScenarios/DataChannelBandwidth/Program.cs b/examples/WebRTCScenarios/DataChannelBandwidth/Program.cs index 7d4a426f2..a6340bd8e 100644 --- a/examples/WebRTCScenarios/DataChannelBandwidth/Program.cs +++ b/examples/WebRTCScenarios/DataChannelBandwidth/Program.cs @@ -1,5 +1,5 @@ using System.Diagnostics; - +using System.Runtime.CompilerServices; using DataChannelBandwidth; using Microsoft.Extensions.Logging; @@ -72,12 +72,14 @@ void Send(RTCDataChannel channel) } } -void SendRecv(RTCDataChannel channel, ref long received) +void SendRecv(RTCDataChannel channel, ref long received, + [CallerArgumentExpression(nameof(channel))] string name = "") { var stream = new DataChannelStream(channel); var sender = new Thread(() => Send(channel)) { IsBackground = true, + Name = $"{name} sender", }; sender.Start(); From 5dc6420dff0318b27093b7c8f7904b7a41625498 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Sat, 13 Jan 2024 10:41:35 -0800 Subject: [PATCH 31/88] avoid allocation when submitting data chunks --- src/net/SCTP/Chunks/SctpChunk.cs | 8 ++-- src/net/SCTP/Chunks/SctpDataChunk.cs | 17 ++++---- src/net/SCTP/SctpDataSender.cs | 62 ++++++++++++++++++++-------- src/sys/BorrowedArray.cs | 5 +++ 4 files changed, 61 insertions(+), 31 deletions(-) diff --git a/src/net/SCTP/Chunks/SctpChunk.cs b/src/net/SCTP/Chunks/SctpChunk.cs index bfb5d1c2d..8e7b92547 100644 --- a/src/net/SCTP/Chunks/SctpChunk.cs +++ b/src/net/SCTP/Chunks/SctpChunk.cs @@ -208,12 +208,14 @@ public ushort ParseFirstWord(ReadOnlySpan buffer, int posn) /// /// The buffer to write the chunk header to. /// The position in the buffer to write at. - /// The padded length of this chunk. - protected void WriteChunkHeader(Span buffer, int posn) + /// Unpadded length of this chunk. + protected ushort WriteChunkHeader(Span buffer, int posn) { buffer[posn] = ChunkType; buffer[posn + 1] = ChunkFlags; - NetConvert.ToBuffer(GetChunkLength(false), buffer, posn + 2); + ushort length = GetChunkLength(false); + NetConvert.ToBuffer(length, buffer, posn + 2); + return length; } /// diff --git a/src/net/SCTP/Chunks/SctpDataChunk.cs b/src/net/SCTP/Chunks/SctpDataChunk.cs index 466b6f5b8..230bc42be 100644 --- a/src/net/SCTP/Chunks/SctpDataChunk.cs +++ b/src/net/SCTP/Chunks/SctpDataChunk.cs @@ -85,7 +85,7 @@ public class SctpDataChunk : SctpChunk, IDisposable /// This is the payload user data. /// public Span UserData => userData; - public bool HasUserData => !userData.IsNull(); + public int UserDataLength => userData.Length; internal struct Timestamp { @@ -124,9 +124,9 @@ public SctpDataChunk( ushort streamID, ushort seqnum, uint ppid, - byte[] data) : base(SctpChunkType.DATA) + ReadOnlySpan data) : base(SctpChunkType.DATA) { - if (data == null || data.Length == 0) + if (data.Length == 0) { throw new ArgumentNullException("data", "The SctpDataChunk data parameter cannot be empty."); } @@ -154,7 +154,7 @@ public SctpDataChunk( public override ushort GetChunkLength(bool padded) { ushort len = SCTP_CHUNK_HEADER_LENGTH + FIXED_PARAMETERS_LENGTH; - len += (ushort)(!userData.IsNull() ? UserData.Length : 0); + len += (ushort)userData.Length; return (padded) ? SctpPadding.PadTo4ByteBoundary(len) : len; } @@ -167,7 +167,7 @@ public override ushort GetChunkLength(bool padded) /// The number of bytes, including padding, written to the buffer. public override ushort WriteTo(Span buffer, int posn) { - WriteChunkHeader(buffer, posn); + ushort length = WriteChunkHeader(buffer, posn); // Write fixed parameters. int startPosn = posn + SCTP_CHUNK_HEADER_LENGTH; @@ -179,12 +179,9 @@ public override ushort WriteTo(Span buffer, int posn) int userDataPosn = startPosn + FIXED_PARAMETERS_LENGTH; - if (!userData.IsNull()) - { - UserData.CopyTo(buffer.Slice(userDataPosn)); - } + userData.DataMayBeEmpty.CopyTo(buffer.Slice(userDataPosn)); - return GetChunkLength(true); + return SctpPadding.PadTo4ByteBoundary(length); } public bool IsEmpty() diff --git a/src/net/SCTP/SctpDataSender.cs b/src/net/SCTP/SctpDataSender.cs index a70a59c1e..e60a3a99a 100644 --- a/src/net/SCTP/SctpDataSender.cs +++ b/src/net/SCTP/SctpDataSender.cs @@ -129,7 +129,7 @@ public class SctpDataSender /// A count of the bytes currently in-flight to the remote peer. /// internal uint _outstandingBytes => - (uint)(_unconfirmedChunks.Sum(x => x.Value.UserData.Length)); + (uint)(_unconfirmedChunks.Sum(x => x.Value.UserDataLength)); /// /// The TSN that the remote peer has acknowledged. @@ -163,7 +163,7 @@ public class SctpDataSender /// /// The total size (in bytes) of queued user data that will be sent to the peer. /// - public ulong BufferedAmount => (ulong)_sendQueue.Sum(x => x.HasUserData ? x.UserData.Length : 0); + public ulong BufferedAmount => (ulong)_sendQueue.Sum(x => x.UserDataLength); /// /// The Transaction Sequence Number (TSN) that will be used in the next DATA chunk sent. @@ -224,7 +224,7 @@ public void GotSack(SctpChunkView sack) UpdateRoundTripTime(result); } - _lastAckedDataChunkSize = result.UserData.Length; + _lastAckedDataChunkSize = result.UserDataLength; } if (!_gotFirstSACK) @@ -273,7 +273,7 @@ public void GotSack(SctpChunkView sack) for (int tsnIndex = 0; tsnIndex < sack.NumDuplicateTSNs; tsnIndex++) { uint duplicateTSN = sack.GetDuplicateTSN(tsnIndex); - _unconfirmedChunks.TryRemove(duplicateTSN, out _); + RemoveUnconfirmedChunk(duplicateTSN); _missingChunks.TryRemove(duplicateTSN, out _); } } @@ -319,6 +319,10 @@ public void SendData(ushort streamID, uint ppid, ReadOnlySpan data) lock (_sendQueue) { + if (_closed.HasOccurred) + { + return; + } ushort seqnum = 0; if (_streamSeqnums.ContainsKey(streamID)) @@ -339,10 +343,6 @@ public void SendData(ushort streamID, uint ppid, ReadOnlySpan data) int offset = (index == 0) ? 0 : (index * _defaultMTU); int payloadLength = (offset + _defaultMTU < data.Length) ? _defaultMTU : data.Length - offset; - // Future TODO: Replace with slice when System.Memory is introduced as a dependency. - byte[] payload = new byte[payloadLength]; - data.Slice(offset, payloadLength).CopyTo(payload); - bool isBegining = index == 0; bool isEnd = ((offset + payloadLength) >= data.Length) ? true : false; @@ -354,7 +354,7 @@ public void SendData(ushort streamID, uint ppid, ReadOnlySpan data) streamID, seqnum, ppid, - payload); + data.Slice(offset, payloadLength)); _sendQueue.Enqueue(dataChunk); @@ -395,6 +395,17 @@ public void StartSending() public void Close() { _closed.TryMarkOccurred(); + foreach (var chunk in _unconfirmedChunks) + { + chunk.Value.Dispose(); + } + lock (_sendQueue) + { + foreach (var chunk in _sendQueue) + { + chunk.Dispose(); + } + } } /// @@ -425,10 +436,11 @@ private void ProcessGapReports(ReadOnlySpan sackGapBlocks, uint maxTSNDist uint goodTSN = _cumulativeAckTSN + offset; _missingChunks.TryRemove(goodTSN, out _); - if (_unconfirmedChunks.TryRemove(goodTSN, out _)) + if (_unconfirmedChunks.TryRemove(goodTSN, out var chunk)) { - logger.LogTrace($"SCTP acknowledged data chunk receipt in gap report for TSN {goodTSN}"); + logger.LogTrace("SCTP acknowledged data chunk receipt in gap report for TSN {TSN}", goodTSN); highestTsnNewlyAcknowledged = goodTSN; + chunk.Dispose(); } } } @@ -523,7 +535,7 @@ private void RemoveAckedUnconfirmedChunks(uint sackTSN) if (_cumulativeAckTSN == sackTSN) { // This is normal for the first SACK received. - _unconfirmedChunks.TryRemove(_cumulativeAckTSN, out _); + RemoveUnconfirmedChunk(_cumulativeAckTSN); _missingChunks.TryRemove(_cumulativeAckTSN, out _); } else @@ -533,7 +545,7 @@ private void RemoveAckedUnconfirmedChunks(uint sackTSN) for (uint offset = 0; offset <= SctpDataReceiver.GetDistance(_cumulativeAckTSN, sackTSN); offset++) { uint ackd = _cumulativeAckTSN + offset; - _unconfirmedChunks.TryRemove(ackd, out _); + RemoveUnconfirmedChunk(ackd); _missingChunks.TryRemove(ackd, out _); } _cumulativeAckTSN = sackTSN; @@ -541,6 +553,14 @@ private void RemoveAckedUnconfirmedChunks(uint sackTSN) } } + private void RemoveUnconfirmedChunk(uint tsn) + { + if (_unconfirmedChunks.TryRemove(tsn, out var chunk)) + { + chunk.Dispose(); + } + } + /// /// Worker thread to process the send and retransmit queues. /// @@ -572,7 +592,7 @@ private void DoSend(object state) missingChunk.LastSentAt = now; missingChunk.SendCount += 1; - logger.LogTrace($"SCTP resending missing data chunk for TSN {missingChunk.TSN}, data length {missingChunk.UserData.Length}, " + + logger.LogTrace($"SCTP resending missing data chunk for TSN {missingChunk.TSN}, data length {missingChunk.UserDataLength}, " + $"flags {missingChunk.ChunkFlags:X2}, send count {missingChunk.SendCount}."); _sendDataChunk(missingChunk); @@ -598,7 +618,7 @@ private void DoSend(object state) chunk.LastSentAt = SctpDataChunk.Timestamp.Now; chunk.SendCount += 1; - logger.LogTrace($"SCTP retransmitting data chunk for TSN {chunk.TSN}, data length {chunk.UserData.Length}, " + + logger.LogTrace($"SCTP retransmitting data chunk for TSN {chunk.TSN}, data length {chunk.UserDataLength}, " + $"flags {chunk.ChunkFlags:X2}, send count {chunk.SendCount}."); _sendDataChunk(chunk); @@ -634,11 +654,17 @@ private void DoSend(object state) dataChunk.LastSentAt = SctpDataChunk.Timestamp.Now; dataChunk.SendCount = 1; - logger.LogTrace($"SCTP sending data chunk for TSN {dataChunk.TSN}, data length {dataChunk.UserData.Length}, " + + logger.LogTrace($"SCTP sending data chunk for TSN {dataChunk.TSN}, data length {dataChunk.UserDataLength}, " + $"flags {dataChunk.ChunkFlags:X2}, send count {dataChunk.SendCount}."); - _unconfirmedChunks.TryAdd(dataChunk.TSN, dataChunk); - _sendDataChunk(dataChunk); + if (_unconfirmedChunks.TryAdd(dataChunk.TSN, dataChunk)) + { + _sendDataChunk(dataChunk); + } + else + { + logger.LogDebug("SCTP duplicate TSN {TSN} detected in send queue.", dataChunk.TSN); + } if (_sendQueue.Count < MaxSendQueueCount) { _queueSpaceAvailable.Set(); diff --git a/src/sys/BorrowedArray.cs b/src/sys/BorrowedArray.cs index a702d43f6..fcbee7106 100644 --- a/src/sys/BorrowedArray.cs +++ b/src/sys/BorrowedArray.cs @@ -12,6 +12,11 @@ internal struct BorrowedArray: IDisposable public readonly bool IsNull() => data == null; public readonly Span Data => data.AsSpan(0, length); + public readonly Span DataMayBeEmpty + => data is { } array + ? array.AsSpan(0, Math.Min(length, array.Length)) + : []; + public readonly int Length => length; public static implicit operator Span(BorrowedArray borrowed) => borrowed.Data; From d6f8cd0700be2aa977defe4f620104acac602d93 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Sat, 13 Jan 2024 11:12:02 -0800 Subject: [PATCH 32/88] avoid allocations in bandwidth test itself --- .../DataChannelBandwidth/DataChannelStream.cs | 15 ++++++++++----- .../DataChannelBandwidth/SpanExtensions.cs | 13 +++++++++++++ 2 files changed, 23 insertions(+), 5 deletions(-) create mode 100644 examples/WebRTCScenarios/DataChannelBandwidth/SpanExtensions.cs diff --git a/examples/WebRTCScenarios/DataChannelBandwidth/DataChannelStream.cs b/examples/WebRTCScenarios/DataChannelBandwidth/DataChannelStream.cs index f3152915b..1a2e2431b 100644 --- a/examples/WebRTCScenarios/DataChannelBandwidth/DataChannelStream.cs +++ b/examples/WebRTCScenarios/DataChannelBandwidth/DataChannelStream.cs @@ -1,5 +1,6 @@ namespace DataChannelBandwidth; +using System.Buffers; using Microsoft.Extensions.Logging; using SIPSorcery.Net; @@ -9,7 +10,7 @@ class DataChannelStream : Stream { readonly RTCDataChannel channel; int currentMessageOffset; - byte[] message = Array.Empty(); + ArraySegment message = Array.Empty(); readonly CancellationTokenSource closed = new(); readonly SemaphoreSlim messageNeeded = new(0, 1); readonly SemaphoreSlim messageAvailable = new(0, maxCount: 1); @@ -83,7 +84,11 @@ void OnMessage(RTCDataChannel _, DataChannelPayloadProtocols protocol, ReadOnlyS messageNeeded.Wait(); lock (sync) { - message = data.ToArray(); + if (message.Array is not null) + { + ArrayPool.Shared.Return(message.Array); + } + message = data.ToArraySegment(ArrayPool.Shared); currentMessageOffset = 0; } messageAvailable.Release(); @@ -138,7 +143,7 @@ public override int Read(Span buffer) lock (sync) { - int remaining = message.Length - currentMessageOffset; + int remaining = message.Count - currentMessageOffset; int toCopy = Math.Min(remaining, buffer.Length); message.AsSpan(currentMessageOffset, toCopy).CopyTo(buffer); currentMessageOffset += toCopy; @@ -192,7 +197,7 @@ public override async ValueTask ReadAsync(Memory buffer, lock (sync) { - int remaining = message.Length - currentMessageOffset; + int remaining = message.Count - currentMessageOffset; int toCopy = Math.Min(remaining, buffer.Length); message.AsSpan(currentMessageOffset, toCopy).CopyTo(buffer.Span); currentMessageOffset += toCopy; @@ -207,7 +212,7 @@ public override async ValueTask ReadAsync(Memory buffer, } long totalRead; - bool MessageNeeded() => message.Length - currentMessageOffset == 0; + bool MessageNeeded() => message.Count - currentMessageOffset == 0; public override void Write(byte[] buffer, int offset, int count) { diff --git a/examples/WebRTCScenarios/DataChannelBandwidth/SpanExtensions.cs b/examples/WebRTCScenarios/DataChannelBandwidth/SpanExtensions.cs new file mode 100644 index 000000000..ebd8c8e9f --- /dev/null +++ b/examples/WebRTCScenarios/DataChannelBandwidth/SpanExtensions.cs @@ -0,0 +1,13 @@ +using System.Buffers; + +namespace DataChannelBandwidth; + +static class SpanExtensions +{ + public static ArraySegment ToArraySegment(this ReadOnlySpan span, ArrayPool pool) + { + var result = pool.Rent(span.Length); + span.CopyTo(result); + return new ArraySegment(result, 0, span.Length); + } +} From 23402ef3137be7280b029db5ae4fa9d0a0c077e6 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Sat, 13 Jan 2024 13:53:30 -0800 Subject: [PATCH 33/88] improved receive and send socket allocations by using SendToAsync overloads that return ValueTask --- src/net/RTP/RTPChannel.cs | 237 ++++++++++++++++++++++++++++---------- 1 file changed, 177 insertions(+), 60 deletions(-) diff --git a/src/net/RTP/RTPChannel.cs b/src/net/RTP/RTPChannel.cs index fdcb1f9fc..ab5233bb3 100644 --- a/src/net/RTP/RTPChannel.cs +++ b/src/net/RTP/RTPChannel.cs @@ -20,6 +20,7 @@ using System; using System.Net; using System.Net.Sockets; +using System.Threading.Tasks; using Microsoft.Extensions.Logging; using SIPSorcery.Sys; @@ -129,7 +130,37 @@ public virtual void BeginReceiveFrom() { m_isRunningReceive = true; EndPoint recvEndPoint = m_addressFamily == AddressFamily.InterNetwork ? IPv4AnyEndPoint : IPv6AnyEndPoint; +#if FALSE // NET6_0_OR_GREATER bandwidth test falters at some point if this is enabled + var recive = m_socket.ReceiveFromAsync(m_recvBuffer.AsMemory(), SocketFlags.None, recvEndPoint); + if (recive.IsCompleted) + { + try + { + var result = recive.GetAwaiter().GetResult(); + EndReceiveFrom(result); + } + catch (Exception excp) + { + EndReceiveFrom(excp); + } + } + else + { + recive.AsTask().ContinueWith(t => + { + try + { + EndReceiveFrom(t.GetAwaiter().GetResult()); + } + catch (Exception excp) + { + EndReceiveFrom(excp); + } + }); + } +#else m_socket.BeginReceiveFrom(m_recvBuffer, 0, m_recvBuffer.Length, SocketFlags.None, ref recvEndPoint, endReceiveFrom, null); +#endif } // Thrown when socket is closed. Can be safely ignored. // This exception can be thrown in response to an ICMP packet. The problem is the ICMP packet can be a false positive. @@ -156,6 +187,30 @@ public virtual void BeginReceiveFrom() } } +#if NET6_0_OR_GREATER + protected virtual void EndReceiveFrom(SocketReceiveFromResult result) + { + OnBytesRead(result.RemoteEndPoint, result.ReceivedBytes); + + try + { + Drain(); + } + catch (Exception error) + { + EndReceiveFrom(error); + } + finally + { + m_isRunningReceive = false; + if (!m_isClosed) + { + BeginReceiveFrom(); + } + } + } +#endif + readonly AsyncCallback endReceiveFrom; /// /// Handler for end of the begin receive call. @@ -170,57 +225,33 @@ protected virtual void EndReceiveFrom(IAsyncResult ar) { EndPoint remoteEP = m_addressFamily == AddressFamily.InterNetwork ? IPv4AnyEndPoint : IPv6AnyEndPoint; int bytesRead = m_socket.EndReceiveFrom(ar, ref remoteEP); - - if (bytesRead > 0) - { - // During experiments IPPacketInformation wasn't getting set on Linux. Without it the local IP address - // cannot be determined when a listener was bound to IPAddress.Any (or IPv6 equivalent). If the caller - // is relying on getting the local IP address on Linux then something may fail. - //if (packetInfo != null && packetInfo.Address != null) - //{ - // localEndPoint = new IPEndPoint(packetInfo.Address, localEndPoint.Port); - //} - - if (bytesRead < 256 * 1024) - { - Span packetBuffer = stackalloc byte[bytesRead]; - CallOnPacketReceivedCallback(m_localEndPoint.Port, remoteEP as IPEndPoint, m_recvBuffer.AsSpan().Slice(0, bytesRead)); - } else - { - logger.LogCritical("UDP packet received was larger than 256KB and was ignored."); - } - } + OnBytesRead(remoteEP, bytesRead); } - // If there is still data available it should be read now. This is more efficient than calling - // BeginReceiveFrom which will incur the overhead of creating the callback and then immediately firing it. - // It also avoids the situation where if the application cannot keep up with the network then BeginReceiveFrom - // will be called synchronously (if data is available it calls the callback method immediately) which can - // create a very nasty stack. - while (!m_isClosed && m_socket.Available > 0) + Drain(); + } + catch (Exception error) + { + EndReceiveFrom(error); + } + finally + { + m_isRunningReceive = false; + if (!m_isClosed) { - EndPoint remoteEP = m_addressFamily == AddressFamily.InterNetwork ? IPv4AnyEndPoint : IPv6AnyEndPoint; - int bytesReadSync = m_socket.ReceiveFrom(m_recvBuffer, 0, m_recvBuffer.Length, SocketFlags.None, ref remoteEP); - - if (bytesReadSync > 0) - { - byte[] packetBufferSync = new byte[bytesReadSync]; - // TODO: When .NET Framework support is dropped switch to using a slice instead of a copy. - Buffer.BlockCopy(m_recvBuffer, 0, packetBufferSync, 0, bytesReadSync); - CallOnPacketReceivedCallback(m_localEndPoint.Port, remoteEP as IPEndPoint, packetBufferSync); - } - else - { - break; - } + BeginReceiveFrom(); } } - catch (SocketException resetSockExcp) when (resetSockExcp.SocketErrorCode == SocketError.ConnectionReset) + } + + private void EndReceiveFrom(Exception excp) + { + switch(excp) { + case SocketException resetSockExcp when (resetSockExcp.SocketErrorCode == SocketError.ConnectionReset): // Thrown when close is called on a socket from this end. Safe to ignore. - } - catch (SocketException sockExcp) - { + break; + case SocketException sockExcp: // Socket errors do not trigger a close. The reason being that there are genuine situations that can cause them during // normal RTP operation. For example: // - the RTP connection may start sending before the remote socket starts listening, @@ -231,20 +262,68 @@ protected virtual void EndReceiveFrom(IAsyncResult ar) // BeginReceive before any packets have been exchanged. This means it's not safe to close if BeginReceive gets an ICMP // error since the remote party may not have initialised their socket yet. logger.LogWarning(sockExcp, $"SocketException UdpReceiver.EndReceiveFrom ({sockExcp.SocketErrorCode}). {sockExcp.Message}"); - } - catch (ObjectDisposedException) // Thrown when socket is closed. Can be safely ignored. - { } - catch (Exception excp) - { + break; + case ObjectDisposedException: // Thrown when socket is closed. Can be safely ignored. + break; + case AggregateException: + foreach (var innerExcp in (excp as AggregateException).InnerExceptions) + { + EndReceiveFrom(innerExcp); + } + break; + default: logger.LogError($"Exception UdpReceiver.EndReceiveFrom. {excp}"); Close(excp.Message); + break; } - finally + } + + private void OnBytesRead(EndPoint remoteEP, int bytesRead) + { + if (bytesRead > 0) { - m_isRunningReceive = false; - if (!m_isClosed) + // During experiments IPPacketInformation wasn't getting set on Linux. Without it the local IP address + // cannot be determined when a listener was bound to IPAddress.Any (or IPv6 equivalent). If the caller + // is relying on getting the local IP address on Linux then something may fail. + //if (packetInfo != null && packetInfo.Address != null) + //{ + // localEndPoint = new IPEndPoint(packetInfo.Address, localEndPoint.Port); + //} + + if (bytesRead < 256 * 1024) { - BeginReceiveFrom(); + Span packetBuffer = stackalloc byte[bytesRead]; + CallOnPacketReceivedCallback(m_localEndPoint.Port, remoteEP as IPEndPoint, m_recvBuffer.AsSpan().Slice(0, bytesRead)); + } + else + { + logger.LogCritical("UDP packet received was larger than 256KB and was ignored."); + } + } + } + + private void Drain() + { + // If there is still data available it should be read now. This is more efficient than calling + // BeginReceiveFrom which will incur the overhead of creating the callback and then immediately firing it. + // It also avoids the situation where if the application cannot keep up with the network then BeginReceiveFrom + // will be called synchronously (if data is available it calls the callback method immediately) which can + // create a very nasty stack. + while (!m_isClosed && m_socket.Available > 0) + { + EndPoint remoteEP = m_addressFamily == AddressFamily.InterNetwork ? IPv4AnyEndPoint : IPv6AnyEndPoint; + int bytesReadSync = m_socket.ReceiveFrom(m_recvBuffer, 0, m_recvBuffer.Length, SocketFlags.None, ref remoteEP); + + if (bytesReadSync > 0) + { + byte[] packetBufferSync = new byte[bytesReadSync]; + // TODO: When .NET Framework support is dropped switch to using a slice instead of a copy. + Buffer.BlockCopy(m_recvBuffer, 0, packetBufferSync, 0, bytesReadSync); + CallOnPacketReceivedCallback(m_localEndPoint.Port, remoteEP as IPEndPoint, packetBufferSync); + } + else + { + break; } } } @@ -524,7 +603,27 @@ public virtual SocketError Send(RTPChannelSocketsEnum sendOn, IPEndPoint dstEndP m_rtpReceiver.BeginReceiveFrom(); } +#if NET6_0_OR_GREATER + var send = sendSocket.SendToAsync(buffer.AsMemory(), SocketFlags.None, dstEndPoint); + if (send.IsCompleted) + { + try + { + send.GetAwaiter().GetResult(); + } + catch (Exception excp) + { + EndSendTo(excp); + } + } + else + { + send.AsTask().ContinueWith(t => EndSendTo(t.Exception), TaskContinuationOptions.OnlyOnFaulted); + } + +#else sendSocket.BeginSendTo(buffer, 0, buffer.Length, SocketFlags.None, dstEndPoint, endSendTo, sendSocket); +#endif return SocketError.Success; } catch (ObjectDisposedException) // Thrown when socket is closed. Can be safely ignored. @@ -555,20 +654,38 @@ private void EndSendTo(IAsyncResult ar) Socket sendSocket = (Socket)ar.AsyncState; int bytesSent = sendSocket.EndSendTo(ar); } - catch (SocketException sockExcp) + catch (Exception excp) { + EndSendTo(excp); + } + } + + private void EndSendTo(Exception exception) + { + switch (exception) + { + case SocketException sockExcp: // Socket errors do not trigger a close. The reason being that there are genuine situations that can cause them during // normal RTP operation. For example: // - the RTP connection may start sending before the remote socket starts listening, // - an on hold, transfer, etc. operation can change the RTP end point which could result in socket errors from the old // or new socket during the transition. logger.LogWarning(sockExcp, $"SocketException RTPChannel EndSendTo ({sockExcp.ErrorCode}). {sockExcp.Message}"); - } - catch (ObjectDisposedException) // Thrown when socket is closed. Can be safely ignored. - { } - catch (Exception excp) - { - logger.LogError($"Exception RTPChannel EndSendTo. {excp.Message}"); + break; + + case ObjectDisposedException: // Thrown when socket is closed. Can be safely ignored. + break; + + case AggregateException aggExcp: + foreach (var innerExcp in aggExcp.InnerExceptions) + { + EndSendTo(innerExcp); + } + break; + + default: + logger.LogError("Exception RTPChannel EndSendTo. {Message}", exception.Message); + break; } } From 805f1f885624c8cb651d1d0015aaccde726c0446 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Mon, 15 Jan 2024 15:27:11 -0800 Subject: [PATCH 34/88] got rid of many unnecessary allocations in DoSend --- src/net/SCTP/SctpDataSender.cs | 35 ++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/src/net/SCTP/SctpDataSender.cs b/src/net/SCTP/SctpDataSender.cs index e60a3a99a..8a6128314 100644 --- a/src/net/SCTP/SctpDataSender.cs +++ b/src/net/SCTP/SctpDataSender.cs @@ -566,7 +566,7 @@ private void RemoveUnconfirmedChunk(uint tsn) /// private void DoSend(object state) { - logger.LogDebug($"SCTP association data send thread started for association {_associationID}."); + logger.LogDebug("SCTP association data send thread started for association {ID}.", _associationID); while (!_closed.HasOccurred) { @@ -592,8 +592,9 @@ private void DoSend(object state) missingChunk.LastSentAt = now; missingChunk.SendCount += 1; - logger.LogTrace($"SCTP resending missing data chunk for TSN {missingChunk.TSN}, data length {missingChunk.UserDataLength}, " + - $"flags {missingChunk.ChunkFlags:X2}, send count {missingChunk.SendCount}."); + logger.LogTrace("SCTP resending missing data chunk for TSN {TSN}, data length {Length}, " + + "flags {Flags:X2}, send count {Count}.", + missingChunk.TSN, missingChunk.UserDataLength, missingChunk.ChunkFlags, missingChunk.SendCount); _sendDataChunk(missingChunk); chunksSent++; @@ -610,16 +611,25 @@ private void DoSend(object state) // Check if there are any unconfirmed transactions that are due for a retransmit. if (chunksSent < burstSize && !_unconfirmedChunks.IsEmpty) { - foreach (var chunk in _unconfirmedChunks - .Select(kv => kv.Value) - .Where(x => now.Milliseconds - x.LastSentAt.Milliseconds > (_hasRoundTripTime ? _rto : _rtoInitialMilliseconds)) - .Take(burstSize - chunksSent)) + int taken = 0, send = burstSize - chunksSent; + foreach (var chunk in _unconfirmedChunks.Select(kv => kv.Value)) { + if (now.Milliseconds - chunk.LastSentAt.Milliseconds <= (_hasRoundTripTime ? _rto : _rtoInitialMilliseconds)) + { + continue; + } + if (taken >= send) + { + break; + } + taken++; + chunk.LastSentAt = SctpDataChunk.Timestamp.Now; chunk.SendCount += 1; - logger.LogTrace($"SCTP retransmitting data chunk for TSN {chunk.TSN}, data length {chunk.UserDataLength}, " + - $"flags {chunk.ChunkFlags:X2}, send count {chunk.SendCount}."); + logger.LogTrace("SCTP retransmitting data chunk for TSN {TSN}, data length {Length}, " + + "flags {Flags:X2}, send count {Count}.", + chunk.TSN, chunk.UserDataLength, chunk.ChunkFlags, chunk.SendCount); _sendDataChunk(chunk); chunksSent++; @@ -654,8 +664,9 @@ private void DoSend(object state) dataChunk.LastSentAt = SctpDataChunk.Timestamp.Now; dataChunk.SendCount = 1; - logger.LogTrace($"SCTP sending data chunk for TSN {dataChunk.TSN}, data length {dataChunk.UserDataLength}, " + - $"flags {dataChunk.ChunkFlags:X2}, send count {dataChunk.SendCount}."); + logger.LogTrace("SCTP sending data chunk for TSN {TSN}, data length {Length}, " + + "flags {Flags:X2}, send count {Count}.", + dataChunk.TSN, dataChunk.UserDataLength, dataChunk.ChunkFlags, dataChunk.SendCount); if (_unconfirmedChunks.TryAdd(dataChunk.TSN, dataChunk)) { @@ -683,7 +694,7 @@ private void DoSend(object state) _senderMre.Wait(wait); } - logger.LogDebug($"SCTP association data send thread stopped for association {_associationID}."); + logger.LogDebug("SCTP association data send thread stopped for association {ID}.", _associationID); } /// From 5b16e226025152e61593382bcfb9f18311e3c4f5 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Mon, 15 Jan 2024 16:25:54 -0800 Subject: [PATCH 35/88] fixed unnecessary string interpolation in logging in SctpAssociation.OnPacketReceived --- src/net/SCTP/SctpAssociation.cs | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/net/SCTP/SctpAssociation.cs b/src/net/SCTP/SctpAssociation.cs index 5ed5e7664..4801c128d 100644 --- a/src/net/SCTP/SctpAssociation.cs +++ b/src/net/SCTP/SctpAssociation.cs @@ -370,22 +370,22 @@ internal void OnPacketReceived(SctpPacketView packet) { if (_wasAborted) { - logger.LogWarning($"SCTP packet received but association has been aborted, ignoring."); + logger.LogWarning("SCTP packet received but association has been aborted, ignoring."); } else if (packet.Header.VerificationTag != VerificationTag) { - logger.LogWarning($"SCTP packet dropped due to wrong verification tag, expected " + - $"{VerificationTag} got {packet.Header.VerificationTag}."); + logger.LogWarning("SCTP packet dropped due to wrong verification tag, expected {Expected} got {Actual}.", + VerificationTag, packet.Header.VerificationTag); } else if (!_sctpTransport.IsPortAgnostic && packet.Header.DestinationPort != _sctpSourcePort) { - logger.LogWarning($"SCTP packet dropped due to wrong SCTP destination port, expected " + - $"{_sctpSourcePort} got {packet.Header.DestinationPort}."); + logger.LogWarning("SCTP packet dropped due to wrong SCTP destination port, expected {Expected} got {Actual}.", + _sctpSourcePort, packet.Header.DestinationPort); } else if (!_sctpTransport.IsPortAgnostic && packet.Header.SourcePort != _sctpDestinationPort) { - logger.LogWarning($"SCTP packet dropped due to wrong SCTP source port, expected " + - $"{_sctpDestinationPort} got {packet.Header.SourcePort}."); + logger.LogWarning("SCTP packet dropped due to wrong SCTP source port, expected {Expected} got {Actual}.", + _sctpDestinationPort, packet.Header.SourcePort); } else { @@ -399,7 +399,7 @@ internal void OnPacketReceived(SctpPacketView packet) case SctpChunkType.ABORT: var abortChunk = (SctpAbortChunk)chunk.AsChunk(); string abortReason = abortChunk.GetAbortReason(); - logger.LogWarning($"SCTP packet ABORT chunk received from remote party, reason {abortReason}."); + logger.LogWarning("SCTP packet ABORT chunk received from remote party, reason {Message}.", abortReason); _wasAborted = true; OnAbortReceived?.Invoke(abortReason); break; @@ -437,7 +437,8 @@ internal void OnPacketReceived(SctpPacketView packet) } else { - logger.LogTrace($"SCTP data chunk received on ID {ID} with TSN {dataChunk.TSN}, payload length {dataChunk.UserData.Length}, flags {dataChunk.Flags}."); + logger.LogTrace("SCTP data chunk received on ID {ID} with TSN {TSN}, payload length {Length}, flags {Flags}.", + ID, dataChunk.TSN, dataChunk.UserData.Length, dataChunk.Flags); // A received data chunk can result in multiple data frames becoming available. // For example if a stream has out of order frames already received and the next @@ -533,7 +534,7 @@ internal void OnPacketReceived(SctpPacketView packet) break; case var ct when ct == SctpChunkType.INIT_ACK && State != SctpAssociationState.CookieWait: - logger.LogWarning($"SCTP association received INIT_ACK chunk in wrong state of {State}, ignoring."); + logger.LogWarning("SCTP association received INIT_ACK chunk in wrong state of {State}, ignoring.", State); break; case SctpChunkType.SACK: @@ -563,7 +564,7 @@ internal void OnPacketReceived(SctpPacketView packet) break; default: - logger.LogWarning($"SCTP association no rule for {chunkType} in state of {State}."); + logger.LogWarning("SCTP association no rule for {chunkType} in state of {State}.", chunkType, State); break; } } From abe7cd39181846b0f38d0dd72ca73e401bd3d151 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Mon, 15 Jan 2024 16:28:00 -0800 Subject: [PATCH 36/88] SackChunk lists don't need heap allocation when small --- src/net/SCTP/Chunks/SctpSackChunk.cs | 7 ++++--- src/net/SCTP/SctpDataReceiver.cs | 10 +++++----- src/sys/CollectionExtensions.cs | 23 +++++++++++++++++++++++ 3 files changed, 32 insertions(+), 8 deletions(-) create mode 100644 src/sys/CollectionExtensions.cs diff --git a/src/net/SCTP/Chunks/SctpSackChunk.cs b/src/net/SCTP/Chunks/SctpSackChunk.cs index 6cf9c4eb9..d7c26e1d7 100644 --- a/src/net/SCTP/Chunks/SctpSackChunk.cs +++ b/src/net/SCTP/Chunks/SctpSackChunk.cs @@ -18,8 +18,9 @@ //----------------------------------------------------------------------------- using System; -using System.Collections.Generic; using SIPSorcery.Sys; +using Small.Collections; +using TypeNum; namespace SIPSorcery.Net { @@ -51,13 +52,13 @@ public class SctpSackChunk : SctpChunk /// The gap ACK blocks. Each entry represents a gap in the forward out of order /// TSNs received. /// - public List GapAckBlocks = new List(); + public SmallList, SctpTsnGapBlock> GapAckBlocks = new (); /// /// Indicates the number of times a TSN was received in duplicate /// since the last SACK was sent. /// - public List DuplicateTSN = new List(); + public SmallList, uint> DuplicateTSN = new(); private SctpSackChunk() : base(SctpChunkType.SACK) { } diff --git a/src/net/SCTP/SctpDataReceiver.cs b/src/net/SCTP/SctpDataReceiver.cs index 8183f172a..b0939238d 100644 --- a/src/net/SCTP/SctpDataReceiver.cs +++ b/src/net/SCTP/SctpDataReceiver.cs @@ -18,11 +18,11 @@ using System.Buffers; using System.Buffers.Binary; using System.Collections.Generic; -using System.Linq; - using Microsoft.Extensions.Logging; using SIPSorcery.Sys; +using Small.Collections; +using TypeNum; namespace SIPSorcery.Net { @@ -355,7 +355,7 @@ public SctpSackChunk GetSackChunk() { SctpSackChunk sack = new SctpSackChunk(_lastInOrderTSN, _receiveWindow); sack.GapAckBlocks = GetForwardTSNGaps(); - sack.DuplicateTSN = _duplicateTSN.Keys.ToList(); + sack.DuplicateTSN.AddRange(_duplicateTSN.Keys.GetEnumerator()); return sack; } else @@ -370,9 +370,9 @@ public SctpSackChunk GetSackChunk() /// TSNs have not yet been received. /// /// A list of TSN gap blocks. An empty list means there are no gaps. - internal List GetForwardTSNGaps() + internal SmallList, SctpTsnGapBlock> GetForwardTSNGaps() { - List gaps = new List(); + var gaps = new SmallList, SctpTsnGapBlock>(); // Can't create gap reports until the initial DATA chunk has been received. if (_inOrderReceiveCount > 0) diff --git a/src/sys/CollectionExtensions.cs b/src/sys/CollectionExtensions.cs new file mode 100644 index 000000000..d049176c8 --- /dev/null +++ b/src/sys/CollectionExtensions.cs @@ -0,0 +1,23 @@ +#nullable enable + +using System.Collections.Generic; + +using Small.Collections; + +using TypeNum; + +namespace SIPSorcery.Sys; + +static class CollectionExtensions +{ + public static void AddRange(this SmallList list, Enumerator enumerator) + where TSize : unmanaged, INumeral + where T : unmanaged + where Enumerator : IEnumerator + { + while (enumerator.MoveNext()) + { + list.Add(enumerator.Current); + } + } +} From 6007cd0894640a7e4d48afd7ca0d9168d0bfdfb4 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Mon, 15 Jan 2024 16:28:36 -0800 Subject: [PATCH 37/88] avoid buffer allocation in RTPChannel.Send --- src/net/DtlsSrtp/DtlsSrtpTransport.cs | 5 +++-- src/net/ICE/RtpIceChannel.cs | 6 +++--- src/net/RTP/RTPChannel.cs | 31 +++++++++++++++++++++++---- 3 files changed, 33 insertions(+), 9 deletions(-) diff --git a/src/net/DtlsSrtp/DtlsSrtpTransport.cs b/src/net/DtlsSrtp/DtlsSrtpTransport.cs index a0ba42233..8e99affff 100644 --- a/src/net/DtlsSrtp/DtlsSrtpTransport.cs +++ b/src/net/DtlsSrtp/DtlsSrtpTransport.cs @@ -63,7 +63,8 @@ public class DtlsSrtpTransport : DatagramTransport, IDisposable /// public int RetransmissionMilliseconds = DEFAULT_RETRANSMISSION_WAIT_MILLIS; - public Action OnDataReady; + public delegate void OnBytesReadyDelegate(ReadOnlySpan bytes); + public OnBytesReadyDelegate OnDataReady; /// /// Parameters: @@ -619,7 +620,7 @@ public void Send(byte[] buf, int off, int len) #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER public void Send(ReadOnlySpan buf) { - OnDataReady?.Invoke(buf.ToArray()); + OnDataReady?.Invoke(buf); } #endif diff --git a/src/net/ICE/RtpIceChannel.cs b/src/net/ICE/RtpIceChannel.cs index ca7c17806..bdc37666e 100644 --- a/src/net/ICE/RtpIceChannel.cs +++ b/src/net/ICE/RtpIceChannel.cs @@ -2589,11 +2589,11 @@ protected override void OnRTPPacketReceived(UdpReceiver receiver, int localPort, /// The data to send to the peer. /// The TURN server end point to send the relayed request to. /// - private SocketError SendRelay(ProtocolType protocol, IPEndPoint dstEndPoint, byte[] buffer, IPEndPoint relayEndPoint, IceServer iceServer) + private SocketError SendRelay(ProtocolType protocol, IPEndPoint dstEndPoint, ReadOnlySpan buffer, IPEndPoint relayEndPoint, IceServer iceServer) { STUNMessage sendReq = new STUNMessage(STUNMessageTypesEnum.SendIndication); sendReq.AddXORPeerAddressAttribute(dstEndPoint.Address, dstEndPoint.Port); - sendReq.Attributes.Add(new STUNAttribute(STUNAttributeTypesEnum.Data, buffer)); + sendReq.Attributes.Add(new STUNAttribute(STUNAttributeTypesEnum.Data, buffer.ToArray())); var request = sendReq.ToByteBuffer(null, false); var sendResult = protocol == ProtocolType.Tcp ? @@ -2666,7 +2666,7 @@ private async Task ResolveMdnsName(RTCIceCandidate candidate) /// The data to send. /// The result of initiating the send. This result does not reflect anything about /// whether the remote party received the packet or not. - public override SocketError Send(RTPChannelSocketsEnum sendOn, IPEndPoint dstEndPoint, byte[] buffer) + public override SocketError Send(RTPChannelSocketsEnum sendOn, IPEndPoint dstEndPoint, ReadOnlySpan buffer) { if (NominatedEntry != null && NominatedEntry.LocalCandidate.type == RTCIceCandidateType.relay && NominatedEntry.LocalCandidate.IceServer != null && diff --git a/src/net/RTP/RTPChannel.cs b/src/net/RTP/RTPChannel.cs index ab5233bb3..98c324ea5 100644 --- a/src/net/RTP/RTPChannel.cs +++ b/src/net/RTP/RTPChannel.cs @@ -18,6 +18,7 @@ //----------------------------------------------------------------------------- using System; +using System.Buffers; using System.Net; using System.Net.Sockets; using System.Threading.Tasks; @@ -550,7 +551,7 @@ public void Close(string reason) /// The data to send. /// The result of initiating the send. This result does not reflect anything about /// whether the remote party received the packet or not. - public virtual SocketError Send(RTPChannelSocketsEnum sendOn, IPEndPoint dstEndPoint, byte[] buffer) + public virtual SocketError Send(RTPChannelSocketsEnum sendOn, IPEndPoint dstEndPoint, ReadOnlySpan buffer) { if (m_isClosed) { @@ -604,7 +605,18 @@ public virtual SocketError Send(RTPChannelSocketsEnum sendOn, IPEndPoint dstEndP } #if NET6_0_OR_GREATER - var send = sendSocket.SendToAsync(buffer.AsMemory(), SocketFlags.None, dstEndPoint); + var tmp = ArrayPool.Shared.Rent(buffer.Length); + buffer.CopyTo(tmp); + ValueTask send; + try + { + send = sendSocket.SendToAsync(tmp.AsMemory(0, buffer.Length), SocketFlags.None, dstEndPoint); + } + catch + { + ArrayPool.Shared.Return(tmp); + throw; + } if (send.IsCompleted) { try @@ -615,14 +627,25 @@ public virtual SocketError Send(RTPChannelSocketsEnum sendOn, IPEndPoint dstEndP { EndSendTo(excp); } + finally + { + ArrayPool.Shared.Return(tmp); + } } else { - send.AsTask().ContinueWith(t => EndSendTo(t.Exception), TaskContinuationOptions.OnlyOnFaulted); + send.AsTask().ContinueWith(t => + { + ArrayPool.Shared.Return(tmp); + if (t.IsFaulted) + { + EndSendTo(t.Exception); + } + }); } #else - sendSocket.BeginSendTo(buffer, 0, buffer.Length, SocketFlags.None, dstEndPoint, endSendTo, sendSocket); + sendSocket.BeginSendTo(buffer.ToArray(), 0, buffer.Length, SocketFlags.None, dstEndPoint, endSendTo, sendSocket); #endif return SocketError.Success; } From ac2a0695748f4e6032c20f19e56a85874f0163c8 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Mon, 15 Jan 2024 16:32:59 -0800 Subject: [PATCH 38/88] removed logging allocations from SctpDataSender.GotSack and SctpDataReceiver.OnDataChunk --- src/net/SCTP/SctpDataReceiver.cs | 29 +++++++++++++++++------------ src/net/SCTP/SctpDataSender.cs | 19 ++++++++++++------- 2 files changed, 29 insertions(+), 19 deletions(-) diff --git a/src/net/SCTP/SctpDataReceiver.cs b/src/net/SCTP/SctpDataReceiver.cs index b0939238d..09dbdc032 100644 --- a/src/net/SCTP/SctpDataReceiver.cs +++ b/src/net/SCTP/SctpDataReceiver.cs @@ -225,27 +225,31 @@ public List OnDataChunk(SctpChunkView dataChunk) if (_inOrderReceiveCount == 0 && GetDistance(_initialTSN, dataChunk.TSN) > _windowSize) { - logger.LogWarning($"SCTP data receiver received a data chunk with a {dataChunk.TSN} " + - $"TSN when the initial TSN was {_initialTSN} and a " + - $"window size of {_windowSize}, ignoring."); + logger.LogWarning("SCTP data receiver received a data chunk with a {TSN} " + + "TSN when the initial TSN was {InitialTSN} and a " + + "window size of {Size}, ignoring.", + dataChunk.TSN, _initialTSN, _windowSize); } else if (_inOrderReceiveCount > 0 && GetDistance(_lastInOrderTSN, dataChunk.TSN) > _windowSize) { - logger.LogWarning($"SCTP data receiver received a data chunk with a {dataChunk.TSN} " + - $"TSN when the expected TSN was {_lastInOrderTSN + 1} and a " + - $"window size of {_windowSize}, ignoring."); + logger.LogWarning("SCTP data receiver received a data chunk with a {TSN} " + + "TSN when the expected TSN was {LastInOrderTSN} and a " + + "window size of {Size}, ignoring.", + dataChunk.TSN, _lastInOrderTSN + 1, _windowSize); } else if (_inOrderReceiveCount > 0 && !IsNewer(_lastInOrderTSN, dataChunk.TSN)) { - logger.LogWarning($"SCTP data receiver received an old data chunk with {dataChunk.TSN} " + - $"TSN when the expected TSN was {_lastInOrderTSN + 1}, ignoring."); + logger.LogWarning("SCTP data receiver received an old data chunk with {TSN} " + + "TSN when the expected TSN was {LastInOrderTSN}, ignoring.", + dataChunk.TSN, _lastInOrderTSN + 1); } else if (!_forwardTSN.ContainsKey(dataChunk.TSN)) { - logger.LogTrace($"SCTP receiver got data chunk with TSN {dataChunk.TSN}, " + - $"last in order TSN {_lastInOrderTSN}, in order receive count {_inOrderReceiveCount}."); + logger.LogTrace("SCTP receiver got data chunk with TSN {TSN}, " + + "last in order TSN {LastInOrderTSN}, in order receive count {InOrderReceiveCount}.", + dataChunk.TSN, _lastInOrderTSN, _inOrderReceiveCount); bool processFrame = true; @@ -277,7 +281,8 @@ public List OnDataChunk(SctpChunkView dataChunk) outOfOrder.Count >= MAXIMUM_OUTOFORDER_FRAMES) { // Stream is nearing capacity, only chunks that advance _lastInOrderTSN can be accepted. - logger.LogWarning($"Stream {dataChunk.StreamID} is at buffer capacity. Rejected out-of-order data chunk TSN {dataChunk.TSN}."); + logger.LogWarning("Stream {StreamID} is at buffer capacity. Rejected out-of-order data chunk TSN {TSN}.", + dataChunk.StreamID, dataChunk.TSN); processFrame = false; } else @@ -317,7 +322,7 @@ public List OnDataChunk(SctpChunkView dataChunk) } else { - logger.LogTrace($"SCTP duplicate TSN received for {dataChunk.TSN}."); + logger.LogTrace("SCTP duplicate TSN received for {TSN}.", dataChunk.TSN); if (!_duplicateTSN.ContainsKey(dataChunk.TSN)) { _duplicateTSN.Add(dataChunk.TSN, 1); diff --git a/src/net/SCTP/SctpDataSender.cs b/src/net/SCTP/SctpDataSender.cs index 8a6128314..cc7119619 100644 --- a/src/net/SCTP/SctpDataSender.cs +++ b/src/net/SCTP/SctpDataSender.cs @@ -206,7 +206,7 @@ public void GotSack(SctpChunkView sack) { if (_inRetransmitMode) { - logger.LogTrace($"SCTP sender exiting retransmit mode."); + logger.LogTrace("SCTP sender exiting retransmit mode."); _inRetransmitMode = false; } @@ -232,7 +232,8 @@ public void GotSack(SctpChunkView sack) if (SctpDataReceiver.GetDistance(_initialTSN, sack.CumulativeTsnAck) < maxTSNDistance && SctpDataReceiver.IsNewerOrEqual(_initialTSN, sack.CumulativeTsnAck)) { - logger.LogTrace($"SCTP first SACK remote peer TSN ACK {sack.CumulativeTsnAck} next sender TSN {TSN}, arwnd {sack.ARwnd} (gap reports {sack.NumGapAckBlocks})."); + logger.LogTrace("SCTP first SACK remote peer TSN ACK {CumulativeTsnAck} next sender TSN {TSN}, arwnd {ARwnd} (gap reports {NumGapAckBlocks}).", + sack.CumulativeTsnAck, TSN, sack.ARwnd, sack.NumGapAckBlocks); _gotFirstSACK = true; _cumulativeAckTSN = _initialTSN; RemoveAckedUnconfirmedChunks(sack.CumulativeTsnAck); @@ -244,23 +245,27 @@ public void GotSack(SctpChunkView sack) { if (SctpDataReceiver.GetDistance(_cumulativeAckTSN, sack.CumulativeTsnAck) > maxTSNDistance) { - logger.LogWarning($"SCTP SACK TSN from remote peer of {sack.CumulativeTsnAck} was too distant from the expected {_cumulativeAckTSN}, ignoring."); + logger.LogWarning("SCTP SACK TSN from remote peer of {PeerCumulativeTsnAck} was too distant from the expected {ExpectedCumulativeAckTSN}, ignoring.", + sack.CumulativeTsnAck, _cumulativeAckTSN); processGapReports = false; } else if (!SctpDataReceiver.IsNewer(_cumulativeAckTSN, sack.CumulativeTsnAck)) { - logger.LogWarning($"SCTP SACK TSN from remote peer of {sack.CumulativeTsnAck} was behind expected {_cumulativeAckTSN}, ignoring."); + logger.LogWarning("SCTP SACK TSN from remote peer of {PeerCumulativeTsnAck} was behind expected {ExpectedCumulativeAckTSN}, ignoring.", + sack.CumulativeTsnAck, _cumulativeAckTSN); processGapReports = false; } else { - logger.LogTrace($"SCTP SACK remote peer TSN ACK {sack.CumulativeTsnAck}, next sender TSN {TSN}, arwnd {sack.ARwnd} (gap reports {sack.NumGapAckBlocks})."); + logger.LogTrace("SCTP SACK remote peer TSN ACK {CumulativeTsnAck}, next sender TSN {TSN}, arwnd {ARwnd} (gap reports {NumGapAckBlocks}).", + sack.CumulativeTsnAck, TSN, sack.ARwnd, sack.NumGapAckBlocks); RemoveAckedUnconfirmedChunks(sack.CumulativeTsnAck); } } else { - logger.LogTrace($"SCTP SACK remote peer TSN ACK no change {_cumulativeAckTSN}, next sender TSN {TSN}, arwnd {sack.ARwnd} (gap reports {sack.NumGapAckBlocks})."); + logger.LogTrace("SCTP SACK remote peer TSN ACK no change {CumulativeAckTSN}, next sender TSN {TSN}, arwnd {ARwnd} (gap reports {NumGapAckBlocks}).", + _cumulativeAckTSN, TSN, sack.ARwnd, sack.NumGapAckBlocks); RemoveAckedUnconfirmedChunks(sack.CumulativeTsnAck); } } @@ -290,7 +295,7 @@ public void GotSack(SctpChunkView sack) // If the Cumulative TSN Ack matches or exceeds the Fast Recovery exitpoint(Section 7.2.4), Fast Recovery is exited. if (_inFastRecoveryMode && SctpDataReceiver.IsNewerOrEqual(_fastRecoveryExitPoint, _cumulativeAckTSN)) { - logger.LogTrace($"SCTP sender exiting fast recovery at TSN {_fastRecoveryExitPoint}"); + logger.LogTrace("SCTP sender exiting fast recovery at TSN {TSN}", _fastRecoveryExitPoint); _inFastRecoveryMode = false; } } From a6e11791e3c393e264a0a1e6c8e7ff020df27ede Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Mon, 15 Jan 2024 21:06:15 -0800 Subject: [PATCH 39/88] cache _outstandingBytes --- src/net/SCTP/SctpDataSender.cs | 36 +++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/src/net/SCTP/SctpDataSender.cs b/src/net/SCTP/SctpDataSender.cs index cc7119619..8c42ea40d 100644 --- a/src/net/SCTP/SctpDataSender.cs +++ b/src/net/SCTP/SctpDataSender.cs @@ -128,8 +128,7 @@ public class SctpDataSender /// /// A count of the bytes currently in-flight to the remote peer. /// - internal uint _outstandingBytes => - (uint)(_unconfirmedChunks.Sum(x => x.Value.UserDataLength)); + internal int _outstandingBytes => _unconfirmedChunks.Sum(x => x.Value.UserDataLength); /// /// The TSN that the remote peer has acknowledged. @@ -300,8 +299,9 @@ public void GotSack(SctpChunkView sack) } } - _receiverWindow = CalculateReceiverWindow(sack.ARwnd); - _congestionWindow = CalculateCongestionWindow(_lastAckedDataChunkSize); + var outstandingBytes = _outstandingBytes; + _receiverWindow = CalculateReceiverWindow(sack.ARwnd, outstandingBytes: (uint)outstandingBytes); + _congestionWindow = CalculateCongestionWindow(_lastAckedDataChunkSize, outstandingBytes: (uint)outstandingBytes); // SACK's will normally allow more data to be sent. _senderMre.Set(); @@ -535,7 +535,8 @@ private void ProcessGapReports(ReadOnlySpan sackGapBlocks, uint maxTSNDist /// The acknowledged TSN received from in a SACK from the remote peer. private void RemoveAckedUnconfirmedChunks(uint sackTSN) { - logger.LogTrace($"SCTP data sender removing unconfirmed chunks cumulative ACK TSN {_cumulativeAckTSN}, SACK TSN {sackTSN}."); + logger.LogTrace("SCTP data sender removing unconfirmed chunks cumulative ACK TSN {CumulativeAckTSN}, SACK TSN {SackTSN}.", + _cumulativeAckTSN, sackTSN); if (_cumulativeAckTSN == sackTSN) { @@ -575,7 +576,7 @@ private void DoSend(object state) while (!_closed.HasOccurred) { - var outstandingBytes = _outstandingBytes; + var outstandingBytes = (uint)_outstandingBytes; // DateTime.Now calls have been a tiny bit expensive in the past so get a small saving by only // calling once per loop. var now = SctpDataChunk.Timestamp.Now; @@ -617,8 +618,9 @@ private void DoSend(object state) if (chunksSent < burstSize && !_unconfirmedChunks.IsEmpty) { int taken = 0, send = burstSize - chunksSent; - foreach (var chunk in _unconfirmedChunks.Select(kv => kv.Value)) + foreach (var entry in _unconfirmedChunks) { + var chunk = entry.Value; if (now.Milliseconds - chunk.LastSentAt.Milliseconds <= (_hasRoundTripTime ? _rto : _rtoInitialMilliseconds)) { continue; @@ -641,7 +643,7 @@ private void DoSend(object state) if (!_inRetransmitMode) { - logger.LogTrace($"SCTP sender entering retransmit mode."); + logger.LogTrace("SCTP sender entering retransmit mode."); _inRetransmitMode = true; // When the T3-rtx timer expires on an address, SCTP should perform slow start. @@ -709,7 +711,7 @@ private int GetSendWaitMilliseconds() { if (!_sendQueue.IsEmpty || !_missingChunks.IsEmpty) { - if (_receiverWindow > 0 && _congestionWindow > _outstandingBytes) + if (_receiverWindow > 0 && _congestionWindow > (uint)_outstandingBytes) { return _burstPeriodMilliseconds; } @@ -775,9 +777,9 @@ private void UpdateRoundTripTime(SctpDataChunk acknowledgedChunk) /// See https://tools.ietf.org/html/rfc4960#section-6.2.1. /// /// The new value to use for the receiver window. - private uint CalculateReceiverWindow(uint advertisedReceiveWindow) + private uint CalculateReceiverWindow(uint advertisedReceiveWindow, uint outstandingBytes) { - return (advertisedReceiveWindow > _outstandingBytes) ? advertisedReceiveWindow - _outstandingBytes : 0; + return (advertisedReceiveWindow > outstandingBytes) ? advertisedReceiveWindow - outstandingBytes : 0; } /// @@ -785,20 +787,21 @@ private uint CalculateReceiverWindow(uint advertisedReceiveWindow) /// /// The size of last ACK'ed DATA chunk. /// A congestion window value. - private uint CalculateCongestionWindow(int lastAckDataChunkSize) + private uint CalculateCongestionWindow(int lastAckDataChunkSize, uint outstandingBytes) { if (_congestionWindow < _slowStartThreshold) { // In Slow-Start mode, see RFC4960 7.2.1. - if (_congestionWindow < _outstandingBytes) + if (_congestionWindow < outstandingBytes) { // When cwnd is less than or equal to ssthresh, an SCTP endpoint MUST // use the slow - start algorithm to increase cwnd only if the current // congestion window is being fully utilized. uint increasedCwnd = (uint)(_congestionWindow + Math.Min(lastAckDataChunkSize, _defaultMTU)); - logger.LogTrace($"SCTP sender congestion window in slow-start increased from {_congestionWindow} to {increasedCwnd}."); + logger.LogTrace("SCTP sender congestion window in slow-start increased from {Original} to {Increased}.", + _congestionWindow, increasedCwnd); return increasedCwnd; } @@ -811,9 +814,10 @@ private uint CalculateCongestionWindow(int lastAckDataChunkSize) { // In Congestion Avoidance mode, see RFC4960 7.2.2. - if (_congestionWindow < _outstandingBytes) + if (_congestionWindow < outstandingBytes) { - logger.LogTrace($"SCTP sender congestion window in congestion avoidance increased from {_congestionWindow} to {_congestionWindow + _defaultMTU}."); + logger.LogTrace("SCTP sender congestion window in congestion avoidance increased from {Original} to {Increased}.", + _congestionWindow, _congestionWindow + _defaultMTU); return _congestionWindow + _defaultMTU; } From e292a9cd9fd146ad58810213ec3d9eb80d1d279c Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Tue, 16 Jan 2024 10:31:16 -0800 Subject: [PATCH 40/88] bandwidth test program improvements --- .../DataChannelBandwidth/Program.cs | 97 ++++++++++++++----- 1 file changed, 72 insertions(+), 25 deletions(-) diff --git a/examples/WebRTCScenarios/DataChannelBandwidth/Program.cs b/examples/WebRTCScenarios/DataChannelBandwidth/Program.cs index a6340bd8e..5023e3795 100644 --- a/examples/WebRTCScenarios/DataChannelBandwidth/Program.cs +++ b/examples/WebRTCScenarios/DataChannelBandwidth/Program.cs @@ -1,5 +1,6 @@ using System.Diagnostics; using System.Runtime.CompilerServices; + using DataChannelBandwidth; using Microsoft.Extensions.Logging; @@ -9,6 +10,8 @@ ILoggerFactory logs = LoggerFactory.Create( builder => builder.AddFilter(level => level >= LogLevel.Debug).AddConsole()); +var log = logs.CreateLogger("Bandwidth"); + // SIPSorcery.LogFactory.Set(logs); var rtcConfig = new RTCConfiguration @@ -18,41 +21,85 @@ }, }; -var server = new RTCPeerConnection(rtcConfig); -var client = new RTCPeerConnection(rtcConfig); - long clientReceived = 0; long serverReceived = 0; -client.ondatachannel += ch => +bool closed = false; + +for (int i = 0; i < 1; i++) { - new Thread(() => SendRecv(ch, ref clientReceived)) + var launch = Stopwatch.StartNew(); + var server = new RTCPeerConnection(rtcConfig); + var client = new RTCPeerConnection(rtcConfig); + + client.onconnectionstatechange += state => { - IsBackground = true, - Name = "client", - }.Start(); -}; + if (state is RTCPeerConnectionState.closed or RTCPeerConnectionState.failed) + { + log.LogInformation("client connection {State}", state); + //Volatile.Write(ref closed, true); + } + }; + client.ondatachannel += ch => + { + new Thread(() => SendRecv(ch, ref clientReceived)) + { + IsBackground = true, + Name = "client", + }.Start(); + if (!ch.IsOpened) + { + log.LogInformation("client channel never opened"); + Volatile.Write(ref closed, true); + } + ch.onclose += () => + { + log.LogInformation("client channel closed"); + Volatile.Write(ref closed, true); + }; + ch.onerror += (s) => + { + log.LogInformation("client channel error: {Message}", s); + Volatile.Write(ref closed, true); + }; + }; -var serverCH = await server.createDataChannel("test"); -serverCH.onopen += () => -{ - new Thread(() => SendRecv(serverCH, ref serverReceived)) + + var serverCH = await server.createDataChannel("test"); + serverCH.onopen += () => { - IsBackground = true, - Name = "server", - }.Start(); -}; -var offer = server.createOffer(); -await server.setLocalDescription(offer); -client.setRemoteDescription(offer); -var answer = client.createAnswer(); -server.setRemoteDescription(answer); -await client.setLocalDescription(answer); + new Thread(() => SendRecv(serverCH, ref serverReceived)) + { + IsBackground = true, + Name = "server", + }.Start(); + }; + serverCH.onclose += () => + { + log.LogInformation("server channel closed"); + Volatile.Write(ref closed, true); + }; + serverCH.onerror += (s) => + { + log.LogInformation("server channel error: {Message}", s); + Volatile.Write(ref closed, true); + }; + var offer = server.createOffer(); + await server.setLocalDescription(offer); + client.setRemoteDescription(offer); + var answer = client.createAnswer(); + server.setRemoteDescription(answer); + await client.setLocalDescription(answer); + + Console.WriteLine($"launched in {launch.ElapsedMilliseconds}ms"); +} var stopwatch = Stopwatch.StartNew(); +Interlocked.Exchange(ref clientReceived, 0); +Interlocked.Exchange(ref serverReceived, 0); -while (true) +while (!Volatile.Read(ref closed)) { long recvC = Interlocked.Read(ref clientReceived); long recvS = Interlocked.Read(ref serverReceived); @@ -82,7 +129,7 @@ void SendRecv(RTCDataChannel channel, ref long received, Name = $"{name} sender", }; sender.Start(); - + byte[] buffer = new byte[200_000]; while (true) { From d089f19ae24269ce98eb242d7a517f1bcf8a5c4b Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Fri, 12 Jan 2024 21:37:10 -0800 Subject: [PATCH 41/88] label data channel bandwidth test threads --- examples/WebRTCScenarios/DataChannelBandwidth/Program.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/examples/WebRTCScenarios/DataChannelBandwidth/Program.cs b/examples/WebRTCScenarios/DataChannelBandwidth/Program.cs index 096c64244..bda8af043 100644 --- a/examples/WebRTCScenarios/DataChannelBandwidth/Program.cs +++ b/examples/WebRTCScenarios/DataChannelBandwidth/Program.cs @@ -1,5 +1,5 @@ using System.Diagnostics; - +using System.Runtime.CompilerServices; using DataChannelBandwidth; using Microsoft.Extensions.Logging; @@ -73,12 +73,14 @@ void Send(RTCDataChannel channel) } } -void SendRecv(RTCDataChannel channel, ref long received) +void SendRecv(RTCDataChannel channel, ref long received, + [CallerArgumentExpression(nameof(channel))] string name = "") { var stream = new DataChannelStream(channel); var sender = new Thread(() => Send(channel)) { IsBackground = true, + Name = $"{name} sender", }; sender.Start(); From 05a419abd06c5dd9b4b17f57212c3e0e2f859d48 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Tue, 19 Mar 2024 08:13:02 -0700 Subject: [PATCH 42/88] v7.0.0-PacketView-24.3.19.1 --- src/SIPSorcery.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SIPSorcery.csproj b/src/SIPSorcery.csproj index cb80476e7..3e1077f4a 100755 --- a/src/SIPSorcery.csproj +++ b/src/SIPSorcery.csproj @@ -59,7 +59,7 @@ -v6.0.2: Set .net6 targetted version as stable. -v6.0.1-pre: Added .net6 target. en - 7.0.0-bc2-23.12.28.2 + 7.0.0-PacketView-24.3.19.1 7.0.0 7.0.0 From 8e6b5c75e5c129656de37554375593eb76510824 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Tue, 16 Jan 2024 10:31:16 -0800 Subject: [PATCH 43/88] bandwidth test program improvements --- .../DataChannelBandwidth/Program.cs | 99 ++++++++++++++----- 1 file changed, 73 insertions(+), 26 deletions(-) diff --git a/examples/WebRTCScenarios/DataChannelBandwidth/Program.cs b/examples/WebRTCScenarios/DataChannelBandwidth/Program.cs index bda8af043..8ae207bc7 100644 --- a/examples/WebRTCScenarios/DataChannelBandwidth/Program.cs +++ b/examples/WebRTCScenarios/DataChannelBandwidth/Program.cs @@ -1,5 +1,6 @@ using System.Diagnostics; using System.Runtime.CompilerServices; + using DataChannelBandwidth; using Microsoft.Extensions.Logging; @@ -10,7 +11,9 @@ ILoggerFactory logs = LoggerFactory.Create( builder => builder.AddFilter(level => level >= LogLevel.Warning).AddConsole()); -LogFactory.Set(logs); +var log = logs.CreateLogger("Bandwidth"); + +// SIPSorcery.LogFactory.Set(logs); var rtcConfig = new RTCConfiguration { @@ -19,41 +22,85 @@ }, }; -var server = new RTCPeerConnection(rtcConfig); -var client = new RTCPeerConnection(rtcConfig); - long clientReceived = 0; long serverReceived = 0; -client.ondatachannel += ch => +bool closed = false; + +for (int i = 0; i < 1; i++) { - new Thread(() => SendRecv(ch, ref clientReceived)) + var launch = Stopwatch.StartNew(); + var server = new RTCPeerConnection(rtcConfig); + var client = new RTCPeerConnection(rtcConfig); + + client.onconnectionstatechange += state => { - IsBackground = true, - Name = "client", - }.Start(); -}; + if (state is RTCPeerConnectionState.closed or RTCPeerConnectionState.failed) + { + log.LogInformation("client connection {State}", state); + //Volatile.Write(ref closed, true); + } + }; + client.ondatachannel += ch => + { + new Thread(() => SendRecv(ch, ref clientReceived)) + { + IsBackground = true, + Name = $"client {i}", + }.Start(); + if (!ch.IsOpened) + { + log.LogInformation("client channel never opened"); + Volatile.Write(ref closed, true); + } + ch.onclose += () => + { + log.LogInformation("client channel closed"); + Volatile.Write(ref closed, true); + }; + ch.onerror += (s) => + { + log.LogInformation("client channel error: {Message}", s); + Volatile.Write(ref closed, true); + }; + }; -var serverCH = await server.createDataChannel("test"); -serverCH.onopen += () => -{ - new Thread(() => SendRecv(serverCH, ref serverReceived)) + + var serverCH = await server.createDataChannel("test"); + serverCH.onopen += () => { - IsBackground = true, - Name = "server", - }.Start(); -}; -var offer = server.createOffer(); -await server.setLocalDescription(offer); -client.setRemoteDescription(offer); -var answer = client.createAnswer(); -server.setRemoteDescription(answer); -await client.setLocalDescription(answer); + new Thread(() => SendRecv(serverCH, ref serverReceived)) + { + IsBackground = true, + Name = $"server {i}", + }.Start(); + }; + serverCH.onclose += () => + { + log.LogInformation("server channel closed"); + Volatile.Write(ref closed, true); + }; + serverCH.onerror += (s) => + { + log.LogInformation("server channel error: {Message}", s); + Volatile.Write(ref closed, true); + }; + var offer = server.createOffer(); + await server.setLocalDescription(offer); + client.setRemoteDescription(offer); + var answer = client.createAnswer(); + server.setRemoteDescription(answer); + await client.setLocalDescription(answer); + + Console.WriteLine($"launched in {launch.ElapsedMilliseconds}ms"); +} var stopwatch = Stopwatch.StartNew(); +Interlocked.Exchange(ref clientReceived, 0); +Interlocked.Exchange(ref serverReceived, 0); -while (true) +while (!Volatile.Read(ref closed)) { long recvC = Interlocked.Read(ref clientReceived); long recvS = Interlocked.Read(ref serverReceived); @@ -83,7 +130,7 @@ void SendRecv(RTCDataChannel channel, ref long received, Name = $"{name} sender", }; sender.Start(); - + byte[] buffer = new byte[200_000]; while (true) { From 05a2a9ddd7960160b080248024145b2c1552f78f Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Tue, 19 Mar 2024 11:11:59 -0700 Subject: [PATCH 44/88] v7.0.0-PacketView-24.3.19.2 --- src/SIPSorcery.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SIPSorcery.csproj b/src/SIPSorcery.csproj index 92bc90ce3..61ad876d7 100644 --- a/src/SIPSorcery.csproj +++ b/src/SIPSorcery.csproj @@ -63,7 +63,7 @@ -v6.0.2: Set .net6 targetted version as stable. -v6.0.1-pre: Added .net6 target. en - 7.0.0-PacketView-24.3.19.1 + 7.0.0-PacketView-24.3.19.2 7.0.0 7.0.0 From bf1be64b23d34e998ee6e5db91715169ae7506b5 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Tue, 19 Mar 2024 12:26:37 -0700 Subject: [PATCH 45/88] v7.0.0-PacketView-24.3.19.3 - include symbols into package --- src/SIPSorcery.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/SIPSorcery.csproj b/src/SIPSorcery.csproj index 61ad876d7..0461739f7 100644 --- a/src/SIPSorcery.csproj +++ b/src/SIPSorcery.csproj @@ -63,7 +63,7 @@ -v6.0.2: Set .net6 targetted version as stable. -v6.0.1-pre: Added .net6 target. en - 7.0.0-PacketView-24.3.19.2 + 7.0.0-PacketView-24.3.19.3 7.0.0 7.0.0 @@ -76,8 +76,8 @@ true + Embedded true - snupkg true From 3c6a81636441240d985a4eb02a4c0fb24a1d0c26 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Tue, 19 Mar 2024 12:49:15 -0700 Subject: [PATCH 46/88] avoid allocation in RTCSctpTransport background thread --- src/net/WebRTC/RTCSctpTransport.cs | 37 ++++++++++++++++++------------ 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/src/net/WebRTC/RTCSctpTransport.cs b/src/net/WebRTC/RTCSctpTransport.cs index 86861ed97..d31e54881 100644 --- a/src/net/WebRTC/RTCSctpTransport.cs +++ b/src/net/WebRTC/RTCSctpTransport.cs @@ -107,8 +107,8 @@ public class RTCSctpTransport : SctpTransport /// public event Action OnStateChanged; - private bool _isStarted; - private bool _isClosed; + private Once _isStarted; + private Once _isClosed; private Thread _receiveThread; /// @@ -163,10 +163,8 @@ public void UpdateDestinationPort(ushort port) /// public void Start(DatagramTransport dtlsTransport, bool isDtlsClient) { - if (!_isStarted) + if (_isStarted.TryMarkOccurred()) { - _isStarted = true; - transport = dtlsTransport; IsDtlsClient = isDtlsClient; @@ -175,6 +173,10 @@ public void Start(DatagramTransport dtlsTransport, bool isDtlsClient) _receiveThread.IsBackground = true; _receiveThread.Start(); } + else + { + logger.LogWarning($"RTCSctpTransport for association {RTCSctpAssociation.ID} has already been started."); + } } /// @@ -197,7 +199,7 @@ public void Close() { RTCSctpAssociation?.Shutdown(); } - _isClosed = true; + _isClosed.TryMarkOccurred(); } /// @@ -267,16 +269,21 @@ protected override SctpTransportCookie GetInitAckCookie( /// private void DoReceive(object state) { - byte[] recvBuffer = new byte[SctpAssociation.DEFAULT_ADVERTISED_RECEIVE_WINDOW]; +#if NET6_0_OR_GREATER + Span recvBuffer = stackalloc byte[checked((int)SctpAssociation.DEFAULT_ADVERTISED_RECEIVE_WINDOW)]; +#else + byte[] recvBufferArray = new byte[SctpAssociation.DEFAULT_ADVERTISED_RECEIVE_WINDOW]; + Span recvBuffer = recvBufferArray.AsSpan(); +#endif - while (!_isClosed) + while (!_isClosed.HasOccurred) { try { #if NET6_0_OR_GREATER - int bytesRead = transport.Receive(recvBuffer.AsSpan(), RECEIVE_TIMEOUT_MILLISECONDS); + int bytesRead = transport.Receive(recvBuffer, RECEIVE_TIMEOUT_MILLISECONDS); #else - int bytesRead = transport.Receive(recvBuffer, 0, recvBuffer.Length, RECEIVE_TIMEOUT_MILLISECONDS); + int bytesRead = transport.Receive(recvBufferArray, 0, recvBuffer.Length, RECEIVE_TIMEOUT_MILLISECONDS); #endif if (bytesRead == DtlsSrtpTransport.DTLS_RETRANSMISSION_CODE) @@ -287,13 +294,13 @@ private void DoReceive(object state) } else if (bytesRead > 0) { - if (!SctpPacket.VerifyChecksum(recvBuffer.AsSpan().Slice(0, bytesRead))) + if (!SctpPacket.VerifyChecksum(recvBuffer.Slice(0, bytesRead))) { logger.LogWarning($"SCTP packet received on DTLS transport dropped due to invalid checksum."); } else { - var pkt = SctpPacketView.Parse(recvBuffer.AsSpan(0, bytesRead)); + var pkt = SctpPacketView.Parse(recvBuffer.Slice(0, bytesRead)); if (pkt.Has(SctpChunkType.INIT)) { @@ -329,7 +336,7 @@ private void DoReceive(object state) } } } - else if (_isClosed) + else if (_isClosed.HasOccurred) { // The DTLS transport has been closed or is no longer available. logger.LogWarning($"SCTP the RTCSctpTransport DTLS transport returned an error."); @@ -354,7 +361,7 @@ private void DoReceive(object state) } } - if (!_isClosed) + if (!_isClosed.HasOccurred) { logger.LogWarning($"SCTP association {RTCSctpAssociation.ID} receive thread stopped."); } @@ -375,7 +382,7 @@ public override void Send(string associationID, ReadOnlySpan data) $" that exceeded the maximum allowed message size of {maxMessageSize}."); } - if (!_isClosed) + if (!_isClosed.HasOccurred) { lock (transport) { From b41852e6473204ba5abd21c017b7b5175f21518f Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Tue, 19 Mar 2024 16:54:31 -0700 Subject: [PATCH 47/88] bumped framework of data channel bandwidth test --- .../DataChannelBandwidth/DataChannelBandwidth.csproj | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/WebRTCScenarios/DataChannelBandwidth/DataChannelBandwidth.csproj b/examples/WebRTCScenarios/DataChannelBandwidth/DataChannelBandwidth.csproj index 2a8b338e1..205cc32ea 100644 --- a/examples/WebRTCScenarios/DataChannelBandwidth/DataChannelBandwidth.csproj +++ b/examples/WebRTCScenarios/DataChannelBandwidth/DataChannelBandwidth.csproj @@ -1,16 +1,16 @@ - + Exe - net6.0 - 10 + net8.0 + 12 enable enable - - + + From 41b3013be0328759aec0d9f301b41a29041d7c3f Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Tue, 19 Mar 2024 16:55:03 -0700 Subject: [PATCH 48/88] added more synchronization to ICE checklists --- src/net/ICE/IceChecklistEntry.cs | 30 ++++++++++++++++++------------ src/net/ICE/RtpIceChannel.cs | 17 ++++++++++++++--- 2 files changed, 32 insertions(+), 15 deletions(-) diff --git a/src/net/ICE/IceChecklistEntry.cs b/src/net/ICE/IceChecklistEntry.cs index 8317a96eb..472f24a4c 100644 --- a/src/net/ICE/IceChecklistEntry.cs +++ b/src/net/ICE/IceChecklistEntry.cs @@ -182,23 +182,29 @@ public ulong Priority public string RequestTransactionID { get - { - return _cachedRequestTransactionIDs?.Count > 0 ? _cachedRequestTransactionIDs[0] : null; + { + lock (_cachedRequestTransactionIDs) + { + return _cachedRequestTransactionIDs?.Count > 0 ? _cachedRequestTransactionIDs[0] : null; + } } set { - var currentValue = _cachedRequestTransactionIDs?.Count > 0 ? _cachedRequestTransactionIDs[0] : null; - if (value != currentValue) + lock (_cachedRequestTransactionIDs) { - const int MAX_CACHED_REQUEST_IDS = 30; - while (_cachedRequestTransactionIDs.Count >= MAX_CACHED_REQUEST_IDS && _cachedRequestTransactionIDs.Count > 0) - { - _cachedRequestTransactionIDs.RemoveAt(_cachedRequestTransactionIDs.Count - 1); - } - - if (MAX_CACHED_REQUEST_IDS > 0) + var currentValue = _cachedRequestTransactionIDs?.Count > 0 ? _cachedRequestTransactionIDs[0] : null; + if (value != currentValue) { - _cachedRequestTransactionIDs.Insert(0, value); + const int MAX_CACHED_REQUEST_IDS = 30; + while (_cachedRequestTransactionIDs.Count >= MAX_CACHED_REQUEST_IDS && _cachedRequestTransactionIDs.Count > 0) + { + _cachedRequestTransactionIDs.RemoveAt(_cachedRequestTransactionIDs.Count - 1); + } + + if (MAX_CACHED_REQUEST_IDS > 0) + { + _cachedRequestTransactionIDs.Insert(0, value); + } } } } diff --git a/src/net/ICE/RtpIceChannel.cs b/src/net/ICE/RtpIceChannel.cs index 5c30d6d76..bd63ded4f 100755 --- a/src/net/ICE/RtpIceChannel.cs +++ b/src/net/ICE/RtpIceChannel.cs @@ -1582,7 +1582,12 @@ private async void ProcessChecklist() // Until that happens there is no work to do. if (IceConnectionState == RTCIceConnectionState.checking) { - if (_checklist.Count > 0) + int count; + lock (_checklist) + { + count = _checklist.Count; + } + if (count > 0) { if (RemoteIceUser == null || RemoteIcePassword == null) { @@ -1974,7 +1979,10 @@ public async Task ProcessStunMessage(STUNMessage stunMessage, IPEndPoint remoteE else if (IsController) { logger.LogDebug($"ICE RTP channel binding response state {matchingChecklistEntry.State} as Controller for {matchingChecklistEntry.RemoteCandidate.ToShortString()}"); - ProcessNominateLogicAsController(matchingChecklistEntry); + lock (_checklist) + { + ProcessNominateLogicAsController(matchingChecklistEntry); + } } } } @@ -2147,7 +2155,10 @@ private void GotStunBindingRequest(STUNMessage bindingRequest, IPEndPoint remote entry.TurnPermissionsResponseAt = DateTime.Now; } - AddChecklistEntry(entry); + lock (_checklist) + { + AddChecklistEntry(entry); + } matchingChecklistEntry = entry; } From 5ae95a181de5a98d025d8d156689b34f12bca90f Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Tue, 19 Mar 2024 16:55:52 -0700 Subject: [PATCH 49/88] don't generate package on every build pfff --- src/SIPSorcery.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/src/SIPSorcery.csproj b/src/SIPSorcery.csproj index 0461739f7..7e8196740 100644 --- a/src/SIPSorcery.csproj +++ b/src/SIPSorcery.csproj @@ -39,7 +39,6 @@ SIP Sorcery PTY LTD SIPSorcery SIPSorcery - true https://sipsorcery-org.github.io/sipsorcery/ http://www.sipsorcery.com/mainsite/favicon.ico icon.png From b050051687cfffd809e409130738a3ccb95f4f72 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Wed, 20 Mar 2024 09:45:17 -0700 Subject: [PATCH 50/88] monkey patched SCTP congestion avoidance some of the fixes are probably legit, but I believe the T3-rtx timer is not implemented correctly kinda works around https://github.com/sipsorcery-org/sipsorcery/issues/1088 --- src/net/SCTP/SctpDataSender.cs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/net/SCTP/SctpDataSender.cs b/src/net/SCTP/SctpDataSender.cs index 8c42ea40d..ad2fe52db 100644 --- a/src/net/SCTP/SctpDataSender.cs +++ b/src/net/SCTP/SctpDataSender.cs @@ -514,7 +514,7 @@ private void ProcessGapReports(ReadOnlySpan sackGapBlocks, uint maxTSNDist logger.LogTrace($"SCTP sender entering fast recovery mode due to missing TSN {missingTSN}. Fast recovery exit point {_fastRecoveryExitPoint}."); // RFC4960 7.2.3 _slowStartThreshold = (uint)Math.Max(_congestionWindow / 2, 4 * _defaultMTU); - _congestionWindow = _defaultMTU; + _congestionWindow = _slowStartThreshold; } } } @@ -644,12 +644,13 @@ private void DoSend(object state) if (!_inRetransmitMode) { logger.LogTrace("SCTP sender entering retransmit mode."); - _inRetransmitMode = true; // When the T3-rtx timer expires on an address, SCTP should perform slow start. // RFC4960 7.2.3 _slowStartThreshold = (uint)Math.Max(_congestionWindow / 2, 4 * _defaultMTU); - _congestionWindow = _defaultMTU; + // did not clarify, but I believe entering retransmit mode is NOT the same + // as T3-rtx timer expiring. Will just use regular halving formula here. + _congestionWindow = _slowStartThreshold; // For the destination address for which the timer expires, set RTO <- RTO * 2("back off the timer") // RFC4960 6.3.3 E2 @@ -664,7 +665,7 @@ private void DoSend(object state) // if it has cwnd or more bytes of data outstanding to that transport address. // Send any new data chunks that have not yet been sent. - if (chunksSent < burstSize && _sendQueue.Count > 0 && _congestionWindow > outstandingBytes) + if (chunksSent < burstSize && !_sendQueue.IsEmpty && _congestionWindow > outstandingBytes) { while (chunksSent < burstSize && _sendQueue.TryDequeue(out var dataChunk)) { @@ -789,11 +790,11 @@ private uint CalculateReceiverWindow(uint advertisedReceiveWindow, uint outstand /// A congestion window value. private uint CalculateCongestionWindow(int lastAckDataChunkSize, uint outstandingBytes) { - if (_congestionWindow < _slowStartThreshold) + if (_congestionWindow <= _slowStartThreshold) { // In Slow-Start mode, see RFC4960 7.2.1. - - if (_congestionWindow < outstandingBytes) + // Updated to RFC9260 7.2.1 + if (_congestionWindow <= outstandingBytes && !_inFastRecoveryMode) { // When cwnd is less than or equal to ssthresh, an SCTP endpoint MUST // use the slow - start algorithm to increase cwnd only if the current @@ -814,7 +815,7 @@ private uint CalculateCongestionWindow(int lastAckDataChunkSize, uint outstandin { // In Congestion Avoidance mode, see RFC4960 7.2.2. - if (_congestionWindow < outstandingBytes) + if (_congestionWindow <= outstandingBytes) { logger.LogTrace("SCTP sender congestion window in congestion avoidance increased from {Original} to {Increased}.", _congestionWindow, _congestionWindow + _defaultMTU); From f4e3a3c2cd9390f60a895a6b32c9e36f04bc3133 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Wed, 20 Mar 2024 09:45:39 -0700 Subject: [PATCH 51/88] style --- src/net/DtlsSrtp/DtlsSrtpTransport.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/net/DtlsSrtp/DtlsSrtpTransport.cs b/src/net/DtlsSrtp/DtlsSrtpTransport.cs index e23ed0444..0e9abc278 100644 --- a/src/net/DtlsSrtp/DtlsSrtpTransport.cs +++ b/src/net/DtlsSrtp/DtlsSrtpTransport.cs @@ -627,7 +627,10 @@ public void Send(ReadOnlySpan buf) public virtual void Close() { - if (_isClosed) return; + if (_isClosed) + { + return; + } _isClosed = true; this._startTime = System.DateTime.MinValue; From e4293cc3dcf05a62181561f15b6d3ca1a3bd87e2 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Wed, 20 Mar 2024 09:46:04 -0700 Subject: [PATCH 52/88] low-pri fix for log message formatting --- src/net/RTP/RTPChannel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/net/RTP/RTPChannel.cs b/src/net/RTP/RTPChannel.cs index 98c324ea5..f53ed0c8d 100644 --- a/src/net/RTP/RTPChannel.cs +++ b/src/net/RTP/RTPChannel.cs @@ -693,7 +693,7 @@ private void EndSendTo(Exception exception) // - the RTP connection may start sending before the remote socket starts listening, // - an on hold, transfer, etc. operation can change the RTP end point which could result in socket errors from the old // or new socket during the transition. - logger.LogWarning(sockExcp, $"SocketException RTPChannel EndSendTo ({sockExcp.ErrorCode}). {sockExcp.Message}"); + logger.LogWarning(sockExcp, "SocketException RTPChannel EndSendTo ({ErrorCode}). {Message}", sockExcp.ErrorCode, sockExcp.Message); break; case ObjectDisposedException: // Thrown when socket is closed. Can be safely ignored. From acefa08ccf8406683ebe73ed5dae961c7aa3203d Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Wed, 20 Mar 2024 09:46:39 -0700 Subject: [PATCH 53/88] v7.0.0-PacketView-24.3.20.1 - slowing patch --- src/SIPSorcery.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SIPSorcery.csproj b/src/SIPSorcery.csproj index 7e8196740..930102232 100644 --- a/src/SIPSorcery.csproj +++ b/src/SIPSorcery.csproj @@ -62,7 +62,7 @@ -v6.0.2: Set .net6 targetted version as stable. -v6.0.1-pre: Added .net6 target. en - 7.0.0-PacketView-24.3.19.3 + 7.0.0-PacketView-24.3.20.1 7.0.0 7.0.0 From 805b1952d1628d94a9d63dfc41c8b3ef3b3eac54 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Wed, 20 Mar 2024 10:50:56 -0700 Subject: [PATCH 54/88] updated BouncyCastle --- src/SIPSorcery.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SIPSorcery.csproj b/src/SIPSorcery.csproj index 930102232..f646d82fa 100644 --- a/src/SIPSorcery.csproj +++ b/src/SIPSorcery.csproj @@ -17,7 +17,7 @@ - + From 8113b7a0c6be71af8bb9f82518464eb08f344f7d Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Wed, 20 Mar 2024 10:51:18 -0700 Subject: [PATCH 55/88] v7.0.0-PacketView-24.3.20.2 - debug --- src/SIPSorcery.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SIPSorcery.csproj b/src/SIPSorcery.csproj index f646d82fa..26ba44e16 100644 --- a/src/SIPSorcery.csproj +++ b/src/SIPSorcery.csproj @@ -62,7 +62,7 @@ -v6.0.2: Set .net6 targetted version as stable. -v6.0.1-pre: Added .net6 target. en - 7.0.0-PacketView-24.3.20.1 + 7.0.0-PacketView-24.3.20.2 7.0.0 7.0.0 From 577b647ad8fcdc592b2fdbd0deec9a14c399b406 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Wed, 20 Mar 2024 11:35:38 -0700 Subject: [PATCH 56/88] revert what seems to be a debugging change in DtlsSrtpTransport that caused receivers to never end and consume CPU see https://github.com/sipsorcery-org/sipsorcery/pull/771 reverting the change from commit ID 3f1bfc9a9d53e627b0f618f69a01cd6de15a0caa --- src/net/DtlsSrtp/DtlsSrtpTransport.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/net/DtlsSrtp/DtlsSrtpTransport.cs b/src/net/DtlsSrtp/DtlsSrtpTransport.cs index 0e9abc278..cae8ac2c7 100644 --- a/src/net/DtlsSrtp/DtlsSrtpTransport.cs +++ b/src/net/DtlsSrtp/DtlsSrtpTransport.cs @@ -600,8 +600,8 @@ public int Receive(byte[] buf, int off, int len, int waitMillis) } else { - //throw new System.Net.Sockets.SocketException((int)System.Net.Sockets.SocketError.NotConnected); - return DTLS_RECEIVE_ERROR_CODE; + throw new System.Net.Sockets.SocketException((int)System.Net.Sockets.SocketError.NotConnected); + //return DTLS_RECEIVE_ERROR_CODE; } } From 2728e4add5fc806d0ccfe150e0dd2f52eb3f9bc4 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Wed, 20 Mar 2024 11:39:59 -0700 Subject: [PATCH 57/88] v7.0.0-PacketView-24.3.20.3 --- src/SIPSorcery.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SIPSorcery.csproj b/src/SIPSorcery.csproj index 26ba44e16..c0b343da4 100644 --- a/src/SIPSorcery.csproj +++ b/src/SIPSorcery.csproj @@ -62,7 +62,7 @@ -v6.0.2: Set .net6 targetted version as stable. -v6.0.1-pre: Added .net6 target. en - 7.0.0-PacketView-24.3.20.2 + 7.0.0-PacketView-24.3.20.3 7.0.0 7.0.0 From 444384dd18627061facca3da11c074a97500f58a Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Wed, 20 Mar 2024 19:31:47 -0700 Subject: [PATCH 58/88] oops accidentally turned off Retransmit mode https://github.com/sipsorcery-org/sipsorcery/issues/1088 --- src/net/SCTP/SctpDataSender.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/net/SCTP/SctpDataSender.cs b/src/net/SCTP/SctpDataSender.cs index ad2fe52db..a087081b9 100644 --- a/src/net/SCTP/SctpDataSender.cs +++ b/src/net/SCTP/SctpDataSender.cs @@ -644,6 +644,7 @@ private void DoSend(object state) if (!_inRetransmitMode) { logger.LogTrace("SCTP sender entering retransmit mode."); + _inRetransmitMode = true; // When the T3-rtx timer expires on an address, SCTP should perform slow start. // RFC4960 7.2.3 From 6eb572dee4096ea15485770f918460a721329620 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Wed, 20 Mar 2024 19:34:11 -0700 Subject: [PATCH 59/88] Interlocked retransmit mode --- src/net/SCTP/SctpDataSender.cs | 22 ++++++++++------------ src/sys/OnOff.cs | 13 +++++++++++++ 2 files changed, 23 insertions(+), 12 deletions(-) create mode 100644 src/sys/OnOff.cs diff --git a/src/net/SCTP/SctpDataSender.cs b/src/net/SCTP/SctpDataSender.cs index a087081b9..d266103d5 100644 --- a/src/net/SCTP/SctpDataSender.cs +++ b/src/net/SCTP/SctpDataSender.cs @@ -76,7 +76,7 @@ public class SctpDataSender private bool _isStarted; private Once _closed; private int _lastAckedDataChunkSize; - private bool _inRetransmitMode; + private OnOff _inRetransmitMode; private bool _inFastRecoveryMode; private uint _fastRecoveryExitPoint; private ManualResetEventSlim _senderMre = new ManualResetEventSlim(); @@ -203,10 +203,9 @@ public void SetReceiverWindow(uint remoteARwnd) public void GotSack(SctpChunkView sack) { { - if (_inRetransmitMode) + if (_inRetransmitMode.TryTurnOff()) { logger.LogTrace("SCTP sender exiting retransmit mode."); - _inRetransmitMode = false; } unchecked @@ -363,14 +362,14 @@ public void SendData(ushort streamID, uint ppid, ReadOnlySpan data) _sendQueue.Enqueue(dataChunk); - if (_sendQueue.Count > MaxSendQueueCount) - { - _queueSpaceAvailable.Reset(); - } - TSN = (TSN == UInt32.MaxValue) ? 0 : TSN + 1; } + if (_sendQueue.Count > MaxSendQueueCount) + { + _queueSpaceAvailable.Reset(); + } + _senderMre.Set(); } } @@ -581,7 +580,7 @@ private void DoSend(object state) // calling once per loop. var now = SctpDataChunk.Timestamp.Now; - int burstSize = (_inRetransmitMode || _inFastRecoveryMode || _congestionWindow < outstandingBytes || _receiverWindow == 0) ? 1 : MAX_BURST; + int burstSize = (_inRetransmitMode.IsOn() || _inFastRecoveryMode || _congestionWindow < outstandingBytes || _receiverWindow == 0) ? 1 : MAX_BURST; int chunksSent = 0; //logger.LogTrace($"SCTP sender burst size {burstSize}, in retransmit mode {_inRetransmitMode}, cwnd {_congestionWindow}, arwnd {_receiverWindow}."); @@ -640,11 +639,10 @@ private void DoSend(object state) _sendDataChunk(chunk); chunksSent++; - - if (!_inRetransmitMode) + + if (_inRetransmitMode.TryTurnOn()) { logger.LogTrace("SCTP sender entering retransmit mode."); - _inRetransmitMode = true; // When the T3-rtx timer expires on an address, SCTP should perform slow start. // RFC4960 7.2.3 diff --git a/src/sys/OnOff.cs b/src/sys/OnOff.cs new file mode 100644 index 000000000..4dbb97cdf --- /dev/null +++ b/src/sys/OnOff.cs @@ -0,0 +1,13 @@ +using System.Threading; + +namespace SIPSorcery.Sys; + +/// A thread-safe struct that represents an on/off state. +struct OnOff +{ + int on; + + public bool TryTurnOn() => Interlocked.CompareExchange(ref on, 1, comparand: 0) == 0; + public bool TryTurnOff() => Interlocked.CompareExchange(ref on, 0, comparand: 1) == 1; + public bool IsOn() => Interlocked.CompareExchange(ref on, 0, 0) == 1; +} From fd2583f74b6c2a75d186ddafac3afc22c9b81950 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Wed, 20 Mar 2024 19:34:34 -0700 Subject: [PATCH 60/88] v7.0.0-PacketView-24.3.20.4 --- src/SIPSorcery.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SIPSorcery.csproj b/src/SIPSorcery.csproj index c0b343da4..22f47a084 100644 --- a/src/SIPSorcery.csproj +++ b/src/SIPSorcery.csproj @@ -62,7 +62,7 @@ -v6.0.2: Set .net6 targetted version as stable. -v6.0.1-pre: Added .net6 target. en - 7.0.0-PacketView-24.3.20.3 + 7.0.0-PacketView-24.3.20.4 7.0.0 7.0.0 From 84a484c6fdbc6c9ecbfc81edfae59a1bb1c8bf7c Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Wed, 20 Mar 2024 20:14:10 -0700 Subject: [PATCH 61/88] Interlocked fast recovery mode --- src/net/SCTP/SctpDataSender.cs | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/src/net/SCTP/SctpDataSender.cs b/src/net/SCTP/SctpDataSender.cs index d266103d5..0af3a005b 100644 --- a/src/net/SCTP/SctpDataSender.cs +++ b/src/net/SCTP/SctpDataSender.cs @@ -77,7 +77,8 @@ public class SctpDataSender private Once _closed; private int _lastAckedDataChunkSize; private OnOff _inRetransmitMode; - private bool _inFastRecoveryMode; + private OnOff _inFastRecoveryMode; + /// Only ever accessed inside private uint _fastRecoveryExitPoint; private ManualResetEventSlim _senderMre = new ManualResetEventSlim(); private readonly ManualResetEventSlim _queueSpaceAvailable = new ManualResetEventSlim(initialState: true); @@ -131,7 +132,7 @@ public class SctpDataSender internal int _outstandingBytes => _unconfirmedChunks.Sum(x => x.Value.UserDataLength); /// - /// The TSN that the remote peer has acknowledged. + /// The TSN that the remote peer has acknowledged. Only ever accessed inside /// private uint _cumulativeAckTSN; @@ -205,7 +206,7 @@ public void GotSack(SctpChunkView sack) { if (_inRetransmitMode.TryTurnOff()) { - logger.LogTrace("SCTP sender exiting retransmit mode."); + logger.LogDebug("SCTP sender exiting retransmit mode."); } unchecked @@ -291,10 +292,9 @@ public void GotSack(SctpChunkView sack) // rfc4960 6.2.1 D iv // If the Cumulative TSN Ack matches or exceeds the Fast Recovery exitpoint(Section 7.2.4), Fast Recovery is exited. - if (_inFastRecoveryMode && SctpDataReceiver.IsNewerOrEqual(_fastRecoveryExitPoint, _cumulativeAckTSN)) + if (SctpDataReceiver.IsNewerOrEqual(_fastRecoveryExitPoint, _cumulativeAckTSN) && _inFastRecoveryMode.TryTurnOff()) { logger.LogTrace("SCTP sender exiting fast recovery at TSN {TSN}", _fastRecoveryExitPoint); - _inFastRecoveryMode = false; } } @@ -493,7 +493,7 @@ private void ProcessGapReports(ReadOnlySpan sackGapBlocks, uint maxTSNDist else if ( // If an endpoint is in Fast Recovery and a SACK arrives that advances the Cumulative TSN Ack // Point, the miss indications are incremented for all TSNs reported missing in the SACK. - (_inFastRecoveryMode && didSackIncrementTSN) || + (_inFastRecoveryMode.IsOn() && didSackIncrementTSN) || // For each incoming SACK, miss indications are incremented only // for missing TSNs prior to the highest TSN newly acknowledged in the SACK. SctpDataReceiver.IsNewer(missingTSN, highestTsnNewlyAcknowledged)) @@ -503,14 +503,13 @@ private void ProcessGapReports(ReadOnlySpan sackGapBlocks, uint maxTSNDist // rfc 7.2.4: When the third consecutive miss indication is received for a TSN(s), the data sender shall do the following... if (missCount + 1 == 3) { - if (!_inFastRecoveryMode) // RFC4960 7.2.4 (2) + if (_inFastRecoveryMode.TryTurnOn()) // RFC4960 7.2.4 (2) { - _inFastRecoveryMode = true; // mark the highest outstanding TSN as the Fast Recovery exit point var last = SctpTsnGapBlock.Read(sackGapBlocks.Slice(sackGapBlocks.Length - SctpSackChunk.GAP_REPORT_LENGTH)); _fastRecoveryExitPoint = _cumulativeAckTSN + last.End; - logger.LogTrace($"SCTP sender entering fast recovery mode due to missing TSN {missingTSN}. Fast recovery exit point {_fastRecoveryExitPoint}."); + logger.LogDebug($"SCTP sender entering fast recovery mode due to missing TSN {missingTSN}. Fast recovery exit point {_fastRecoveryExitPoint}."); // RFC4960 7.2.3 _slowStartThreshold = (uint)Math.Max(_congestionWindow / 2, 4 * _defaultMTU); _congestionWindow = _slowStartThreshold; @@ -580,7 +579,7 @@ private void DoSend(object state) // calling once per loop. var now = SctpDataChunk.Timestamp.Now; - int burstSize = (_inRetransmitMode.IsOn() || _inFastRecoveryMode || _congestionWindow < outstandingBytes || _receiverWindow == 0) ? 1 : MAX_BURST; + int burstSize = (_inRetransmitMode.IsOn() || _inFastRecoveryMode.IsOn() || _congestionWindow < outstandingBytes || _receiverWindow == 0) ? 1 : MAX_BURST; int chunksSent = 0; //logger.LogTrace($"SCTP sender burst size {burstSize}, in retransmit mode {_inRetransmitMode}, cwnd {_congestionWindow}, arwnd {_receiverWindow}."); @@ -642,7 +641,7 @@ private void DoSend(object state) if (_inRetransmitMode.TryTurnOn()) { - logger.LogTrace("SCTP sender entering retransmit mode."); + logger.LogDebug("SCTP sender entering retransmit mode."); // When the T3-rtx timer expires on an address, SCTP should perform slow start. // RFC4960 7.2.3 @@ -793,7 +792,7 @@ private uint CalculateCongestionWindow(int lastAckDataChunkSize, uint outstandin { // In Slow-Start mode, see RFC4960 7.2.1. // Updated to RFC9260 7.2.1 - if (_congestionWindow <= outstandingBytes && !_inFastRecoveryMode) + if (_congestionWindow <= outstandingBytes && !_inFastRecoveryMode.IsOn()) { // When cwnd is less than or equal to ssthresh, an SCTP endpoint MUST // use the slow - start algorithm to increase cwnd only if the current From 257fa9c3d24e8de44a8789421973dcdd05949add Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Wed, 20 Mar 2024 20:14:56 -0700 Subject: [PATCH 62/88] v7.0.0-PacketView-24.3.20.5 --- src/SIPSorcery.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SIPSorcery.csproj b/src/SIPSorcery.csproj index 22f47a084..debcbded3 100644 --- a/src/SIPSorcery.csproj +++ b/src/SIPSorcery.csproj @@ -62,7 +62,7 @@ -v6.0.2: Set .net6 targetted version as stable. -v6.0.1-pre: Added .net6 target. en - 7.0.0-PacketView-24.3.20.4 + 7.0.0-PacketView-24.3.20.5 7.0.0 7.0.0 From 50cc1ddf25b9c882539ae6eee3c44969afd38926 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Wed, 20 Mar 2024 20:31:04 -0700 Subject: [PATCH 63/88] Interlocked TSN --- src/net/SCTP/SctpDataSender.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/net/SCTP/SctpDataSender.cs b/src/net/SCTP/SctpDataSender.cs index 0af3a005b..a29181311 100644 --- a/src/net/SCTP/SctpDataSender.cs +++ b/src/net/SCTP/SctpDataSender.cs @@ -165,10 +165,11 @@ public class SctpDataSender /// public ulong BufferedAmount => (ulong)_sendQueue.Sum(x => x.UserDataLength); + int tsn; /// /// The Transaction Sequence Number (TSN) that will be used in the next DATA chunk sent. /// - public uint TSN { get; internal set; } + public uint TSN => unchecked((uint)Interlocked.CompareExchange(ref tsn, 0, 0)); public SctpDataSender( string associationID, @@ -181,7 +182,7 @@ public SctpDataSender( _sendDataChunk = sendDataChunk; _defaultMTU = defaultMTU > 0 ? defaultMTU : DEFAULT_SCTP_MTU; _initialTSN = initialTSN; - TSN = initialTSN; + tsn = unchecked((int)initialTSN); _initialRemoteARwnd = remoteARwnd; _receiverWindow = remoteARwnd; @@ -362,7 +363,7 @@ public void SendData(ushort streamID, uint ppid, ReadOnlySpan data) _sendQueue.Enqueue(dataChunk); - TSN = (TSN == UInt32.MaxValue) ? 0 : TSN + 1; + Interlocked.Increment(ref tsn); } if (_sendQueue.Count > MaxSendQueueCount) From c5cf5e3daadfd8d98947fa0fae37e7e6afd20ffd Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Wed, 20 Mar 2024 21:21:57 -0700 Subject: [PATCH 64/88] reenabled unit tests; fixed ReceptionReportSample parsing --- src/net/RTCP/ReceptionReport.cs | 2 +- src/net/SCTP/Chunks/SctpChunk.cs | 7 + src/sys/Crypto/Crypto.cs | 7 + src/sys/TypeExtensions.cs | 8 ++ test/unit/net/SCTP/SctpAssociationUnitTest.cs | 20 +-- test/unit/net/SCTP/SctpChunkUnitTest.cs | 2 +- .../unit/net/SCTP/SctpDataReceiverUnitTest.cs | 124 +++++++++--------- .../unit/net/SCTP/SctpDataSendRecvUnitTest.cs | 32 ++--- test/unit/net/SCTP/SctpDataSenderUnitTest.cs | 6 +- test/unit/net/SCTP/SctpHeaderUnitTest.cs | 4 +- test/unit/net/SCTP/SctpPacketUnitTest.cs | 54 ++++---- test/unit/net/SCTP/SctpTransportUnitTest.cs | 3 +- test/unit/sys/InterlockedExUnitTest.cs | 47 +++++++ 13 files changed, 194 insertions(+), 122 deletions(-) create mode 100644 test/unit/sys/InterlockedExUnitTest.cs diff --git a/src/net/RTCP/ReceptionReport.cs b/src/net/RTCP/ReceptionReport.cs index 66d422a90..17ec2b040 100644 --- a/src/net/RTCP/ReceptionReport.cs +++ b/src/net/RTCP/ReceptionReport.cs @@ -114,7 +114,7 @@ public ReceptionReportSample( public ReceptionReportSample(ReadOnlySpan packet) { - SSRC = BinaryPrimitives.ReadUInt32BigEndian(packet.Slice(4)); + SSRC = BinaryPrimitives.ReadUInt32BigEndian(packet.Slice(0, 4)); FractionLost = packet[4]; Span packetsLost = stackalloc byte[] { 0x00, packet[5], packet[6], packet[7] }; PacketsLost = BinaryPrimitives.ReadInt32BigEndian(packetsLost); diff --git a/src/net/SCTP/Chunks/SctpChunk.cs b/src/net/SCTP/Chunks/SctpChunk.cs index 8e7b92547..b96ee0338 100644 --- a/src/net/SCTP/Chunks/SctpChunk.cs +++ b/src/net/SCTP/Chunks/SctpChunk.cs @@ -239,6 +239,13 @@ public virtual ushort WriteTo(Span buffer, int posn) return GetChunkLength(true); } + internal SctpChunkView View() + { + byte[] bytes = new byte[GetChunkLength(padded: true)]; + ushort written = WriteTo(bytes, 0); + return new SctpChunkView(bytes.AsSpan().Slice(0, written)); + } + /// /// Handler for processing an unrecognised chunk parameter. /// diff --git a/src/sys/Crypto/Crypto.cs b/src/sys/Crypto/Crypto.cs index bd4a64a3e..24fc4fd78 100644 --- a/src/sys/Crypto/Crypto.cs +++ b/src/sys/Crypto/Crypto.cs @@ -371,6 +371,13 @@ public static string GetSHA256Hash(byte[] buffer) } } + /// + /// Gets the HSA256 hash of an arbitrary buffer. + /// + /// A hex string representing the hashed buffer. + public static string GetSHA256Hash(ReadOnlySpan buffer) + => GetSHA256Hash(buffer.ToArray()); + /// /// Attempts to load an X509 certificate from a Windows OS certificate store. /// diff --git a/src/sys/TypeExtensions.cs b/src/sys/TypeExtensions.cs index dbbad2f91..d4ec8207b 100644 --- a/src/sys/TypeExtensions.cs +++ b/src/sys/TypeExtensions.cs @@ -119,12 +119,20 @@ public static string HexStr(this ReadOnlySpan buffer, char? separator = nu { return HexStr(buffer, buffer.Length, separator); } + public static string HexStr(this Span buffer, char? separator = null) + { + return HexStr(buffer, buffer.Length, separator); + } public static string HexStr(this byte[] buffer, char? separator = null) { return HexStr(buffer, buffer.Length, separator); } + public static string HexStr(this byte[] buffer, int length, char? separator = null) + { + return HexStr(buffer.AsSpan(), length, separator); + } public static string HexStr(this ReadOnlySpan buffer, int length, char? separator = null) { string rv = string.Empty; diff --git a/test/unit/net/SCTP/SctpAssociationUnitTest.cs b/test/unit/net/SCTP/SctpAssociationUnitTest.cs index 689c670ed..6bee215fd 100644 --- a/test/unit/net/SCTP/SctpAssociationUnitTest.cs +++ b/test/unit/net/SCTP/SctpAssociationUnitTest.cs @@ -98,7 +98,7 @@ public void SendDataChunk() string message = "hello world"; var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - bAssoc.OnData += (frame) => tcs.TrySetResult(Encoding.UTF8.GetString(frame.UserData)); + bAssoc.OnData += (frame) => tcs.TrySetResult(Encoding.UTF8.GetString(frame.UserData.ToArray())); aAssoc.SendData(0, 0, Encoding.UTF8.GetBytes(message)); tcs.Task.Wait(3000); @@ -123,7 +123,7 @@ public void SendFragmentedDataChunk() string message = "hello world"; var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - bAssoc.OnData += (frame) => tcs.TrySetResult(Encoding.UTF8.GetString(frame.UserData)); + bAssoc.OnData += (frame) => tcs.TrySetResult(Encoding.UTF8.GetString(frame.UserData.ToArray())); aAssoc.SendData(0, 0, Encoding.UTF8.GetBytes(message)); tcs.Task.Wait(3000); @@ -228,7 +228,9 @@ internal class MockB2BSctpTransport : SctpTransport private bool _exit; - public event Action OnSctpPacket; + public delegate void SctpPacketViewAction(SctpPacketView pkt); + + public event SctpPacketViewAction OnSctpPacket; public event Action OnCookieEcho; public MockB2BSctpTransport(BlockingCollection output, BlockingCollection input) @@ -243,19 +245,19 @@ public void Listen() { if (_input.TryTake(out var buffer, 1000)) { - SctpPacket pkt = SctpPacket.Parse(buffer, 0, buffer.Length); + SctpPacket pkt = SctpPacket.Parse(buffer); // Process packet. if (pkt.Chunks.Any(x => x.KnownType == SctpChunkType.INIT)) { var initAckPacket = base.GetInitAck(pkt, null); var initAckBuffer = initAckPacket.GetBytes(); - Send(null, initAckBuffer, 0, initAckBuffer.Length); + Send(null, initAckBuffer); } else if (pkt.Chunks.Any(x => x.KnownType == SctpChunkType.COOKIE_ECHO)) { var cookieEcho = pkt.Chunks.Single(x => x.KnownType == SctpChunkType.COOKIE_ECHO); - var cookie = base.GetCookie(pkt); + var cookie = base.GetCookie(SctpPacketView.Parse(buffer)); if (cookie.IsEmpty()) { throw new ApplicationException($"MockB2BSctpTransport gave itself an invalid INIT cookie."); @@ -267,15 +269,15 @@ public void Listen() } else { - OnSctpPacket?.Invoke(pkt); + OnSctpPacket?.Invoke(SctpPacketView.Parse(buffer)); } } } } - public override void Send(string associationID, byte[] buffer, int offset, int length) + public override void Send(string associationID, ReadOnlySpan buffer) { - _output.Add(buffer.Skip(offset).Take(length).ToArray()); + _output.Add(buffer.ToArray()); } public void Close() diff --git a/test/unit/net/SCTP/SctpChunkUnitTest.cs b/test/unit/net/SCTP/SctpChunkUnitTest.cs index d9188fe1d..ed78c344e 100644 --- a/test/unit/net/SCTP/SctpChunkUnitTest.cs +++ b/test/unit/net/SCTP/SctpChunkUnitTest.cs @@ -63,7 +63,7 @@ public void ParseSACKChunk() { var sackBuffer = BufferUtils.ParseHexStr("13881388E48092946AB2050003000014D19244F60002000000000001A7498379"); - var sackPkt = SctpPacket.Parse(sackBuffer, 0, sackBuffer.Length); + var sackPkt = SctpPacket.Parse(sackBuffer); Assert.NotNull(sackPkt); Assert.Single(sackPkt.Chunks); diff --git a/test/unit/net/SCTP/SctpDataReceiverUnitTest.cs b/test/unit/net/SCTP/SctpDataReceiverUnitTest.cs index deb35614b..916cc9a4e 100644 --- a/test/unit/net/SCTP/SctpDataReceiverUnitTest.cs +++ b/test/unit/net/SCTP/SctpDataReceiverUnitTest.cs @@ -39,7 +39,7 @@ public void SinglePacketFrame() SctpDataChunk chunk = new SctpDataChunk(false, true, true, 0, 0, 0, 0, new byte[] { 0x00 }); - var sortedFrames = receiver.OnDataChunk(chunk); + var sortedFrames = receiver.OnDataChunk(chunk.View()); Assert.Single(sortedFrames); Assert.Equal("00", sortedFrames.Single().UserData.HexStr()); @@ -59,11 +59,11 @@ public void ThreeFragments() SctpDataChunk chunk2 = new SctpDataChunk(false, false, false, 1, 0, 0, 0, new byte[] { 0x01 }); SctpDataChunk chunk3 = new SctpDataChunk(false, false, true, 2, 0, 0, 0, new byte[] { 0x02 }); - var sortFrames1 = receiver.OnDataChunk(chunk1); + var sortFrames1 = receiver.OnDataChunk(chunk1.View()); Assert.Equal(0U, receiver.CumulativeAckTSN); - var sortFrames2 = receiver.OnDataChunk(chunk2); + var sortFrames2 = receiver.OnDataChunk(chunk2.View()); Assert.Equal(1U, receiver.CumulativeAckTSN); - var sortFrames3 = receiver.OnDataChunk(chunk3); + var sortFrames3 = receiver.OnDataChunk(chunk3.View()); Assert.Equal(2U, receiver.CumulativeAckTSN); Assert.Empty(sortFrames1); @@ -86,11 +86,11 @@ public void ThreeFragmentsOutOfOrder() SctpDataChunk chunk2 = new SctpDataChunk(false, false, false, 1, 0, 0, 0, new byte[] { 0x01 }); SctpDataChunk chunk3 = new SctpDataChunk(false, false, true, 2, 0, 0, 0, new byte[] { 0x02 }); - var sortFrames1 = receiver.OnDataChunk(chunk1); + var sortFrames1 = receiver.OnDataChunk(chunk1.View()); Assert.Equal(0U, receiver.CumulativeAckTSN); - var sortFrames2 = receiver.OnDataChunk(chunk3); + var sortFrames2 = receiver.OnDataChunk(chunk3.View()); Assert.Equal(0U, receiver.CumulativeAckTSN); - var sortFrames3 = receiver.OnDataChunk(chunk2); + var sortFrames3 = receiver.OnDataChunk(chunk2.View()); Assert.Equal(2U, receiver.CumulativeAckTSN); Assert.Empty(sortFrames1); @@ -113,11 +113,11 @@ public void ThreeFragmentsBeginLast() SctpDataChunk chunk2 = new SctpDataChunk(false, false, false, 1, 0, 0, 0, new byte[] { 0x01 }); SctpDataChunk chunk3 = new SctpDataChunk(false, false, true, 2, 0, 0, 0, new byte[] { 0x02 }); - var sortFrames1 = receiver.OnDataChunk(chunk3); + var sortFrames1 = receiver.OnDataChunk(chunk3.View()); Assert.Null(receiver.CumulativeAckTSN); - var sortFrames2 = receiver.OnDataChunk(chunk2); + var sortFrames2 = receiver.OnDataChunk(chunk2.View()); Assert.Null(receiver.CumulativeAckTSN); - var sortFrames3 = receiver.OnDataChunk(chunk1); + var sortFrames3 = receiver.OnDataChunk(chunk1.View()); Assert.Equal(2U, receiver.CumulativeAckTSN); Assert.Empty(sortFrames1); @@ -141,15 +141,15 @@ public void FragmentWithTSNWrap() SctpDataChunk chunk4 = new SctpDataChunk(false, false, false, 0, 0, 0, 0, new byte[] { 0x03 }); SctpDataChunk chunk5 = new SctpDataChunk(false, false, true, 1, 0, 0, 0, new byte[] { 0x04 }); - var sFrames1 = receiver.OnDataChunk(chunk1); + var sFrames1 = receiver.OnDataChunk(chunk1.View()); Assert.Equal(uint.MaxValue - 2, receiver.CumulativeAckTSN); - var sFrames2 = receiver.OnDataChunk(chunk2); + var sFrames2 = receiver.OnDataChunk(chunk2.View()); Assert.Equal(uint.MaxValue - 1, receiver.CumulativeAckTSN); - var sFrames3 = receiver.OnDataChunk(chunk3); + var sFrames3 = receiver.OnDataChunk(chunk3.View()); Assert.Equal(uint.MaxValue, receiver.CumulativeAckTSN); - var sFrames4 = receiver.OnDataChunk(chunk4); + var sFrames4 = receiver.OnDataChunk(chunk4.View()); Assert.Equal(0U, receiver.CumulativeAckTSN); - var sFrames5 = receiver.OnDataChunk(chunk5); + var sFrames5 = receiver.OnDataChunk(chunk5.View()); Assert.Equal(1U, receiver.CumulativeAckTSN); Assert.Empty(sFrames1); @@ -180,19 +180,19 @@ public void FragmentWithTSNWrapAndOutOfOrder() SctpDataChunk chunk6 = new SctpDataChunk(true, true, true, 6, 0, 0, 0, new byte[] { 0x06 }); SctpDataChunk chunk9 = new SctpDataChunk(true, true, true, 9, 0, 0, 0, new byte[] { 0x09 }); - var sframes9 = receiver.OnDataChunk(chunk9); + var sframes9 = receiver.OnDataChunk(chunk9.View()); Assert.Null(receiver.CumulativeAckTSN); - var sframes1 = receiver.OnDataChunk(chunk1); + var sframes1 = receiver.OnDataChunk(chunk1.View()); Assert.Equal(uint.MaxValue - 2, receiver.CumulativeAckTSN); - var sframes2 = receiver.OnDataChunk(chunk2); + var sframes2 = receiver.OnDataChunk(chunk2.View()); Assert.Equal(uint.MaxValue - 1, receiver.CumulativeAckTSN); - var sframes3 = receiver.OnDataChunk(chunk3); + var sframes3 = receiver.OnDataChunk(chunk3.View()); Assert.Equal(uint.MaxValue, receiver.CumulativeAckTSN); - var sframes6 = receiver.OnDataChunk(chunk6); + var sframes6 = receiver.OnDataChunk(chunk6.View()); Assert.Equal(uint.MaxValue, receiver.CumulativeAckTSN); - var sframes4 = receiver.OnDataChunk(chunk4); + var sframes4 = receiver.OnDataChunk(chunk4.View()); Assert.Equal(0U, receiver.CumulativeAckTSN); - var sframes5 = receiver.OnDataChunk(chunk5); + var sframes5 = receiver.OnDataChunk(chunk5.View()); Assert.Equal(1U, receiver.CumulativeAckTSN); Assert.Empty(sframes1); @@ -221,11 +221,11 @@ public void FragmentWithExpectedTSNWrap() SctpDataChunk chunk4 = new SctpDataChunk(false, false, false, 0, 0, 0, 0, new byte[] { 0x03 }); SctpDataChunk chunk5 = new SctpDataChunk(false, false, true, 1, 0, 0, 0, new byte[] { 0x04 }); - var sframes1 = receiver.OnDataChunk(chunk1); - var sframes2 = receiver.OnDataChunk(chunk2); - var sframes3 = receiver.OnDataChunk(chunk3); - var sframes4 = receiver.OnDataChunk(chunk4); - var sframes5 = receiver.OnDataChunk(chunk5); + var sframes1 = receiver.OnDataChunk(chunk1.View()); + var sframes2 = receiver.OnDataChunk(chunk2.View()); + var sframes3 = receiver.OnDataChunk(chunk3.View()); + var sframes4 = receiver.OnDataChunk(chunk4.View()); + var sframes5 = receiver.OnDataChunk(chunk5.View()); Assert.Empty(sframes1); Assert.Empty(sframes2); @@ -290,7 +290,7 @@ public void CheckExpiryWithSinglePacketChunksUnordered() { SctpDataChunk chunk = new SctpDataChunk(true, true, true, tsn++, 0, 0, 0, new byte[] { 0x55 }); - var sortedFrames = receiver.OnDataChunk(chunk); + var sortedFrames = receiver.OnDataChunk(chunk.View()); Assert.Single(sortedFrames); Assert.Equal("55", sortedFrames.Single().UserData.HexStr()); @@ -316,7 +316,7 @@ public void CheckExpiryWithSinglePacketChunksOrdered() { SctpDataChunk chunk = new SctpDataChunk(false, true, true, tsn++, 0, streamSeqnum++, 0, new byte[] { 0x55 }); - var sortedFrames = receiver.OnDataChunk(chunk); + var sortedFrames = receiver.OnDataChunk(chunk.View()); Assert.Single(sortedFrames); Assert.Equal("55", sortedFrames.Single().UserData.HexStr()); @@ -337,9 +337,9 @@ public void ThreeStreamPackets() SctpDataChunk chunk2 = new SctpDataChunk(false, true, true, 1, 0, 1, 0, new byte[] { 0x01 }); SctpDataChunk chunk3 = new SctpDataChunk(false, true, true, 2, 0, 2, 0, new byte[] { 0x02 }); - var sortFrames1 = receiver.OnDataChunk(chunk1); - var sortFrames2 = receiver.OnDataChunk(chunk2); - var sortFrames3 = receiver.OnDataChunk(chunk3); + var sortFrames1 = receiver.OnDataChunk(chunk1.View()); + var sortFrames2 = receiver.OnDataChunk(chunk2.View()); + var sortFrames3 = receiver.OnDataChunk(chunk3.View()); Assert.Single(sortFrames1); Assert.Equal(0, sortFrames1.Single().StreamSeqNum); @@ -367,10 +367,10 @@ public void StreamPacketsReceviedOutOfOrder() SctpDataChunk chunk2 = new SctpDataChunk(false, true, true, 1, 0, 1, 0, new byte[] { 0x01 }); SctpDataChunk chunk3 = new SctpDataChunk(false, true, true, 2, 0, 2, 0, new byte[] { 0x02 }); - var sortFrames0 = receiver.OnDataChunk(chunk0); - var sortFrames1 = receiver.OnDataChunk(chunk3); - var sortFrames2 = receiver.OnDataChunk(chunk2); - var sortFrames3 = receiver.OnDataChunk(chunk1); + var sortFrames0 = receiver.OnDataChunk(chunk0.View()); + var sortFrames1 = receiver.OnDataChunk(chunk3.View()); + var sortFrames2 = receiver.OnDataChunk(chunk2.View()); + var sortFrames3 = receiver.OnDataChunk(chunk1.View()); Assert.Single(sortFrames0); Assert.Empty(sortFrames1); @@ -390,8 +390,8 @@ public void StreamPacketsReceviedOutOfOrder() public void GetSingleGapReport() { SctpDataReceiver receiver = new SctpDataReceiver(0, 0, 25); - receiver.OnDataChunk(new SctpDataChunk(true, true, true, 25, 0, 0, 0, new byte[] { 0x33 })); - receiver.OnDataChunk(new SctpDataChunk(true, true, true, 30, 0, 0, 0, new byte[] { 0x33 })); + receiver.OnDataChunk(new SctpDataChunk(true, true, true, 25, 0, 0, 0, new byte[] { 0x33 }).View()); + receiver.OnDataChunk(new SctpDataChunk(true, true, true, 30, 0, 0, 0, new byte[] { 0x33 }).View()); var gapReports = receiver.GetForwardTSNGaps(); @@ -412,8 +412,8 @@ public void GetSingleGapReportWithWrap() { uint initialTSN = uint.MaxValue - 2; SctpDataReceiver receiver = new SctpDataReceiver(0, 0, initialTSN); - receiver.OnDataChunk(new SctpDataChunk(true, true, true, initialTSN, 0, 0, 0, new byte[] { 0x33 })); - receiver.OnDataChunk(new SctpDataChunk(true, true, true, 2, 0, 0, 0, new byte[] { 0x33 })); + receiver.OnDataChunk(new SctpDataChunk(true, true, true, initialTSN, 0, 0, 0, new byte[] { 0x33 }).View()); + receiver.OnDataChunk(new SctpDataChunk(true, true, true, 2, 0, 0, 0, new byte[] { 0x33 }).View()); var gapReports = receiver.GetForwardTSNGaps(); @@ -432,10 +432,10 @@ public void GetSingleGapReportWithWrap() public void GetTwoGapReports() { SctpDataReceiver receiver = new SctpDataReceiver(0, 0, 15005); - receiver.OnDataChunk(new SctpDataChunk(true, true, true, 15005, 0, 0, 0, new byte[] { 0x33 })); - receiver.OnDataChunk(new SctpDataChunk(true, true, true, 15007, 0, 0, 0, new byte[] { 0x33 })); - receiver.OnDataChunk(new SctpDataChunk(true, true, true, 15008, 0, 0, 0, new byte[] { 0x33 })); - receiver.OnDataChunk(new SctpDataChunk(true, true, true, 15010, 0, 0, 0, new byte[] { 0x33 })); + receiver.OnDataChunk(new SctpDataChunk(true, true, true, 15005, 0, 0, 0, new byte[] { 0x33 }).View()); + receiver.OnDataChunk(new SctpDataChunk(true, true, true, 15007, 0, 0, 0, new byte[] { 0x33 }).View()); + receiver.OnDataChunk(new SctpDataChunk(true, true, true, 15008, 0, 0, 0, new byte[] { 0x33 }).View()); + receiver.OnDataChunk(new SctpDataChunk(true, true, true, 15010, 0, 0, 0, new byte[] { 0x33 }).View()); var gapReports = receiver.GetForwardTSNGaps(); @@ -451,13 +451,13 @@ public void GetTwoGapReports() public void GetThreeGapReports() { SctpDataReceiver receiver = new SctpDataReceiver(0, 0, 3); - receiver.OnDataChunk(new SctpDataChunk(true, true, true, 3, 0, 0, 0, new byte[] { 0x33 })); - receiver.OnDataChunk(new SctpDataChunk(true, true, true, 7, 0, 0, 0, new byte[] { 0x33 })); - receiver.OnDataChunk(new SctpDataChunk(true, true, true, 8, 0, 0, 0, new byte[] { 0x33 })); - receiver.OnDataChunk(new SctpDataChunk(true, true, true, 9, 0, 0, 0, new byte[] { 0x33 })); - receiver.OnDataChunk(new SctpDataChunk(true, true, true, 11, 0, 0, 0, new byte[] { 0x33 })); - receiver.OnDataChunk(new SctpDataChunk(true, true, true, 12, 0, 0, 0, new byte[] { 0x33 })); - receiver.OnDataChunk(new SctpDataChunk(true, true, true, 14, 0, 0, 0, new byte[] { 0x33 })); + receiver.OnDataChunk(new SctpDataChunk(true, true, true, 3, 0, 0, 0, new byte[] { 0x33 }).View()); + receiver.OnDataChunk(new SctpDataChunk(true, true, true, 7, 0, 0, 0, new byte[] { 0x33 }).View()); + receiver.OnDataChunk(new SctpDataChunk(true, true, true, 8, 0, 0, 0, new byte[] { 0x33 }).View()); + receiver.OnDataChunk(new SctpDataChunk(true, true, true, 9, 0, 0, 0, new byte[] { 0x33 }).View()); + receiver.OnDataChunk(new SctpDataChunk(true, true, true, 11, 0, 0, 0, new byte[] { 0x33 }).View()); + receiver.OnDataChunk(new SctpDataChunk(true, true, true, 12, 0, 0, 0, new byte[] { 0x33 }).View()); + receiver.OnDataChunk(new SctpDataChunk(true, true, true, 14, 0, 0, 0, new byte[] { 0x33 }).View()); var gapReports = receiver.GetForwardTSNGaps(); @@ -478,11 +478,11 @@ public void GeGapReportWithDuplicateForwardTSN() SctpDataReceiver receiver = new SctpDataReceiver(0, 0, initialTSN); // Forward TSN. - receiver.OnDataChunk(new SctpDataChunk(true, true, true, initialTSN + 1, 0, 0, 0, new byte[] { 0x33 })); + receiver.OnDataChunk(new SctpDataChunk(true, true, true, initialTSN + 1, 0, 0, 0, new byte[] { 0x33 }).View()); // Initial expected TSN. - receiver.OnDataChunk(new SctpDataChunk(true, true, true, initialTSN, 0, 0, 0, new byte[] { 0x33 })); + receiver.OnDataChunk(new SctpDataChunk(true, true, true, initialTSN, 0, 0, 0, new byte[] { 0x33 }).View()); // Duplicate of first received TSN. - receiver.OnDataChunk(new SctpDataChunk(true, true, true, initialTSN + 1, 0, 0, 0, new byte[] { 0x33 })); + receiver.OnDataChunk(new SctpDataChunk(true, true, true, initialTSN + 1, 0, 0, 0, new byte[] { 0x33 }).View()); var gapReports = receiver.GetForwardTSNGaps(); @@ -501,11 +501,11 @@ public void GetSackForSingleMissingChunk() SctpDataReceiver receiver = new SctpDataReceiver(arwnd, mtu, initialTSN); - receiver.OnDataChunk(new SctpDataChunk(true, true, true, initialTSN, 0, 0, 0, new byte[] { 0x44 })); + receiver.OnDataChunk(new SctpDataChunk(true, true, true, initialTSN, 0, 0, 0, new byte[] { 0x44 }).View()); Assert.Equal(initialTSN, receiver.CumulativeAckTSN); // Simulate a missing chunk by incrementing the TSN by 2. - receiver.OnDataChunk(new SctpDataChunk(true, true, true, initialTSN + 2, 0, 0, 0, new byte[] { 0x44 })); + receiver.OnDataChunk(new SctpDataChunk(true, true, true, initialTSN + 2, 0, 0, 0, new byte[] { 0x44 }).View()); Assert.Equal(initialTSN, receiver.CumulativeAckTSN); var sack = receiver.GetSackChunk(); @@ -527,7 +527,7 @@ public void GetSackForInitialChunkMissing() SctpDataReceiver receiver = new SctpDataReceiver(arwnd, mtu, initialTSN); - receiver.OnDataChunk(new SctpDataChunk(true, true, true, initialTSN + 1, 0, 0, 0, new byte[] { 0x44 })); + receiver.OnDataChunk(new SctpDataChunk(true, true, true, initialTSN + 1, 0, 0, 0, new byte[] { 0x44 }).View()); Assert.Null(receiver.CumulativeAckTSN); Assert.Null(receiver.GetSackChunk()); } @@ -545,12 +545,12 @@ public void InitialChunkOutOfOrder() SctpDataReceiver receiver = new SctpDataReceiver(arwnd, mtu, initialTSN); // Skip initial DATA chunk. - receiver.OnDataChunk(new SctpDataChunk(true, true, true, initialTSN + 1, 0, 0, 0, new byte[] { 0x44 })); + receiver.OnDataChunk(new SctpDataChunk(true, true, true, initialTSN + 1, 0, 0, 0, new byte[] { 0x44 }).View()); Assert.Null(receiver.CumulativeAckTSN); Assert.Null(receiver.GetSackChunk()); // Give the receiver the initial DATA chunk. - receiver.OnDataChunk(new SctpDataChunk(true, true, true, initialTSN, 0, 0, 0, new byte[] { 0x44 })); + receiver.OnDataChunk(new SctpDataChunk(true, true, true, initialTSN, 0, 0, 0, new byte[] { 0x44 }).View()); Assert.Equal(initialTSN + 1, receiver.CumulativeAckTSN); var sack = receiver.GetSackChunk(); @@ -572,16 +572,16 @@ public void InitialChunkTwoChunkDelay() SctpDataReceiver receiver = new SctpDataReceiver(arwnd, mtu, initialTSN); // Skip initial DATA chunk. - receiver.OnDataChunk(new SctpDataChunk(true, true, true, initialTSN + 1, 0, 0, 0, new byte[] { 0x44 })); + receiver.OnDataChunk(new SctpDataChunk(true, true, true, initialTSN + 1, 0, 0, 0, new byte[] { 0x44 }).View()); Assert.Null(receiver.CumulativeAckTSN); Assert.Null(receiver.GetSackChunk()); - receiver.OnDataChunk(new SctpDataChunk(true, true, true, initialTSN + 2, 0, 0, 0, new byte[] { 0x44 })); + receiver.OnDataChunk(new SctpDataChunk(true, true, true, initialTSN + 2, 0, 0, 0, new byte[] { 0x44 }).View()); Assert.Null(receiver.CumulativeAckTSN); Assert.Null(receiver.GetSackChunk()); // Give the receiver the initial DATA chunk. - receiver.OnDataChunk(new SctpDataChunk(true, true, true, initialTSN, 0, 0, 0, new byte[] { 0x44 })); + receiver.OnDataChunk(new SctpDataChunk(true, true, true, initialTSN, 0, 0, 0, new byte[] { 0x44 }).View()); Assert.Equal(initialTSN + 2, receiver.CumulativeAckTSN); var sack = receiver.GetSackChunk(); diff --git a/test/unit/net/SCTP/SctpDataSendRecvUnitTest.cs b/test/unit/net/SCTP/SctpDataSendRecvUnitTest.cs index 5f29a2dd1..c940e004d 100644 --- a/test/unit/net/SCTP/SctpDataSendRecvUnitTest.cs +++ b/test/unit/net/SCTP/SctpDataSendRecvUnitTest.cs @@ -52,8 +52,8 @@ public async Task SACKChunkRetransmit() // sender to the receiver of a remote peer and the return of the SACK. Action doSend = (chunk) => { - receiver.OnDataChunk(chunk); - sender.GotSack(receiver.GetSackChunk()); + receiver.OnDataChunk(chunk.View()); + sender.GotSack(receiver.GetSackChunk().View()); }; Action dontSend = (chunk) => { }; @@ -116,8 +116,8 @@ public async Task InitialDataChunkDropped() } else { - receiver.OnDataChunk(chunk); - sender.GotSack(receiver.GetSackChunk()); + receiver.OnDataChunk(chunk.View()); + sender.GotSack(receiver.GetSackChunk().View()); } }; sender._sendDataChunk = doSend; @@ -162,12 +162,12 @@ public void MediumBufferSend() Action doSend = (chunk) => { logger.LogDebug($"Data chunk {chunk.TSN} provided to receiver."); - var frames = receiver.OnDataChunk(chunk); - sender.GotSack(receiver.GetSackChunk()); + var frames = receiver.OnDataChunk(chunk.View()); + sender.GotSack(receiver.GetSackChunk().View()); if (frames.Count > 0) { - logger.LogDebug($"Receiver got frame of length {frames.First().UserData?.Length}."); + logger.LogDebug($"Receiver got frame of length {frames.First().UserData.Length}."); frame = frames.First(); frameReady.Set(); } @@ -210,12 +210,12 @@ public void MaxBufferSend() Action doSend = (chunk) => { logger.LogDebug($"Data chunk {chunk.TSN} provided to receiver."); - var frames = receiver.OnDataChunk(chunk); - sender.GotSack(receiver.GetSackChunk()); + var frames = receiver.OnDataChunk(chunk.View()); + sender.GotSack(receiver.GetSackChunk().View()); if (frames.Count > 0) { - logger.LogDebug($"Receiver got frame of length {frames.First().UserData?.Length}."); + logger.LogDebug($"Receiver got frame of length {frames.First().UserData.Length}."); frame = frames.First(); frameReady.Set(); } @@ -267,12 +267,12 @@ public void MediumBufferSendWithRandomDrops() else { logger.LogDebug($"Data chunk {chunk.TSN} provided to receiver."); - var frames = receiver.OnDataChunk(chunk); - sender.GotSack(receiver.GetSackChunk()); + var frames = receiver.OnDataChunk(chunk.View()); + sender.GotSack(receiver.GetSackChunk().View()); if (frames.Count > 0) { - logger.LogDebug($"Receiver got frame of length {frames.First().UserData?.Length}."); + logger.LogDebug($"Receiver got frame of length {frames.First().UserData.Length}."); frame = frames.First(); frameReady.Set(); } @@ -325,12 +325,12 @@ public async Task MaxBufferSendWithRandomDrops() else { logger.LogDebug($"Data chunk {chunk.TSN} provided to receiver."); - var frames = receiver.OnDataChunk(chunk); - sender.GotSack(receiver.GetSackChunk()); + var frames = receiver.OnDataChunk(chunk.View()); + sender.GotSack(receiver.GetSackChunk().View()); if (frames.Count > 0) { - logger.LogDebug($"Receiver got frame of length {frames.First().UserData?.Length}."); + logger.LogDebug($"Receiver got frame of length {frames.First().UserData.Length}."); frame = frames.First(); frameReady.Set(); } diff --git a/test/unit/net/SCTP/SctpDataSenderUnitTest.cs b/test/unit/net/SCTP/SctpDataSenderUnitTest.cs index 80eb79dc6..0dd7bb768 100755 --- a/test/unit/net/SCTP/SctpDataSenderUnitTest.cs +++ b/test/unit/net/SCTP/SctpDataSenderUnitTest.cs @@ -52,7 +52,7 @@ public async Task SmallBufferSend() Assert.Single(outStm); byte[] sendBuffer = outStm.Single(); - SctpPacket pkt = SctpPacket.Parse(sendBuffer, 0, sendBuffer.Length); + SctpPacket pkt = SctpPacket.Parse(sendBuffer); Assert.NotNull(pkt); Assert.NotNull(pkt.Chunks.Single() as SctpDataChunk); @@ -108,8 +108,8 @@ public async Task IncreaseCongestionWindowSlowStart() { if (chunk.TSN % 5 == 0) { - receiver.OnDataChunk(chunk); - sender.GotSack(receiver.GetSackChunk()); + receiver.OnDataChunk(chunk.View()); + sender.GotSack(receiver.GetSackChunk().View()); } }; diff --git a/test/unit/net/SCTP/SctpHeaderUnitTest.cs b/test/unit/net/SCTP/SctpHeaderUnitTest.cs index 7efbdf9c7..ffe6b5937 100644 --- a/test/unit/net/SCTP/SctpHeaderUnitTest.cs +++ b/test/unit/net/SCTP/SctpHeaderUnitTest.cs @@ -51,7 +51,7 @@ public void RoundtripSctpHeader() header.WriteToBuffer(buffer, 0); - var rndTripHeader = SctpHeader.Parse(buffer, 0); + var rndTripHeader = SctpHeader.Parse(buffer); Assert.Equal(srcPort, rndTripHeader.SourcePort); Assert.Equal(dstPort, rndTripHeader.DestinationPort); @@ -68,7 +68,7 @@ public void ParseUsrSctpInitHeader() byte[] buffer = { 0xdf, 0x90, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x6a, 0xb8, 0x0e, 0x99 }; - var sctpHdr = SctpHeader.Parse(buffer, 0); + var sctpHdr = SctpHeader.Parse(buffer); Assert.Equal(57232, sctpHdr.SourcePort); Assert.Equal(7, sctpHdr.DestinationPort); diff --git a/test/unit/net/SCTP/SctpPacketUnitTest.cs b/test/unit/net/SCTP/SctpPacketUnitTest.cs index 2391a679f..7c791a049 100644 --- a/test/unit/net/SCTP/SctpPacketUnitTest.cs +++ b/test/unit/net/SCTP/SctpPacketUnitTest.cs @@ -49,9 +49,9 @@ public void ParseUsrSctpInitPacket() 0x69, 0x81, 0x78, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x05, 0x00, 0x08, 0xc0, 0xa8, 0x0b, 0x32, 0x00, 0x05, 0x00, 0x08, 0xc0, 0xa8, 0x00, 0x32 }; - Assert.True(SctpPacket.IsValid(buffer, 0, buffer.Length, 0U)); + Assert.True(SctpPacket.IsValid(buffer, requiredTag: 0U)); - var sctpPkt = SctpPacket.Parse(buffer, 0, buffer.Length); + var sctpPkt = SctpPacket.Parse(buffer); Assert.NotNull(sctpPkt); Assert.Equal(57232, sctpPkt.Header.SourcePort); @@ -122,9 +122,9 @@ public void ParseUsrSctpInitAckPacket() 0x00, 0x05, 0x00, 0x08, 0xc0, 0xa8, 0x00, 0x32, 0xc5, 0x35, 0x15, 0xc8, 0x35, 0x57, 0x0a, 0xd5, 0x96, 0x29, 0xc8, 0xbf, 0x38, 0x7b, 0xc2, 0x16, 0xe9, 0x4c, 0x81, 0xbe }; - Assert.True(SctpPacket.IsValid(buffer, 0, buffer.Length, 0xe31c5536U)); + Assert.True(SctpPacket.IsValid(buffer, requiredTag: 0xe31c5536U)); - var sctpPkt = SctpPacket.Parse(buffer, 0, buffer.Length); + var sctpPkt = SctpPacket.Parse(buffer); Assert.NotNull(sctpPkt); Assert.Equal(7, sctpPkt.Header.SourcePort); @@ -155,10 +155,10 @@ public void ParseUsrSctpCookieEchoPacket() 0x4b, 0x41, 0x4d, 0x45, 0x2d, 0x42, 0x53, 0x44, 0x20, 0x31, 0x2e, 0x31, 0x00, 0x00, 0x00, 0x00 }; // Checksum does not match because the original cookie was too long and was truncated for testing purposes. - Assert.False(SctpPacket.VerifyChecksum(buffer, 0, buffer.Length)); - Assert.Equal(0xcd6e6150U, SctpPacket.GetVerificationTag(buffer, 0, buffer.Length)); + Assert.False(SctpPacket.VerifyChecksum(buffer)); + Assert.Equal(0xcd6e6150U, SctpPacket.GetVerificationTag(buffer)); - var sctpPkt = SctpPacket.Parse(buffer, 0, buffer.Length); + var sctpPkt = SctpPacket.Parse(buffer); Assert.NotNull(sctpPkt); Assert.Equal(57232, sctpPkt.Header.SourcePort); @@ -184,9 +184,9 @@ public void ParseUsrSctpCookieAckPacket() byte[] buffer = { 0x00, 0x07, 0xdf, 0x90, 0xe3, 0x1c, 0x55, 0x36, 0xb2, 0x04, 0xdf, 0x21, 0x0b, 0x00, 0x00, 0x04 }; - Assert.True(SctpPacket.IsValid(buffer, 0, buffer.Length, 0xe31c5536U)); + Assert.True(SctpPacket.IsValid(buffer, requiredTag: 0xe31c5536U)); - var sctpPkt = SctpPacket.Parse(buffer, 0, buffer.Length); + var sctpPkt = SctpPacket.Parse(buffer); Assert.NotNull(sctpPkt); Assert.Equal(7, sctpPkt.Header.SourcePort); @@ -220,18 +220,18 @@ public void RoundTripInitPacket() 0x69, 0x81, 0x78, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x05, 0x00, 0x08, 0xc0, 0xa8, 0x0b, 0x32, 0x00, 0x05, 0x00, 0x08, 0xc0, 0xa8, 0x00, 0x32 }; - Assert.True(SctpPacket.IsValid(buffer, 0, buffer.Length, 0x0U)); + Assert.True(SctpPacket.IsValid(buffer, requiredTag: 0x0U)); - var initPkt = SctpPacket.Parse(buffer, 0, buffer.Length); + var initPkt = SctpPacket.Parse(buffer); var rndTripBuffer = initPkt.GetBytes(); logger.LogDebug($"Before: {buffer.HexStr()}"); logger.LogDebug($"After : {rndTripBuffer.HexStr()}"); - Assert.True(SctpPacket.IsValid(rndTripBuffer, 0, rndTripBuffer.Length, 0x0U)); + Assert.True(SctpPacket.IsValid(rndTripBuffer, requiredTag: 0x0U)); - var sctpPkt = SctpPacket.Parse(rndTripBuffer, 0, rndTripBuffer.Length); + var sctpPkt = SctpPacket.Parse(rndTripBuffer); Assert.NotNull(sctpPkt); Assert.Equal(57232, sctpPkt.Header.SourcePort); @@ -264,9 +264,9 @@ public void ParseUsrSctpHeartbeatPacket() 0x00, 0x00, 0x00, 0x00, 0x02, 0x10, 0x00, 0x00, 0xc0, 0xa8, 0x00, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; - Assert.True(SctpPacket.IsValid(buffer, 0, buffer.Length, 0x054a3af0U)); + Assert.True(SctpPacket.IsValid(buffer, requiredTag: 0x054a3af0U)); - var sctpPkt = SctpPacket.Parse(buffer, 0, buffer.Length); + var sctpPkt = SctpPacket.Parse(buffer); Assert.NotNull(sctpPkt); Assert.Equal(7, sctpPkt.Header.SourcePort); @@ -293,18 +293,18 @@ public void RoundTripHeartbeatPacket() 0x00, 0x00, 0x00, 0x00, 0x02, 0x10, 0x00, 0x00, 0xc0, 0xa8, 0x00, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; - Assert.True(SctpPacket.IsValid(buffer, 0, buffer.Length, 0x054a3af0U)); + Assert.True(SctpPacket.IsValid(buffer, requiredTag: 0x054a3af0U)); - var heartbeatPkt = SctpPacket.Parse(buffer, 0, buffer.Length); + var heartbeatPkt = SctpPacket.Parse(buffer); var rndTripBuffer = heartbeatPkt.GetBytes(); logger.LogDebug($"Before: {buffer.HexStr()}"); logger.LogDebug($"After : {rndTripBuffer.HexStr()}"); - Assert.True(SctpPacket.IsValid(rndTripBuffer, 0, rndTripBuffer.Length, 0x054a3af0U)); + Assert.True(SctpPacket.IsValid(rndTripBuffer, requiredTag: 0x054a3af0U)); - var sctpPkt = SctpPacket.Parse(rndTripBuffer, 0, buffer.Length); + var sctpPkt = SctpPacket.Parse(rndTripBuffer); Assert.NotNull(sctpPkt); Assert.Equal(7, sctpPkt.Header.SourcePort); @@ -330,9 +330,9 @@ public void ParseUsrSctpDataPacket() 0x00, 0x07, 0x11, 0x5c, 0x2e, 0x0b, 0x82, 0xc7, 0x5d, 0x2c, 0xeb, 0xa7, 0x00, 0x07, 0x00, 0x13, 0xdf, 0x08, 0xc1, 0xb7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, 0x69, 0x0a, 0x00}; - Assert.True(SctpPacket.IsValid(buffer, 0, buffer.Length, 0x2e0b82c7U)); + Assert.True(SctpPacket.IsValid(buffer, requiredTag: 0x2e0b82c7U)); - var sctpPkt = SctpPacket.Parse(buffer, 0, buffer.Length); + var sctpPkt = SctpPacket.Parse(buffer); Assert.NotNull(sctpPkt); Assert.Equal(7, sctpPkt.Header.SourcePort); @@ -360,18 +360,18 @@ public void RoundTripDataPacket() 0x00, 0x07, 0x11, 0x5c, 0x2e, 0x0b, 0x82, 0xc7, 0x5d, 0x2c, 0xeb, 0xa7, 0x00, 0x07, 0x00, 0x13, 0xdf, 0x08, 0xc1, 0xb7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, 0x69, 0x0a, 0x00}; - Assert.True(SctpPacket.IsValid(buffer, 0, buffer.Length, 0x2e0b82c7U)); + Assert.True(SctpPacket.IsValid(buffer, requiredTag: 0x2e0b82c7U)); - var dataPkt = SctpPacket.Parse(buffer, 0, buffer.Length); + var dataPkt = SctpPacket.Parse(buffer); var rndTripBuffer = dataPkt.GetBytes(); logger.LogDebug($"Before: {buffer.HexStr()}"); logger.LogDebug($"After : {rndTripBuffer.HexStr()}"); - Assert.True(SctpPacket.IsValid(rndTripBuffer, 0, rndTripBuffer.Length, 0x2e0b82c7U)); + Assert.True(SctpPacket.IsValid(rndTripBuffer, requiredTag: 0x2e0b82c7U)); - var sctpPkt = SctpPacket.Parse(rndTripBuffer, 0, buffer.Length); + var sctpPkt = SctpPacket.Parse(rndTripBuffer); Assert.NotNull(sctpPkt); Assert.Equal(7, sctpPkt.Header.SourcePort); @@ -405,9 +405,9 @@ public void ParseUsrSctpAbortPacket() 0x65, 0x71, 0x75, 0x61, 0x6c, 0x20, 0x74, 0x68, 0x61, 0x6e, 0x20, 0x54, 0x53, 0x4e, 0x20, 0x63, 0x36, 0x65, 0x33, 0x61, 0x64, 0x33, 0x63, 0x00}; - Assert.True(SctpPacket.IsValid(buffer, 0, buffer.Length, 0x93c9d98aU)); + Assert.True(SctpPacket.IsValid(buffer, requiredTag: 0x93c9d98aU)); - var sctpPkt = SctpPacket.Parse(buffer, 0, buffer.Length); + var sctpPkt = SctpPacket.Parse(buffer); Assert.NotNull(sctpPkt); Assert.Equal(7, sctpPkt.Header.SourcePort); diff --git a/test/unit/net/SCTP/SctpTransportUnitTest.cs b/test/unit/net/SCTP/SctpTransportUnitTest.cs index ca1d3b3a5..3c2dfa2f5 100644 --- a/test/unit/net/SCTP/SctpTransportUnitTest.cs +++ b/test/unit/net/SCTP/SctpTransportUnitTest.cs @@ -13,6 +13,7 @@ // BSD 3-Clause "New" or "Revised" License, see included LICENSE.md file. //----------------------------------------------------------------------------- +using System; using System.Linq; using System.Text; using Microsoft.Extensions.Logging; @@ -89,7 +90,7 @@ public SctpPacket GetInitAck(SctpPacket initPacket) return base.GetCookieHMAC(buffer); } - public override void Send(string associationID, byte[] buffer, int offset, int length) + public override void Send(string associationID, ReadOnlySpan buffer) { } } } diff --git a/test/unit/sys/InterlockedExUnitTest.cs b/test/unit/sys/InterlockedExUnitTest.cs new file mode 100644 index 000000000..884223861 --- /dev/null +++ b/test/unit/sys/InterlockedExUnitTest.cs @@ -0,0 +1,47 @@ +//----------------------------------------------------------------------------- +// Filename: TypeExtensionsUnitTest.cs +// +// Description: Unit tests for methods in the TypeExtensions class. +// +// Author(s): +// Aaron Clauson (aaron@sipsorcery.com) +// +// History: +// ?? Aaron Clauson Created. +// +// License: +// BSD 3-Clause "New" or "Revised" License, see included LICENSE.md file. +//----------------------------------------------------------------------------- + +using System; +using System.Net; +using System.Text; +using Microsoft.Extensions.Logging; +using Xunit; + +namespace SIPSorcery.Sys.UnitTests +{ + [Trait("Category", "unit")] + public class InterlockedExUnitTest + { + private Microsoft.Extensions.Logging.ILogger logger = null; + + public InterlockedExUnitTest(Xunit.Abstractions.ITestOutputHelper output) + { + logger = SIPSorcery.UnitTests.TestLogHelper.InitTestLogger(output); + } + + [Fact] + public void CompareExchangeU32() + { + logger.LogDebug("--> " + System.Reflection.MethodBase.GetCurrentMethod().Name); + logger.BeginScope(System.Reflection.MethodBase.GetCurrentMethod().Name); + + uint value = 10; + uint was = InterlockedEx.CompareExchange(ref value, value: 20, comparand: 10); + + Assert.Equal(10u, was); + Assert.Equal(20u, value); + } + } +} From b5f91e5c8c2d5a6966e6b1c6a5ba44d79ccf66b6 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Wed, 20 Mar 2024 21:22:22 -0700 Subject: [PATCH 65/88] Interlocked _lastAckedDataChunkSize --- src/net/SCTP/SctpDataSender.cs | 4 ++-- src/sys/InterlockedEx.cs | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 src/sys/InterlockedEx.cs diff --git a/src/net/SCTP/SctpDataSender.cs b/src/net/SCTP/SctpDataSender.cs index a29181311..bacf291d5 100644 --- a/src/net/SCTP/SctpDataSender.cs +++ b/src/net/SCTP/SctpDataSender.cs @@ -224,7 +224,7 @@ public void GotSack(SctpChunkView sack) UpdateRoundTripTime(result); } - _lastAckedDataChunkSize = result.UserDataLength; + Interlocked.Exchange(ref _lastAckedDataChunkSize, result.UserDataLength); } if (!_gotFirstSACK) @@ -301,7 +301,7 @@ public void GotSack(SctpChunkView sack) var outstandingBytes = _outstandingBytes; _receiverWindow = CalculateReceiverWindow(sack.ARwnd, outstandingBytes: (uint)outstandingBytes); - _congestionWindow = CalculateCongestionWindow(_lastAckedDataChunkSize, outstandingBytes: (uint)outstandingBytes); + _congestionWindow = CalculateCongestionWindow(InterlockedEx.Read(ref _lastAckedDataChunkSize), outstandingBytes: (uint)outstandingBytes); // SACK's will normally allow more data to be sent. _senderMre.Set(); diff --git a/src/sys/InterlockedEx.cs b/src/sys/InterlockedEx.cs new file mode 100644 index 000000000..decfa5af1 --- /dev/null +++ b/src/sys/InterlockedEx.cs @@ -0,0 +1,20 @@ +using System.Threading; + +namespace SIPSorcery.Sys; + +static class InterlockedEx +{ + public static int Read(ref int location) => Interlocked.CompareExchange(ref location, 0, 0); + public unsafe static uint CompareExchange(ref uint location, uint value, uint comparand) +#if NET6_0_OR_GREATER + => Interlocked.CompareExchange(ref location, value: value, comparand: comparand); +#else + { + fixed (uint* ptr = &location) + { + return unchecked((uint)Interlocked.CompareExchange(ref *(int*)ptr, (int)value, (int)comparand)); + } + } +#endif + public unsafe static uint Read(ref uint location) => CompareExchange(ref location, 0, 0); +} From 8d3e0581e7c6de4bb1869742993db512d39ece30 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Wed, 20 Mar 2024 23:05:43 -0700 Subject: [PATCH 66/88] fixed DtlsSrtpTransport getting stuck in Close --- src/net/DtlsSrtp/DtlsSrtpTransport.cs | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/src/net/DtlsSrtp/DtlsSrtpTransport.cs b/src/net/DtlsSrtp/DtlsSrtpTransport.cs index cae8ac2c7..b4b514b40 100644 --- a/src/net/DtlsSrtp/DtlsSrtpTransport.cs +++ b/src/net/DtlsSrtp/DtlsSrtpTransport.cs @@ -75,7 +75,7 @@ public class DtlsSrtpTransport : DatagramTransport, IDisposable public event Action OnAlert; private System.DateTime _startTime = System.DateTime.MinValue; - private bool _isClosed = false; + private Once _isClosed; // Network properties private int _waitMillis = DEFAULT_RETRANSMISSION_WAIT_MILLIS; @@ -487,11 +487,18 @@ public int GetSendLimit() public void WriteToRecvStream(ReadOnlySpan buf) { - if (!_isClosed) + if (!_isClosed.HasOccurred) { var chunk = ArrayPool.Shared.Rent(buf.Length); buf.CopyTo(chunk); - _chunks.Add(new(chunk, 0, buf.Length)); + try + { + _chunks.Add(new(chunk, 0, buf.Length)); + } + catch (Exception) when (_isClosed.HasOccurred) + { + ArrayPool.Shared.Return(chunk); + } } } @@ -501,7 +508,7 @@ private int Read(byte[] buffer, int offset, int count, int timeout) { try { - if (_isClosed) + if (_isClosed.HasOccurred) { throw new System.Net.Sockets.SocketException((int)System.Net.Sockets.SocketError.NotConnected); //return DTLS_RECEIVE_ERROR_CODE; @@ -570,7 +577,7 @@ public int Receive(byte[] buf, int off, int len, int waitMillis) logger.LogWarning($"DTLS transport timed out after {TimeoutMilliseconds}ms waiting for handshake from remote {(connection.IsClient() ? "server" : "client")}."); throw new TimeoutException(); } - else if (!_isClosed) + else if (!_isClosed.HasOccurred) { waitMillis = Math.Min(waitMillis, millisecondsRemaining); var receiveLen = Read(buf, off, len, waitMillis); @@ -594,7 +601,7 @@ public int Receive(byte[] buf, int off, int len, int waitMillis) //return DTLS_RECEIVE_ERROR_CODE; } } - else if (!_isClosed) + else if (!_isClosed.HasOccurred) { return Read(buf, off, len, waitMillis); } @@ -627,13 +634,13 @@ public void Send(ReadOnlySpan buf) public virtual void Close() { - if (_isClosed) + if (!_isClosed.TryMarkOccurred()) { return; } - _isClosed = true; this._startTime = System.DateTime.MinValue; + _chunks.CompleteAdding(); foreach(var chunk in _chunks.GetConsumingEnumerable()) { ArrayPool.Shared.Return(chunk.Array); @@ -651,7 +658,7 @@ public virtual void Close() /// protected void Dispose(bool disposing) { - if (!_isClosed) + if (!_isClosed.HasOccurred) { Close(); } @@ -662,7 +669,7 @@ protected void Dispose(bool disposing) /// public void Dispose() { - if (!_isClosed) + if (!_isClosed.HasOccurred) { Close(); } From 81c761af2141942d2a1081d342e4f645ed44677c Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Wed, 20 Mar 2024 23:06:22 -0700 Subject: [PATCH 67/88] RTPSession atomic IsClosed and IsStarted --- src/net/RTP/RTPSession.cs | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/net/RTP/RTPSession.cs b/src/net/RTP/RTPSession.cs index 32c2716f1..c79927a02 100644 --- a/src/net/RTP/RTPSession.cs +++ b/src/net/RTP/RTPSession.cs @@ -335,17 +335,19 @@ public bool IsSecureContextReady() /// public int MaxReconstructedVideoFrameSize { get => VideoStream.MaxReconstructedVideoFrameSize; set => VideoStream.MaxReconstructedVideoFrameSize = value; } + Once isClosed; /// /// Indicates whether the session has been closed. Once a session is closed it cannot /// be restarted. /// - public bool IsClosed { get; private set; } + public bool IsClosed => isClosed.HasOccurred; + Once isStarted; /// /// Indicates whether the session has been started. Starting a session tells the RTP /// socket to start receiving, /// - public bool IsStarted { get; private set; } + public bool IsStarted => isStarted.HasOccurred; /// /// Indicates whether this session is using audio. @@ -1881,11 +1883,8 @@ protected List GetMediaStreams() /// public virtual Task Start() { - if (!IsStarted) + if (isStarted.TryMarkOccurred()) { - IsStarted = true; - - foreach (var audioStream in AudioStreamList) { if (audioStream.HasAudio && audioStream.RtcpSession != null && audioStream.LocalTrack.StreamStatus != MediaStreamStatusEnum.Inactive) @@ -1955,11 +1954,8 @@ public Task SendDtmfEvent(RTPEvent rtpEvent, CancellationToken cancellationToken /// public virtual void Close(string reason) { - if (!IsClosed) + if (isClosed.TryMarkOccurred()) { - IsClosed = true; - - foreach (var audioStream in AudioStreamList) { if (audioStream != null) From 0b987184d548be1f1710fc9571b2863d97d93018 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Wed, 20 Mar 2024 23:07:46 -0700 Subject: [PATCH 68/88] reenabled integration tests --- test/integration/SIPSorcery.IntegrationTests.csproj | 7 ++++--- test/integration/net/ICE/MockTurnServer.cs | 6 +++--- test/unit/Initialise.cs | 1 + test/unit/SIPSorcery.UnitTests.csproj | 7 ++++--- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/test/integration/SIPSorcery.IntegrationTests.csproj b/test/integration/SIPSorcery.IntegrationTests.csproj index f07661375..19bdb5210 100755 --- a/test/integration/SIPSorcery.IntegrationTests.csproj +++ b/test/integration/SIPSorcery.IntegrationTests.csproj @@ -19,9 +19,10 @@ - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/integration/net/ICE/MockTurnServer.cs b/test/integration/net/ICE/MockTurnServer.cs index f56486aa9..e0c796eec 100644 --- a/test/integration/net/ICE/MockTurnServer.cs +++ b/test/integration/net/ICE/MockTurnServer.cs @@ -60,7 +60,7 @@ public MockTurnServer(IPAddress listenAddress, int port) _listener.BeginReceiveFrom(); } - private void OnPacketReceived(UdpReceiver receiver, int localPort, IPEndPoint remoteEndPoint, byte[] packet) + private void OnPacketReceived(UdpReceiver receiver, int localPort, IPEndPoint remoteEndPoint, ReadOnlySpan packet) { STUNMessage stunMessage = STUNMessage.ParseSTUNMessage(packet, packet.Length); @@ -140,10 +140,10 @@ private void OnPacketReceived(UdpReceiver receiver, int localPort, IPEndPoint re /// The port number the packet was received on. /// The end point of the peer sending traffic to the TURN server. /// The byes received from the peer. - private void OnRelayPacketReceived(UdpReceiver receiver, int localPort, IPEndPoint remoteEndPoint, byte[] packet) + private void OnRelayPacketReceived(UdpReceiver receiver, int localPort, IPEndPoint remoteEndPoint, ReadOnlySpan packet) { STUNMessage dataInd = new STUNMessage(STUNMessageTypesEnum.DataIndication); - dataInd.Attributes.Add(new STUNAttribute(STUNAttributeTypesEnum.Data, packet)); + dataInd.Attributes.Add(new STUNAttribute(STUNAttributeTypesEnum.Data, packet.ToArray())); dataInd.AddXORPeerAddressAttribute(remoteEndPoint.Address, remoteEndPoint.Port); _clientSocket.SendTo(dataInd.ToByteBuffer(null, false), _clientEndPoint); diff --git a/test/unit/Initialise.cs b/test/unit/Initialise.cs index f303527a4..a904c9796 100644 --- a/test/unit/Initialise.cs +++ b/test/unit/Initialise.cs @@ -40,6 +40,7 @@ public static Microsoft.Extensions.Logging.ILogger InitTestLogger(Xunit.Abstract .MinimumLevel.Is(Serilog.Events.LogEventLevel.Verbose) .Enrich.WithProperty("ThreadId", System.Threading.Thread.CurrentThread.ManagedThreadId) .WriteTo.TestOutput(output, outputTemplate: template) + .WriteTo.Debug(outputTemplate: template) .WriteTo.Console(outputTemplate: template) .CreateLogger(); SIPSorcery.LogFactory.Set(new SerilogLoggerFactory(serilog)); diff --git a/test/unit/SIPSorcery.UnitTests.csproj b/test/unit/SIPSorcery.UnitTests.csproj index 4b40cfc2e..fa810bc73 100755 --- a/test/unit/SIPSorcery.UnitTests.csproj +++ b/test/unit/SIPSorcery.UnitTests.csproj @@ -19,9 +19,10 @@ - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive From a040a6bf2c72057e2f9b658baf30c14ef1ec9c7b Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Wed, 20 Mar 2024 23:10:59 -0700 Subject: [PATCH 69/88] v7.0.0-PacketView-24.3.20.6 --- src/SIPSorcery.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SIPSorcery.csproj b/src/SIPSorcery.csproj index debcbded3..902de8e83 100644 --- a/src/SIPSorcery.csproj +++ b/src/SIPSorcery.csproj @@ -62,7 +62,7 @@ -v6.0.2: Set .net6 targetted version as stable. -v6.0.1-pre: Added .net6 target. en - 7.0.0-PacketView-24.3.20.5 + 7.0.0-PacketView-24.3.20.6 7.0.0 7.0.0 From 43a9234f4d48384430f4b27691d03cf9ebfec96f Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Wed, 24 Apr 2024 19:06:27 -0700 Subject: [PATCH 70/88] workaround for Chrome sending SSRC=1 instead of real SSRC also added logging around such hacks --- src/net/RTP/RTPSession.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/net/RTP/RTPSession.cs b/src/net/RTP/RTPSession.cs index c79927a02..ff5581c93 100644 --- a/src/net/RTP/RTPSession.cs +++ b/src/net/RTP/RTPSession.cs @@ -2267,6 +2267,7 @@ private MediaStream GetMediaStream(uint ssrc) { if (!HasVideo) { + logger.LogDebug("An RTP packet with SSRC {ssrc} force matched to the only audio stream.", ssrc); return AudioStream; } } @@ -2274,10 +2275,17 @@ private MediaStream GetMediaStream(uint ssrc) { if (HasVideo) { + logger.LogDebug("An RTP packet with SSRC {ssrc} force matched to the only video stream.", ssrc); return VideoStream; } } + if (ssrc == 1) + { + logger.LogDebug("An RTP packet with SSRC {ssrc} force matched to the primary stream.", ssrc); + return PrimaryStream; + } + return null; } From ee04da5bc56b37e026667a2b26ce62028f17213d Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Wed, 24 Apr 2024 19:07:19 -0700 Subject: [PATCH 71/88] v7.0.0-PacketView-24.4.20.0 --- src/SIPSorcery.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SIPSorcery.csproj b/src/SIPSorcery.csproj index 902de8e83..a39693b6f 100644 --- a/src/SIPSorcery.csproj +++ b/src/SIPSorcery.csproj @@ -62,7 +62,7 @@ -v6.0.2: Set .net6 targetted version as stable. -v6.0.1-pre: Added .net6 target. en - 7.0.0-PacketView-24.3.20.6 + 7.0.0-PacketView-24.4.24.0 7.0.0 7.0.0 From 1b2ed30b7ecf70f6868ec649e0a0c267923e2dd5 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Fri, 3 May 2024 11:10:54 -0700 Subject: [PATCH 72/88] better info on EndSendTo failures; stop spamming exception traces --- src/net/RTP/RTPChannel.cs | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/net/RTP/RTPChannel.cs b/src/net/RTP/RTPChannel.cs index f53ed0c8d..6d7b5bdf5 100644 --- a/src/net/RTP/RTPChannel.cs +++ b/src/net/RTP/RTPChannel.cs @@ -625,7 +625,7 @@ public virtual SocketError Send(RTPChannelSocketsEnum sendOn, IPEndPoint dstEndP } catch (Exception excp) { - EndSendTo(excp); + EndSendTo(excp, dstEndPoint, sendSocket); } finally { @@ -639,7 +639,7 @@ public virtual SocketError Send(RTPChannelSocketsEnum sendOn, IPEndPoint dstEndP ArrayPool.Shared.Return(tmp); if (t.IsFaulted) { - EndSendTo(t.Exception); + EndSendTo(t.Exception, dstEndPoint, sendSocket); } }); } @@ -672,19 +672,25 @@ public virtual SocketError Send(RTPChannelSocketsEnum sendOn, IPEndPoint dstEndP /// The async result to complete the send with. private void EndSendTo(IAsyncResult ar) { + Socket sendSocket = (Socket)ar.AsyncState; try { - Socket sendSocket = (Socket)ar.AsyncState; int bytesSent = sendSocket.EndSendTo(ar); } catch (Exception excp) { - EndSendTo(excp); + EndSendTo(excp, socket: sendSocket); } } - private void EndSendTo(Exception exception) + private void EndSendTo(Exception exception, IPEndPoint? endPoint = null, Socket? socket = null) { + string kind = socket switch + { + null => "Unknown", + { } m when m == m_controlSocket => "Control", + _ => "RTP", + }; switch (exception) { case SocketException sockExcp: @@ -693,7 +699,7 @@ private void EndSendTo(Exception exception) // - the RTP connection may start sending before the remote socket starts listening, // - an on hold, transfer, etc. operation can change the RTP end point which could result in socket errors from the old // or new socket during the transition. - logger.LogWarning(sockExcp, "SocketException RTPChannel EndSendTo ({ErrorCode}). {Message}", sockExcp.ErrorCode, sockExcp.Message); + logger.LogWarning("SocketException RTPChannel EndSendTo {Kind} {EndPoint} ({ErrorCode}). {Message}", kind, endPoint, sockExcp.ErrorCode, sockExcp.Message); break; case ObjectDisposedException: // Thrown when socket is closed. Can be safely ignored. @@ -702,12 +708,12 @@ private void EndSendTo(Exception exception) case AggregateException aggExcp: foreach (var innerExcp in aggExcp.InnerExceptions) { - EndSendTo(innerExcp); + EndSendTo(innerExcp, endPoint, socket); } break; default: - logger.LogError("Exception RTPChannel EndSendTo. {Message}", exception.Message); + logger.LogError("Exception RTPChannel EndSendTo. {Kind} {EndPoint} {Message}", kind, endPoint, exception.Message); break; } } From 1e122717bf502250dbdffde07075435c9d7b8399 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Fri, 3 May 2024 11:11:34 -0700 Subject: [PATCH 73/88] v7.0.0-PacketView-24.5.3.0 --- src/SIPSorcery.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SIPSorcery.csproj b/src/SIPSorcery.csproj index a39693b6f..555a94faf 100644 --- a/src/SIPSorcery.csproj +++ b/src/SIPSorcery.csproj @@ -62,7 +62,7 @@ -v6.0.2: Set .net6 targetted version as stable. -v6.0.1-pre: Added .net6 target. en - 7.0.0-PacketView-24.4.24.0 + 7.0.0-PacketView-24.5.3.0 7.0.0 7.0.0 From b24193d0313a61fdf5c4e3409355229ba115f31a Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Sat, 4 May 2024 19:50:28 -0700 Subject: [PATCH 74/88] handle unmatched RTCP_RR_NOSTREAM_SSRC 0xFA17FA17 --- src/net/RTP/MediaStream.cs | 2 ++ src/net/RTP/RTPSession.cs | 9 ++------- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/net/RTP/MediaStream.cs b/src/net/RTP/MediaStream.cs index c0f2052cb..5c0f2c1b7 100644 --- a/src/net/RTP/MediaStream.cs +++ b/src/net/RTP/MediaStream.cs @@ -857,6 +857,8 @@ public void ProcessHeaderExtensions(RTPHeader header) }); } + public override string ToString() => $"{MediaType}[{Index}]"; + public MediaStream(RtpSessionConfig config, int index) { RtpSessionConfig = config; diff --git a/src/net/RTP/RTPSession.cs b/src/net/RTP/RTPSession.cs index ff5581c93..8d0e67a13 100644 --- a/src/net/RTP/RTPSession.cs +++ b/src/net/RTP/RTPSession.cs @@ -2072,7 +2072,6 @@ private void OnReceiveRTCPPacket(int localPort, IPEndPoint remoteEndPoint, ReadO } var rtcpPkt = new RTCPCompoundPacket(buffer); - if (rtcpPkt != null) { mediaStream = GetMediaStream(rtcpPkt); if (rtcpPkt.Bye != null) @@ -2130,10 +2129,6 @@ private void OnReceiveRTCPPacket(int localPort, IPEndPoint remoteEndPoint, ReadO } } } - else - { - logger.LogWarning("Failed to parse RTCP compound report."); - } #endregion } @@ -2280,9 +2275,9 @@ private MediaStream GetMediaStream(uint ssrc) } } - if (ssrc == 1) + if (ssrc == 1 || ssrc == RTCP_RR_NOSTREAM_SSRC) { - logger.LogDebug("An RTP packet with SSRC {ssrc} force matched to the primary stream.", ssrc); + logger.LogDebug("An RTP packet with SSRC {ssrc} force matched to the primary stream {Stream}.", ssrc, PrimaryStream); return PrimaryStream; } From e394f0ef39b00db58f4b38e016342db36727f5d3 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Sat, 4 May 2024 19:52:12 -0700 Subject: [PATCH 75/88] v7.0.0-PacketView-24.5.4.0 --- src/SIPSorcery.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SIPSorcery.csproj b/src/SIPSorcery.csproj index 555a94faf..e6f9add22 100644 --- a/src/SIPSorcery.csproj +++ b/src/SIPSorcery.csproj @@ -62,7 +62,7 @@ -v6.0.2: Set .net6 targetted version as stable. -v6.0.1-pre: Added .net6 target. en - 7.0.0-PacketView-24.5.3.0 + 7.0.0-PacketView-24.5.4.0 7.0.0 7.0.0 From e3e80d1c2e2a153e36c06fff63edf58fc5e4d0dc Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Tue, 28 May 2024 15:09:24 -0700 Subject: [PATCH 76/88] updated BouncyCastle.Cryptography due to a security vulnerability --- src/SIPSorcery.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SIPSorcery.csproj b/src/SIPSorcery.csproj index e6f9add22..03f998d38 100644 --- a/src/SIPSorcery.csproj +++ b/src/SIPSorcery.csproj @@ -17,7 +17,7 @@ - + From cf38d0d47e71e8823be159ab6bacf59981e85a61 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Tue, 28 May 2024 15:10:42 -0700 Subject: [PATCH 77/88] createDataChannel supports explicit CancellationToken --- src/net/WebRTC/RTCPeerConnection.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/net/WebRTC/RTCPeerConnection.cs b/src/net/WebRTC/RTCPeerConnection.cs index 28f14dd18..bf74883be 100644 --- a/src/net/WebRTC/RTCPeerConnection.cs +++ b/src/net/WebRTC/RTCPeerConnection.cs @@ -1488,7 +1488,7 @@ private void OnSctpAssociationDataChunk(SctpDataFrame frame) /// When a data channel is requested an SCTP association is needed. This method attempts to /// initialise the association if it is not already available. /// - private async Task InitialiseSctpAssociation() + private async Task InitialiseSctpAssociation(CancellationToken cancel = default) { if (sctp.RTCSctpAssociation.State != SctpAssociationState.Established) { @@ -1510,7 +1510,7 @@ private async Task InitialiseSctpAssociation() DateTime startTime = DateTime.Now; - var completedTask = await Task.WhenAny(onSctpConnectedTcs.Task, Task.Delay(SCTP_ASSOCIATE_TIMEOUT_SECONDS * 1000)).ConfigureAwait(false); + var completedTask = await Task.WhenAny(onSctpConnectedTcs.Task, Task.Delay(SCTP_ASSOCIATE_TIMEOUT_SECONDS * 1000, cancel)).ConfigureAwait(false); if (sctp.state != RTCSctpTransportState.Connected) { @@ -1537,7 +1537,7 @@ private async Task InitialiseSctpAssociation() /// /// The label used to identify the data channel. /// The data channel created. - public async Task createDataChannel(string label, RTCDataChannelInit init = null) + public async Task createDataChannel(string label, RTCDataChannelInit init = null, CancellationToken cancel = default) { logger.LogDebug($"Data channel create request for label {label}."); @@ -1561,7 +1561,7 @@ public async Task createDataChannel(string label, RTCDataChannel if (sctp.RTCSctpAssociation == null || sctp.RTCSctpAssociation.State != SctpAssociationState.Established) { - await InitialiseSctpAssociation().ConfigureAwait(false); + await InitialiseSctpAssociation(cancel).ConfigureAwait(false); } dataChannels.AddActiveChannel(channel); @@ -1571,6 +1571,7 @@ public async Task createDataChannel(string label, RTCDataChannel TaskCompletionSource isopen = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); channel.onopen += () => isopen.TrySetResult(string.Empty); channel.onerror += (err) => isopen.TrySetResult(err); + using var _ = cancel.Register(() => isopen.TrySetResult("cancelled")); var error = await isopen.Task.ConfigureAwait(false); if (error != string.Empty) From f3d43891725e8e991c393957d614fa42e87e580c Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Tue, 28 May 2024 15:16:11 -0700 Subject: [PATCH 78/88] don't balk on error chunks just because we don't know how to validate them efficiently --- src/net/SCTP/SctpChunkView.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/net/SCTP/SctpChunkView.cs b/src/net/SCTP/SctpChunkView.cs index 7eb227b98..ba296bf3a 100644 --- a/src/net/SCTP/SctpChunkView.cs +++ b/src/net/SCTP/SctpChunkView.cs @@ -202,7 +202,8 @@ bool ValidateData() bool ValidateError(bool isAbort) { ValidateBase(); - throw new NotImplementedException(); + SctpErrorChunk.ParseChunk(buffer, 0, isAbort: isAbort); + return true; } bool ValidateUnknownBase() From 833ba4c7301dd6286f66720cfabadb0a3d455c44 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Tue, 28 May 2024 15:17:33 -0700 Subject: [PATCH 79/88] v7.0.0-PacketView-24.5.28.0 --- src/SIPSorcery.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SIPSorcery.csproj b/src/SIPSorcery.csproj index 03f998d38..1e56f8816 100644 --- a/src/SIPSorcery.csproj +++ b/src/SIPSorcery.csproj @@ -62,7 +62,7 @@ -v6.0.2: Set .net6 targetted version as stable. -v6.0.1-pre: Added .net6 target. en - 7.0.0-PacketView-24.5.4.0 + 7.0.0-PacketView-24.5.28.0 7.0.0 7.0.0 From a94a8b1b0289512568f582f27724bfe3212f5b77 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Wed, 29 May 2024 11:41:55 -0700 Subject: [PATCH 80/88] breaking: RTCDataChannel.send fails with InvalidOperationException when underlying transport is not connected --- src/net/WebRTC/RTCDataChannel.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/net/WebRTC/RTCDataChannel.cs b/src/net/WebRTC/RTCDataChannel.cs index dd811126b..2d5c17396 100644 --- a/src/net/WebRTC/RTCDataChannel.cs +++ b/src/net/WebRTC/RTCDataChannel.cs @@ -130,6 +130,7 @@ public void close() /// Sends a string data payload on the data channel. /// /// The string message to send. + /// SCTP transport is not connected. public void send(string message) { if (message != null && Encoding.UTF8.GetByteCount(message) > _transport.maxMessageSize) @@ -139,7 +140,7 @@ public void send(string message) } else if (_transport.state != RTCSctpTransportState.Connected) { - logger.LogWarning($"WebRTC data channel send failed due to SCTP transport in state {_transport.state}."); + throw new InvalidOperationException("SCTP transport is not connected."); } else { @@ -165,6 +166,7 @@ public void send(string message) /// Sends a binary data payload on the data channel. /// /// The data to send. + /// SCTP transport is not connected. public void send(ReadOnlySpan data) { if (data.Length > _transport.maxMessageSize) @@ -174,7 +176,7 @@ public void send(ReadOnlySpan data) } else if (_transport.state != RTCSctpTransportState.Connected) { - logger.LogWarning($"WebRTC data channel send failed due to SCTP transport in state {_transport.state}."); + throw new InvalidOperationException("SCTP transport is not connected."); } else { From 4157fe416373c56c16934243de60f68a283d14f1 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Wed, 29 May 2024 11:57:06 -0700 Subject: [PATCH 81/88] v7.0.0-PacketView-24.5.29.0 --- src/SIPSorcery.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SIPSorcery.csproj b/src/SIPSorcery.csproj index 1e56f8816..eb26cfd45 100644 --- a/src/SIPSorcery.csproj +++ b/src/SIPSorcery.csproj @@ -62,7 +62,7 @@ -v6.0.2: Set .net6 targetted version as stable. -v6.0.1-pre: Added .net6 target. en - 7.0.0-PacketView-24.5.28.0 + 7.0.0-PacketView-24.5.29.0 7.0.0 7.0.0 From 86898a912a6535c1ec715b0cc8939466e05fd3fb Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Thu, 30 May 2024 23:26:17 -0700 Subject: [PATCH 82/88] reduce verbosity of initial binding failures during candidate probing --- src/net/ICE/RtpIceChannel.cs | 33 ++++++++++++++++++++++----------- src/net/RTP/RTPChannel.cs | 15 ++++++++++----- 2 files changed, 32 insertions(+), 16 deletions(-) diff --git a/src/net/ICE/RtpIceChannel.cs b/src/net/ICE/RtpIceChannel.cs index bd63ded4f..b65d0d962 100755 --- a/src/net/ICE/RtpIceChannel.cs +++ b/src/net/ICE/RtpIceChannel.cs @@ -1827,12 +1827,12 @@ private void SendSTUNBindingRequest(ChecklistEntry candidatePair, bool setUseCan { IPEndPoint relayServerEP = candidatePair.LocalCandidate.IceServer.ServerEndPoint; var protocol = candidatePair.LocalCandidate.IceServer.Protocol; - SendRelay(protocol, candidatePair.RemoteCandidate.DestinationEndPoint, stunReqBytes, relayServerEP, candidatePair.LocalCandidate.IceServer); + SendRelay(protocol, candidatePair.RemoteCandidate.DestinationEndPoint, stunReqBytes, relayServerEP, candidatePair.LocalCandidate.IceServer, OnBindingFailure); } else { IPEndPoint remoteEndPoint = candidatePair.RemoteCandidate.DestinationEndPoint; - var sendResult = base.Send(RTPChannelSocketsEnum.RTP, remoteEndPoint, stunReqBytes); + var sendResult = base.Send(RTPChannelSocketsEnum.RTP, remoteEndPoint, stunReqBytes, OnBindingFailure); if (sendResult != SocketError.Success) { @@ -1845,6 +1845,17 @@ private void SendSTUNBindingRequest(ChecklistEntry candidatePair, bool setUseCan } } + bool OnBindingFailure(Exception exception) + { + if (exception is SocketException socketException) + { + logger.LogDebug("Socket exception binding RTP channel: {Code} {Message}.", socketException.ErrorCode, socketException.Message); + return true; + } + + return false; + } + /// /// Builds and sends the connectivity check on a candidate pair that is set /// as the current nominated, connected pair. @@ -2204,7 +2215,7 @@ private void GotStunBindingRequest(STUNMessage bindingRequest, IPEndPoint remote if (wasRelayed) { var protocol = matchingChecklistEntry.LocalCandidate.IceServer.Protocol; - SendRelay(protocol, remoteEndPoint, stunRespBytes, matchingChecklistEntry.LocalCandidate.IceServer.ServerEndPoint, matchingChecklistEntry.LocalCandidate.IceServer); + SendRelay(protocol, remoteEndPoint, stunRespBytes, matchingChecklistEntry.LocalCandidate.IceServer.ServerEndPoint, matchingChecklistEntry.LocalCandidate.IceServer, onFailure: null); OnStunMessageSent?.Invoke(stunResponse, remoteEndPoint, true); } else @@ -2294,7 +2305,7 @@ private SocketError SendStunBindingRequest(IceServer iceServer) var sendResult = iceServer.Protocol == ProtocolType.Tcp ? SendOverTCP(iceServer, stunReqBytes) : - base.Send(RTPChannelSocketsEnum.RTP, iceServer.ServerEndPoint, stunReqBytes); + base.Send(RTPChannelSocketsEnum.RTP, iceServer.ServerEndPoint, stunReqBytes, OnBindingFailure); if (sendResult != SocketError.Success) { @@ -2340,7 +2351,7 @@ private SocketError SendTurnAllocateRequest(IceServer iceServer) var sendResult = iceServer.Protocol == ProtocolType.Tcp ? SendOverTCP(iceServer, allocateReqBytes) : - base.Send(RTPChannelSocketsEnum.RTP, iceServer.ServerEndPoint, allocateReqBytes); + base.Send(RTPChannelSocketsEnum.RTP, iceServer.ServerEndPoint, allocateReqBytes, OnBindingFailure); if (sendResult != SocketError.Success) { @@ -2617,7 +2628,7 @@ protected override void OnRTPPacketReceived(UdpReceiver receiver, int localPort, /// The data to send to the peer. /// The TURN server end point to send the relayed request to. /// - private SocketError SendRelay(ProtocolType protocol, IPEndPoint dstEndPoint, ReadOnlySpan buffer, IPEndPoint relayEndPoint, IceServer iceServer) + private SocketError SendRelay(ProtocolType protocol, IPEndPoint dstEndPoint, ReadOnlySpan buffer, IPEndPoint relayEndPoint, IceServer iceServer, Func? onFailure) { STUNMessage sendReq = new STUNMessage(STUNMessageTypesEnum.SendIndication); sendReq.AddXORPeerAddressAttribute(dstEndPoint.Address, dstEndPoint.Port); @@ -2626,7 +2637,7 @@ private SocketError SendRelay(ProtocolType protocol, IPEndPoint dstEndPoint, Rea var request = sendReq.ToByteBuffer(null, false); var sendResult = protocol == ProtocolType.Tcp ? SendOverTCP(iceServer, request) : - base.Send(RTPChannelSocketsEnum.RTP, relayEndPoint, request); + base.Send(RTPChannelSocketsEnum.RTP, relayEndPoint, request, onFailure); if (sendResult != SocketError.Success) { @@ -2694,7 +2705,7 @@ private async Task ResolveMdnsName(RTCIceCandidate candidate) /// The data to send. /// The result of initiating the send. This result does not reflect anything about /// whether the remote party received the packet or not. - public override SocketError Send(RTPChannelSocketsEnum sendOn, IPEndPoint dstEndPoint, ReadOnlySpan buffer) + public override SocketError Send(RTPChannelSocketsEnum sendOn, IPEndPoint dstEndPoint, ReadOnlySpan buffer, Func? onFailure = null) { if (NominatedEntry != null && NominatedEntry.LocalCandidate.type == RTCIceCandidateType.relay && NominatedEntry.LocalCandidate.IceServer != null && @@ -2704,12 +2715,12 @@ public override SocketError Send(RTPChannelSocketsEnum sendOn, IPEndPoint dstEnd // A TURN relay channel is being used to communicate with the remote peer. var protocol = NominatedEntry.LocalCandidate.IceServer.Protocol; var serverEndPoint = NominatedEntry.LocalCandidate.IceServer.ServerEndPoint; - return SendRelay(protocol, dstEndPoint, buffer, serverEndPoint, NominatedEntry.LocalCandidate.IceServer); + return SendRelay(protocol, dstEndPoint, buffer, serverEndPoint, NominatedEntry.LocalCandidate.IceServer, onFailure); } else { - return base.Send(sendOn, dstEndPoint, buffer); + return base.Send(sendOn, dstEndPoint, buffer, onFailure); } } } -} \ No newline at end of file +} diff --git a/src/net/RTP/RTPChannel.cs b/src/net/RTP/RTPChannel.cs index 6d7b5bdf5..18c0f631b 100644 --- a/src/net/RTP/RTPChannel.cs +++ b/src/net/RTP/RTPChannel.cs @@ -549,9 +549,10 @@ public void Close(string reason) /// The socket to send on. Can be the RTP or Control socket. /// The destination end point to send to. /// The data to send. + /// If supplied and given an exception returns true, disables further error processing. /// The result of initiating the send. This result does not reflect anything about /// whether the remote party received the packet or not. - public virtual SocketError Send(RTPChannelSocketsEnum sendOn, IPEndPoint dstEndPoint, ReadOnlySpan buffer) + public virtual SocketError Send(RTPChannelSocketsEnum sendOn, IPEndPoint dstEndPoint, ReadOnlySpan buffer, Func? onFailure = null) { if (m_isClosed) { @@ -625,7 +626,7 @@ public virtual SocketError Send(RTPChannelSocketsEnum sendOn, IPEndPoint dstEndP } catch (Exception excp) { - EndSendTo(excp, dstEndPoint, sendSocket); + EndSendTo(excp, dstEndPoint, sendSocket, onFailure); } finally { @@ -639,7 +640,7 @@ public virtual SocketError Send(RTPChannelSocketsEnum sendOn, IPEndPoint dstEndP ArrayPool.Shared.Return(tmp); if (t.IsFaulted) { - EndSendTo(t.Exception, dstEndPoint, sendSocket); + EndSendTo(t.Exception, dstEndPoint, sendSocket, onFailure); } }); } @@ -683,7 +684,7 @@ private void EndSendTo(IAsyncResult ar) } } - private void EndSendTo(Exception exception, IPEndPoint? endPoint = null, Socket? socket = null) + private void EndSendTo(Exception exception, IPEndPoint? endPoint = null, Socket? socket = null, Func? onFailure = null) { string kind = socket switch { @@ -691,6 +692,10 @@ private void EndSendTo(Exception exception, IPEndPoint? endPoint = null, Socket? { } m when m == m_controlSocket => "Control", _ => "RTP", }; + if (onFailure?.Invoke(exception) == true) + { + return; + } switch (exception) { case SocketException sockExcp: @@ -708,7 +713,7 @@ private void EndSendTo(Exception exception, IPEndPoint? endPoint = null, Socket? case AggregateException aggExcp: foreach (var innerExcp in aggExcp.InnerExceptions) { - EndSendTo(innerExcp, endPoint, socket); + EndSendTo(innerExcp, endPoint, socket, onFailure); } break; From 9f00edac34c98a72df0c5756f6123281545934ac Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Thu, 30 May 2024 23:26:32 -0700 Subject: [PATCH 83/88] minor optimization --- src/net/RTP/MediaStream.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/net/RTP/MediaStream.cs b/src/net/RTP/MediaStream.cs index 5c0f2c1b7..1f48536d9 100644 --- a/src/net/RTP/MediaStream.cs +++ b/src/net/RTP/MediaStream.cs @@ -386,7 +386,7 @@ protected void SendRtpRaw(byte[] data, uint timestamp, int markerBit, int payloa } else { - rtpChannel.Send(RTPChannelSocketsEnum.RTP, DestinationEndPoint, rtpBuffer.Take(outBufLen).ToArray()); + rtpChannel.Send(RTPChannelSocketsEnum.RTP, DestinationEndPoint, rtpBuffer.AsSpan(0, outBufLen)); } } m_lastRtpTimestamp = timestamp; @@ -484,7 +484,7 @@ private bool SendRtcpReport(byte[] reportBuffer) } else { - rtpChannel.Send(sendOnSocket, ControlDestinationEndPoint, sendBuffer.Take(outBufLen).ToArray()); + rtpChannel.Send(sendOnSocket, ControlDestinationEndPoint, sendBuffer.AsSpan(0, outBufLen)); } } } From aeb954633640c31d1e371f42dd99c7beca907ca3 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Thu, 30 May 2024 23:27:27 -0700 Subject: [PATCH 84/88] v7.0.0-PacketView-24.5.30.0 --- src/SIPSorcery.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SIPSorcery.csproj b/src/SIPSorcery.csproj index eb26cfd45..c2b1513e2 100644 --- a/src/SIPSorcery.csproj +++ b/src/SIPSorcery.csproj @@ -62,7 +62,7 @@ -v6.0.2: Set .net6 targetted version as stable. -v6.0.1-pre: Added .net6 target. en - 7.0.0-PacketView-24.5.29.0 + 7.0.0-PacketView-24.5.30.0 7.0.0 7.0.0 From a4814c0b809b49bb9628edb281255c1eed5db2ec Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Sun, 2 Jun 2024 12:46:37 -0700 Subject: [PATCH 85/88] reduce verbosity of the "old data chunk" message --- src/net/SCTP/SctpDataReceiver.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/net/SCTP/SctpDataReceiver.cs b/src/net/SCTP/SctpDataReceiver.cs index 09dbdc032..986aaf369 100644 --- a/src/net/SCTP/SctpDataReceiver.cs +++ b/src/net/SCTP/SctpDataReceiver.cs @@ -241,7 +241,7 @@ public List OnDataChunk(SctpChunkView dataChunk) else if (_inOrderReceiveCount > 0 && !IsNewer(_lastInOrderTSN, dataChunk.TSN)) { - logger.LogWarning("SCTP data receiver received an old data chunk with {TSN} " + + logger.LogDebug("SCTP received an old data chunk with {TSN} " + "TSN when the expected TSN was {LastInOrderTSN}, ignoring.", dataChunk.TSN, _lastInOrderTSN + 1); } From 68c534b6e9c74c01c9bdb803b68beb45da8a524e Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Mon, 3 Jun 2024 10:06:05 -0700 Subject: [PATCH 86/88] reduced logging level of user initiated SCTP abort --- src/net/SCTP/SctpAssociation.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/net/SCTP/SctpAssociation.cs b/src/net/SCTP/SctpAssociation.cs index 4801c128d..6b396b1b6 100644 --- a/src/net/SCTP/SctpAssociation.cs +++ b/src/net/SCTP/SctpAssociation.cs @@ -399,6 +399,8 @@ internal void OnPacketReceived(SctpPacketView packet) case SctpChunkType.ABORT: var abortChunk = (SctpAbortChunk)chunk.AsChunk(); string abortReason = abortChunk.GetAbortReason(); + var logLevel = abortChunk.ErrorCauses.Any(cause => cause.CauseCode == SctpErrorCauseCode.UserInitiatedAbort) + ? LogLevel.Debug : LogLevel.Warning; logger.LogWarning("SCTP packet ABORT chunk received from remote party, reason {Message}.", abortReason); _wasAborted = true; OnAbortReceived?.Invoke(abortReason); From 569b81c0dff0aed0f2ce5b3d512bd4a78f223980 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Wed, 21 Aug 2024 17:04:49 -0700 Subject: [PATCH 87/88] don't report timeout on a closed media stream --- src/net/RTP/MediaStream.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/net/RTP/MediaStream.cs b/src/net/RTP/MediaStream.cs index 1f48536d9..22aee83c2 100644 --- a/src/net/RTP/MediaStream.cs +++ b/src/net/RTP/MediaStream.cs @@ -126,6 +126,11 @@ public bool IsClosed } _isClosed = value; + if (value) + { + RtcpSession.OnTimeout -= RaiseOnTimeoutByIndex; + } + //Clear previous buffer ClearPendingPackages(); From 9b9b8870e7e1eae2fa14a4b4efca390db3386f58 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Wed, 21 Aug 2024 17:10:08 -0700 Subject: [PATCH 88/88] allow modifying timeout on RTCPSession --- src/net/RTCP/RTCPSession.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/net/RTCP/RTCPSession.cs b/src/net/RTCP/RTCPSession.cs index 1bf465ce6..4b9efc983 100644 --- a/src/net/RTCP/RTCPSession.cs +++ b/src/net/RTCP/RTCPSession.cs @@ -91,6 +91,11 @@ public class RTCPSession /// public DateTime LastActivityAt { get; private set; } = DateTime.MinValue; + /// + /// Time to wait before classifying the session as timed out due to inactivity. + /// + public TimeSpan NoActivityTimeout { get; private set; } = new(ticks: NO_ACTIVITY_TIMEOUT_MILLISECONDS * TimeSpan.TicksPerMillisecond); + /// /// Indicates whether the session is currently in a timed out state. This /// occurs if no RTP or RTCP packets have been received during an expected @@ -313,8 +318,8 @@ private void SendReportTimerCallback(Object stateInfo) { lock (m_rtcpReportTimer) { - if ((LastActivityAt != DateTime.MinValue && DateTime.Now.Subtract(LastActivityAt).TotalMilliseconds > NO_ACTIVITY_TIMEOUT_MILLISECONDS) || - (LastActivityAt == DateTime.MinValue && DateTime.Now.Subtract(CreatedAt).TotalMilliseconds > NO_ACTIVITY_TIMEOUT_MILLISECONDS)) + if ((LastActivityAt != DateTime.MinValue && DateTime.Now.Subtract(LastActivityAt) > NoActivityTimeout) || + (LastActivityAt == DateTime.MinValue && DateTime.Now.Subtract(CreatedAt) > NoActivityTimeout)) { if (!IsTimedOut) {