From 88aa58305eccb5732b072da31e4116b6bf1666ae Mon Sep 17 00:00:00 2001 From: Haves Irfan <20160532+ha-ves@users.noreply.github.com> Date: Sat, 15 Nov 2025 01:28:22 +0900 Subject: [PATCH] * Initial .NET 10 support * Use 'latest' C# language version * TFM conditional compilation * update packages * additional extensions --- src/SIPSorcery.csproj | 8 ++++---- .../Media/Sources/VideoTestPatternSource.cs | 4 ++++ src/core/SIP/Channels/SIPTLSChannel.cs | 12 ++++++++++-- src/core/SIPTransportConfig.cs | 4 ++++ src/net/DtlsSrtp/DtlsUtils.cs | 4 ++++ src/net/ICE/IceChecklistEntry.cs | 2 +- src/net/ICE/IceServer.cs | 4 ++-- .../STUNAttributes/STUNXORAddressAttribute.cs | 2 +- src/net/TURN/TurnClient.cs | 4 ++-- src/sys/Crypto/Crypto.cs | 4 ++++ src/sys/Crypto/PasswordHash.cs | 19 +++++++------------ src/sys/Net/NetConvert.cs | 10 +++++----- src/sys/TypeExtensions.cs | 6 ++++++ .../SIPSorcery.IntegrationTests.csproj | 2 +- .../core/SIPTransportIntegrationTest.cs | 18 ++++++++++++++++++ .../net/DtlsSrtp/DtlsUtilsUnitTest.cs | 12 ++++++++++++ test/unit/SIPSorcery.UnitTests.csproj | 2 +- 17 files changed, 86 insertions(+), 31 deletions(-) diff --git a/src/SIPSorcery.csproj b/src/SIPSorcery.csproj index af4e9984bc..27c3035546 100755 --- a/src/SIPSorcery.csproj +++ b/src/SIPSorcery.csproj @@ -19,7 +19,7 @@ - + @@ -29,15 +29,15 @@ - + - netstandard2.0;netstandard2.1;netcoreapp3.1;net462;net5.0;net6.0;net8.0 - 12.0 + netstandard2.0;netstandard2.1;netcoreapp3.1;net462;net5.0;net6.0;net8.0;net9.0;net10.0 + latest true $(NoWarn);SYSLIB0050 True diff --git a/src/app/Media/Sources/VideoTestPatternSource.cs b/src/app/Media/Sources/VideoTestPatternSource.cs index f7f2d88570..771f73df89 100755 --- a/src/app/Media/Sources/VideoTestPatternSource.cs +++ b/src/app/Media/Sources/VideoTestPatternSource.cs @@ -95,7 +95,11 @@ public VideoTestPatternSource(IVideoEncoder encoder = null) else { _testI420Buffer = new byte[TEST_PATTERN_WIDTH * TEST_PATTERN_HEIGHT * 3 / 2]; +#if NET9_0_OR_GREATER + testPatternStm.ReadExactly(_testI420Buffer, 0, _testI420Buffer.Length); +#else testPatternStm.Read(_testI420Buffer, 0, _testI420Buffer.Length); +#endif testPatternStm.Close(); _sendTestPatternTimer = new Timer(GenerateTestPattern, null, Timeout.Infinite, Timeout.Infinite); _frameSpacing = 1000 / DEFAULT_FRAMES_PER_SECOND; diff --git a/src/core/SIP/Channels/SIPTLSChannel.cs b/src/core/SIP/Channels/SIPTLSChannel.cs index ad149c46ea..42973bcb0c 100644 --- a/src/core/SIP/Channels/SIPTLSChannel.cs +++ b/src/core/SIP/Channels/SIPTLSChannel.cs @@ -355,7 +355,15 @@ private void DisplayCertificateChain(X509Certificate2 certificate) private void DisplaySecurityLevel(SslStream stream) { - logger.LogDebug("Cipher: {CipherAlgorithm} strength {CipherStrength}, Hash: {HashAlgorithm} strength {HashStrength}, Key exchange: {KeyExchangeAlgorithm} strength {KeyExchangeStrength}, Protocol: {SslProtocol}", stream.CipherAlgorithm, stream.CipherStrength, stream.HashAlgorithm, stream.HashStrength, stream.KeyExchangeAlgorithm, stream.KeyExchangeStrength, stream.SslProtocol); +#if NET5_0_OR_GREATER + // Use the negotiated cipher suite property available in .NET 5+. + var cipherSuite = stream.NegotiatedCipherSuite; + logger.LogDebug("Negotiated cipher suite: {CipherSuite}, Protocol: {SslProtocol}", cipherSuite, stream.SslProtocol); +#else + logger.LogDebug("Cipher: {CipherAlgorithm} strength {CipherStrength}, Hash: {HashAlgorithm} strength {HashStrength}, Key exchange: {KeyExchangeAlgorithm} strength {KeyExchangeStrength}, Protocol: {SslProtocol}", + stream.CipherAlgorithm, stream.CipherStrength, stream.HashAlgorithm, stream.HashStrength, stream.KeyExchangeAlgorithm, stream.KeyExchangeStrength, stream.SslProtocol + ); +#endif } private void DisplaySecurityServices(SslStream stream) @@ -399,6 +407,6 @@ private void DisplayCertificateInformation(SslStream stream) } } - #endregion +#endregion } } diff --git a/src/core/SIPTransportConfig.cs b/src/core/SIPTransportConfig.cs index 1ea5e70cca..be494f8f3c 100644 --- a/src/core/SIPTransportConfig.cs +++ b/src/core/SIPTransportConfig.cs @@ -126,7 +126,11 @@ private static X509Certificate2 LoadCertificate(string certificateType, string c if (certificateType == "file") { +#if NET9_0_OR_GREATER + var serverCertificate = X509CertificateLoader.LoadPkcs12FromFile(certifcateLocation, certKeyPassword); +#else var serverCertificate = new X509Certificate2(certifcateLocation, certKeyPassword); +#endif //DisplayCertificateChain(m_serverCertificate); var verifyCert = serverCertificate.Verify(); logger.LogDebug("Server Certificate loaded from file, Subject={Subject}, valid={Valid}.", serverCertificate.Subject, verifyCert); diff --git a/src/net/DtlsSrtp/DtlsUtils.cs b/src/net/DtlsSrtp/DtlsUtils.cs index 09f5d37ac7..95a2224464 100755 --- a/src/net/DtlsSrtp/DtlsUtils.cs +++ b/src/net/DtlsSrtp/DtlsUtils.cs @@ -435,7 +435,11 @@ public static X509Certificate2 ConvertBouncyCert(Org.BouncyCastle.X509.X509Certi { pkcs12Store.Save(pfxStream, new char[] { }, new SecureRandom()); pfxStream.Seek(0, SeekOrigin.Begin); +#if NET9_0_OR_GREATER + keyedCert = X509CertificateLoader.LoadPkcs12(pfxStream.ToArray(), string.Empty, X509KeyStorageFlags.Exportable); +#else keyedCert = new X509Certificate2(pfxStream.ToArray(), string.Empty, X509KeyStorageFlags.Exportable); +#endif } return keyedCert; diff --git a/src/net/ICE/IceChecklistEntry.cs b/src/net/ICE/IceChecklistEntry.cs index 40cdff5453..79765619c4 100644 --- a/src/net/ICE/IceChecklistEntry.cs +++ b/src/net/ICE/IceChecklistEntry.cs @@ -310,7 +310,7 @@ internal void GotStunResponse(STUNMessage stunResponse, IPEndPoint remoteEndPoin if (lifetime != null) { LocalCandidate.IceServer.TurnTimeToExpiry = DateTime.Now + - TimeSpan.FromSeconds(BitConverter.ToUInt32(lifetime.Value.Reverse().ToArray(), 0)); + TimeSpan.FromSeconds(BitConverter.ToUInt32(lifetime.Value.FluentReverse().ToArray(), 0)); } } else if (stunResponse.Header.MessageType == STUNMessageTypesEnum.RefreshErrorResponse) diff --git a/src/net/ICE/IceServer.cs b/src/net/ICE/IceServer.cs index ea35ec52a9..71710c6874 100755 --- a/src/net/ICE/IceServer.cs +++ b/src/net/ICE/IceServer.cs @@ -428,7 +428,7 @@ internal bool GotStunResponse(STUNMessage stunResponse, IPEndPoint remoteEndPoin if (lifetime != null) { TurnTimeToExpiry = DateTime.Now + - TimeSpan.FromSeconds(BitConverter.ToUInt32(lifetime.Value.Reverse().ToArray(), 0)); + TimeSpan.FromSeconds(BitConverter.ToUInt32(lifetime.Value.FluentReverse().ToArray(), 0)); } else { @@ -536,7 +536,7 @@ internal bool GotStunResponse(STUNMessage stunResponse, IPEndPoint remoteEndPoin if (lifetime != null) { TurnTimeToExpiry = DateTime.Now + - TimeSpan.FromSeconds(BitConverter.ToUInt32(lifetime.Value.Reverse().ToArray(), 0)); + TimeSpan.FromSeconds(BitConverter.ToUInt32(lifetime.Value.FluentReverse().ToArray(), 0)); } } diff --git a/src/net/STUN/STUNAttributes/STUNXORAddressAttribute.cs b/src/net/STUN/STUNAttributes/STUNXORAddressAttribute.cs index c6cc025c0e..5ff64d7d1c 100644 --- a/src/net/STUN/STUNAttributes/STUNXORAddressAttribute.cs +++ b/src/net/STUN/STUNAttributes/STUNXORAddressAttribute.cs @@ -59,7 +59,7 @@ public STUNXORAddressAttribute(STUNAttributeTypesEnum attributeType, byte[] attr if (BitConverter.IsLittleEndian) { Port = NetConvert.DoReverseEndian(BitConverter.ToUInt16(attributeValue, 2)) ^ (UInt16)(STUNHeader.MAGIC_COOKIE >> 16); - address = BitConverter.GetBytes(NetConvert.DoReverseEndian(BitConverter.ToUInt32(attributeValue, 4)) ^ STUNHeader.MAGIC_COOKIE).Reverse().ToArray(); + address = BitConverter.GetBytes(NetConvert.DoReverseEndian(BitConverter.ToUInt32(attributeValue, 4)) ^ STUNHeader.MAGIC_COOKIE).FluentReverse().ToArray(); } else { diff --git a/src/net/TURN/TurnClient.cs b/src/net/TURN/TurnClient.cs index f4be0ff4c3..eb17956696 100644 --- a/src/net/TURN/TurnClient.cs +++ b/src/net/TURN/TurnClient.cs @@ -271,7 +271,7 @@ private void GotStunResponse(STUNMessage stunResponse, IPEndPoint remoteEndPoint if (permissionLifetime != null) { - permissionDuration = TimeSpan.FromSeconds(BitConverter.ToUInt32(permissionLifetime.Value.Reverse().ToArray(), 0)); + permissionDuration = TimeSpan.FromSeconds(BitConverter.ToUInt32(permissionLifetime.Value.FluentReverse().ToArray(), 0)); logger.LogDebug("TURN permission lifetime attribute value {lifetimeSeconds}s.", permissionDuration.TotalSeconds); } @@ -349,7 +349,7 @@ private void ScheduleAllocateRefresh(STUNAttribute lifetimeAttribute) if (lifetimeAttribute != null) { - var lifetimeSpan = TimeSpan.FromSeconds(BitConverter.ToUInt32(lifetimeAttribute.Value.Reverse().ToArray(), 0)); + var lifetimeSpan = TimeSpan.FromSeconds(BitConverter.ToUInt32(lifetimeAttribute.Value.FluentReverse().ToArray(), 0)); logger.LogDebug("TURN allocate lifetime attribute value {lifetimeSeconds}s.", lifetimeSpan.TotalSeconds); diff --git a/src/sys/Crypto/Crypto.cs b/src/sys/Crypto/Crypto.cs index 99e7dbb4fd..f8b6ba10ba 100644 --- a/src/sys/Crypto/Crypto.cs +++ b/src/sys/Crypto/Crypto.cs @@ -269,7 +269,11 @@ public static string GetHash(string filepath) // Buffer to read in plain text blocks. byte[] fileBuffer = new byte[fileStream.Length]; +#if NET9_0_OR_GREATER + fileStream.ReadExactly(fileBuffer, 0, (int)fileStream.Length); +#else fileStream.Read(fileBuffer, 0, (int)fileStream.Length); +#endif fileStream.Close(); byte[] overallHash = shaM.ComputeHash(fileBuffer); diff --git a/src/sys/Crypto/PasswordHash.cs b/src/sys/Crypto/PasswordHash.cs index 6dacb3b4c5..10c4fe27f6 100755 --- a/src/sys/Crypto/PasswordHash.cs +++ b/src/sys/Crypto/PasswordHash.cs @@ -66,22 +66,17 @@ public static string Hash(string value, string salt) salt = salt.Substring(i + 1); byte[] key = null; + byte[] saltBytes = Convert.FromBase64String(salt); + #if NET8_0_OR_GREATER - // Use the updated constructor for .NET 8.0 or later - using (var pbkdf2 = new Rfc2898DeriveBytes( - Encoding.UTF8.GetBytes(value), - Convert.FromBase64String(salt), - iters, - HashAlgorithmName.SHA256)) + // Use PBKDF2 with SHA256 on .NET 8+. + key = Rfc2898DeriveBytes.Pbkdf2(Encoding.UTF8.GetBytes(value), saltBytes, iters, System.Security.Cryptography.HashAlgorithmName.SHA256, 24); +#else + // Fallback to the existing approach for .NET Framework and .NET Standard + using (var pbkdf2 = new Rfc2898DeriveBytes(Encoding.UTF8.GetBytes(value), saltBytes, iters)) { key = pbkdf2.GetBytes(24); } -#else - // Fallback to the existing approach for .NET Framework and .NET Standard - using (var pbkdf2 = new Rfc2898DeriveBytes(Encoding.UTF8.GetBytes(value), Convert.FromBase64String(salt), iters)) - { - key = pbkdf2.GetBytes(24); - } #endif return Convert.ToBase64String(key); diff --git a/src/sys/Net/NetConvert.cs b/src/sys/Net/NetConvert.cs index 8c223f9d4c..31a27de100 100644 --- a/src/sys/Net/NetConvert.cs +++ b/src/sys/Net/NetConvert.cs @@ -1,4 +1,4 @@ -//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- // Filename: Utilities.cs // // Description: Useful functions for VoIP protocol implementation. @@ -23,24 +23,24 @@ public class NetConvert public static UInt16 DoReverseEndian(UInt16 x) { //return Convert.ToUInt16((x << 8 & 0xff00) | (x >> 8)); - return BitConverter.ToUInt16(BitConverter.GetBytes(x).Reverse().ToArray(), 0); + return BitConverter.ToUInt16(BitConverter.GetBytes(x).FluentReverse().ToArray(), 0); } public static uint DoReverseEndian(uint x) { //return (x << 24 | (x & 0xff00) << 8 | (x & 0xff0000) >> 8 | x >> 24); - return BitConverter.ToUInt32(BitConverter.GetBytes(x).Reverse().ToArray(), 0); + return BitConverter.ToUInt32(BitConverter.GetBytes(x).FluentReverse().ToArray(), 0); } public static ulong DoReverseEndian(ulong x) { //return (x << 56 | (x & 0xff00) << 40 | (x & 0xff0000) << 24 | (x & 0xff000000) << 8 | (x & 0xff00000000) >> 8 | (x & 0xff0000000000) >> 24 | (x & 0xff000000000000) >> 40 | x >> 56); - return BitConverter.ToUInt64(BitConverter.GetBytes(x).Reverse().ToArray(), 0); + return BitConverter.ToUInt64(BitConverter.GetBytes(x).FluentReverse().ToArray(), 0); } public static int DoReverseEndian(int x) { - return BitConverter.ToInt32(BitConverter.GetBytes(x).Reverse().ToArray(), 0); + return BitConverter.ToInt32(BitConverter.GetBytes(x).FluentReverse().ToArray(), 0); } /// diff --git a/src/sys/TypeExtensions.cs b/src/sys/TypeExtensions.cs index 47d7e29998..0fdc5f3cc9 100755 --- a/src/sys/TypeExtensions.cs +++ b/src/sys/TypeExtensions.cs @@ -50,6 +50,12 @@ public static class TypeExtensions private static readonly char[] hexmap = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; + public static Span FluentReverse(this Span span) + { + span.Reverse(); + return span; + } + /// /// Gets a value that indicates whether or not the string is empty. /// diff --git a/test/integration/SIPSorcery.IntegrationTests.csproj b/test/integration/SIPSorcery.IntegrationTests.csproj index 79a95c5dae..a71896afb7 100755 --- a/test/integration/SIPSorcery.IntegrationTests.csproj +++ b/test/integration/SIPSorcery.IntegrationTests.csproj @@ -1,7 +1,7 @@  - net462;net8.0 + net462;net8.0;net9;net10.0 false true True diff --git a/test/integration/core/SIPTransportIntegrationTest.cs b/test/integration/core/SIPTransportIntegrationTest.cs index 14fcc89eef..171e620321 100644 --- a/test/integration/core/SIPTransportIntegrationTest.cs +++ b/test/integration/core/SIPTransportIntegrationTest.cs @@ -351,7 +351,13 @@ public async Task IPv6TlsLoopbackSendReceiveTest() Assert.True(File.Exists(@"certs/localhost.pfx"), "The TLS transport channel test was missing the localhost.pfx certificate file."); +#if NET9_0_OR_GREATER + var serverCertificate = X509CertificateLoader.LoadPkcs12FromFile(@"certs/localhost.pfx", ""); +#else +#pragma warning disable SYSLIB0057 // X509Certificate2 constructor is obsolete in NET9+ var serverCertificate = new X509Certificate2(@"certs/localhost.pfx", ""); +#pragma warning restore SYSLIB0057 +#endif var verifyCert = serverCertificate.Verify(); logger.LogDebug("Server Certificate loaded from file, Subject={Subject}, valid={Valid}.", serverCertificate.Subject, verifyCert); @@ -416,7 +422,13 @@ public async Task IPv4TlsLoopbackSendReceiveTest() Assert.True(File.Exists(@"certs/localhost.pfx"), "The TLS transport channel test was missing the localhost.pfx certificate file."); +#if NET9_0_OR_GREATER + var serverCertificate = X509CertificateLoader.LoadPkcs12FromFile(@"certs/localhost.pfx", ""); +#else +#pragma warning disable SYSLIB0057 // X509Certificate2 constructor is obsolete in NET9+ var serverCertificate = new X509Certificate2(@"certs/localhost.pfx", ""); +#pragma warning restore SYSLIB0057 +#endif var verifyCert = serverCertificate.Verify(); logger.LogDebug("Server Certificate loaded from file, Subject={Subject}, valid={Valid}.", serverCertificate.Subject, verifyCert); @@ -686,7 +698,13 @@ public async Task TlsDoesNotGetStuckOnIncompleteTcpConnection() TaskCompletionSource testComplete = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); Assert.True(File.Exists(@"certs/localhost.pfx"), "The TLS transport channel test was missing the localhost.pfx certificate file."); +#if NET9_0_OR_GREATER + var serverCertificate = X509CertificateLoader.LoadPkcs12FromFile(@"certs/localhost.pfx", ""); +#else +#pragma warning disable SYSLIB0057 // X509Certificate2 constructor is obsolete in NET9+ var serverCertificate = new X509Certificate2(@"certs/localhost.pfx", ""); +#pragma warning restore SYSLIB0057 +#endif serverCertificate.Verify(); var serverChannel = new SIPTLSChannel(serverCertificate, IPAddress.Loopback, 0); diff --git a/test/integration/net/DtlsSrtp/DtlsUtilsUnitTest.cs b/test/integration/net/DtlsSrtp/DtlsUtilsUnitTest.cs index 34b701f8dc..df557b5bbf 100644 --- a/test/integration/net/DtlsSrtp/DtlsUtilsUnitTest.cs +++ b/test/integration/net/DtlsSrtp/DtlsUtilsUnitTest.cs @@ -90,7 +90,13 @@ public void LoadSecretFromArchiveUnitTest() return; } #endif +#if NET9_0_OR_GREATER + var cert = X509CertificateLoader.LoadPkcs12FromFile("certs/localhost.pfx", string.Empty, X509KeyStorageFlags.Exportable); +#else +#pragma warning disable SYSLIB0057 // X509Certificate2 constructor is obsolete in NET9+ var cert = new X509Certificate2("certs/localhost.pfx", string.Empty, X509KeyStorageFlags.Exportable); +#pragma warning restore SYSLIB0057 +#endif Assert.NotNull(cert); var key = DtlsUtils.LoadPrivateKeyResource(cert); Assert.NotNull(key); @@ -114,7 +120,13 @@ public void BouncyCertFromCoreFxCert() } #endif +#if NET9_0_OR_GREATER + var coreFxCert = X509CertificateLoader.LoadPkcs12FromFile("certs/localhost.pfx", string.Empty, X509KeyStorageFlags.Exportable); +#else +#pragma warning disable SYSLIB0057 // X509Certificate2 constructor is obsolete in NET9+ var coreFxCert = new X509Certificate2("certs/localhost.pfx", string.Empty, X509KeyStorageFlags.Exportable); +#pragma warning restore SYSLIB0057 +#endif Assert.NotNull(coreFxCert); Assert.NotNull(coreFxCert.PrivateKey); diff --git a/test/unit/SIPSorcery.UnitTests.csproj b/test/unit/SIPSorcery.UnitTests.csproj index 256f16cadb..e37e155486 100755 --- a/test/unit/SIPSorcery.UnitTests.csproj +++ b/test/unit/SIPSorcery.UnitTests.csproj @@ -1,7 +1,7 @@  - net462;net8.0 + net462;net8.0;net9;net10.0 false true True