diff --git a/Build/MQTTnet.nuspec b/Build/MQTTnet.nuspec index 40f5616..82233fd 100644 --- a/Build/MQTTnet.nuspec +++ b/Build/MQTTnet.nuspec @@ -14,12 +14,12 @@ * [Core] Renamed some topic filter relevant classes (BREAKING CHANGE!). * [Core] Improved task management for UWP connections (thanks to @xgstation). -* [LowLevelMqttClient] * [Client] Added method to trigger PING/PONG manually (connection check etc.). +* [Client] Added support for certificate validation callback when using Web Sockets (requires netstandard2.1+). +* [Client] Fixed a memory leak when web socket based connections trying to reconnect with an offline server. * [ManagedClient] Added method to trigger PING/PONG manually (connection check etc.). -* [Server] -* [MQTTnet.AspNetCore] improved compatibility with AspNetCore 3.1 -* [MQTTnet.Server] +* [MQTTnet.AspNetCore] improved compatibility with AspNetCore 3.1. +* [MQTTnet.Server] Fixed wrong version output. Copyright Christian Kratky 2016-2020 MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin @@ -36,6 +36,12 @@ + + + + + + @@ -55,6 +61,9 @@ + + + diff --git a/Build/build.ps1 b/Build/build.ps1 index 1fe4ae3..4eea1dd 100644 --- a/Build/build.ps1 +++ b/Build/build.ps1 @@ -28,6 +28,7 @@ vstest.console.exe ..\Tests\MQTTnet.AspNetCore.Tests\bin\Release\netcoreapp3.1\M &$msbuild ..\Source\MQTTnet\MQTTnet.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="net461" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx" &$msbuild ..\Source\MQTTnet\MQTTnet.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard1.3" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx" &$msbuild ..\Source\MQTTnet\MQTTnet.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard2.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx" +&$msbuild ..\Source\MQTTnet\MQTTnet.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard2.1" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx" &$msbuild ..\Source\MQTTnet\MQTTnet.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="uap10.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx" # Build the ASP.NET Core 2.0 extension @@ -38,6 +39,7 @@ vstest.console.exe ..\Tests\MQTTnet.AspNetCore.Tests\bin\Release\netcoreapp3.1\M &$msbuild ..\Source\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="net461" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx" &$msbuild ..\Source\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard1.3" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx" &$msbuild ..\Source\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard2.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx" +&$msbuild ..\Source\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard2.1" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx" &$msbuild ..\Source\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="uap10.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx" # Build the Managed Client extension @@ -45,6 +47,7 @@ vstest.console.exe ..\Tests\MQTTnet.AspNetCore.Tests\bin\Release\netcoreapp3.1\M &$msbuild ..\Source\MQTTnet.Extensions.ManagedClient\MQTTnet.Extensions.ManagedClient.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="net461" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx" &$msbuild ..\Source\MQTTnet.Extensions.ManagedClient\MQTTnet.Extensions.ManagedClient.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard1.3" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx" &$msbuild ..\Source\MQTTnet.Extensions.ManagedClient\MQTTnet.Extensions.ManagedClient.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard2.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx" +&$msbuild ..\Source\MQTTnet.Extensions.ManagedClient\MQTTnet.Extensions.ManagedClient.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard2.1" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx" &$msbuild ..\Source\MQTTnet.Extensions.ManagedClient\MQTTnet.Extensions.ManagedClient.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="uap10.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx" # Build the WebSocket4Net extension @@ -52,6 +55,7 @@ vstest.console.exe ..\Tests\MQTTnet.AspNetCore.Tests\bin\Release\netcoreapp3.1\M &$msbuild ..\Source\MQTTnet.Extensions.WebSocket4Net\MQTTnet.Extensions.WebSocket4Net.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="net461" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx" &$msbuild ..\Source\MQTTnet.Extensions.WebSocket4Net\MQTTnet.Extensions.WebSocket4Net.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard1.3" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx" &$msbuild ..\Source\MQTTnet.Extensions.WebSocket4Net\MQTTnet.Extensions.WebSocket4Net.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard2.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx" +&$msbuild ..\Source\MQTTnet.Extensions.WebSocket4Net\MQTTnet.Extensions.WebSocket4Net.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard2.1" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx" &$msbuild ..\Source\MQTTnet.Extensions.WebSocket4Net\MQTTnet.Extensions.WebSocket4Net.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="uap10.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx" # Create NuGet packages. diff --git a/Source/MQTTnet/Client/MqttClient.cs b/Source/MQTTnet/Client/MqttClient.cs index 52929ef..4ace908 100644 --- a/Source/MQTTnet/Client/MqttClient.cs +++ b/Source/MQTTnet/Client/MqttClient.cs @@ -61,7 +61,7 @@ namespace MQTTnet.Client { get { - return _isConnected || Interlocked.Read(ref _isDisconnectPending) != 0; + return _isConnected && Interlocked.Read(ref _isDisconnectPending) == 0; } } diff --git a/Source/MQTTnet/Client/Options/MqttClientCertificateValidationCallbackContext.cs b/Source/MQTTnet/Client/Options/MqttClientCertificateValidationCallbackContext.cs new file mode 100644 index 0000000..3827f05 --- /dev/null +++ b/Source/MQTTnet/Client/Options/MqttClientCertificateValidationCallbackContext.cs @@ -0,0 +1,16 @@ +using System.Net.Security; +using System.Security.Cryptography.X509Certificates; + +namespace MQTTnet.Client.Options +{ + public class MqttClientCertificateValidationCallbackContext + { + public X509Certificate Certificate { get; set; } + + public X509Chain Chain { get; set; } + + public SslPolicyErrors SslPolicyErrors { get; set; } + + public IMqttClientChannelOptions ClientOptions { get; set; } + } +} diff --git a/Source/MQTTnet/Client/Options/MqttClientOptionsBuilder.cs b/Source/MQTTnet/Client/Options/MqttClientOptionsBuilder.cs index b301819..316e5b5 100644 --- a/Source/MQTTnet/Client/Options/MqttClientOptionsBuilder.cs +++ b/Source/MQTTnet/Client/Options/MqttClientOptionsBuilder.cs @@ -262,6 +262,7 @@ namespace MQTTnet.Client.Options Certificates = _tlsParameters.Certificates?.ToList(), #endif CertificateValidationCallback = _tlsParameters.CertificateValidationCallback, + CertificateValidationHandler = _tlsParameters.CertificateValidationHandler, IgnoreCertificateChainErrors = _tlsParameters.IgnoreCertificateChainErrors, IgnoreCertificateRevocationErrors = _tlsParameters.IgnoreCertificateRevocationErrors }; diff --git a/Source/MQTTnet/Client/Options/MqttClientOptionsBuilderTlsParameters.cs b/Source/MQTTnet/Client/Options/MqttClientOptionsBuilderTlsParameters.cs index d1854ff..fd9aad0 100644 --- a/Source/MQTTnet/Client/Options/MqttClientOptionsBuilderTlsParameters.cs +++ b/Source/MQTTnet/Client/Options/MqttClientOptionsBuilderTlsParameters.cs @@ -10,12 +10,15 @@ namespace MQTTnet.Client.Options { public bool UseTls { get; set; } + [Obsolete("This property will be removed soon. Use CertificateValidationHandler instead.")] public Func CertificateValidationCallback { get; set; } + public Func CertificateValidationHandler { get; set; } + public SslProtocols SslProtocol { get; set; } = SslProtocols.Tls12; #if WINDOWS_UWP @@ -23,7 +26,6 @@ namespace MQTTnet.Client.Options #else public IEnumerable Certificates { get; set; } #endif - public bool AllowUntrustedCertificates { get; set; } diff --git a/Source/MQTTnet/Client/Options/MqttClientTlsOptions.cs b/Source/MQTTnet/Client/Options/MqttClientTlsOptions.cs index 0d1a3a5..b1b3fe8 100644 --- a/Source/MQTTnet/Client/Options/MqttClientTlsOptions.cs +++ b/Source/MQTTnet/Client/Options/MqttClientTlsOptions.cs @@ -15,6 +15,7 @@ namespace MQTTnet.Client.Options public bool IgnoreCertificateChainErrors { get; set; } public bool AllowUntrustedCertificates { get; set; } + #if WINDOWS_UWP public List Certificates { get; set; } #else @@ -23,6 +24,9 @@ namespace MQTTnet.Client.Options public SslProtocols SslProtocol { get; set; } = SslProtocols.Tls12; + [Obsolete("This property will be removed soon. Use CertificateValidationHandler instead.")] public Func CertificateValidationCallback { get; set; } + + public Func CertificateValidationHandler { get; set; } } } diff --git a/Source/MQTTnet/Diagnostics/TargetFrameworkProvider.cs b/Source/MQTTnet/Diagnostics/TargetFrameworkProvider.cs index 7135af6..ea408ca 100644 --- a/Source/MQTTnet/Diagnostics/TargetFrameworkProvider.cs +++ b/Source/MQTTnet/Diagnostics/TargetFrameworkProvider.cs @@ -16,6 +16,8 @@ return "netstandard1.3"; #elif NETSTANDARD2_0 return "netstandard2.0"; +#elif NETSTANDARD2_1 + return "netstandard2.1"; #elif WINDOWS_UWP return "uap10.0"; #endif diff --git a/Source/MQTTnet/Implementations/MqttTcpChannel.cs b/Source/MQTTnet/Implementations/MqttTcpChannel.cs index 021a7d4..756993f 100644 --- a/Source/MQTTnet/Implementations/MqttTcpChannel.cs +++ b/Source/MQTTnet/Implementations/MqttTcpChannel.cs @@ -73,7 +73,7 @@ namespace MQTTnet.Implementations var networkStream = socket.GetStream(); - if (_options.TlsOptions.UseTls) + if (_options.TlsOptions?.UseTls == true) { var sslStream = new SslStream(networkStream, false, InternalUserCertificateValidationCallback); try @@ -181,9 +181,28 @@ namespace MQTTnet.Implementations bool InternalUserCertificateValidationCallback(object sender, X509Certificate x509Certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { - if (_options.TlsOptions.CertificateValidationCallback != null) + #region OBSOLETE + + var certificateValidationCallback = _options?.TlsOptions?.CertificateValidationCallback; + if (certificateValidationCallback != null) { - return _options.TlsOptions.CertificateValidationCallback(x509Certificate, chain, sslPolicyErrors, _clientOptions); + return certificateValidationCallback(x509Certificate, chain, sslPolicyErrors, _clientOptions); + } + + #endregion + + var certificateValidationHandler = _options?.TlsOptions?.CertificateValidationHandler; + if (certificateValidationHandler != null) + { + var context = new MqttClientCertificateValidationCallbackContext + { + Certificate = x509Certificate, + Chain = chain, + SslPolicyErrors = sslPolicyErrors, + ClientOptions = _options + }; + + return certificateValidationHandler(context); } if (sslPolicyErrors == SslPolicyErrors.None) diff --git a/Source/MQTTnet/Implementations/MqttWebSocketChannel.cs b/Source/MQTTnet/Implementations/MqttWebSocketChannel.cs index c159b91..e69db2c 100644 --- a/Source/MQTTnet/Implementations/MqttWebSocketChannel.cs +++ b/Source/MQTTnet/Implementations/MqttWebSocketChannel.cs @@ -1,21 +1,21 @@ -using System; +using MQTTnet.Channel; +using MQTTnet.Client.Options; +using MQTTnet.Internal; +using System; using System.Net; using System.Net.WebSockets; using System.Security.Cryptography.X509Certificates; using System.Threading; using System.Threading.Tasks; -using MQTTnet.Channel; -using MQTTnet.Client.Options; -using MQTTnet.Internal; namespace MQTTnet.Implementations { - public class MqttWebSocketChannel : Disposable, IMqttChannel + public sealed class MqttWebSocketChannel : IMqttChannel { - private readonly MqttClientWebSocketOptions _options; + readonly MqttClientWebSocketOptions _options; - private SemaphoreSlim _sendLock = new SemaphoreSlim(1, 1); - private WebSocket _webSocket; + AsyncLock _sendLock = new AsyncLock(); + WebSocket _webSocket; public MqttWebSocketChannel(MqttClientWebSocketOptions options) { @@ -53,50 +53,20 @@ namespace MQTTnet.Implementations } var clientWebSocket = new ClientWebSocket(); - - if (_options.ProxyOptions != null) + try { - clientWebSocket.Options.Proxy = CreateProxy(); - } + SetupClientWebSocket(clientWebSocket); - if (_options.RequestHeaders != null) - { - foreach (var requestHeader in _options.RequestHeaders) - { - clientWebSocket.Options.SetRequestHeader(requestHeader.Key, requestHeader.Value); - } + await clientWebSocket.ConnectAsync(new Uri(uri), cancellationToken).ConfigureAwait(false); } - - if (_options.SubProtocols != null) + catch (Exception) { - foreach (var subProtocol in _options.SubProtocols) - { - clientWebSocket.Options.AddSubProtocol(subProtocol); - } + // Prevent a memory leak when always creating new instance which will fail while connecting. + clientWebSocket.Dispose(); + throw; } - if (_options.CookieContainer != null) - { - clientWebSocket.Options.Cookies = _options.CookieContainer; - } - - if (_options.TlsOptions?.UseTls == true && _options.TlsOptions?.Certificates != null) - { - clientWebSocket.Options.ClientCertificates = new X509CertificateCollection(); - foreach (var certificate in _options.TlsOptions.Certificates) - { -#if WINDOWS_UWP - clientWebSocket.Options.ClientCertificates.Add(new X509Certificate(certificate)); -#else - clientWebSocket.Options.ClientCertificates.Add(certificate); -#endif - - } - } - - await clientWebSocket.ConnectAsync(new Uri(uri), cancellationToken).ConfigureAwait(false); _webSocket = clientWebSocket; - IsSecureConnection = uri.StartsWith("wss://", StringComparison.OrdinalIgnoreCase); } @@ -131,27 +101,87 @@ namespace MQTTnet.Implementations return; } - await _sendLock.WaitAsync(cancellationToken).ConfigureAwait(false); - try + using (await _sendLock.WaitAsync(cancellationToken).ConfigureAwait(false)) { await _webSocket.SendAsync(new ArraySegment(buffer, offset, count), WebSocketMessageType.Binary, true, cancellationToken).ConfigureAwait(false); } - finally - { - _sendLock?.Release(); - } } - protected override void Dispose(bool disposing) + public void Dispose() + { + Cleanup(); + } + + void SetupClientWebSocket(ClientWebSocket clientWebSocket) { - if (disposing) + + if (_options.ProxyOptions != null) + { + clientWebSocket.Options.Proxy = CreateProxy(); + } + + if (_options.RequestHeaders != null) + { + foreach (var requestHeader in _options.RequestHeaders) + { + clientWebSocket.Options.SetRequestHeader(requestHeader.Key, requestHeader.Value); + } + } + + if (_options.SubProtocols != null) { - Cleanup(); + foreach (var subProtocol in _options.SubProtocols) + { + clientWebSocket.Options.AddSubProtocol(subProtocol); + } } - base.Dispose(disposing); + + if (_options.CookieContainer != null) + { + clientWebSocket.Options.Cookies = _options.CookieContainer; + } + + if (_options.TlsOptions?.UseTls == true && _options.TlsOptions?.Certificates != null) + { + clientWebSocket.Options.ClientCertificates = new X509CertificateCollection(); + foreach (var certificate in _options.TlsOptions.Certificates) + { +#if WINDOWS_UWP + clientWebSocket.Options.ClientCertificates.Add(new X509Certificate(certificate)); +#else + clientWebSocket.Options.ClientCertificates.Add(certificate); +#endif + + } + } + + var certificateValidationHandler = _options.TlsOptions?.CertificateValidationHandler; +#if NETSTANDARD2_1 + if (certificateValidationHandler != null) + { + clientWebSocket.Options.RemoteCertificateValidationCallback = new System.Net.Security.RemoteCertificateValidationCallback((sender, certificate, chain, sslPolicyErrors) => + { + // TODO: Find a way to add client options to same callback. Problem is that they have a different type. + var context = new MqttClientCertificateValidationCallbackContext + { + Certificate = certificate, + Chain = chain, + SslPolicyErrors = sslPolicyErrors, + ClientOptions = _options + }; + + return certificateValidationHandler(context); + }); + } +#else + if (certificateValidationHandler != null) + { + throw new NotSupportedException("The remote certificate validation callback for Web Sockets is only supported for netstandard 2.1+"); + } +#endif } - private void Cleanup() + void Cleanup() { _sendLock?.Dispose(); _sendLock = null; @@ -169,7 +199,7 @@ namespace MQTTnet.Implementations } } - private IWebProxy CreateProxy() + IWebProxy CreateProxy() { if (string.IsNullOrEmpty(_options.ProxyOptions?.Address)) { diff --git a/Source/MQTTnet/Internal/BlockingQueue.cs b/Source/MQTTnet/Internal/BlockingQueue.cs index 2fa21be..7ca5429 100644 --- a/Source/MQTTnet/Internal/BlockingQueue.cs +++ b/Source/MQTTnet/Internal/BlockingQueue.cs @@ -4,11 +4,12 @@ using System.Threading; namespace MQTTnet.Internal { - public class BlockingQueue : Disposable + public sealed class BlockingQueue : IDisposable { - private readonly object _syncRoot = new object(); - private readonly LinkedList _items = new LinkedList(); - private readonly ManualResetEventSlim _gate = new ManualResetEventSlim(false); + readonly object _syncRoot = new object(); + readonly LinkedList _items = new LinkedList(); + + ManualResetEventSlim _gate = new ManualResetEventSlim(false); public int Count { @@ -28,13 +29,13 @@ namespace MQTTnet.Internal lock (_syncRoot) { _items.AddLast(item); - _gate.Set(); + _gate?.Set(); } } - public TItem Dequeue(CancellationToken cancellationToken = default(CancellationToken)) + public TItem Dequeue(CancellationToken cancellationToken = default) { - while (true) + while (!cancellationToken.IsCancellationRequested) { lock (_syncRoot) { @@ -48,17 +49,19 @@ namespace MQTTnet.Internal if (_items.Count == 0) { - _gate.Reset(); + _gate?.Reset(); } } - _gate.Wait(cancellationToken); + _gate?.Wait(cancellationToken); } + + throw new OperationCanceledException(); } - - public TItem PeekAndWait(CancellationToken cancellationToken = default(CancellationToken)) + + public TItem PeekAndWait(CancellationToken cancellationToken = default) { - while (true) + while (!cancellationToken.IsCancellationRequested) { lock (_syncRoot) { @@ -69,12 +72,14 @@ namespace MQTTnet.Internal if (_items.Count == 0) { - _gate.Reset(); + _gate?.Reset(); } } - _gate.Wait(cancellationToken); + _gate?.Wait(cancellationToken); } + + throw new OperationCanceledException(); } public void RemoveFirst(Predicate match) @@ -109,13 +114,10 @@ namespace MQTTnet.Internal } } - protected override void Dispose(bool disposing) + public void Dispose() { - if (disposing) - { - _gate.Dispose(); - } - base.Dispose(disposing); + _gate?.Dispose(); + _gate = null; } } } diff --git a/Source/MQTTnet/MQTTnet.csproj b/Source/MQTTnet/MQTTnet.csproj index a08f843..896f7a8 100644 --- a/Source/MQTTnet/MQTTnet.csproj +++ b/Source/MQTTnet/MQTTnet.csproj @@ -1,7 +1,7 @@  - netstandard1.3;netstandard2.0 + netstandard1.3;netstandard2.0;netstandard2.1 $(TargetFrameworks);net452;net461 $(TargetFrameworks);uap10.0 MQTTnet diff --git a/Tests/MQTTnet.Core.Tests/BlockingQueue_Tests.cs b/Tests/MQTTnet.Core.Tests/BlockingQueue_Tests.cs index da4069a..01e786f 100644 --- a/Tests/MQTTnet.Core.Tests/BlockingQueue_Tests.cs +++ b/Tests/MQTTnet.Core.Tests/BlockingQueue_Tests.cs @@ -1,7 +1,8 @@ -using System.Threading; -using System.Threading.Tasks; -using Microsoft.VisualStudio.TestTools.UnitTesting; +using Microsoft.VisualStudio.TestTools.UnitTesting; using MQTTnet.Internal; +using System; +using System.Threading; +using System.Threading.Tasks; namespace MQTTnet.Tests { @@ -33,7 +34,7 @@ namespace MQTTnet.Tests Assert.AreEqual("a", queue.RemoveFirst()); Assert.AreEqual("b", queue.RemoveFirst()); - + Assert.AreEqual(1, queue.Count); Assert.AreEqual("c", queue.Dequeue()); @@ -81,7 +82,7 @@ namespace MQTTnet.Tests } [TestMethod] - public void Wait_For_Times() + public void Wait_For_Items() { var number = 0; @@ -104,5 +105,21 @@ namespace MQTTnet.Tests Interlocked.Increment(ref number); } } + + [TestMethod] + [ExpectedException(typeof(OperationCanceledException))] + public void Use_Disposed_Queue() + { + var queue = new BlockingQueue(); +#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed + Task.Run(() => + { + Thread.Sleep(1000); + queue.Dispose(); + }); +#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed + + queue.Dequeue(new CancellationTokenSource(TimeSpan.FromSeconds(2)).Token); + } } -} +} \ No newline at end of file diff --git a/Tests/MQTTnet.Core.Tests/MqttClient_Tests.cs b/Tests/MQTTnet.Core.Tests/MqttClient_Tests.cs index 5c99031..b7c8a47 100644 --- a/Tests/MQTTnet.Core.Tests/MqttClient_Tests.cs +++ b/Tests/MQTTnet.Core.Tests/MqttClient_Tests.cs @@ -23,6 +23,18 @@ namespace MQTTnet.Tests { public TestContext TestContext { get; set; } + [TestMethod] + public async Task Send_Manual_Ping() + { + using (var testEnvironment = new TestEnvironment(TestContext)) + { + await testEnvironment.StartServerAsync(); + var client = await testEnvironment.ConnectClientAsync(); + + await client.PingAsync(CancellationToken.None); + } + } + [TestMethod] public async Task Send_Reply_In_Message_Handler_For_Same_Client() { diff --git a/Tests/MQTTnet.TestApp.NetCore/Program.cs b/Tests/MQTTnet.TestApp.NetCore/Program.cs index 9b2cbeb..2dc2e0e 100644 --- a/Tests/MQTTnet.TestApp.NetCore/Program.cs +++ b/Tests/MQTTnet.TestApp.NetCore/Program.cs @@ -1,15 +1,13 @@ -using System; +using MQTTnet.Client.Options; +using MQTTnet.Diagnostics; +using MQTTnet.Server; +using Newtonsoft.Json; +using System; using System.Collections.Generic; using System.IO; using System.Net.Security; -using System.Security.Cryptography.X509Certificates; using System.Threading; using System.Threading.Tasks; -using MQTTnet.Client; -using MQTTnet.Client.Options; -using MQTTnet.Diagnostics; -using MQTTnet.Server; -using Newtonsoft.Json; namespace MQTTnet.TestApp.NetCore { @@ -129,10 +127,15 @@ namespace MQTTnet.TestApp.NetCore var options = new MqttClientOptionsBuilder() .WithTls(new MqttClientOptionsBuilderTlsParameters { - CertificateValidationCallback = (X509Certificate x, X509Chain y, SslPolicyErrors z, IMqttClientOptions o) => + CertificateValidationHandler = context => { - // TODO: Check conditions of certificate by using above parameters. - return true; + // TODO: Check conditions of certificate by using above context. + if (context.SslPolicyErrors == SslPolicyErrors.None) + { + return true; + } + + return false; } }) .Build();