From 1a41415dd165560cff97bb4ab52592133a013c55 Mon Sep 17 00:00:00 2001 From: Benjamin Crosnier Date: Mon, 19 Aug 2019 10:41:28 +0200 Subject: [PATCH 01/51] Allow AssignedClientIdentifier in ClientConnected, ClientDisconnected and ConnectedHandler - Fixes chkr1011/MQTTnet#745 --- Source/MQTTnet/Server/MqttClientConnection.cs | 14 ++--- .../Server/MqttClientSessionsManager.cs | 6 +- .../MQTTnet.Core.Tests/MQTTv5/Client_Tests.cs | 60 +++++++++++++++++++ 3 files changed, 68 insertions(+), 12 deletions(-) diff --git a/Source/MQTTnet/Server/MqttClientConnection.cs b/Source/MQTTnet/Server/MqttClientConnection.cs index e71d1a8..ed36b6a 100644 --- a/Source/MQTTnet/Server/MqttClientConnection.cs +++ b/Source/MQTTnet/Server/MqttClientConnection.cs @@ -118,13 +118,13 @@ namespace MQTTnet.Server _cancellationToken.Dispose(); } - public Task RunAsync() + public Task RunAsync(MqttConnectionValidatorContext connectionValidatorContext) { - _packageReceiverTask = RunInternalAsync(); + _packageReceiverTask = RunInternalAsync(connectionValidatorContext); return _packageReceiverTask; } - private async Task RunInternalAsync() + private async Task RunInternalAsync(MqttConnectionValidatorContext connectionValidatorContext) { var disconnectType = MqttClientDisconnectType.NotClean; try @@ -142,12 +142,8 @@ namespace MQTTnet.Server _keepAliveMonitor.Start(ConnectPacket.KeepAlivePeriod, _cancellationToken.Token); await SendAsync( - new MqttConnAckPacket - { - ReturnCode = MqttConnectReturnCode.ConnectionAccepted, - ReasonCode = MqttConnectReasonCode.Success, - IsSessionPresent = !Session.IsCleanSession - }).ConfigureAwait(false); + _channelAdapter.PacketFormatterAdapter.DataConverter.CreateConnAckPacket(connectionValidatorContext) + ).ConfigureAwait(false); Session.IsCleanSession = false; diff --git a/Source/MQTTnet/Server/MqttClientSessionsManager.cs b/Source/MQTTnet/Server/MqttClientSessionsManager.cs index db70e95..82d3c76 100644 --- a/Source/MQTTnet/Server/MqttClientSessionsManager.cs +++ b/Source/MQTTnet/Server/MqttClientSessionsManager.cs @@ -240,10 +240,10 @@ namespace MQTTnet.Server return; } - clientId = connectPacket.ClientId; - var connectionValidatorContext = await ValidateConnectionAsync(connectPacket, channelAdapter).ConfigureAwait(false); + clientId = connectPacket.ClientId; + if (connectionValidatorContext.ReasonCode != MqttConnectReasonCode.Success) { // Send failure response here without preparing a session. The result for a successful connect @@ -258,7 +258,7 @@ namespace MQTTnet.Server await _eventDispatcher.HandleClientConnectedAsync(clientId).ConfigureAwait(false); - disconnectType = await connection.RunAsync().ConfigureAwait(false); + disconnectType = await connection.RunAsync(connectionValidatorContext).ConfigureAwait(false); } catch (OperationCanceledException) { diff --git a/Tests/MQTTnet.Core.Tests/MQTTv5/Client_Tests.cs b/Tests/MQTTnet.Core.Tests/MQTTv5/Client_Tests.cs index 3b813c3..c4c7634 100644 --- a/Tests/MQTTnet.Core.Tests/MQTTv5/Client_Tests.cs +++ b/Tests/MQTTnet.Core.Tests/MQTTv5/Client_Tests.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using MQTTnet.Client; @@ -57,6 +58,65 @@ namespace MQTTnet.Tests.MQTTv5 Assert.AreEqual(2, receivedMessage.UserProperties.Count); } } + [TestMethod] + public async Task Connect_With_AssignedClientId() + { + using (var testEnvironment = new TestEnvironment()) + { + string serverConnectedClientId = null; + string serverDisconnectedClientId = null; + string clientAssignedClientId = null; + + // Arrange server + var disconnectedMre = new ManualResetEventSlim(); + var serverOptions = new MqttServerOptionsBuilder() + .WithConnectionValidator((context) => + { + if (string.IsNullOrEmpty(context.ClientId)) + { + context.AssignedClientIdentifier = "test123"; + context.ReasonCode = MqttConnectReasonCode.Success; + } + }); + await testEnvironment.StartServerAsync(serverOptions); + testEnvironment.Server.UseClientConnectedHandler((args) => + { + serverConnectedClientId = args.ClientId; + }); + testEnvironment.Server.UseClientDisconnectedHandler((args) => + { + serverDisconnectedClientId = args.ClientId; + disconnectedMre.Set(); + }); + + // Arrange client + var client = testEnvironment.CreateClient(); + client.UseConnectedHandler((args) => + { + clientAssignedClientId = args.AuthenticateResult.AssignedClientIdentifier; + }); + + // Act + await client.ConnectAsync(new MqttClientOptionsBuilder() + .WithTcpServer("127.0.0.1", testEnvironment.ServerPort) + .WithProtocolVersion(MqttProtocolVersion.V500) + .WithClientId(null) + .Build()); + await client.DisconnectAsync(); + + // Wait for ClientDisconnectedHandler to trigger + disconnectedMre.Wait(500); + + // Assert + Assert.IsNotNull(serverConnectedClientId); + Assert.IsNotNull(serverDisconnectedClientId); + Assert.IsNotNull(clientAssignedClientId); + Assert.AreEqual("test123", serverConnectedClientId); + Assert.AreEqual("test123", serverDisconnectedClientId); + Assert.AreEqual("test123", clientAssignedClientId); + + } + } [TestMethod] public async Task Connect() From ad1c198e435385e9dde4586dd3abea16900bfc6b Mon Sep 17 00:00:00 2001 From: Johan x Lindqvist Date: Thu, 12 Sep 2019 09:05:08 +0200 Subject: [PATCH 02/51] Remove if clause that stopped handling of tasks with exceptions. Now the task is always awaited and thus the exception in the task is handled. --- Source/MQTTnet/Client/MqttClient.cs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/Source/MQTTnet/Client/MqttClient.cs b/Source/MQTTnet/Client/MqttClient.cs index 27b56ff..68045a2 100644 --- a/Source/MQTTnet/Client/MqttClient.cs +++ b/Source/MQTTnet/Client/MqttClient.cs @@ -149,7 +149,7 @@ namespace MQTTnet.Client Properties = new MqttAuthPacketProperties { // This must always be equal to the value from the CONNECT packet. So we use it here to ensure that. - AuthenticationMethod = Options.AuthenticationMethod, + AuthenticationMethod = Options.AuthenticationMethod, AuthenticationData = data.AuthenticationData, ReasonString = data.ReasonString, UserProperties = data.UserProperties @@ -567,7 +567,7 @@ namespace MQTTnet.Client }; await SendAsync(pubRecPacket, cancellationToken).ConfigureAwait(false); - } + } } else { @@ -633,11 +633,6 @@ namespace MQTTnet.Client return; } - if (task.IsCanceled || task.IsCompleted || task.IsFaulted) - { - return; - } - try { await task.ConfigureAwait(false); From ba9ceed7cec0b12132f9e3e8a638b266134fefbb Mon Sep 17 00:00:00 2001 From: Johan x Lindqvist Date: Mon, 16 Sep 2019 11:33:37 +0200 Subject: [PATCH 03/51] Use Task.WhenAll to handle errors in both tasks. Previously if there was an exception in the first task that is awaited the second task would not be awaited. --- Source/MQTTnet/Client/MqttClient.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Source/MQTTnet/Client/MqttClient.cs b/Source/MQTTnet/Client/MqttClient.cs index 68045a2..c5224cb 100644 --- a/Source/MQTTnet/Client/MqttClient.cs +++ b/Source/MQTTnet/Client/MqttClient.cs @@ -269,8 +269,10 @@ namespace MQTTnet.Client await _adapter.DisconnectAsync(Options.CommunicationTimeout, CancellationToken.None).ConfigureAwait(false); } - await WaitForTaskAsync(_packetReceiverTask, sender).ConfigureAwait(false); - await WaitForTaskAsync(_keepAlivePacketsSenderTask, sender).ConfigureAwait(false); + var receiverTask = WaitForTaskAsync(_packetReceiverTask, sender); + var keepAliveTask = WaitForTaskAsync(_keepAlivePacketsSenderTask, sender); + + await Task.WhenAll(receiverTask, keepAliveTask).ConfigureAwait(false); _logger.Verbose("Disconnected from adapter."); } From 5618c4c1f7f427d7672f3756c681860c1837dc91 Mon Sep 17 00:00:00 2001 From: Johan x Lindqvist Date: Wed, 18 Sep 2019 13:48:29 +0200 Subject: [PATCH 04/51] Always access the exception property on the task if it has status IsFaulted. By checking the Exception property it will consider the task exception as handled which means the finalizer won't throw an unhandled task exception. --- Source/MQTTnet/Client/MqttClient.cs | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/Source/MQTTnet/Client/MqttClient.cs b/Source/MQTTnet/Client/MqttClient.cs index c5224cb..6717f61 100644 --- a/Source/MQTTnet/Client/MqttClient.cs +++ b/Source/MQTTnet/Client/MqttClient.cs @@ -271,7 +271,7 @@ namespace MQTTnet.Client var receiverTask = WaitForTaskAsync(_packetReceiverTask, sender); var keepAliveTask = WaitForTaskAsync(_keepAlivePacketsSenderTask, sender); - + await Task.WhenAll(receiverTask, keepAliveTask).ConfigureAwait(false); _logger.Verbose("Disconnected from adapter."); @@ -628,10 +628,25 @@ namespace MQTTnet.Client return true; } - private static async Task WaitForTaskAsync(Task task, Task sender) + private async Task WaitForTaskAsync(Task task, Task sender) { - if (task == sender || task == null) + if (task == null) + { + return; + } + + if (task == sender) { + // Return here to avoid deadlocks, but first any eventual exception in the task + // must be handled to avoid not getting an unhandled task exception + if (!task.IsFaulted) + { + return; + } + + // By accessing the Exception property the exception is considered handled and will + // not result in an unhandled task exception later by the finalizer + _logger.Warning(task.Exception, "Exception when waiting for background task."); return; } From 61c3e02242826f8a9c775777976169ee2b575f91 Mon Sep 17 00:00:00 2001 From: Johan x Lindqvist Date: Thu, 19 Sep 2019 13:31:57 +0200 Subject: [PATCH 05/51] Updated exception message to be in line with other exception messages. --- Source/MQTTnet/Client/MqttClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/MQTTnet/Client/MqttClient.cs b/Source/MQTTnet/Client/MqttClient.cs index 6717f61..8520374 100644 --- a/Source/MQTTnet/Client/MqttClient.cs +++ b/Source/MQTTnet/Client/MqttClient.cs @@ -646,7 +646,7 @@ namespace MQTTnet.Client // By accessing the Exception property the exception is considered handled and will // not result in an unhandled task exception later by the finalizer - _logger.Warning(task.Exception, "Exception when waiting for background task."); + _logger.Warning(task.Exception, "Error while waiting for background task."); return; } From b12e3bc61134ad358be1dda8c273e36c307dee2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=91=D0=B5=D1=81=D1=81=D0=BE=D0=BD=D0=BE=D0=B2=20=D0=94?= =?UTF-8?q?=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9?= Date: Mon, 30 Sep 2019 16:05:23 +0300 Subject: [PATCH 06/51] fix for #762 --- .../ManagedMqttClient.cs | 2 +- Source/MQTTnet/Internal/BlockingQueue.cs | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs b/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs index c9053e4..0607d93 100644 --- a/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs +++ b/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs @@ -349,7 +349,7 @@ namespace MQTTnet.Extensions.ManagedClient // of the messages, the DropOldestQueuedMessage strategy would // be unable to know which message is actually the oldest and would // instead drop the first item in the queue. - var message = _messageQueue.PeekAndWait(); + var message = _messageQueue.PeekAndWait(cancellationToken); if (message == null) { continue; diff --git a/Source/MQTTnet/Internal/BlockingQueue.cs b/Source/MQTTnet/Internal/BlockingQueue.cs index 485f644..bac1a67 100644 --- a/Source/MQTTnet/Internal/BlockingQueue.cs +++ b/Source/MQTTnet/Internal/BlockingQueue.cs @@ -8,7 +8,7 @@ namespace MQTTnet.Internal { private readonly object _syncRoot = new object(); private readonly LinkedList _items = new LinkedList(); - private readonly ManualResetEvent _gate = new ManualResetEvent(false); + private readonly ManualResetEventSlim _gate = new ManualResetEventSlim(false); public int Count { @@ -32,7 +32,7 @@ namespace MQTTnet.Internal } } - public TItem Dequeue() + public TItem Dequeue(CancellationToken cancellationToken = default) { while (true) { @@ -52,11 +52,11 @@ namespace MQTTnet.Internal } } - _gate.WaitOne(); + _gate.Wait(cancellationToken); } } - public TItem PeekAndWait() + public TItem PeekAndWait(CancellationToken cancellationToken = default) { while (true) { @@ -73,7 +73,7 @@ namespace MQTTnet.Internal } } - _gate.WaitOne(); + _gate.Wait(cancellationToken); } } From 15ff03c136a65269aaaa2868388ba820f35de679 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=91=D0=B5=D1=81=D1=81=D0=BE=D0=BD=D0=BE=D0=B2=20=D0=94?= =?UTF-8?q?=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9?= Date: Mon, 30 Sep 2019 16:10:30 +0300 Subject: [PATCH 07/51] use C#7.0 for default arguments --- Source/MQTTnet/Internal/BlockingQueue.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/MQTTnet/Internal/BlockingQueue.cs b/Source/MQTTnet/Internal/BlockingQueue.cs index bac1a67..871c40a 100644 --- a/Source/MQTTnet/Internal/BlockingQueue.cs +++ b/Source/MQTTnet/Internal/BlockingQueue.cs @@ -32,7 +32,7 @@ namespace MQTTnet.Internal } } - public TItem Dequeue(CancellationToken cancellationToken = default) + public TItem Dequeue(CancellationToken cancellationToken = default(CancellationToken)) { while (true) { @@ -56,7 +56,7 @@ namespace MQTTnet.Internal } } - public TItem PeekAndWait(CancellationToken cancellationToken = default) + public TItem PeekAndWait(CancellationToken cancellationToken = default(CancellationToken)) { while (true) { From 699558e47a86d09511c1dee2c3fc53750e21ce24 Mon Sep 17 00:00:00 2001 From: Jimmy Rosenskog Date: Thu, 3 Oct 2019 09:46:40 +0200 Subject: [PATCH 08/51] Added exception handling to make sure all tasks are observed to avoid UnobservedTaskException. --- Source/MQTTnet/Client/MqttClient.cs | 37 ++++++++++++++++++++++------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/Source/MQTTnet/Client/MqttClient.cs b/Source/MQTTnet/Client/MqttClient.cs index 8520374..527a5cd 100644 --- a/Source/MQTTnet/Client/MqttClient.cs +++ b/Source/MQTTnet/Client/MqttClient.cs @@ -258,28 +258,34 @@ namespace MQTTnet.Client var clientWasConnected = IsConnected; TryInitiateDisconnect(); + IsConnected = false; try { - IsConnected = false; - if (_adapter != null) { _logger.Verbose("Disconnecting [Timeout={0}]", Options.CommunicationTimeout); await _adapter.DisconnectAsync(Options.CommunicationTimeout, CancellationToken.None).ConfigureAwait(false); } - var receiverTask = WaitForTaskAsync(_packetReceiverTask, sender); - var keepAliveTask = WaitForTaskAsync(_keepAlivePacketsSenderTask, sender); - - await Task.WhenAll(receiverTask, keepAliveTask).ConfigureAwait(false); - _logger.Verbose("Disconnected from adapter."); } catch (Exception adapterException) { _logger.Warning(adapterException, "Error while disconnecting from adapter."); } + + try + { + var receiverTask = WaitForTaskAsync(_packetReceiverTask, sender); + var keepAliveTask = WaitForTaskAsync(_keepAlivePacketsSenderTask, sender); + + await Task.WhenAll(receiverTask, keepAliveTask).ConfigureAwait(false); + } + catch (Exception e) + { + _logger.Warning(e, "Error while waiting for tasks."); + } finally { Dispose(); @@ -346,11 +352,24 @@ namespace MQTTnet.Client try { await _adapter.SendPacketAsync(requestPacket, Options.CommunicationTimeout, cancellationToken).ConfigureAwait(false); + } + catch (Exception e) + { + _logger.Warning(e, "Error when sending packet of type '{0}'.", typeof(TResponsePacket).Name); + packetAwaiter.Cancel(); + } + + try + { return await packetAwaiter.WaitOneAsync(Options.CommunicationTimeout).ConfigureAwait(false); } - catch (MqttCommunicationTimedOutException) + catch (Exception exception) { - _logger.Warning(null, "Timeout while waiting for packet of type '{0}'.", typeof(TResponsePacket).Name); + if (exception is MqttCommunicationTimedOutException) + { + _logger.Warning(null, "Timeout while waiting for packet of type '{0}'.", typeof(TResponsePacket).Name); + } + throw; } } From 3ff5806dbb4b081dbeeaab047f2343491cbed0ce Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Thu, 3 Oct 2019 15:26:33 +0200 Subject: [PATCH 09/51] Dispose queue in managed client. --- .../MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs | 1 + Source/MQTTnet/Internal/BlockingQueue.cs | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs b/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs index 0607d93..50310be 100644 --- a/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs +++ b/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs @@ -252,6 +252,7 @@ namespace MQTTnet.Extensions.ManagedClient _maintainConnectionTask = null; } + _messageQueue.Dispose(); _messageQueueLock.Dispose(); _mqttClient.Dispose(); } diff --git a/Source/MQTTnet/Internal/BlockingQueue.cs b/Source/MQTTnet/Internal/BlockingQueue.cs index 871c40a..6225105 100644 --- a/Source/MQTTnet/Internal/BlockingQueue.cs +++ b/Source/MQTTnet/Internal/BlockingQueue.cs @@ -4,7 +4,7 @@ using System.Threading; namespace MQTTnet.Internal { - public class BlockingQueue + public class BlockingQueue : IDisposable { private readonly object _syncRoot = new object(); private readonly LinkedList _items = new LinkedList(); @@ -108,5 +108,10 @@ namespace MQTTnet.Internal _items.Clear(); } } + + public void Dispose() + { + _gate.Dispose(); + } } } From cfcb858423b97c4563ab4ad7c6e386980932338c Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Fri, 4 Oct 2019 19:51:35 +0200 Subject: [PATCH 10/51] Update libs. --- .../MQTTnet.AspNetCore.Tests.csproj | 8 ++++---- Tests/MQTTnet.Core.Tests/MQTTnet.Tests.csproj | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Tests/MQTTnet.AspNetCore.Tests/MQTTnet.AspNetCore.Tests.csproj b/Tests/MQTTnet.AspNetCore.Tests/MQTTnet.AspNetCore.Tests.csproj index 4569af3..293fa3d 100644 --- a/Tests/MQTTnet.AspNetCore.Tests/MQTTnet.AspNetCore.Tests.csproj +++ b/Tests/MQTTnet.AspNetCore.Tests/MQTTnet.AspNetCore.Tests.csproj @@ -1,14 +1,14 @@  - netcoreapp2.1 + netcoreapp2.2 false - - - + + + diff --git a/Tests/MQTTnet.Core.Tests/MQTTnet.Tests.csproj b/Tests/MQTTnet.Core.Tests/MQTTnet.Tests.csproj index 830051d..fb76bba 100644 --- a/Tests/MQTTnet.Core.Tests/MQTTnet.Tests.csproj +++ b/Tests/MQTTnet.Core.Tests/MQTTnet.Tests.csproj @@ -1,14 +1,14 @@  - netcoreapp2.1 + netcoreapp2.2 false - - - + + + From b1751190083cdd7e8aca90c4b668d101290b09ca Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Fri, 4 Oct 2019 20:01:51 +0200 Subject: [PATCH 11/51] Refactoring and doc updates. --- Build/MQTTnet.nuspec | 2 ++ .../ManagedMqttClient.cs | 6 +++--- Source/MQTTnet/Client/MqttClient.cs | 16 ++++++++-------- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/Build/MQTTnet.nuspec b/Build/MQTTnet.nuspec index cc9aeaa..25d1acf 100644 --- a/Build/MQTTnet.nuspec +++ b/Build/MQTTnet.nuspec @@ -11,6 +11,8 @@ false MQTTnet is a high performance .NET library for MQTT based communication. It provides a MQTT client and a MQTT server (broker) and supports v3.1.0, v3.1.1 and v5.0.0 of the MQTT protocol. +* [Client] Improve connection stability (thanks to @jltjohanlindqvist). +* [ManagedClient] Fixed a memory leak (thanks to @zawodskoj). * [Server] Added support for assigned client IDs (MQTTv5 only) (thanks to @bcrosnier). Copyright Christian Kratky 2016-2019 diff --git a/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs b/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs index 50310be..add5f76 100644 --- a/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs +++ b/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs @@ -279,7 +279,7 @@ namespace MQTTnet.Extensions.ManagedClient } catch (Exception exception) { - _logger.Error(exception, "Unhandled exception while maintaining connection."); + _logger.Error(exception, "Error exception while maintaining connection."); } finally { @@ -328,11 +328,11 @@ namespace MQTTnet.Extensions.ManagedClient } catch (MqttCommunicationException exception) { - _logger.Warning(exception, "Communication exception while maintaining connection."); + _logger.Warning(exception, "Communication error while maintaining connection."); } catch (Exception exception) { - _logger.Error(exception, "Unhandled exception while maintaining connection."); + _logger.Error(exception, "Error exception while maintaining connection."); } } diff --git a/Source/MQTTnet/Client/MqttClient.cs b/Source/MQTTnet/Client/MqttClient.cs index 527a5cd..c8aa859 100644 --- a/Source/MQTTnet/Client/MqttClient.cs +++ b/Source/MQTTnet/Client/MqttClient.cs @@ -284,7 +284,7 @@ namespace MQTTnet.Client } catch (Exception e) { - _logger.Warning(e, "Error while waiting for tasks."); + _logger.Warning(e, "Error while waiting for internal tasks."); } finally { @@ -412,11 +412,11 @@ namespace MQTTnet.Client } else if (exception is MqttCommunicationException) { - _logger.Warning(exception, "MQTT communication exception while sending/receiving keep alive packets."); + _logger.Warning(exception, "Communication error while sending/receiving keep alive packets."); } else { - _logger.Error(exception, "Unhandled exception while sending/receiving keep alive packets."); + _logger.Error(exception, "Error exception while sending/receiving keep alive packets."); } if (!DisconnectIsPending()) @@ -470,11 +470,11 @@ namespace MQTTnet.Client } else if (exception is MqttCommunicationException) { - _logger.Warning(exception, "MQTT communication exception while receiving packets."); + _logger.Warning(exception, "Communication error while receiving packets."); } else { - _logger.Error(exception, "Unhandled exception while receiving packets."); + _logger.Error(exception, "Error while receiving packets."); } _packetDispatcher.Dispatch(exception); @@ -542,11 +542,11 @@ namespace MQTTnet.Client } else if (exception is MqttCommunicationException) { - _logger.Warning(exception, "MQTT communication exception while receiving packets."); + _logger.Warning(exception, "Communication error while receiving packets."); } else { - _logger.Error(exception, "Unhandled exception while receiving packets."); + _logger.Error(exception, "Error while receiving packets."); } _packetDispatcher.Dispatch(exception); @@ -597,7 +597,7 @@ namespace MQTTnet.Client } catch (Exception exception) { - _logger.Error(exception, "Unhandled exception while handling application message."); + _logger.Error(exception, "Error while handling application message."); } } From 781c5d4d7daea6c832c4e23bab93e1d8e006ff7a Mon Sep 17 00:00:00 2001 From: Christoph Stichlberger Date: Tue, 15 Oct 2019 15:26:05 +0200 Subject: [PATCH 12/51] Separate current and reconnect subscsriptions in managed client This change kills several birds with one stone: - by performing the re-publishing of all subscriptions only at reconnect it fixes issue https://github.com/chkr1011/MQTTnet/issues/569 (retained messages are received again with every new subscription that is added) - not sending the subscriptions again with every (un)subscription is also a performance improvement if subscriptions are modified on a regular basis, because only the updated subscriptions are sent to the broker (previously, if you had 100 subscriptions and added a new one, 100 suscriptions were re-sent to the broker along with the new one, causing a significant delay) -until now subscriptions were sent to the broker only every ConnectionCheckInterval which caused unnecessary delays, and for request-response patterns could cause reponse messages to be missed due to the subscription delay. Now (un)subscriptions are published immediately Subscriptions are now cleaned up at logout (after the connection stops being maintained). This is in line with the clearing of the _messageQueue in StopAsync Explanatory note: the _subscriptionsQueuedSignal could ideally be a ManualResetEvent(Slim) but those do not offer an awaitable Wait method, so a SemaphoreSlim is used. This possibly causes a few empty loops in PublishSubscriptionsAsync due to the semaphore being incremented with every SubscribeAsync call but all subscriptions getting processed in one sweep, but those empty loops shouldn't be a problem --- .../ManagedMqttClient.cs | 139 +++++++--- .../ManagedMqttClient_Tests.cs | 259 ++++++++++++++++-- 2 files changed, 336 insertions(+), 62 deletions(-) diff --git a/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs b/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs index add5f76..9dc1ba4 100644 --- a/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs +++ b/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs @@ -19,12 +19,22 @@ namespace MQTTnet.Extensions.ManagedClient public class ManagedMqttClient : IManagedMqttClient { private readonly BlockingQueue _messageQueue = new BlockingQueue(); + + /// + /// The subscriptions are managed in 2 separate buckets: + /// and are processed during normal operation + /// and are moved to the when they get processed. They can be accessed by + /// any thread and are therefore mutex'ed. get sent to the broker + /// at reconnect and are solely owned by . + /// + private readonly Dictionary _reconnectSubscriptions = new Dictionary(); private readonly Dictionary _subscriptions = new Dictionary(); private readonly HashSet _unsubscriptions = new HashSet(); + private readonly SemaphoreSlim _subscriptionsQueuedSignal = new SemaphoreSlim(0); private readonly IMqttClient _mqttClient; private readonly IMqttNetChildLogger _logger; - + private readonly AsyncLock _messageQueueLock = new AsyncLock(); private CancellationTokenSource _connectionCancellationToken; @@ -34,7 +44,6 @@ namespace MQTTnet.Extensions.ManagedClient private ManagedMqttClientStorageManager _storageManager; private bool _disposed; - private bool _subscriptionsNotPushed; public ManagedMqttClient(IMqttClient mqttClient, IMqttNetChildLogger logger) { @@ -169,7 +178,7 @@ namespace MQTTnet.Extensions.ManagedClient } _messageQueue.Enqueue(applicationMessage); - + if (_storageManager != null) { if (removedMessage != null) @@ -206,9 +215,10 @@ namespace MQTTnet.Extensions.ManagedClient foreach (var topicFilter in topicFilters) { _subscriptions[topicFilter.Topic] = topicFilter.QualityOfServiceLevel; - _subscriptionsNotPushed = true; + _unsubscriptions.Remove(topicFilter.Topic); } } + _subscriptionsQueuedSignal.Release(); return Task.FromResult(0); } @@ -223,13 +233,11 @@ namespace MQTTnet.Extensions.ManagedClient { foreach (var topic in topics) { - if (_subscriptions.Remove(topic)) - { - _unsubscriptions.Add(topic); - _subscriptionsNotPushed = true; - } + _subscriptions.Remove(topic); + _unsubscriptions.Add(topic); } } + _subscriptionsQueuedSignal.Release(); return Task.FromResult(0); } @@ -255,6 +263,7 @@ namespace MQTTnet.Extensions.ManagedClient _messageQueue.Dispose(); _messageQueueLock.Dispose(); _mqttClient.Dispose(); + _subscriptionsQueuedSignal.Dispose(); } private void ThrowIfDisposed() @@ -296,6 +305,12 @@ namespace MQTTnet.Extensions.ManagedClient _logger.Info("Stopped"); } + _reconnectSubscriptions.Clear(); + lock (_subscriptions) + { + _subscriptions.Clear(); + _unsubscriptions.Clear(); + } } } @@ -311,16 +326,16 @@ namespace MQTTnet.Extensions.ManagedClient return; } - if (connectionState == ReconnectionResult.Reconnected || _subscriptionsNotPushed) + if (connectionState == ReconnectionResult.Reconnected) { - await SynchronizeSubscriptionsAsync().ConfigureAwait(false); + await PublishReconnectSubscriptionsAsync().ConfigureAwait(false); StartPublishing(); return; } if (connectionState == ReconnectionResult.StillConnected) { - await Task.Delay(Options.ConnectionCheckInterval, cancellationToken).ConfigureAwait(false); + await PublishSubscriptionsAsync(Options.ConnectionCheckInterval, cancellationToken).ConfigureAwait(false); } } catch (OperationCanceledException) @@ -390,7 +405,7 @@ namespace MQTTnet.Extensions.ManagedClient // it from the queue. If not, that means this.PublishAsync has already // removed it, in which case we don't want to do anything. _messageQueue.RemoveFirst(i => i.Id.Equals(message.Id)); - + if (_storageManager != null) { await _storageManager.RemoveAsync(message).ConfigureAwait(false); @@ -415,7 +430,7 @@ namespace MQTTnet.Extensions.ManagedClient using (await _messageQueueLock.WaitAsync(CancellationToken.None).ConfigureAwait(false)) //lock to avoid conflict with this.PublishAsync { _messageQueue.RemoveFirst(i => i.Id.Equals(message.Id)); - + if (_storageManager != null) { await _storageManager.RemoveAsync(message).ConfigureAwait(false); @@ -439,50 +454,84 @@ namespace MQTTnet.Extensions.ManagedClient } } - private async Task SynchronizeSubscriptionsAsync() + private async Task PublishSubscriptionsAsync(TimeSpan timeout, CancellationToken cancellationToken) { - _logger.Info("Synchronizing subscriptions"); + var endTime = DateTime.UtcNow + timeout; + while (await _subscriptionsQueuedSignal.WaitAsync(GetRemainingTime(endTime), cancellationToken).ConfigureAwait(false)) + { + List subscriptions; + HashSet unsubscriptions; - List subscriptions; - HashSet unsubscriptions; + lock (_subscriptions) + { + subscriptions = _subscriptions.Select(i => new TopicFilter { Topic = i.Key, QualityOfServiceLevel = i.Value }).ToList(); + _subscriptions.Clear(); + unsubscriptions = new HashSet(_unsubscriptions); + _unsubscriptions.Clear(); + } - lock (_subscriptions) - { - subscriptions = _subscriptions.Select(i => new TopicFilter { Topic = i.Key, QualityOfServiceLevel = i.Value }).ToList(); + if (!subscriptions.Any() && !unsubscriptions.Any()) + { + continue; + } - unsubscriptions = new HashSet(_unsubscriptions); - _unsubscriptions.Clear(); + _logger.Info("Publishing subscriptions"); - _subscriptionsNotPushed = false; - } + foreach (var unsubscription in unsubscriptions) + { + _reconnectSubscriptions.Remove(unsubscription); + } - if (!subscriptions.Any() && !unsubscriptions.Any()) - { - return; - } + foreach (var subscription in subscriptions) + { + _reconnectSubscriptions[subscription.Topic] = subscription.QualityOfServiceLevel; + } - try - { - if (unsubscriptions.Any()) + try { - await _mqttClient.UnsubscribeAsync(unsubscriptions.ToArray()).ConfigureAwait(false); + if (unsubscriptions.Any()) + { + await _mqttClient.UnsubscribeAsync(unsubscriptions.ToArray()).ConfigureAwait(false); + } + + if (subscriptions.Any()) + { + await _mqttClient.SubscribeAsync(subscriptions.ToArray()).ConfigureAwait(false); + } } + catch (Exception exception) + { + await HandleSubscriptionExceptionAsync(exception).ConfigureAwait(false); + } + } + } - if (subscriptions.Any()) + private async Task PublishReconnectSubscriptionsAsync() + { + _logger.Info("Publishing subscriptions at reconnect"); + + try + { + if (_reconnectSubscriptions.Any()) { + var subscriptions = _reconnectSubscriptions.Select(i => new TopicFilter { Topic = i.Key, QualityOfServiceLevel = i.Value }); await _mqttClient.SubscribeAsync(subscriptions.ToArray()).ConfigureAwait(false); } } catch (Exception exception) { - _logger.Warning(exception, "Synchronizing subscriptions failed."); - _subscriptionsNotPushed = true; + await HandleSubscriptionExceptionAsync(exception).ConfigureAwait(false); + } + } - var synchronizingSubscriptionsFailedHandler = SynchronizingSubscriptionsFailedHandler; - if (SynchronizingSubscriptionsFailedHandler != null) - { - await synchronizingSubscriptionsFailedHandler.HandleSynchronizingSubscriptionsFailedAsync(new ManagedProcessFailedEventArgs(exception)).ConfigureAwait(false); - } + private async Task HandleSubscriptionExceptionAsync(Exception exception) + { + _logger.Warning(exception, "Synchronizing subscriptions failed."); + + var synchronizingSubscriptionsFailedHandler = SynchronizingSubscriptionsFailedHandler; + if (SynchronizingSubscriptionsFailedHandler != null) + { + await synchronizingSubscriptionsFailedHandler.HandleSynchronizingSubscriptionsFailedAsync(new ManagedProcessFailedEventArgs(exception)).ConfigureAwait(false); } } @@ -509,7 +558,7 @@ namespace MQTTnet.Extensions.ManagedClient return ReconnectionResult.NotConnected; } } - + private void StartPublishing() { if (_publishingCancellationToken != null) @@ -536,5 +585,11 @@ namespace MQTTnet.Extensions.ManagedClient _connectionCancellationToken?.Dispose(); _connectionCancellationToken = null; } + + private TimeSpan GetRemainingTime(DateTime endTime) + { + var remainingTime = endTime - DateTime.UtcNow; + return remainingTime < TimeSpan.Zero ? TimeSpan.Zero : remainingTime; + } } } diff --git a/Tests/MQTTnet.Core.Tests/ManagedMqttClient_Tests.cs b/Tests/MQTTnet.Core.Tests/ManagedMqttClient_Tests.cs index 0aeea6d..1cf9ab9 100644 --- a/Tests/MQTTnet.Core.Tests/ManagedMqttClient_Tests.cs +++ b/Tests/MQTTnet.Core.Tests/ManagedMqttClient_Tests.cs @@ -1,10 +1,13 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using MQTTnet.Client; using MQTTnet.Client.Connecting; using MQTTnet.Client.Options; +using MQTTnet.Client.Receiving; using MQTTnet.Diagnostics; using MQTTnet.Extensions.ManagedClient; using MQTTnet.Server; @@ -95,21 +98,20 @@ namespace MQTTnet.Tests var clientOptions = new MqttClientOptionsBuilder() .WithTcpServer("localhost", testEnvironment.ServerPort); - TaskCompletionSource connected = new TaskCompletionSource(); - managedClient.ConnectedHandler = new MqttClientConnectedHandlerDelegate(e => { connected.SetResult(true);}); + var connected = GetConnectedTask(managedClient); await managedClient.StartAsync(new ManagedMqttClientOptionsBuilder() .WithClientOptions(clientOptions) .Build()); - await connected.Task; + await connected; await managedClient.StopAsync(); Assert.AreEqual(0, (await server.GetClientStatusAsync()).Count); } } - + [TestMethod] public async Task Storage_Queue_Drains() { @@ -127,12 +129,7 @@ namespace MQTTnet.Tests .WithTcpServer("localhost", testEnvironment.ServerPort); var storage = new ManagedMqttClientTestStorage(); - TaskCompletionSource connected = new TaskCompletionSource(); - managedClient.ConnectedHandler = new MqttClientConnectedHandlerDelegate(e => - { - managedClient.ConnectedHandler = null; - connected.SetResult(true); - }); + var connected = GetConnectedTask(managedClient); await managedClient.StartAsync(new ManagedMqttClientOptionsBuilder() .WithClientOptions(clientOptions) @@ -140,7 +137,7 @@ namespace MQTTnet.Tests .WithAutoReconnectDelay(System.TimeSpan.FromSeconds(5)) .Build()); - await connected.Task; + await connected; await testEnvironment.Server.StopAsync(); @@ -151,17 +148,12 @@ namespace MQTTnet.Tests //in storage at this point (i.e. no waiting). Assert.AreEqual(1, storage.GetMessageCount()); - connected = new TaskCompletionSource(); - managedClient.ConnectedHandler = new MqttClientConnectedHandlerDelegate(e => - { - managedClient.ConnectedHandler = null; - connected.SetResult(true); - }); + connected = GetConnectedTask(managedClient); await testEnvironment.Server.StartAsync(new MqttServerOptionsBuilder() .WithDefaultEndpointPort(testEnvironment.ServerPort).Build()); - await connected.Task; + await connected; //Wait 500ms here so the client has time to publish the queued message await Task.Delay(500); @@ -171,8 +163,235 @@ namespace MQTTnet.Tests await managedClient.StopAsync(); } } + + [TestMethod] + public async Task Subscriptions_And_Unsubscriptions_Are_Made_And_Reestablished_At_Reconnect() + { + using (var testEnvironment = new TestEnvironment()) + { + var unmanagedClient = testEnvironment.CreateClient(); + var managedClient = await CreateManagedClientAsync(testEnvironment, unmanagedClient); + + var received = SetupReceivingOfMessages(managedClient, 2); + + // Perform some opposing subscriptions and unsubscriptions to verify + // that these conflicting subscriptions are handled correctly + await managedClient.SubscribeAsync("keptSubscribed"); + await managedClient.SubscribeAsync("subscribedThenUnsubscribed"); + + await managedClient.UnsubscribeAsync("subscribedThenUnsubscribed"); + await managedClient.UnsubscribeAsync("unsubscribedThenSubscribed"); + + await managedClient.SubscribeAsync("unsubscribedThenSubscribed"); + + //wait a bit for the subscriptions to become established before the messages are published + await Task.Delay(500); + + var sendingClient = await testEnvironment.ConnectClientAsync(); + + async Task PublishMessages() + { + await sendingClient.PublishAsync("keptSubscribed", new byte[] { 1 }); + await sendingClient.PublishAsync("subscribedThenUnsubscribed", new byte[] { 1 }); + await sendingClient.PublishAsync("unsubscribedThenSubscribed", new byte[] { 1 }); + } + + await PublishMessages(); + + async Task AssertMessagesReceived() + { + var messages = await received; + Assert.AreEqual("keptSubscribed", messages[0].Topic); + Assert.AreEqual("unsubscribedThenSubscribed", messages[1].Topic); + } + + await AssertMessagesReceived(); + + var connected = GetConnectedTask(managedClient); + + await unmanagedClient.DisconnectAsync(); + + // the managed client has to reconnect by itself + await connected; + + // wait a bit so that the managed client can reestablish the subscriptions + await Task.Delay(500); + + received = SetupReceivingOfMessages(managedClient, 2); + + await PublishMessages(); + + // and then the same subscriptions need to exist again + await AssertMessagesReceived(); + } + } + + // This case also serves as a regression test for the previous behavior which re-published + // each and every existing subscriptions with every new subscription that was made + // (causing performance problems and having the visible symptom of retained messages being received again) + [TestMethod] + public async Task Subscriptions_Subscribe_Only_New_Subscriptions() + { + using (var testEnvironment = new TestEnvironment()) + { + var managedClient = await CreateManagedClientAsync(testEnvironment); + + var sendingClient = await testEnvironment.ConnectClientAsync(); + + await managedClient.SubscribeAsync("topic"); + + //wait a bit for the subscription to become established + await Task.Delay(500); + + await sendingClient.PublishAsync(new MqttApplicationMessage { Topic = "topic", Payload = new byte[] { 1 }, Retain = true }); + + var messages = await SetupReceivingOfMessages(managedClient, 1); + + Assert.AreEqual(1, messages.Count); + Assert.AreEqual("topic", messages.Single().Topic); + + await managedClient.SubscribeAsync("anotherTopic"); + + await Task.Delay(500); + + // The subscription of the other topic must not trigger a re-subscription of the existing topic + // (and thus renewed receiving of the retained message) + Assert.AreEqual(1, messages.Count); + } + } + + // This case also serves as a regression test for the previous behavior + // that subscriptions were only published at the ConnectionCheckInterval + [TestMethod] + public async Task Subscriptions_Are_Published_Immediately() + { + using (var testEnvironment = new TestEnvironment()) + { + // Use a long connection check interval to verify that the subscriptions + // do not depend on the connection check interval anymore + var connectionCheckInterval = TimeSpan.FromSeconds(10); + var managedClient = await CreateManagedClientAsync(testEnvironment, null, connectionCheckInterval); + var sendingClient = await testEnvironment.ConnectClientAsync(); + await sendingClient.PublishAsync(new MqttApplicationMessage { Topic = "topic", Payload = new byte[] { 1 }, Retain = true }); + + await managedClient.SubscribeAsync("topic"); + + var subscribeTime = DateTime.UtcNow; + + var messages = await SetupReceivingOfMessages(managedClient, 1); + + var elapsed = DateTime.UtcNow - subscribeTime; + Assert.IsTrue(elapsed < TimeSpan.FromSeconds(1), $"Subscriptions must be activated immediately, this one took {elapsed}"); + Assert.AreEqual(messages.Single().Topic, "topic"); + } + } + + [TestMethod] + public async Task Subscriptions_Are_Cleared_At_Logout() + { + using (var testEnvironment = new TestEnvironment()) + { + var managedClient = await CreateManagedClientAsync(testEnvironment); + + var sendingClient = await testEnvironment.ConnectClientAsync(); + await sendingClient.PublishAsync(new MqttApplicationMessage + { Topic = "topic", Payload = new byte[] { 1 }, Retain = true }); + + // Wait a bit for the retained message to be available + await Task.Delay(500); + + await managedClient.SubscribeAsync("topic"); + + await SetupReceivingOfMessages(managedClient, 1); + + await managedClient.StopAsync(); + + var clientOptions = new MqttClientOptionsBuilder() + .WithTcpServer("localhost", testEnvironment.ServerPort); + await managedClient.StartAsync(new ManagedMqttClientOptionsBuilder() + .WithClientOptions(clientOptions) + .WithAutoReconnectDelay(TimeSpan.FromSeconds(1)) + .Build()); + + var messages = new List(); + managedClient.ApplicationMessageReceivedHandler = new MqttApplicationMessageReceivedHandlerDelegate(r => + { + messages.Add(r.ApplicationMessage); + }); + + await Task.Delay(500); + + // After reconnect and then some delay, the retained message must not be received, + // showing that the subscriptions were cleared + Assert.AreEqual(0, messages.Count); + } + } + + private async Task CreateManagedClientAsync( + TestEnvironment testEnvironment, + IMqttClient underlyingClient = null, + TimeSpan? connectionCheckInterval = null) + { + await testEnvironment.StartServerAsync(); + + var clientOptions = new MqttClientOptionsBuilder() + .WithTcpServer("localhost", testEnvironment.ServerPort); + + var managedOptions = new ManagedMqttClientOptionsBuilder() + .WithClientOptions(clientOptions) + .Build(); + + // Use a short connection check interval so that subscription operations are performed quickly + // in order to verify against a previous implementation that performed subscriptions only + // at connection check intervals + managedOptions.ConnectionCheckInterval = connectionCheckInterval ?? TimeSpan.FromSeconds(0.1); + + var managedClient = + new ManagedMqttClient(underlyingClient ?? testEnvironment.CreateClient(), new MqttNetLogger().CreateChildLogger()); + + var connected = GetConnectedTask(managedClient); + + await managedClient.StartAsync(managedOptions); + + await connected; + + return managedClient; + } + + /// + /// Returns a task that will finish when the has connected + /// + private Task GetConnectedTask(ManagedMqttClient managedClient) + { + TaskCompletionSource connected = new TaskCompletionSource(); + managedClient.ConnectedHandler = new MqttClientConnectedHandlerDelegate(e => + { + managedClient.ConnectedHandler = null; + connected.SetResult(true); + }); + return connected.Task; + } + + /// + /// Returns a task that will return the messages received on + /// when have been received + /// + private Task> SetupReceivingOfMessages(ManagedMqttClient managedClient, int expectedNumberOfMessages) + { + var receivedMessages = new List(); + var allReceived = new TaskCompletionSource>(); + managedClient.ApplicationMessageReceivedHandler = new MqttApplicationMessageReceivedHandlerDelegate(r => + { + receivedMessages.Add(r.ApplicationMessage); + if (receivedMessages.Count == expectedNumberOfMessages) + { + allReceived.SetResult(receivedMessages); + } + }); + return allReceived.Task; + } } - + public class ManagedMqttClientTestStorage : IManagedMqttClientStorage { private IList _messages = null; From 44d71b884c0a21dcd5a7fb9526ee4bb72abbfcae Mon Sep 17 00:00:00 2001 From: Christoph Stichlberger Date: Thu, 17 Oct 2019 06:45:46 +0200 Subject: [PATCH 13/51] Adapt test assembly paths in buildscript --- appveyor.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 498a7bf..ca539e1 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -14,5 +14,5 @@ build: verbosity: minimal test_script: -- cmd: dotnet vstest "%APPVEYOR_BUILD_FOLDER%\Tests\MQTTnet.Core.Tests\bin\Release\netcoreapp2.1\MQTTnet.Tests.dll" -- cmd: dotnet vstest "%APPVEYOR_BUILD_FOLDER%\Tests\MQTTnet.AspNetCore.Tests\bin\Release\netcoreapp2.1\MQTTnet.AspNetCore.Tests.dll" +- cmd: dotnet vstest "%APPVEYOR_BUILD_FOLDER%\Tests\MQTTnet.Core.Tests\bin\Release\netcoreapp2.2\MQTTnet.Tests.dll" +- cmd: dotnet vstest "%APPVEYOR_BUILD_FOLDER%\Tests\MQTTnet.AspNetCore.Tests\bin\Release\netcoreapp2.2\MQTTnet.AspNetCore.Tests.dll" From 152303dfbd29ec611b453be081a7cbf6be43c391 Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Fri, 18 Oct 2019 19:31:40 +0200 Subject: [PATCH 14/51] Update docs. --- Build/MQTTnet.nuspec | 1 + 1 file changed, 1 insertion(+) diff --git a/Build/MQTTnet.nuspec b/Build/MQTTnet.nuspec index 25d1acf..8227d22 100644 --- a/Build/MQTTnet.nuspec +++ b/Build/MQTTnet.nuspec @@ -13,6 +13,7 @@ * [Client] Improve connection stability (thanks to @jltjohanlindqvist). * [ManagedClient] Fixed a memory leak (thanks to @zawodskoj). +* [ManagedClient] Improved internal subscription management (#569, thanks to @cstichlberger). * [Server] Added support for assigned client IDs (MQTTv5 only) (thanks to @bcrosnier). Copyright Christian Kratky 2016-2019 From 7479e320ee1f9577b40799b20a905b4f55f92f58 Mon Sep 17 00:00:00 2001 From: Christoph Stichlberger Date: Wed, 23 Oct 2019 17:01:44 +0200 Subject: [PATCH 15/51] Turn subscription publishing loglevel down - the Info level for publishing subscriptions is quite noisy - previously this wasn't a problem because subscription processing was buffered, now the message is logged for nearly every single un/subscription that is made - all other frequently recurring log events are also on level Verbose --- Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs b/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs index 9dc1ba4..89efe6b 100644 --- a/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs +++ b/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs @@ -475,7 +475,7 @@ namespace MQTTnet.Extensions.ManagedClient continue; } - _logger.Info("Publishing subscriptions"); + _logger.Verbose($"Publishing subscriptions ({subscriptions.Count} subscriptions and {unsubscriptions.Count} unsubscriptions)"); foreach (var unsubscription in unsubscriptions) { From 78b57531b60ed3c1f772074bf5f4550a5bc3eeb0 Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Thu, 24 Oct 2019 20:09:11 +0200 Subject: [PATCH 16/51] Update docs. --- Build/MQTTnet.nuspec | 1 + 1 file changed, 1 insertion(+) diff --git a/Build/MQTTnet.nuspec b/Build/MQTTnet.nuspec index 8227d22..016bfdb 100644 --- a/Build/MQTTnet.nuspec +++ b/Build/MQTTnet.nuspec @@ -14,6 +14,7 @@ * [Client] Improve connection stability (thanks to @jltjohanlindqvist). * [ManagedClient] Fixed a memory leak (thanks to @zawodskoj). * [ManagedClient] Improved internal subscription management (#569, thanks to @cstichlberger). +* [ManagedClient] Refactored log messages (thanks to @cstichlberger). * [Server] Added support for assigned client IDs (MQTTv5 only) (thanks to @bcrosnier). Copyright Christian Kratky 2016-2019 From c467c9c818b54b87b6a6c1755e2045d088f75c2e Mon Sep 17 00:00:00 2001 From: Lucas Rosa Date: Tue, 29 Oct 2019 11:44:41 -0300 Subject: [PATCH 17/51] Debugging duplicated client id issue --- .../Server/MqttClientSessionsManager.cs | 80 +++++++++-- .../Server/MqttClientSubscriptionsManager.cs | 7 + .../Server/MqttServerEventDispatcher.cs | 2 + .../MQTTnet.Core.Tests/Server_Status_Tests.cs | 5 +- Tests/MQTTnet.Core.Tests/Server_Tests.cs | 128 +++++++++++++++++- 5 files changed, 208 insertions(+), 14 deletions(-) diff --git a/Source/MQTTnet/Server/MqttClientSessionsManager.cs b/Source/MQTTnet/Server/MqttClientSessionsManager.cs index db70e95..1e7c1b6 100644 --- a/Source/MQTTnet/Server/MqttClientSessionsManager.cs +++ b/Source/MQTTnet/Server/MqttClientSessionsManager.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.IO; using System.Threading; using System.Threading.Tasks; using MQTTnet.Adapter; @@ -29,6 +30,27 @@ namespace MQTTnet.Server private readonly IMqttServerOptions _options; private readonly IMqttNetChildLogger _logger; + public static class TestLogger + { + public static void WriteLine(string message) + { + var path = @"c:\temp\test1.txt"; + FileStream logFile; + if (!System.IO.File.Exists(path)) + logFile = System.IO.File.Create(path); + else + logFile = System.IO.File.Open(path, FileMode.Append); + + using (var writer = new System.IO.StreamWriter(logFile)) + { + writer.WriteLine($"{DateTime.Now} - {message}"); + } + + logFile.Dispose(); + } + } + + public MqttClientSessionsManager( IMqttServerOptions options, MqttRetainedMessagesManager retainedMessagesManager, @@ -36,6 +58,7 @@ namespace MQTTnet.Server MqttServerEventDispatcher eventDispatcher, IMqttNetChildLogger logger) { + TestLogger.WriteLine("Newly new"); _cancellationToken = cancellationToken; if (logger == null) throw new ArgumentNullException(nameof(logger)); @@ -48,11 +71,13 @@ namespace MQTTnet.Server public void Start() { + TestLogger.WriteLine("Start"); Task.Run(() => TryProcessQueuedApplicationMessagesAsync(_cancellationToken), _cancellationToken).Forget(_logger); } public async Task StopAsync() { + TestLogger.WriteLine("Stop"); foreach (var connection in _connections.Values) { await connection.StopAsync().ConfigureAwait(false); @@ -66,6 +91,7 @@ namespace MQTTnet.Server public Task> GetClientStatusAsync() { + TestLogger.WriteLine("Status"); var result = new List(); foreach (var connection in _connections.Values) @@ -85,6 +111,7 @@ namespace MQTTnet.Server public Task> GetSessionStatusAsync() { + TestLogger.WriteLine("Session"); var result = new List(); foreach (var session in _sessions.Values) @@ -100,6 +127,7 @@ namespace MQTTnet.Server public void DispatchApplicationMessage(MqttApplicationMessage applicationMessage, MqttClientConnection sender) { + TestLogger.WriteLine("Message"); if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage)); _messageQueue.Enqueue(new MqttEnqueuedApplicationMessage(applicationMessage, sender)); @@ -107,6 +135,7 @@ namespace MQTTnet.Server public Task SubscribeAsync(string clientId, ICollection topicFilters) { + TestLogger.WriteLine("sub"); if (clientId == null) throw new ArgumentNullException(nameof(clientId)); if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); @@ -120,6 +149,7 @@ namespace MQTTnet.Server public Task UnsubscribeAsync(string clientId, IEnumerable topicFilters) { + TestLogger.WriteLine("unsub"); if (clientId == null) throw new ArgumentNullException(nameof(clientId)); if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); @@ -133,6 +163,7 @@ namespace MQTTnet.Server public async Task DeleteSessionAsync(string clientId) { + TestLogger.WriteLine("Delete"); if (_connections.TryGetValue(clientId, out var connection)) { await connection.StopAsync().ConfigureAwait(false); @@ -147,11 +178,13 @@ namespace MQTTnet.Server public void Dispose() { + TestLogger.WriteLine("byebye"); _messageQueue?.Dispose(); } private async Task TryProcessQueuedApplicationMessagesAsync(CancellationToken cancellationToken) { + TestLogger.WriteLine("queue"); while (!cancellationToken.IsCancellationRequested) { try @@ -170,6 +203,7 @@ namespace MQTTnet.Server private async Task TryProcessNextQueuedApplicationMessageAsync(CancellationToken cancellationToken) { + TestLogger.WriteLine("process message"); try { if (cancellationToken.IsCancellationRequested) @@ -178,6 +212,7 @@ namespace MQTTnet.Server } var dequeueResult = await _messageQueue.TryDequeueAsync(cancellationToken).ConfigureAwait(false); + TestLogger.WriteLine("dequeued"); var queuedApplicationMessage = dequeueResult.Item; var sender = queuedApplicationMessage.Sender; @@ -209,6 +244,7 @@ namespace MQTTnet.Server await _retainedMessagesManager.HandleMessageAsync(sender?.ClientId, applicationMessage).ConfigureAwait(false); } + TestLogger.WriteLine($"sessions: {_sessions.Count}"); foreach (var clientSession in _sessions.Values) { clientSession.EnqueueApplicationMessage( @@ -219,18 +255,23 @@ namespace MQTTnet.Server } catch (OperationCanceledException) { + TestLogger.WriteLine($"no queue"); } catch (Exception exception) { + TestLogger.WriteLine($"no queue {exception}"); _logger.Error(exception, "Unhandled exception while processing next queued application message."); } } private async Task HandleClientAsync(IMqttChannelAdapter channelAdapter, CancellationToken cancellationToken) { + TestLogger.WriteLine($"handle"); var disconnectType = MqttClientDisconnectType.NotClean; string clientId = null; + var ok = true; + try { var firstPacket = await channelAdapter.ReceivePacketAsync(_options.DefaultCommunicationTimeout, cancellationToken).ConfigureAwait(false); @@ -241,11 +282,14 @@ namespace MQTTnet.Server } clientId = connectPacket.ClientId; + TestLogger.WriteLine($"validating {clientId}"); var connectionValidatorContext = await ValidateConnectionAsync(connectPacket, channelAdapter).ConfigureAwait(false); if (connectionValidatorContext.ReasonCode != MqttConnectReasonCode.Success) { + TestLogger.WriteLine($"{clientId} not good"); + ok = false; // Send failure response here without preparing a session. The result for a successful connect // will be sent from the session itself. var connAckPacket = channelAdapter.PacketFormatterAdapter.DataConverter.CreateConnAckPacket(connectionValidatorContext); @@ -254,42 +298,53 @@ namespace MQTTnet.Server return; } + TestLogger.WriteLine($"{clientId} good"); + var connection = await CreateConnectionAsync(connectPacket, connectionValidatorContext, channelAdapter).ConfigureAwait(false); await _eventDispatcher.HandleClientConnectedAsync(clientId).ConfigureAwait(false); disconnectType = await connection.RunAsync().ConfigureAwait(false); + + TestLogger.WriteLine($"{clientId} all good"); } catch (OperationCanceledException) { + TestLogger.WriteLine($"no"); } catch (Exception exception) { + TestLogger.WriteLine($"no {exception}"); _logger.Error(exception, exception.Message); } finally { - if (clientId != null) - { - _connections.TryRemove(clientId, out _); - - if (!_options.EnablePersistentSessions) + if (ok) + { + TestLogger.WriteLine($"finally {clientId}"); + if (clientId != null) { - await DeleteSessionAsync(clientId).ConfigureAwait(false); + _connections.TryRemove(clientId, out _); + + if (!_options.EnablePersistentSessions) + { + await DeleteSessionAsync(clientId).ConfigureAwait(false); + } } - } - await TryCleanupChannelAsync(channelAdapter).ConfigureAwait(false); + await TryCleanupChannelAsync(channelAdapter).ConfigureAwait(false); - if (clientId != null) - { - await _eventDispatcher.TryHandleClientDisconnectedAsync(clientId, disconnectType).ConfigureAwait(false); + if (clientId != null) + { + await _eventDispatcher.TryHandleClientDisconnectedAsync(clientId, disconnectType).ConfigureAwait(false); + } } } } private async Task ValidateConnectionAsync(MqttConnectPacket connectPacket, IMqttChannelAdapter channelAdapter) { + TestLogger.WriteLine("validate"); var context = new MqttConnectionValidatorContext(connectPacket, channelAdapter, new ConcurrentDictionary()); var connectionValidator = _options.ConnectionValidator; @@ -318,6 +373,7 @@ namespace MQTTnet.Server private async Task CreateConnectionAsync(MqttConnectPacket connectPacket, MqttConnectionValidatorContext connectionValidatorContext, IMqttChannelAdapter channelAdapter) { + TestLogger.WriteLine("create"); await _createConnectionGate.WaitAsync(_cancellationToken).ConfigureAwait(false); try { @@ -364,6 +420,7 @@ namespace MQTTnet.Server private async Task InterceptApplicationMessageAsync(MqttClientConnection senderConnection, MqttApplicationMessage applicationMessage) { + TestLogger.WriteLine("intercept"); var interceptor = _options.ApplicationMessageInterceptor; if (interceptor == null) { @@ -392,6 +449,7 @@ namespace MQTTnet.Server private async Task TryCleanupChannelAsync(IMqttChannelAdapter channelAdapter) { + TestLogger.WriteLine("clean"); try { await channelAdapter.DisconnectAsync(_options.DefaultCommunicationTimeout, CancellationToken.None).ConfigureAwait(false); diff --git a/Source/MQTTnet/Server/MqttClientSubscriptionsManager.cs b/Source/MQTTnet/Server/MqttClientSubscriptionsManager.cs index c84a018..3b166ba 100644 --- a/Source/MQTTnet/Server/MqttClientSubscriptionsManager.cs +++ b/Source/MQTTnet/Server/MqttClientSubscriptionsManager.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Threading.Tasks; using MQTTnet.Packets; using MQTTnet.Protocol; +using static MQTTnet.Server.MqttClientSessionsManager; namespace MQTTnet.Server { @@ -16,6 +17,7 @@ namespace MQTTnet.Server public MqttClientSubscriptionsManager(MqttClientSession clientSession, MqttServerEventDispatcher eventDispatcher, IMqttServerOptions serverOptions) { + TestLogger.WriteLine("sub manager"); _clientSession = clientSession ?? throw new ArgumentNullException(nameof(clientSession)); // TODO: Consider removing the server options here and build a new class "ISubscriptionInterceptor" and just pass it. The instance is generated in the root server class upon start. @@ -25,6 +27,7 @@ namespace MQTTnet.Server public async Task SubscribeAsync(MqttSubscribePacket subscribePacket, MqttConnectPacket connectPacket) { + TestLogger.WriteLine("sub1"); if (subscribePacket == null) throw new ArgumentNullException(nameof(subscribePacket)); if (connectPacket == null) throw new ArgumentNullException(nameof(connectPacket)); @@ -76,6 +79,7 @@ namespace MQTTnet.Server public async Task SubscribeAsync(IEnumerable topicFilters) { + TestLogger.WriteLine("sub2"); if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); foreach (var topicFilter in topicFilters) @@ -100,6 +104,7 @@ namespace MQTTnet.Server public async Task UnsubscribeAsync(MqttUnsubscribePacket unsubscribePacket) { + TestLogger.WriteLine("unsub1"); if (unsubscribePacket == null) throw new ArgumentNullException(nameof(unsubscribePacket)); var unsubAckPacket = new MqttUnsubAckPacket @@ -132,6 +137,7 @@ namespace MQTTnet.Server public Task UnsubscribeAsync(IEnumerable topicFilters) { + TestLogger.WriteLine("unsub2"); if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); lock (_subscriptions) @@ -147,6 +153,7 @@ namespace MQTTnet.Server public CheckSubscriptionsResult CheckSubscriptions(string topic, MqttQualityOfServiceLevel qosLevel) { + TestLogger.WriteLine("check"); var qosLevels = new HashSet(); lock (_subscriptions) diff --git a/Source/MQTTnet/Server/MqttServerEventDispatcher.cs b/Source/MQTTnet/Server/MqttServerEventDispatcher.cs index e6e608a..d230eac 100644 --- a/Source/MQTTnet/Server/MqttServerEventDispatcher.cs +++ b/Source/MQTTnet/Server/MqttServerEventDispatcher.cs @@ -2,6 +2,7 @@ using System.Threading.Tasks; using MQTTnet.Client.Receiving; using MQTTnet.Diagnostics; +using static MQTTnet.Server.MqttClientSessionsManager; namespace MQTTnet.Server { @@ -55,6 +56,7 @@ namespace MQTTnet.Server public Task HandleClientSubscribedTopicAsync(string clientId, TopicFilter topicFilter) { + TestLogger.WriteLine("handle sub"); var handler = ClientSubscribedTopicHandler; if (handler == null) { diff --git a/Tests/MQTTnet.Core.Tests/Server_Status_Tests.cs b/Tests/MQTTnet.Core.Tests/Server_Status_Tests.cs index 8111bd0..c4a5d27 100644 --- a/Tests/MQTTnet.Core.Tests/Server_Status_Tests.cs +++ b/Tests/MQTTnet.Core.Tests/Server_Status_Tests.cs @@ -6,6 +6,7 @@ using MQTTnet.Tests.Mockups; using MQTTnet.Client; using MQTTnet.Protocol; using MQTTnet.Server; +using System.Threading; namespace MQTTnet.Tests { @@ -55,10 +56,10 @@ namespace MQTTnet.Tests var c1 = await testEnvironment.ConnectClientAsync(new MqttClientOptionsBuilder().WithClientId("client1")); - await Task.Delay(500); + await Task.Delay(1000); var clientStatus = await server.GetClientStatusAsync(); - + Assert.AreEqual(1, clientStatus.Count); Assert.IsTrue(clientStatus.Any(s => s.ClientId == "client1")); diff --git a/Tests/MQTTnet.Core.Tests/Server_Tests.cs b/Tests/MQTTnet.Core.Tests/Server_Tests.cs index b2b3b70..5353ebd 100644 --- a/Tests/MQTTnet.Core.Tests/Server_Tests.cs +++ b/Tests/MQTTnet.Core.Tests/Server_Tests.cs @@ -917,6 +917,109 @@ namespace MQTTnet.Tests } } + + private Dictionary _connected; + private void ConnectionValidationHandler(MqttConnectionValidatorContext eventArgs) + { + if (_connected.ContainsKey(eventArgs.ClientId)) + { + eventArgs.ReasonCode = MqttConnectReasonCode.BadUserNameOrPassword; + return; + } + _connected[eventArgs.ClientId] = true; + eventArgs.ReasonCode = MqttConnectReasonCode.Success; + return; + } + + [TestMethod] + public async Task Same_Client_Id_Refuse_Connection() + { + using (var testEnvironment = new TestEnvironment()) + { + _connected = new Dictionary(); + var options = new MqttServerOptionsBuilder(); + options.WithConnectionValidator(e => ConnectionValidationHandler(e)); + var server = await testEnvironment.StartServerAsync(options); + + var events = new List(); + + server.ClientConnectedHandler = new MqttServerClientConnectedHandlerDelegate(_ => + { + lock (events) + { + events.Add("c"); + } + }); + + server.ClientDisconnectedHandler = new MqttServerClientDisconnectedHandlerDelegate(_ => + { + lock (events) + { + events.Add("d"); + } + }); + + var clientOptions = new MqttClientOptionsBuilder() + .WithClientId("same_id"); + + // c + var c1 = await testEnvironment.ConnectClientAsync(clientOptions); + + c1.UseDisconnectedHandler(_ => + { + lock (events) + { + events.Add("x"); + } + }); + + + c1.UseApplicationMessageReceivedHandler(_ => + { + lock (events) + { + events.Add("r"); + } + + }); + + c1.SubscribeAsync("topic").Wait(); + + await Task.Delay(500); + + c1.PublishAsync("topic").Wait(); + + await Task.Delay(500); + + + var flow = string.Join(string.Empty, events); + Assert.AreEqual("cr", flow); + + try + { + await testEnvironment.ConnectClientAsync(clientOptions); + Assert.Fail("same id connection is expected to fail"); + } + catch + { + //same id connection is expected to fail + } + + await Task.Delay(500); + + flow = string.Join(string.Empty, events); + Assert.AreEqual("cr", flow); + + c1.PublishAsync("topic").Wait(); + + await Task.Delay(500); + + flow = string.Join(string.Empty, events); + Assert.AreEqual("crr", flow); + + } + } + [TestMethod] public async Task Same_Client_Id_Connect_Disconnect_Event_Order() { @@ -956,17 +1059,40 @@ namespace MQTTnet.Tests // dc var c2 = await testEnvironment.ConnectClientAsync(clientOptions); + c2.UseApplicationMessageReceivedHandler(_ => + { + lock (events) + { + events.Add("r"); + } + + }); + c2.SubscribeAsync("topic").Wait(); + await Task.Delay(500); flow = string.Join(string.Empty, events); Assert.AreEqual("cdc", flow); + // r + c2.PublishAsync("topic").Wait(); + + await Task.Delay(500); + + flow = string.Join(string.Empty, events); + Assert.AreEqual("cdcr", flow); + + // nothing + + Assert.AreEqual(false, c1.IsConnected); await c1.DisconnectAsync(); + Assert.AreEqual (false, c1.IsConnected); await Task.Delay(500); // d + Assert.AreEqual(true, c2.IsConnected); await c2.DisconnectAsync(); await Task.Delay(500); @@ -974,7 +1100,7 @@ namespace MQTTnet.Tests await server.StopAsync(); flow = string.Join(string.Empty, events); - Assert.AreEqual("cdcd", flow); + Assert.AreEqual("cdcrd", flow); } } From e3f6b579b2dc56020cdcc26157d40a8edd39a904 Mon Sep 17 00:00:00 2001 From: Lucas Rosa Date: Tue, 29 Oct 2019 12:32:20 -0300 Subject: [PATCH 18/51] Working PoC --- Source/MQTTnet/Server/MqttClientConnection.cs | 11 +++++ .../Server/MqttClientSessionsManager.cs | 40 +++++++++++-------- .../Server/MqttClientSubscriptionsManager.cs | 4 +- .../Mockups/TestEnvironment.cs | 4 +- Tests/MQTTnet.Core.Tests/Server_Tests.cs | 21 ++++++++++ 5 files changed, 60 insertions(+), 20 deletions(-) diff --git a/Source/MQTTnet/Server/MqttClientConnection.cs b/Source/MQTTnet/Server/MqttClientConnection.cs index e71d1a8..677f920 100644 --- a/Source/MQTTnet/Server/MqttClientConnection.cs +++ b/Source/MQTTnet/Server/MqttClientConnection.cs @@ -12,6 +12,7 @@ using MQTTnet.PacketDispatcher; using MQTTnet.Packets; using MQTTnet.Protocol; using MQTTnet.Server.Status; +using static MQTTnet.Server.MqttClientSessionsManager; namespace MQTTnet.Server { @@ -51,6 +52,7 @@ namespace MQTTnet.Server MqttRetainedMessagesManager retainedMessagesManager, IMqttNetChildLogger logger) { + TestLogger.WriteLine($"MqttClientConnection init"); Session = session ?? throw new ArgumentNullException(nameof(session)); _serverOptions = serverOptions ?? throw new ArgumentNullException(nameof(serverOptions)); _sessionsManager = sessionsManager ?? throw new ArgumentNullException(nameof(sessionsManager)); @@ -79,6 +81,7 @@ namespace MQTTnet.Server public async Task StopAsync() { + TestLogger.WriteLine($"MqttClientConnection stop"); StopInternal(); var task = _packageReceiverTask; @@ -153,12 +156,14 @@ namespace MQTTnet.Server while (!_cancellationToken.IsCancellationRequested) { + TestLogger.WriteLine($"MqttClientConnection while"); var packet = await _channelAdapter.ReceivePacketAsync(TimeSpan.Zero, _cancellationToken.Token).ConfigureAwait(false); if (packet == null) { // The client has closed the connection gracefully. break; } + TestLogger.WriteLine($"MqttClientConnection pack"); Interlocked.Increment(ref _sentPacketsCount); _lastPacketReceivedTimestamp = DateTime.UtcNow; @@ -257,11 +262,13 @@ namespace MQTTnet.Server private void StopInternal() { + TestLogger.WriteLine($"MqttClientConnection stop int"); _cancellationToken.Cancel(false); } private async Task EnqueueSubscribedRetainedMessagesAsync(ICollection topicFilters) { + TestLogger.WriteLine($"MqttClientConnection retainedmessages"); var retainedMessages = await _retainedMessagesManager.GetSubscribedMessagesAsync(topicFilters).ConfigureAwait(false); foreach (var applicationMessage in retainedMessages) { @@ -271,6 +278,7 @@ namespace MQTTnet.Server private async Task HandleIncomingSubscribePacketAsync(MqttSubscribePacket subscribePacket) { + TestLogger.WriteLine($"MqttClientConnection subpacket"); // TODO: Let the channel adapter create the packet. var subscribeResult = await Session.SubscriptionsManager.SubscribeAsync(subscribePacket, ConnectPacket).ConfigureAwait(false); @@ -294,6 +302,7 @@ namespace MQTTnet.Server private Task HandleIncomingPublishPacketAsync(MqttPublishPacket publishPacket) { + TestLogger.WriteLine($"MqttClientConnection pub"); Interlocked.Increment(ref _sentApplicationMessagesCount); switch (publishPacket.QualityOfServiceLevel) @@ -351,6 +360,7 @@ namespace MQTTnet.Server private async Task SendPendingPacketsAsync(CancellationToken cancellationToken) { + TestLogger.WriteLine($"MqttClientConnection send"); MqttQueuedApplicationMessage queuedApplicationMessage = null; MqttPublishPacket publishPacket = null; @@ -466,6 +476,7 @@ namespace MQTTnet.Server private async Task SendAsync(MqttBasePacket packet) { + TestLogger.WriteLine($"MqttClientConnection send"); await _channelAdapter.SendPacketAsync(packet, _serverOptions.DefaultCommunicationTimeout, _cancellationToken.Token).ConfigureAwait(false); Interlocked.Increment(ref _receivedPacketsCount); diff --git a/Source/MQTTnet/Server/MqttClientSessionsManager.cs b/Source/MQTTnet/Server/MqttClientSessionsManager.cs index 1e7c1b6..61a3bf2 100644 --- a/Source/MQTTnet/Server/MqttClientSessionsManager.cs +++ b/Source/MQTTnet/Server/MqttClientSessionsManager.cs @@ -32,21 +32,26 @@ namespace MQTTnet.Server public static class TestLogger { + private static object _lock = new object(); public static void WriteLine(string message) { - var path = @"c:\temp\test1.txt"; - FileStream logFile; - if (!System.IO.File.Exists(path)) - logFile = System.IO.File.Create(path); - else - logFile = System.IO.File.Open(path, FileMode.Append); - - using (var writer = new System.IO.StreamWriter(logFile)) + lock (_lock) { - writer.WriteLine($"{DateTime.Now} - {message}"); + var path = @"c:\temp\test1.txt"; + FileStream logFile; + if (!System.IO.File.Exists(path)) + logFile = System.IO.File.Create(path); + else + logFile = System.IO.File.Open(path, FileMode.Append); + + using (var writer = new System.IO.StreamWriter(logFile)) + { + writer.WriteLine($"{DateTime.Now} - {message}"); + } + + logFile.Dispose(); } - logFile.Dispose(); } } @@ -203,7 +208,7 @@ namespace MQTTnet.Server private async Task TryProcessNextQueuedApplicationMessageAsync(CancellationToken cancellationToken) { - TestLogger.WriteLine("process message"); + TestLogger.WriteLine("pm process message"); try { if (cancellationToken.IsCancellationRequested) @@ -212,7 +217,7 @@ namespace MQTTnet.Server } var dequeueResult = await _messageQueue.TryDequeueAsync(cancellationToken).ConfigureAwait(false); - TestLogger.WriteLine("dequeued"); + TestLogger.WriteLine("pm dequeued"); var queuedApplicationMessage = dequeueResult.Item; var sender = queuedApplicationMessage.Sender; @@ -244,7 +249,7 @@ namespace MQTTnet.Server await _retainedMessagesManager.HandleMessageAsync(sender?.ClientId, applicationMessage).ConfigureAwait(false); } - TestLogger.WriteLine($"sessions: {_sessions.Count}"); + TestLogger.WriteLine($"pm sessions: {_sessions.Count}"); foreach (var clientSession in _sessions.Values) { clientSession.EnqueueApplicationMessage( @@ -255,11 +260,11 @@ namespace MQTTnet.Server } catch (OperationCanceledException) { - TestLogger.WriteLine($"no queue"); + TestLogger.WriteLine($"pm no queue"); } catch (Exception exception) { - TestLogger.WriteLine($"no queue {exception}"); + TestLogger.WriteLine($"pm no queue {exception}"); _logger.Error(exception, "Unhandled exception while processing next queued application message."); } } @@ -373,7 +378,7 @@ namespace MQTTnet.Server private async Task CreateConnectionAsync(MqttConnectPacket connectPacket, MqttConnectionValidatorContext connectionValidatorContext, IMqttChannelAdapter channelAdapter) { - TestLogger.WriteLine("create"); + TestLogger.WriteLine("cc create"); await _createConnectionGate.WaitAsync(_cancellationToken).ConfigureAwait(false); try { @@ -402,6 +407,7 @@ namespace MQTTnet.Server if (session == null) { session = new MqttClientSession(connectPacket.ClientId, connectionValidatorContext.SessionItems, _eventDispatcher, _options, _logger); + TestLogger.WriteLine("cc created"); _logger.Verbose("Created a new session for client '{0}'.", connectPacket.ClientId); } @@ -409,6 +415,8 @@ namespace MQTTnet.Server _connections[connection.ClientId] = connection; _sessions[session.ClientId] = session; + TestLogger.WriteLine($"cc connections {_connections.Count}"); + TestLogger.WriteLine($"cc sessions {_sessions.Count}"); return connection; } diff --git a/Source/MQTTnet/Server/MqttClientSubscriptionsManager.cs b/Source/MQTTnet/Server/MqttClientSubscriptionsManager.cs index 3b166ba..3e39bab 100644 --- a/Source/MQTTnet/Server/MqttClientSubscriptionsManager.cs +++ b/Source/MQTTnet/Server/MqttClientSubscriptionsManager.cs @@ -153,7 +153,7 @@ namespace MQTTnet.Server public CheckSubscriptionsResult CheckSubscriptions(string topic, MqttQualityOfServiceLevel qosLevel) { - TestLogger.WriteLine("check"); + TestLogger.WriteLine("check subs"); var qosLevels = new HashSet(); lock (_subscriptions) @@ -164,7 +164,7 @@ namespace MQTTnet.Server { continue; } - + TestLogger.WriteLine("is match"); qosLevels.Add(subscription.Value.QualityOfServiceLevel); } } diff --git a/Tests/MQTTnet.Core.Tests/Mockups/TestEnvironment.cs b/Tests/MQTTnet.Core.Tests/Mockups/TestEnvironment.cs index 5cda537..b5fa8a3 100644 --- a/Tests/MQTTnet.Core.Tests/Mockups/TestEnvironment.cs +++ b/Tests/MQTTnet.Core.Tests/Mockups/TestEnvironment.cs @@ -114,7 +114,7 @@ namespace MQTTnet.Tests.Mockups { if (!IgnoreServerLogErrors && _serverErrors.Count > 0) { - throw new Exception($"Server had {_serverErrors.Count} errors (${string.Join(Environment.NewLine, _serverErrors)})."); + //throw new Exception($"Server had {_serverErrors.Count} errors (${string.Join(Environment.NewLine, _serverErrors)})."); } } @@ -122,7 +122,7 @@ namespace MQTTnet.Tests.Mockups { if (!IgnoreClientLogErrors && _clientErrors.Count > 0) { - throw new Exception($"Client(s) had {_clientErrors.Count} errors (${string.Join(Environment.NewLine, _clientErrors)})."); + //throw new Exception($"Client(s) had {_clientErrors.Count} errors (${string.Join(Environment.NewLine, _clientErrors)})."); } } } diff --git a/Tests/MQTTnet.Core.Tests/Server_Tests.cs b/Tests/MQTTnet.Core.Tests/Server_Tests.cs index 5353ebd..de3e871 100644 --- a/Tests/MQTTnet.Core.Tests/Server_Tests.cs +++ b/Tests/MQTTnet.Core.Tests/Server_Tests.cs @@ -943,6 +943,8 @@ namespace MQTTnet.Tests var events = new List(); + var connected = true; + server.ClientConnectedHandler = new MqttServerClientConnectedHandlerDelegate(_ => { lock (events) @@ -970,6 +972,15 @@ namespace MQTTnet.Tests lock (events) { events.Add("x"); + connected = false; + } + }); + + c1.UseConnectedHandler(_ => + { + lock (events) + { + connected = true; } }); @@ -1007,6 +1018,16 @@ namespace MQTTnet.Tests await Task.Delay(500); + /*if (!connected) + { + c1.ReconnectAsync().Wait(); + } + + if (!c1.IsConnected) + { + c1.ReconnectAsync().Wait(); + }*/ + flow = string.Join(string.Empty, events); Assert.AreEqual("cr", flow); From 5c25905510d2da368df6bd7626ede57057b0d9f5 Mon Sep 17 00:00:00 2001 From: Lucas Rosa Date: Tue, 29 Oct 2019 12:48:04 -0300 Subject: [PATCH 19/51] Clean up --- Source/MQTTnet/Server/MqttClientConnection.cs | 10 ---- .../Server/MqttClientSessionsManager.cs | 58 ------------------- .../Server/MqttClientSubscriptionsManager.cs | 7 --- .../Server/MqttServerEventDispatcher.cs | 1 - .../Mockups/TestEnvironment.cs | 4 +- Tests/MQTTnet.Core.Tests/Server_Tests.cs | 25 +------- 6 files changed, 4 insertions(+), 101 deletions(-) diff --git a/Source/MQTTnet/Server/MqttClientConnection.cs b/Source/MQTTnet/Server/MqttClientConnection.cs index 677f920..a114e28 100644 --- a/Source/MQTTnet/Server/MqttClientConnection.cs +++ b/Source/MQTTnet/Server/MqttClientConnection.cs @@ -52,7 +52,6 @@ namespace MQTTnet.Server MqttRetainedMessagesManager retainedMessagesManager, IMqttNetChildLogger logger) { - TestLogger.WriteLine($"MqttClientConnection init"); Session = session ?? throw new ArgumentNullException(nameof(session)); _serverOptions = serverOptions ?? throw new ArgumentNullException(nameof(serverOptions)); _sessionsManager = sessionsManager ?? throw new ArgumentNullException(nameof(sessionsManager)); @@ -81,7 +80,6 @@ namespace MQTTnet.Server public async Task StopAsync() { - TestLogger.WriteLine($"MqttClientConnection stop"); StopInternal(); var task = _packageReceiverTask; @@ -156,14 +154,12 @@ namespace MQTTnet.Server while (!_cancellationToken.IsCancellationRequested) { - TestLogger.WriteLine($"MqttClientConnection while"); var packet = await _channelAdapter.ReceivePacketAsync(TimeSpan.Zero, _cancellationToken.Token).ConfigureAwait(false); if (packet == null) { // The client has closed the connection gracefully. break; } - TestLogger.WriteLine($"MqttClientConnection pack"); Interlocked.Increment(ref _sentPacketsCount); _lastPacketReceivedTimestamp = DateTime.UtcNow; @@ -262,13 +258,11 @@ namespace MQTTnet.Server private void StopInternal() { - TestLogger.WriteLine($"MqttClientConnection stop int"); _cancellationToken.Cancel(false); } private async Task EnqueueSubscribedRetainedMessagesAsync(ICollection topicFilters) { - TestLogger.WriteLine($"MqttClientConnection retainedmessages"); var retainedMessages = await _retainedMessagesManager.GetSubscribedMessagesAsync(topicFilters).ConfigureAwait(false); foreach (var applicationMessage in retainedMessages) { @@ -278,7 +272,6 @@ namespace MQTTnet.Server private async Task HandleIncomingSubscribePacketAsync(MqttSubscribePacket subscribePacket) { - TestLogger.WriteLine($"MqttClientConnection subpacket"); // TODO: Let the channel adapter create the packet. var subscribeResult = await Session.SubscriptionsManager.SubscribeAsync(subscribePacket, ConnectPacket).ConfigureAwait(false); @@ -302,7 +295,6 @@ namespace MQTTnet.Server private Task HandleIncomingPublishPacketAsync(MqttPublishPacket publishPacket) { - TestLogger.WriteLine($"MqttClientConnection pub"); Interlocked.Increment(ref _sentApplicationMessagesCount); switch (publishPacket.QualityOfServiceLevel) @@ -360,7 +352,6 @@ namespace MQTTnet.Server private async Task SendPendingPacketsAsync(CancellationToken cancellationToken) { - TestLogger.WriteLine($"MqttClientConnection send"); MqttQueuedApplicationMessage queuedApplicationMessage = null; MqttPublishPacket publishPacket = null; @@ -476,7 +467,6 @@ namespace MQTTnet.Server private async Task SendAsync(MqttBasePacket packet) { - TestLogger.WriteLine($"MqttClientConnection send"); await _channelAdapter.SendPacketAsync(packet, _serverOptions.DefaultCommunicationTimeout, _cancellationToken.Token).ConfigureAwait(false); Interlocked.Increment(ref _receivedPacketsCount); diff --git a/Source/MQTTnet/Server/MqttClientSessionsManager.cs b/Source/MQTTnet/Server/MqttClientSessionsManager.cs index 61a3bf2..5a27b46 100644 --- a/Source/MQTTnet/Server/MqttClientSessionsManager.cs +++ b/Source/MQTTnet/Server/MqttClientSessionsManager.cs @@ -30,32 +30,6 @@ namespace MQTTnet.Server private readonly IMqttServerOptions _options; private readonly IMqttNetChildLogger _logger; - public static class TestLogger - { - private static object _lock = new object(); - public static void WriteLine(string message) - { - lock (_lock) - { - var path = @"c:\temp\test1.txt"; - FileStream logFile; - if (!System.IO.File.Exists(path)) - logFile = System.IO.File.Create(path); - else - logFile = System.IO.File.Open(path, FileMode.Append); - - using (var writer = new System.IO.StreamWriter(logFile)) - { - writer.WriteLine($"{DateTime.Now} - {message}"); - } - - logFile.Dispose(); - } - - } - } - - public MqttClientSessionsManager( IMqttServerOptions options, MqttRetainedMessagesManager retainedMessagesManager, @@ -63,7 +37,6 @@ namespace MQTTnet.Server MqttServerEventDispatcher eventDispatcher, IMqttNetChildLogger logger) { - TestLogger.WriteLine("Newly new"); _cancellationToken = cancellationToken; if (logger == null) throw new ArgumentNullException(nameof(logger)); @@ -76,13 +49,11 @@ namespace MQTTnet.Server public void Start() { - TestLogger.WriteLine("Start"); Task.Run(() => TryProcessQueuedApplicationMessagesAsync(_cancellationToken), _cancellationToken).Forget(_logger); } public async Task StopAsync() { - TestLogger.WriteLine("Stop"); foreach (var connection in _connections.Values) { await connection.StopAsync().ConfigureAwait(false); @@ -96,7 +67,6 @@ namespace MQTTnet.Server public Task> GetClientStatusAsync() { - TestLogger.WriteLine("Status"); var result = new List(); foreach (var connection in _connections.Values) @@ -116,7 +86,6 @@ namespace MQTTnet.Server public Task> GetSessionStatusAsync() { - TestLogger.WriteLine("Session"); var result = new List(); foreach (var session in _sessions.Values) @@ -132,7 +101,6 @@ namespace MQTTnet.Server public void DispatchApplicationMessage(MqttApplicationMessage applicationMessage, MqttClientConnection sender) { - TestLogger.WriteLine("Message"); if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage)); _messageQueue.Enqueue(new MqttEnqueuedApplicationMessage(applicationMessage, sender)); @@ -140,7 +108,6 @@ namespace MQTTnet.Server public Task SubscribeAsync(string clientId, ICollection topicFilters) { - TestLogger.WriteLine("sub"); if (clientId == null) throw new ArgumentNullException(nameof(clientId)); if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); @@ -154,7 +121,6 @@ namespace MQTTnet.Server public Task UnsubscribeAsync(string clientId, IEnumerable topicFilters) { - TestLogger.WriteLine("unsub"); if (clientId == null) throw new ArgumentNullException(nameof(clientId)); if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); @@ -168,7 +134,6 @@ namespace MQTTnet.Server public async Task DeleteSessionAsync(string clientId) { - TestLogger.WriteLine("Delete"); if (_connections.TryGetValue(clientId, out var connection)) { await connection.StopAsync().ConfigureAwait(false); @@ -183,13 +148,11 @@ namespace MQTTnet.Server public void Dispose() { - TestLogger.WriteLine("byebye"); _messageQueue?.Dispose(); } private async Task TryProcessQueuedApplicationMessagesAsync(CancellationToken cancellationToken) { - TestLogger.WriteLine("queue"); while (!cancellationToken.IsCancellationRequested) { try @@ -208,7 +171,6 @@ namespace MQTTnet.Server private async Task TryProcessNextQueuedApplicationMessageAsync(CancellationToken cancellationToken) { - TestLogger.WriteLine("pm process message"); try { if (cancellationToken.IsCancellationRequested) @@ -217,7 +179,6 @@ namespace MQTTnet.Server } var dequeueResult = await _messageQueue.TryDequeueAsync(cancellationToken).ConfigureAwait(false); - TestLogger.WriteLine("pm dequeued"); var queuedApplicationMessage = dequeueResult.Item; var sender = queuedApplicationMessage.Sender; @@ -249,7 +210,6 @@ namespace MQTTnet.Server await _retainedMessagesManager.HandleMessageAsync(sender?.ClientId, applicationMessage).ConfigureAwait(false); } - TestLogger.WriteLine($"pm sessions: {_sessions.Count}"); foreach (var clientSession in _sessions.Values) { clientSession.EnqueueApplicationMessage( @@ -260,18 +220,15 @@ namespace MQTTnet.Server } catch (OperationCanceledException) { - TestLogger.WriteLine($"pm no queue"); } catch (Exception exception) { - TestLogger.WriteLine($"pm no queue {exception}"); _logger.Error(exception, "Unhandled exception while processing next queued application message."); } } private async Task HandleClientAsync(IMqttChannelAdapter channelAdapter, CancellationToken cancellationToken) { - TestLogger.WriteLine($"handle"); var disconnectType = MqttClientDisconnectType.NotClean; string clientId = null; @@ -287,13 +244,11 @@ namespace MQTTnet.Server } clientId = connectPacket.ClientId; - TestLogger.WriteLine($"validating {clientId}"); var connectionValidatorContext = await ValidateConnectionAsync(connectPacket, channelAdapter).ConfigureAwait(false); if (connectionValidatorContext.ReasonCode != MqttConnectReasonCode.Success) { - TestLogger.WriteLine($"{clientId} not good"); ok = false; // Send failure response here without preparing a session. The result for a successful connect // will be sent from the session itself. @@ -303,30 +258,24 @@ namespace MQTTnet.Server return; } - TestLogger.WriteLine($"{clientId} good"); - var connection = await CreateConnectionAsync(connectPacket, connectionValidatorContext, channelAdapter).ConfigureAwait(false); await _eventDispatcher.HandleClientConnectedAsync(clientId).ConfigureAwait(false); disconnectType = await connection.RunAsync().ConfigureAwait(false); - TestLogger.WriteLine($"{clientId} all good"); } catch (OperationCanceledException) { - TestLogger.WriteLine($"no"); } catch (Exception exception) { - TestLogger.WriteLine($"no {exception}"); _logger.Error(exception, exception.Message); } finally { if (ok) { - TestLogger.WriteLine($"finally {clientId}"); if (clientId != null) { _connections.TryRemove(clientId, out _); @@ -349,7 +298,6 @@ namespace MQTTnet.Server private async Task ValidateConnectionAsync(MqttConnectPacket connectPacket, IMqttChannelAdapter channelAdapter) { - TestLogger.WriteLine("validate"); var context = new MqttConnectionValidatorContext(connectPacket, channelAdapter, new ConcurrentDictionary()); var connectionValidator = _options.ConnectionValidator; @@ -378,7 +326,6 @@ namespace MQTTnet.Server private async Task CreateConnectionAsync(MqttConnectPacket connectPacket, MqttConnectionValidatorContext connectionValidatorContext, IMqttChannelAdapter channelAdapter) { - TestLogger.WriteLine("cc create"); await _createConnectionGate.WaitAsync(_cancellationToken).ConfigureAwait(false); try { @@ -407,7 +354,6 @@ namespace MQTTnet.Server if (session == null) { session = new MqttClientSession(connectPacket.ClientId, connectionValidatorContext.SessionItems, _eventDispatcher, _options, _logger); - TestLogger.WriteLine("cc created"); _logger.Verbose("Created a new session for client '{0}'.", connectPacket.ClientId); } @@ -415,8 +361,6 @@ namespace MQTTnet.Server _connections[connection.ClientId] = connection; _sessions[session.ClientId] = session; - TestLogger.WriteLine($"cc connections {_connections.Count}"); - TestLogger.WriteLine($"cc sessions {_sessions.Count}"); return connection; } @@ -428,7 +372,6 @@ namespace MQTTnet.Server private async Task InterceptApplicationMessageAsync(MqttClientConnection senderConnection, MqttApplicationMessage applicationMessage) { - TestLogger.WriteLine("intercept"); var interceptor = _options.ApplicationMessageInterceptor; if (interceptor == null) { @@ -457,7 +400,6 @@ namespace MQTTnet.Server private async Task TryCleanupChannelAsync(IMqttChannelAdapter channelAdapter) { - TestLogger.WriteLine("clean"); try { await channelAdapter.DisconnectAsync(_options.DefaultCommunicationTimeout, CancellationToken.None).ConfigureAwait(false); diff --git a/Source/MQTTnet/Server/MqttClientSubscriptionsManager.cs b/Source/MQTTnet/Server/MqttClientSubscriptionsManager.cs index 3e39bab..1d675ba 100644 --- a/Source/MQTTnet/Server/MqttClientSubscriptionsManager.cs +++ b/Source/MQTTnet/Server/MqttClientSubscriptionsManager.cs @@ -17,7 +17,6 @@ namespace MQTTnet.Server public MqttClientSubscriptionsManager(MqttClientSession clientSession, MqttServerEventDispatcher eventDispatcher, IMqttServerOptions serverOptions) { - TestLogger.WriteLine("sub manager"); _clientSession = clientSession ?? throw new ArgumentNullException(nameof(clientSession)); // TODO: Consider removing the server options here and build a new class "ISubscriptionInterceptor" and just pass it. The instance is generated in the root server class upon start. @@ -27,7 +26,6 @@ namespace MQTTnet.Server public async Task SubscribeAsync(MqttSubscribePacket subscribePacket, MqttConnectPacket connectPacket) { - TestLogger.WriteLine("sub1"); if (subscribePacket == null) throw new ArgumentNullException(nameof(subscribePacket)); if (connectPacket == null) throw new ArgumentNullException(nameof(connectPacket)); @@ -79,7 +77,6 @@ namespace MQTTnet.Server public async Task SubscribeAsync(IEnumerable topicFilters) { - TestLogger.WriteLine("sub2"); if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); foreach (var topicFilter in topicFilters) @@ -104,7 +101,6 @@ namespace MQTTnet.Server public async Task UnsubscribeAsync(MqttUnsubscribePacket unsubscribePacket) { - TestLogger.WriteLine("unsub1"); if (unsubscribePacket == null) throw new ArgumentNullException(nameof(unsubscribePacket)); var unsubAckPacket = new MqttUnsubAckPacket @@ -137,7 +133,6 @@ namespace MQTTnet.Server public Task UnsubscribeAsync(IEnumerable topicFilters) { - TestLogger.WriteLine("unsub2"); if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); lock (_subscriptions) @@ -153,7 +148,6 @@ namespace MQTTnet.Server public CheckSubscriptionsResult CheckSubscriptions(string topic, MqttQualityOfServiceLevel qosLevel) { - TestLogger.WriteLine("check subs"); var qosLevels = new HashSet(); lock (_subscriptions) @@ -164,7 +158,6 @@ namespace MQTTnet.Server { continue; } - TestLogger.WriteLine("is match"); qosLevels.Add(subscription.Value.QualityOfServiceLevel); } } diff --git a/Source/MQTTnet/Server/MqttServerEventDispatcher.cs b/Source/MQTTnet/Server/MqttServerEventDispatcher.cs index d230eac..f899081 100644 --- a/Source/MQTTnet/Server/MqttServerEventDispatcher.cs +++ b/Source/MQTTnet/Server/MqttServerEventDispatcher.cs @@ -56,7 +56,6 @@ namespace MQTTnet.Server public Task HandleClientSubscribedTopicAsync(string clientId, TopicFilter topicFilter) { - TestLogger.WriteLine("handle sub"); var handler = ClientSubscribedTopicHandler; if (handler == null) { diff --git a/Tests/MQTTnet.Core.Tests/Mockups/TestEnvironment.cs b/Tests/MQTTnet.Core.Tests/Mockups/TestEnvironment.cs index b5fa8a3..5cda537 100644 --- a/Tests/MQTTnet.Core.Tests/Mockups/TestEnvironment.cs +++ b/Tests/MQTTnet.Core.Tests/Mockups/TestEnvironment.cs @@ -114,7 +114,7 @@ namespace MQTTnet.Tests.Mockups { if (!IgnoreServerLogErrors && _serverErrors.Count > 0) { - //throw new Exception($"Server had {_serverErrors.Count} errors (${string.Join(Environment.NewLine, _serverErrors)})."); + throw new Exception($"Server had {_serverErrors.Count} errors (${string.Join(Environment.NewLine, _serverErrors)})."); } } @@ -122,7 +122,7 @@ namespace MQTTnet.Tests.Mockups { if (!IgnoreClientLogErrors && _clientErrors.Count > 0) { - //throw new Exception($"Client(s) had {_clientErrors.Count} errors (${string.Join(Environment.NewLine, _clientErrors)})."); + throw new Exception($"Client(s) had {_clientErrors.Count} errors (${string.Join(Environment.NewLine, _clientErrors)})."); } } } diff --git a/Tests/MQTTnet.Core.Tests/Server_Tests.cs b/Tests/MQTTnet.Core.Tests/Server_Tests.cs index de3e871..c3d6df1 100644 --- a/Tests/MQTTnet.Core.Tests/Server_Tests.cs +++ b/Tests/MQTTnet.Core.Tests/Server_Tests.cs @@ -936,6 +936,8 @@ namespace MQTTnet.Tests { using (var testEnvironment = new TestEnvironment()) { + testEnvironment.IgnoreClientLogErrors = true; + _connected = new Dictionary(); var options = new MqttServerOptionsBuilder(); options.WithConnectionValidator(e => ConnectionValidationHandler(e)); @@ -943,8 +945,6 @@ namespace MQTTnet.Tests var events = new List(); - var connected = true; - server.ClientConnectedHandler = new MqttServerClientConnectedHandlerDelegate(_ => { lock (events) @@ -972,15 +972,6 @@ namespace MQTTnet.Tests lock (events) { events.Add("x"); - connected = false; - } - }); - - c1.UseConnectedHandler(_ => - { - lock (events) - { - connected = true; } }); @@ -1002,7 +993,6 @@ namespace MQTTnet.Tests await Task.Delay(500); - var flow = string.Join(string.Empty, events); Assert.AreEqual("cr", flow); @@ -1018,16 +1008,6 @@ namespace MQTTnet.Tests await Task.Delay(500); - /*if (!connected) - { - c1.ReconnectAsync().Wait(); - } - - if (!c1.IsConnected) - { - c1.ReconnectAsync().Wait(); - }*/ - flow = string.Join(string.Empty, events); Assert.AreEqual("cr", flow); @@ -1037,7 +1017,6 @@ namespace MQTTnet.Tests flow = string.Join(string.Empty, events); Assert.AreEqual("crr", flow); - } } From 4105beb7e68a7068d9b9d3b9dacd71af8eaec828 Mon Sep 17 00:00:00 2001 From: Lucas Rosa Date: Tue, 29 Oct 2019 12:55:57 -0300 Subject: [PATCH 20/51] Some more cleaning up: --- Source/MQTTnet/Server/MqttClientConnection.cs | 1 - Source/MQTTnet/Server/MqttClientSessionsManager.cs | 9 +++------ Source/MQTTnet/Server/MqttClientSubscriptionsManager.cs | 2 +- Source/MQTTnet/Server/MqttServerEventDispatcher.cs | 1 - 4 files changed, 4 insertions(+), 9 deletions(-) diff --git a/Source/MQTTnet/Server/MqttClientConnection.cs b/Source/MQTTnet/Server/MqttClientConnection.cs index a114e28..e71d1a8 100644 --- a/Source/MQTTnet/Server/MqttClientConnection.cs +++ b/Source/MQTTnet/Server/MqttClientConnection.cs @@ -12,7 +12,6 @@ using MQTTnet.PacketDispatcher; using MQTTnet.Packets; using MQTTnet.Protocol; using MQTTnet.Server.Status; -using static MQTTnet.Server.MqttClientSessionsManager; namespace MQTTnet.Server { diff --git a/Source/MQTTnet/Server/MqttClientSessionsManager.cs b/Source/MQTTnet/Server/MqttClientSessionsManager.cs index 5a27b46..b800d85 100644 --- a/Source/MQTTnet/Server/MqttClientSessionsManager.cs +++ b/Source/MQTTnet/Server/MqttClientSessionsManager.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.IO; using System.Threading; using System.Threading.Tasks; using MQTTnet.Adapter; @@ -231,8 +230,7 @@ namespace MQTTnet.Server { var disconnectType = MqttClientDisconnectType.NotClean; string clientId = null; - - var ok = true; + var clientWasConnected = true; try { @@ -249,7 +247,7 @@ namespace MQTTnet.Server if (connectionValidatorContext.ReasonCode != MqttConnectReasonCode.Success) { - ok = false; + clientWasConnected = false; // Send failure response here without preparing a session. The result for a successful connect // will be sent from the session itself. var connAckPacket = channelAdapter.PacketFormatterAdapter.DataConverter.CreateConnAckPacket(connectionValidatorContext); @@ -263,7 +261,6 @@ namespace MQTTnet.Server await _eventDispatcher.HandleClientConnectedAsync(clientId).ConfigureAwait(false); disconnectType = await connection.RunAsync().ConfigureAwait(false); - } catch (OperationCanceledException) { @@ -274,7 +271,7 @@ namespace MQTTnet.Server } finally { - if (ok) + if (clientWasConnected) { if (clientId != null) { diff --git a/Source/MQTTnet/Server/MqttClientSubscriptionsManager.cs b/Source/MQTTnet/Server/MqttClientSubscriptionsManager.cs index 1d675ba..c84a018 100644 --- a/Source/MQTTnet/Server/MqttClientSubscriptionsManager.cs +++ b/Source/MQTTnet/Server/MqttClientSubscriptionsManager.cs @@ -4,7 +4,6 @@ using System.Linq; using System.Threading.Tasks; using MQTTnet.Packets; using MQTTnet.Protocol; -using static MQTTnet.Server.MqttClientSessionsManager; namespace MQTTnet.Server { @@ -158,6 +157,7 @@ namespace MQTTnet.Server { continue; } + qosLevels.Add(subscription.Value.QualityOfServiceLevel); } } diff --git a/Source/MQTTnet/Server/MqttServerEventDispatcher.cs b/Source/MQTTnet/Server/MqttServerEventDispatcher.cs index f899081..e6e608a 100644 --- a/Source/MQTTnet/Server/MqttServerEventDispatcher.cs +++ b/Source/MQTTnet/Server/MqttServerEventDispatcher.cs @@ -2,7 +2,6 @@ using System.Threading.Tasks; using MQTTnet.Client.Receiving; using MQTTnet.Diagnostics; -using static MQTTnet.Server.MqttClientSessionsManager; namespace MQTTnet.Server { From 896e9ed0aa5d1750a34b829d7dcc3946a78e2473 Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Thu, 14 Nov 2019 20:47:33 +0100 Subject: [PATCH 21/51] Add interceptor for unsubscriptions. --- .../MQTTnet.Server/Mqtt/MqttServerService.cs | 4 ++ .../Mqtt/MqttUnsubscriptionInterceptor.cs | 48 +++++++++++++++++++ Source/MQTTnet/Server/IMqttServerOptions.cs | 1 + .../IMqttServerUnsubscriptionInterceptor.cs | 9 ++++ .../Server/MqttClientSubscriptionsManager.cs | 38 +++++++++++---- Source/MQTTnet/Server/MqttServerOptions.cs | 2 + .../Server/MqttServerOptionsBuilder.cs | 6 +++ .../MqttUnsubscriptionInterceptorContext.cs | 27 +++++++++++ 8 files changed, 127 insertions(+), 8 deletions(-) create mode 100644 Source/MQTTnet.Server/Mqtt/MqttUnsubscriptionInterceptor.cs create mode 100644 Source/MQTTnet/Server/IMqttServerUnsubscriptionInterceptor.cs create mode 100644 Source/MQTTnet/Server/MqttUnsubscriptionInterceptorContext.cs diff --git a/Source/MQTTnet.Server/Mqtt/MqttServerService.cs b/Source/MQTTnet.Server/Mqtt/MqttServerService.cs index b8c463f..85c4176 100644 --- a/Source/MQTTnet.Server/Mqtt/MqttServerService.cs +++ b/Source/MQTTnet.Server/Mqtt/MqttServerService.cs @@ -33,6 +33,7 @@ namespace MQTTnet.Server.Mqtt private readonly MqttServerConnectionValidator _mqttConnectionValidator; private readonly IMqttServer _mqttServer; private readonly MqttSubscriptionInterceptor _mqttSubscriptionInterceptor; + private readonly MqttUnsubscriptionInterceptor _mqttUnsubscriptionInterceptor; private readonly PythonScriptHostService _pythonScriptHostService; private readonly MqttWebSocketServerAdapter _webSocketServerAdapter; @@ -45,6 +46,7 @@ namespace MQTTnet.Server.Mqtt MqttClientUnsubscribedTopicHandler mqttClientUnsubscribedTopicHandler, MqttServerConnectionValidator mqttConnectionValidator, MqttSubscriptionInterceptor mqttSubscriptionInterceptor, + MqttUnsubscriptionInterceptor mqttUnsubscriptionInterceptor, MqttApplicationMessageInterceptor mqttApplicationMessageInterceptor, MqttServerStorage mqttServerStorage, PythonScriptHostService pythonScriptHostService, @@ -57,6 +59,7 @@ namespace MQTTnet.Server.Mqtt _mqttClientUnsubscribedTopicHandler = mqttClientUnsubscribedTopicHandler ?? throw new ArgumentNullException(nameof(mqttClientUnsubscribedTopicHandler)); _mqttConnectionValidator = mqttConnectionValidator ?? throw new ArgumentNullException(nameof(mqttConnectionValidator)); _mqttSubscriptionInterceptor = mqttSubscriptionInterceptor ?? throw new ArgumentNullException(nameof(mqttSubscriptionInterceptor)); + _mqttUnsubscriptionInterceptor = mqttUnsubscriptionInterceptor ?? throw new ArgumentNullException(nameof(mqttUnsubscriptionInterceptor)); _mqttApplicationMessageInterceptor = mqttApplicationMessageInterceptor ?? throw new ArgumentNullException(nameof(mqttApplicationMessageInterceptor)); _mqttServerStorage = mqttServerStorage ?? throw new ArgumentNullException(nameof(mqttServerStorage)); _pythonScriptHostService = pythonScriptHostService ?? throw new ArgumentNullException(nameof(pythonScriptHostService)); @@ -178,6 +181,7 @@ namespace MQTTnet.Server.Mqtt .WithConnectionValidator(_mqttConnectionValidator) .WithApplicationMessageInterceptor(_mqttApplicationMessageInterceptor) .WithSubscriptionInterceptor(_mqttSubscriptionInterceptor) + .WithUnsubscriptionInterceptor(_mqttUnsubscriptionInterceptor) .WithStorage(_mqttServerStorage); // Configure unencrypted connections diff --git a/Source/MQTTnet.Server/Mqtt/MqttUnsubscriptionInterceptor.cs b/Source/MQTTnet.Server/Mqtt/MqttUnsubscriptionInterceptor.cs new file mode 100644 index 0000000..1a460af --- /dev/null +++ b/Source/MQTTnet.Server/Mqtt/MqttUnsubscriptionInterceptor.cs @@ -0,0 +1,48 @@ +using System; +using System.Threading.Tasks; +using IronPython.Runtime; +using Microsoft.Extensions.Logging; +using MQTTnet.Server.Scripting; + +namespace MQTTnet.Server.Mqtt +{ + public class MqttUnsubscriptionInterceptor : IMqttServerUnsubscriptionInterceptor + { + private readonly PythonScriptHostService _pythonScriptHostService; + private readonly ILogger _logger; + + public MqttUnsubscriptionInterceptor(PythonScriptHostService pythonScriptHostService, ILogger logger) + { + _pythonScriptHostService = pythonScriptHostService ?? throw new ArgumentNullException(nameof(pythonScriptHostService)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + public Task InterceptUnsubscriptionAsync(MqttUnsubscriptionInterceptorContext context) + { + try + { + var sessionItems = (PythonDictionary)context.SessionItems[MqttServerConnectionValidator.WrappedSessionItemsKey]; + + var pythonContext = new PythonDictionary + { + { "client_id", context.ClientId }, + { "session_items", sessionItems }, + { "topic", context.Topic }, + { "accept_unsubscription", context.AcceptUnsubscription }, + { "close_connection", context.CloseConnection } + }; + + _pythonScriptHostService.InvokeOptionalFunction("on_intercept_unsubscription", pythonContext); + + context.AcceptUnsubscription = (bool)pythonContext["accept_unsubscription"]; + context.CloseConnection = (bool)pythonContext["close_connection"]; + } + catch (Exception exception) + { + _logger.LogError(exception, "Error while intercepting unsubscription."); + } + + return Task.CompletedTask; + } + } +} diff --git a/Source/MQTTnet/Server/IMqttServerOptions.cs b/Source/MQTTnet/Server/IMqttServerOptions.cs index 7c5fde4..2145845 100644 --- a/Source/MQTTnet/Server/IMqttServerOptions.cs +++ b/Source/MQTTnet/Server/IMqttServerOptions.cs @@ -15,6 +15,7 @@ namespace MQTTnet.Server IMqttServerConnectionValidator ConnectionValidator { get; } IMqttServerSubscriptionInterceptor SubscriptionInterceptor { get; } + IMqttServerUnsubscriptionInterceptor UnsubscriptionInterceptor { get; } IMqttServerApplicationMessageInterceptor ApplicationMessageInterceptor { get; } IMqttServerClientMessageQueueInterceptor ClientMessageQueueInterceptor { get; } diff --git a/Source/MQTTnet/Server/IMqttServerUnsubscriptionInterceptor.cs b/Source/MQTTnet/Server/IMqttServerUnsubscriptionInterceptor.cs new file mode 100644 index 0000000..9669383 --- /dev/null +++ b/Source/MQTTnet/Server/IMqttServerUnsubscriptionInterceptor.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace MQTTnet.Server +{ + public interface IMqttServerUnsubscriptionInterceptor + { + Task InterceptUnsubscriptionAsync(MqttUnsubscriptionInterceptorContext context); + } +} diff --git a/Source/MQTTnet/Server/MqttClientSubscriptionsManager.cs b/Source/MQTTnet/Server/MqttClientSubscriptionsManager.cs index c84a018..deeadf4 100644 --- a/Source/MQTTnet/Server/MqttClientSubscriptionsManager.cs +++ b/Source/MQTTnet/Server/MqttClientSubscriptionsManager.cs @@ -107,9 +107,16 @@ namespace MQTTnet.Server PacketIdentifier = unsubscribePacket.PacketIdentifier }; - lock (_subscriptions) + foreach (var topicFilter in unsubscribePacket.TopicFilters) { - foreach (var topicFilter in unsubscribePacket.TopicFilters) + var interceptorContext = await InterceptUnsubscribeAsync(topicFilter).ConfigureAwait(false); + if (!interceptorContext.AcceptUnsubscription) + { + unsubAckPacket.ReasonCodes.Add(MqttUnsubscribeReasonCode.ImplementationSpecificError); + continue; + } + + lock (_subscriptions) { if (_subscriptions.Remove(topicFilter)) { @@ -130,19 +137,23 @@ namespace MQTTnet.Server return unsubAckPacket; } - public Task UnsubscribeAsync(IEnumerable topicFilters) + public async Task UnsubscribeAsync(IEnumerable topicFilters) { if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); - lock (_subscriptions) + foreach (var topicFilter in topicFilters) { - foreach (var topicFilter in topicFilters) + var interceptorContext = await InterceptUnsubscribeAsync(topicFilter).ConfigureAwait(false); + if (!interceptorContext.AcceptUnsubscription) { - _subscriptions.Remove(topicFilter); + continue; } - } - return Task.FromResult(0); + lock (_subscriptions) + { + _subscriptions.Remove(topicFilter); + } + } } public CheckSubscriptionsResult CheckSubscriptions(string topic, MqttQualityOfServiceLevel qosLevel) @@ -206,6 +217,17 @@ namespace MQTTnet.Server return context; } + private async Task InterceptUnsubscribeAsync(string topicFilter) + { + var context = new MqttUnsubscriptionInterceptorContext(_clientSession.ClientId, topicFilter, _clientSession.Items); + if (_serverOptions.UnsubscriptionInterceptor != null) + { + await _serverOptions.UnsubscriptionInterceptor.InterceptUnsubscriptionAsync(context).ConfigureAwait(false); + } + + return context; + } + private static CheckSubscriptionsResult CreateSubscriptionResult(MqttQualityOfServiceLevel qosLevel, HashSet subscribedQoSLevels) { MqttQualityOfServiceLevel effectiveQoS; diff --git a/Source/MQTTnet/Server/MqttServerOptions.cs b/Source/MQTTnet/Server/MqttServerOptions.cs index 7147ef8..d5f6737 100644 --- a/Source/MQTTnet/Server/MqttServerOptions.cs +++ b/Source/MQTTnet/Server/MqttServerOptions.cs @@ -26,6 +26,8 @@ namespace MQTTnet.Server public IMqttServerSubscriptionInterceptor SubscriptionInterceptor { get; set; } + public IMqttServerUnsubscriptionInterceptor UnsubscriptionInterceptor { get; set; } + public IMqttServerStorage Storage { get; set; } } } diff --git a/Source/MQTTnet/Server/MqttServerOptionsBuilder.cs b/Source/MQTTnet/Server/MqttServerOptionsBuilder.cs index c25af84..15126a1 100644 --- a/Source/MQTTnet/Server/MqttServerOptionsBuilder.cs +++ b/Source/MQTTnet/Server/MqttServerOptionsBuilder.cs @@ -155,6 +155,12 @@ namespace MQTTnet.Server return this; } + public MqttServerOptionsBuilder WithUnsubscriptionInterceptor(IMqttServerUnsubscriptionInterceptor value) + { + _options.UnsubscriptionInterceptor = value; + return this; + } + public MqttServerOptionsBuilder WithSubscriptionInterceptor(Action value) { _options.SubscriptionInterceptor = new MqttServerSubscriptionInterceptorDelegate(value); diff --git a/Source/MQTTnet/Server/MqttUnsubscriptionInterceptorContext.cs b/Source/MQTTnet/Server/MqttUnsubscriptionInterceptorContext.cs new file mode 100644 index 0000000..b33cbac --- /dev/null +++ b/Source/MQTTnet/Server/MqttUnsubscriptionInterceptorContext.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; + +namespace MQTTnet.Server +{ + public class MqttUnsubscriptionInterceptorContext + { + public MqttUnsubscriptionInterceptorContext(string clientId, string topic, IDictionary sessionItems) + { + ClientId = clientId; + Topic = topic; + SessionItems = sessionItems; + } + + public string ClientId { get; } + + public string Topic { get; set; } + + /// + /// Gets or sets a key/value collection that can be used to share data within the scope of this session. + /// + public IDictionary SessionItems { get; } + + public bool AcceptUnsubscription { get; set; } = true; + + public bool CloseConnection { get; set; } + } +} From a63489fa5ed73546ed41fbcd3e599c1eacb79540 Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Thu, 14 Nov 2019 20:52:35 +0100 Subject: [PATCH 22/51] Update docs. --- Build/MQTTnet.nuspec | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Build/MQTTnet.nuspec b/Build/MQTTnet.nuspec index 016bfdb..4d31cf9 100644 --- a/Build/MQTTnet.nuspec +++ b/Build/MQTTnet.nuspec @@ -16,6 +16,8 @@ * [ManagedClient] Improved internal subscription management (#569, thanks to @cstichlberger). * [ManagedClient] Refactored log messages (thanks to @cstichlberger). * [Server] Added support for assigned client IDs (MQTTv5 only) (thanks to @bcrosnier). +* [Server] Added interceptor for unsubscriptions. +* [MQTTnet.Server] Added interceptor for unsubscriptions. Copyright Christian Kratky 2016-2019 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 From a38623cb5a7a9bacc751929b162caa9c852901e0 Mon Sep 17 00:00:00 2001 From: Anton Yaroshenko Date: Sun, 17 Nov 2019 19:42:23 +0200 Subject: [PATCH 23/51] replace raw byte with x509 certificate to allow specify passwords --- .../WebSocket4NetMqttChannel.cs | 2 +- Source/MQTTnet/Client/Options/MqttClientOptionsBuilder.cs | 2 +- .../Client/Options/MqttClientOptionsBuilderTlsParameters.cs | 2 +- Source/MQTTnet/Client/Options/MqttClientTlsOptions.cs | 2 +- Source/MQTTnet/Implementations/MqttTcpChannel.cs | 2 +- Source/MQTTnet/Implementations/MqttWebSocketChannel.cs | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Source/MQTTnet.Extensions.WebSocket4Net/WebSocket4NetMqttChannel.cs b/Source/MQTTnet.Extensions.WebSocket4Net/WebSocket4NetMqttChannel.cs index 68b12ef..1bbc65c 100644 --- a/Source/MQTTnet.Extensions.WebSocket4Net/WebSocket4NetMqttChannel.cs +++ b/Source/MQTTnet.Extensions.WebSocket4Net/WebSocket4NetMqttChannel.cs @@ -85,7 +85,7 @@ namespace MQTTnet.Extensions.WebSocket4Net { foreach (var certificate in _webSocketOptions.TlsOptions.Certificates) { - certificates.Add(new X509Certificate(certificate)); + certificates.Add(certificate); } } diff --git a/Source/MQTTnet/Client/Options/MqttClientOptionsBuilder.cs b/Source/MQTTnet/Client/Options/MqttClientOptionsBuilder.cs index 65a1ec9..e9ab3df 100644 --- a/Source/MQTTnet/Client/Options/MqttClientOptionsBuilder.cs +++ b/Source/MQTTnet/Client/Options/MqttClientOptionsBuilder.cs @@ -256,7 +256,7 @@ namespace MQTTnet.Client.Options UseTls = true, SslProtocol = _tlsParameters.SslProtocol, AllowUntrustedCertificates = _tlsParameters.AllowUntrustedCertificates, - Certificates = _tlsParameters.Certificates?.Select(c => c.ToArray()).ToList(), + Certificates = _tlsParameters.Certificates?.ToList(), CertificateValidationCallback = _tlsParameters.CertificateValidationCallback, IgnoreCertificateChainErrors = _tlsParameters.IgnoreCertificateChainErrors, IgnoreCertificateRevocationErrors = _tlsParameters.IgnoreCertificateRevocationErrors diff --git a/Source/MQTTnet/Client/Options/MqttClientOptionsBuilderTlsParameters.cs b/Source/MQTTnet/Client/Options/MqttClientOptionsBuilderTlsParameters.cs index ea36baa..79be24a 100644 --- a/Source/MQTTnet/Client/Options/MqttClientOptionsBuilderTlsParameters.cs +++ b/Source/MQTTnet/Client/Options/MqttClientOptionsBuilderTlsParameters.cs @@ -18,7 +18,7 @@ namespace MQTTnet.Client.Options public SslProtocols SslProtocol { get; set; } = SslProtocols.Tls12; - public IEnumerable> Certificates { get; set; } + public IEnumerable Certificates { get; set; } public bool AllowUntrustedCertificates { get; set; } diff --git a/Source/MQTTnet/Client/Options/MqttClientTlsOptions.cs b/Source/MQTTnet/Client/Options/MqttClientTlsOptions.cs index db4077d..cf04646 100644 --- a/Source/MQTTnet/Client/Options/MqttClientTlsOptions.cs +++ b/Source/MQTTnet/Client/Options/MqttClientTlsOptions.cs @@ -16,7 +16,7 @@ namespace MQTTnet.Client.Options public bool AllowUntrustedCertificates { get; set; } - public List Certificates { get; set; } + public List Certificates { get; set; } public SslProtocols SslProtocol { get; set; } = SslProtocols.Tls12; diff --git a/Source/MQTTnet/Implementations/MqttTcpChannel.cs b/Source/MQTTnet/Implementations/MqttTcpChannel.cs index d7943ad..8f012cb 100644 --- a/Source/MQTTnet/Implementations/MqttTcpChannel.cs +++ b/Source/MQTTnet/Implementations/MqttTcpChannel.cs @@ -214,7 +214,7 @@ namespace MQTTnet.Implementations foreach (var certificate in _options.TlsOptions.Certificates) { - certificates.Add(new X509Certificate2(certificate)); + certificates.Add(certificate); } return certificates; diff --git a/Source/MQTTnet/Implementations/MqttWebSocketChannel.cs b/Source/MQTTnet/Implementations/MqttWebSocketChannel.cs index 38e4342..c53a131 100644 --- a/Source/MQTTnet/Implementations/MqttWebSocketChannel.cs +++ b/Source/MQTTnet/Implementations/MqttWebSocketChannel.cs @@ -84,7 +84,7 @@ namespace MQTTnet.Implementations clientWebSocket.Options.ClientCertificates = new X509CertificateCollection(); foreach (var certificate in _options.TlsOptions.Certificates) { - clientWebSocket.Options.ClientCertificates.Add(new X509Certificate(certificate)); + clientWebSocket.Options.ClientCertificates.Add(certificate); } } From ac326babdf0fecdb8fa1008026b7b1d65434aa36 Mon Sep 17 00:00:00 2001 From: Anton Yaroshenko Date: Sun, 17 Nov 2019 20:12:03 +0200 Subject: [PATCH 24/51] blind uwp fix --- Source/MQTTnet/Implementations/MqttTcpChannel.Uwp.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/MQTTnet/Implementations/MqttTcpChannel.Uwp.cs b/Source/MQTTnet/Implementations/MqttTcpChannel.Uwp.cs index cb0f71c..2b84899 100644 --- a/Source/MQTTnet/Implementations/MqttTcpChannel.Uwp.cs +++ b/Source/MQTTnet/Implementations/MqttTcpChannel.Uwp.cs @@ -132,7 +132,7 @@ namespace MQTTnet.Implementations throw new NotSupportedException("Only one client certificate is supported for UWP."); } - return new Certificate(options.TlsOptions.Certificates.First().AsBuffer()); + return new Certificate(options.TlsOptions.Certificates.First().GetRawCertData()); } private IEnumerable ResolveIgnorableServerCertificateErrors() From b2c2209b4f0460de4ea8b453c8b309ee96baaf2b Mon Sep 17 00:00:00 2001 From: Christian Date: Sun, 17 Nov 2019 21:06:37 +0100 Subject: [PATCH 25/51] Update MQTTnet.nuspec --- Build/MQTTnet.nuspec | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Build/MQTTnet.nuspec b/Build/MQTTnet.nuspec index 4d31cf9..bd19de4 100644 --- a/Build/MQTTnet.nuspec +++ b/Build/MQTTnet.nuspec @@ -17,6 +17,7 @@ * [ManagedClient] Refactored log messages (thanks to @cstichlberger). * [Server] Added support for assigned client IDs (MQTTv5 only) (thanks to @bcrosnier). * [Server] Added interceptor for unsubscriptions. +* [Server] Added support for direct specification of X502 certificates instead of byte arrays (BREAKING CHANGE! thanks to @stepkillah). * [MQTTnet.Server] Added interceptor for unsubscriptions. Copyright Christian Kratky 2016-2019 @@ -61,4 +62,4 @@ - \ No newline at end of file + From 5db05f186f4356b9b821697b18c70d60bf17d476 Mon Sep 17 00:00:00 2001 From: Anton Yaroshenko Date: Sun, 17 Nov 2019 19:42:23 +0200 Subject: [PATCH 26/51] Revert "replace raw byte with x509 certificate to allow specify passwords" This reverts commit a38623cb5a7a9bacc751929b162caa9c852901e0. --- .../WebSocket4NetMqttChannel.cs | 2 +- Source/MQTTnet/Client/Options/MqttClientOptionsBuilder.cs | 2 +- .../Client/Options/MqttClientOptionsBuilderTlsParameters.cs | 2 +- Source/MQTTnet/Client/Options/MqttClientTlsOptions.cs | 2 +- Source/MQTTnet/Implementations/MqttTcpChannel.cs | 2 +- Source/MQTTnet/Implementations/MqttWebSocketChannel.cs | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Source/MQTTnet.Extensions.WebSocket4Net/WebSocket4NetMqttChannel.cs b/Source/MQTTnet.Extensions.WebSocket4Net/WebSocket4NetMqttChannel.cs index 1bbc65c..68b12ef 100644 --- a/Source/MQTTnet.Extensions.WebSocket4Net/WebSocket4NetMqttChannel.cs +++ b/Source/MQTTnet.Extensions.WebSocket4Net/WebSocket4NetMqttChannel.cs @@ -85,7 +85,7 @@ namespace MQTTnet.Extensions.WebSocket4Net { foreach (var certificate in _webSocketOptions.TlsOptions.Certificates) { - certificates.Add(certificate); + certificates.Add(new X509Certificate(certificate)); } } diff --git a/Source/MQTTnet/Client/Options/MqttClientOptionsBuilder.cs b/Source/MQTTnet/Client/Options/MqttClientOptionsBuilder.cs index e9ab3df..65a1ec9 100644 --- a/Source/MQTTnet/Client/Options/MqttClientOptionsBuilder.cs +++ b/Source/MQTTnet/Client/Options/MqttClientOptionsBuilder.cs @@ -256,7 +256,7 @@ namespace MQTTnet.Client.Options UseTls = true, SslProtocol = _tlsParameters.SslProtocol, AllowUntrustedCertificates = _tlsParameters.AllowUntrustedCertificates, - Certificates = _tlsParameters.Certificates?.ToList(), + Certificates = _tlsParameters.Certificates?.Select(c => c.ToArray()).ToList(), CertificateValidationCallback = _tlsParameters.CertificateValidationCallback, IgnoreCertificateChainErrors = _tlsParameters.IgnoreCertificateChainErrors, IgnoreCertificateRevocationErrors = _tlsParameters.IgnoreCertificateRevocationErrors diff --git a/Source/MQTTnet/Client/Options/MqttClientOptionsBuilderTlsParameters.cs b/Source/MQTTnet/Client/Options/MqttClientOptionsBuilderTlsParameters.cs index 79be24a..ea36baa 100644 --- a/Source/MQTTnet/Client/Options/MqttClientOptionsBuilderTlsParameters.cs +++ b/Source/MQTTnet/Client/Options/MqttClientOptionsBuilderTlsParameters.cs @@ -18,7 +18,7 @@ namespace MQTTnet.Client.Options public SslProtocols SslProtocol { get; set; } = SslProtocols.Tls12; - public IEnumerable Certificates { get; set; } + public IEnumerable> Certificates { get; set; } public bool AllowUntrustedCertificates { get; set; } diff --git a/Source/MQTTnet/Client/Options/MqttClientTlsOptions.cs b/Source/MQTTnet/Client/Options/MqttClientTlsOptions.cs index cf04646..db4077d 100644 --- a/Source/MQTTnet/Client/Options/MqttClientTlsOptions.cs +++ b/Source/MQTTnet/Client/Options/MqttClientTlsOptions.cs @@ -16,7 +16,7 @@ namespace MQTTnet.Client.Options public bool AllowUntrustedCertificates { get; set; } - public List Certificates { get; set; } + public List Certificates { get; set; } public SslProtocols SslProtocol { get; set; } = SslProtocols.Tls12; diff --git a/Source/MQTTnet/Implementations/MqttTcpChannel.cs b/Source/MQTTnet/Implementations/MqttTcpChannel.cs index 8f012cb..d7943ad 100644 --- a/Source/MQTTnet/Implementations/MqttTcpChannel.cs +++ b/Source/MQTTnet/Implementations/MqttTcpChannel.cs @@ -214,7 +214,7 @@ namespace MQTTnet.Implementations foreach (var certificate in _options.TlsOptions.Certificates) { - certificates.Add(certificate); + certificates.Add(new X509Certificate2(certificate)); } return certificates; diff --git a/Source/MQTTnet/Implementations/MqttWebSocketChannel.cs b/Source/MQTTnet/Implementations/MqttWebSocketChannel.cs index c53a131..38e4342 100644 --- a/Source/MQTTnet/Implementations/MqttWebSocketChannel.cs +++ b/Source/MQTTnet/Implementations/MqttWebSocketChannel.cs @@ -84,7 +84,7 @@ namespace MQTTnet.Implementations clientWebSocket.Options.ClientCertificates = new X509CertificateCollection(); foreach (var certificate in _options.TlsOptions.Certificates) { - clientWebSocket.Options.ClientCertificates.Add(certificate); + clientWebSocket.Options.ClientCertificates.Add(new X509Certificate(certificate)); } } From e8ff48bfd3836df74729d763cab4c8ed75a74160 Mon Sep 17 00:00:00 2001 From: Anton Yaroshenko Date: Sun, 17 Nov 2019 20:12:03 +0200 Subject: [PATCH 27/51] Revert "blind uwp fix" This reverts commit ac326babdf0fecdb8fa1008026b7b1d65434aa36. --- Source/MQTTnet/Implementations/MqttTcpChannel.Uwp.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/MQTTnet/Implementations/MqttTcpChannel.Uwp.cs b/Source/MQTTnet/Implementations/MqttTcpChannel.Uwp.cs index 2b84899..cb0f71c 100644 --- a/Source/MQTTnet/Implementations/MqttTcpChannel.Uwp.cs +++ b/Source/MQTTnet/Implementations/MqttTcpChannel.Uwp.cs @@ -132,7 +132,7 @@ namespace MQTTnet.Implementations throw new NotSupportedException("Only one client certificate is supported for UWP."); } - return new Certificate(options.TlsOptions.Certificates.First().GetRawCertData()); + return new Certificate(options.TlsOptions.Certificates.First().AsBuffer()); } private IEnumerable ResolveIgnorableServerCertificateErrors() From 9aaa94e821094d0838f64cabfa822ee699626869 Mon Sep 17 00:00:00 2001 From: Christian Date: Sun, 17 Nov 2019 21:24:30 +0100 Subject: [PATCH 28/51] Revert "Replace raw bytes with x509 certificate to allow specify passwords and flags" --- .../WebSocket4NetMqttChannel.cs | 2 +- Source/MQTTnet/Client/Options/MqttClientOptionsBuilder.cs | 2 +- .../Client/Options/MqttClientOptionsBuilderTlsParameters.cs | 2 +- Source/MQTTnet/Client/Options/MqttClientTlsOptions.cs | 2 +- Source/MQTTnet/Implementations/MqttTcpChannel.Uwp.cs | 2 +- Source/MQTTnet/Implementations/MqttTcpChannel.cs | 2 +- Source/MQTTnet/Implementations/MqttWebSocketChannel.cs | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Source/MQTTnet.Extensions.WebSocket4Net/WebSocket4NetMqttChannel.cs b/Source/MQTTnet.Extensions.WebSocket4Net/WebSocket4NetMqttChannel.cs index 1bbc65c..68b12ef 100644 --- a/Source/MQTTnet.Extensions.WebSocket4Net/WebSocket4NetMqttChannel.cs +++ b/Source/MQTTnet.Extensions.WebSocket4Net/WebSocket4NetMqttChannel.cs @@ -85,7 +85,7 @@ namespace MQTTnet.Extensions.WebSocket4Net { foreach (var certificate in _webSocketOptions.TlsOptions.Certificates) { - certificates.Add(certificate); + certificates.Add(new X509Certificate(certificate)); } } diff --git a/Source/MQTTnet/Client/Options/MqttClientOptionsBuilder.cs b/Source/MQTTnet/Client/Options/MqttClientOptionsBuilder.cs index e9ab3df..65a1ec9 100644 --- a/Source/MQTTnet/Client/Options/MqttClientOptionsBuilder.cs +++ b/Source/MQTTnet/Client/Options/MqttClientOptionsBuilder.cs @@ -256,7 +256,7 @@ namespace MQTTnet.Client.Options UseTls = true, SslProtocol = _tlsParameters.SslProtocol, AllowUntrustedCertificates = _tlsParameters.AllowUntrustedCertificates, - Certificates = _tlsParameters.Certificates?.ToList(), + Certificates = _tlsParameters.Certificates?.Select(c => c.ToArray()).ToList(), CertificateValidationCallback = _tlsParameters.CertificateValidationCallback, IgnoreCertificateChainErrors = _tlsParameters.IgnoreCertificateChainErrors, IgnoreCertificateRevocationErrors = _tlsParameters.IgnoreCertificateRevocationErrors diff --git a/Source/MQTTnet/Client/Options/MqttClientOptionsBuilderTlsParameters.cs b/Source/MQTTnet/Client/Options/MqttClientOptionsBuilderTlsParameters.cs index 79be24a..ea36baa 100644 --- a/Source/MQTTnet/Client/Options/MqttClientOptionsBuilderTlsParameters.cs +++ b/Source/MQTTnet/Client/Options/MqttClientOptionsBuilderTlsParameters.cs @@ -18,7 +18,7 @@ namespace MQTTnet.Client.Options public SslProtocols SslProtocol { get; set; } = SslProtocols.Tls12; - public IEnumerable Certificates { get; set; } + public IEnumerable> Certificates { get; set; } public bool AllowUntrustedCertificates { get; set; } diff --git a/Source/MQTTnet/Client/Options/MqttClientTlsOptions.cs b/Source/MQTTnet/Client/Options/MqttClientTlsOptions.cs index cf04646..db4077d 100644 --- a/Source/MQTTnet/Client/Options/MqttClientTlsOptions.cs +++ b/Source/MQTTnet/Client/Options/MqttClientTlsOptions.cs @@ -16,7 +16,7 @@ namespace MQTTnet.Client.Options public bool AllowUntrustedCertificates { get; set; } - public List Certificates { get; set; } + public List Certificates { get; set; } public SslProtocols SslProtocol { get; set; } = SslProtocols.Tls12; diff --git a/Source/MQTTnet/Implementations/MqttTcpChannel.Uwp.cs b/Source/MQTTnet/Implementations/MqttTcpChannel.Uwp.cs index 2b84899..cb0f71c 100644 --- a/Source/MQTTnet/Implementations/MqttTcpChannel.Uwp.cs +++ b/Source/MQTTnet/Implementations/MqttTcpChannel.Uwp.cs @@ -132,7 +132,7 @@ namespace MQTTnet.Implementations throw new NotSupportedException("Only one client certificate is supported for UWP."); } - return new Certificate(options.TlsOptions.Certificates.First().GetRawCertData()); + return new Certificate(options.TlsOptions.Certificates.First().AsBuffer()); } private IEnumerable ResolveIgnorableServerCertificateErrors() diff --git a/Source/MQTTnet/Implementations/MqttTcpChannel.cs b/Source/MQTTnet/Implementations/MqttTcpChannel.cs index 8f012cb..d7943ad 100644 --- a/Source/MQTTnet/Implementations/MqttTcpChannel.cs +++ b/Source/MQTTnet/Implementations/MqttTcpChannel.cs @@ -214,7 +214,7 @@ namespace MQTTnet.Implementations foreach (var certificate in _options.TlsOptions.Certificates) { - certificates.Add(certificate); + certificates.Add(new X509Certificate2(certificate)); } return certificates; diff --git a/Source/MQTTnet/Implementations/MqttWebSocketChannel.cs b/Source/MQTTnet/Implementations/MqttWebSocketChannel.cs index c53a131..38e4342 100644 --- a/Source/MQTTnet/Implementations/MqttWebSocketChannel.cs +++ b/Source/MQTTnet/Implementations/MqttWebSocketChannel.cs @@ -84,7 +84,7 @@ namespace MQTTnet.Implementations clientWebSocket.Options.ClientCertificates = new X509CertificateCollection(); foreach (var certificate in _options.TlsOptions.Certificates) { - clientWebSocket.Options.ClientCertificates.Add(certificate); + clientWebSocket.Options.ClientCertificates.Add(new X509Certificate(certificate)); } } From 1c89a8bc75afc29af4c880b1ff848cad182d6649 Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Sun, 17 Nov 2019 21:25:30 +0100 Subject: [PATCH 29/51] Update docs. --- Build/MQTTnet.nuspec | 1 - 1 file changed, 1 deletion(-) diff --git a/Build/MQTTnet.nuspec b/Build/MQTTnet.nuspec index bd19de4..67feb06 100644 --- a/Build/MQTTnet.nuspec +++ b/Build/MQTTnet.nuspec @@ -17,7 +17,6 @@ * [ManagedClient] Refactored log messages (thanks to @cstichlberger). * [Server] Added support for assigned client IDs (MQTTv5 only) (thanks to @bcrosnier). * [Server] Added interceptor for unsubscriptions. -* [Server] Added support for direct specification of X502 certificates instead of byte arrays (BREAKING CHANGE! thanks to @stepkillah). * [MQTTnet.Server] Added interceptor for unsubscriptions. Copyright Christian Kratky 2016-2019 From 9fb1cc33208459e017804f892da0eb776e20141d Mon Sep 17 00:00:00 2001 From: Anton Yaroshenko Date: Mon, 18 Nov 2019 11:16:34 +0200 Subject: [PATCH 30/51] added UWP compiler flags --- .../WebSocket4NetMqttChannel.cs | 5 +++++ Source/MQTTnet/Client/Options/MqttClientOptionsBuilder.cs | 4 ++++ .../Client/Options/MqttClientOptionsBuilderTlsParameters.cs | 5 +++++ Source/MQTTnet/Client/Options/MqttClientTlsOptions.cs | 5 ++++- Source/MQTTnet/Implementations/MqttTcpChannel.Uwp.cs | 2 +- Source/MQTTnet/Implementations/MqttWebSocketChannel.cs | 5 +++++ 6 files changed, 24 insertions(+), 2 deletions(-) diff --git a/Source/MQTTnet.Extensions.WebSocket4Net/WebSocket4NetMqttChannel.cs b/Source/MQTTnet.Extensions.WebSocket4Net/WebSocket4NetMqttChannel.cs index 1bbc65c..47d9682 100644 --- a/Source/MQTTnet.Extensions.WebSocket4Net/WebSocket4NetMqttChannel.cs +++ b/Source/MQTTnet.Extensions.WebSocket4Net/WebSocket4NetMqttChannel.cs @@ -85,7 +85,12 @@ namespace MQTTnet.Extensions.WebSocket4Net { foreach (var certificate in _webSocketOptions.TlsOptions.Certificates) { +#if WINDOWS_UWP + certificates.Add(new X509Certificate(certificate)); +#else certificates.Add(certificate); +#endif + } } diff --git a/Source/MQTTnet/Client/Options/MqttClientOptionsBuilder.cs b/Source/MQTTnet/Client/Options/MqttClientOptionsBuilder.cs index e9ab3df..4fd0ccf 100644 --- a/Source/MQTTnet/Client/Options/MqttClientOptionsBuilder.cs +++ b/Source/MQTTnet/Client/Options/MqttClientOptionsBuilder.cs @@ -256,7 +256,11 @@ namespace MQTTnet.Client.Options UseTls = true, SslProtocol = _tlsParameters.SslProtocol, AllowUntrustedCertificates = _tlsParameters.AllowUntrustedCertificates, +#if WINDOWS_UWP + Certificates = _tlsParameters.Certificates?.Select(c => c.ToArray()).ToList(), +#else Certificates = _tlsParameters.Certificates?.ToList(), +#endif CertificateValidationCallback = _tlsParameters.CertificateValidationCallback, IgnoreCertificateChainErrors = _tlsParameters.IgnoreCertificateChainErrors, IgnoreCertificateRevocationErrors = _tlsParameters.IgnoreCertificateRevocationErrors diff --git a/Source/MQTTnet/Client/Options/MqttClientOptionsBuilderTlsParameters.cs b/Source/MQTTnet/Client/Options/MqttClientOptionsBuilderTlsParameters.cs index 79be24a..d1854ff 100644 --- a/Source/MQTTnet/Client/Options/MqttClientOptionsBuilderTlsParameters.cs +++ b/Source/MQTTnet/Client/Options/MqttClientOptionsBuilderTlsParameters.cs @@ -18,7 +18,12 @@ namespace MQTTnet.Client.Options public SslProtocols SslProtocol { get; set; } = SslProtocols.Tls12; +#if WINDOWS_UWP + public IEnumerable> Certificates { get; set; } +#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 cf04646..0d1a3a5 100644 --- a/Source/MQTTnet/Client/Options/MqttClientTlsOptions.cs +++ b/Source/MQTTnet/Client/Options/MqttClientTlsOptions.cs @@ -15,8 +15,11 @@ namespace MQTTnet.Client.Options public bool IgnoreCertificateChainErrors { get; set; } public bool AllowUntrustedCertificates { get; set; } - +#if WINDOWS_UWP + public List Certificates { get; set; } +#else public List Certificates { get; set; } +#endif public SslProtocols SslProtocol { get; set; } = SslProtocols.Tls12; diff --git a/Source/MQTTnet/Implementations/MqttTcpChannel.Uwp.cs b/Source/MQTTnet/Implementations/MqttTcpChannel.Uwp.cs index 2b84899..cb0f71c 100644 --- a/Source/MQTTnet/Implementations/MqttTcpChannel.Uwp.cs +++ b/Source/MQTTnet/Implementations/MqttTcpChannel.Uwp.cs @@ -132,7 +132,7 @@ namespace MQTTnet.Implementations throw new NotSupportedException("Only one client certificate is supported for UWP."); } - return new Certificate(options.TlsOptions.Certificates.First().GetRawCertData()); + return new Certificate(options.TlsOptions.Certificates.First().AsBuffer()); } private IEnumerable ResolveIgnorableServerCertificateErrors() diff --git a/Source/MQTTnet/Implementations/MqttWebSocketChannel.cs b/Source/MQTTnet/Implementations/MqttWebSocketChannel.cs index c53a131..db53936 100644 --- a/Source/MQTTnet/Implementations/MqttWebSocketChannel.cs +++ b/Source/MQTTnet/Implementations/MqttWebSocketChannel.cs @@ -84,7 +84,12 @@ namespace MQTTnet.Implementations 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 + } } From 624fafc6625c26e1e783d862ed21f58096f43819 Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Sun, 8 Dec 2019 20:10:46 +0100 Subject: [PATCH 31/51] Update nugets. --- Source/MQTTnet/MQTTnet.csproj | 4 ++++ .../MQTTnet.AspNetCore.Tests/MQTTnet.AspNetCore.Tests.csproj | 2 +- .../MQTTnet.Benchmarks/Configurations/RuntimeCompareConfig.cs | 4 ++-- Tests/MQTTnet.Benchmarks/MQTTnet.Benchmarks.csproj | 2 +- Tests/MQTTnet.Core.Tests/MQTTnet.Tests.csproj | 2 +- Tests/MQTTnet.TestApp.NetCore/MQTTnet.TestApp.NetCore.csproj | 2 +- 6 files changed, 10 insertions(+), 6 deletions(-) diff --git a/Source/MQTTnet/MQTTnet.csproj b/Source/MQTTnet/MQTTnet.csproj index 61ca517..28e3f3d 100644 --- a/Source/MQTTnet/MQTTnet.csproj +++ b/Source/MQTTnet/MQTTnet.csproj @@ -62,5 +62,9 @@ + + + + \ No newline at end of file diff --git a/Tests/MQTTnet.AspNetCore.Tests/MQTTnet.AspNetCore.Tests.csproj b/Tests/MQTTnet.AspNetCore.Tests/MQTTnet.AspNetCore.Tests.csproj index 293fa3d..13bb3d5 100644 --- a/Tests/MQTTnet.AspNetCore.Tests/MQTTnet.AspNetCore.Tests.csproj +++ b/Tests/MQTTnet.AspNetCore.Tests/MQTTnet.AspNetCore.Tests.csproj @@ -6,7 +6,7 @@ - + diff --git a/Tests/MQTTnet.Benchmarks/Configurations/RuntimeCompareConfig.cs b/Tests/MQTTnet.Benchmarks/Configurations/RuntimeCompareConfig.cs index 608271e..5838424 100644 --- a/Tests/MQTTnet.Benchmarks/Configurations/RuntimeCompareConfig.cs +++ b/Tests/MQTTnet.Benchmarks/Configurations/RuntimeCompareConfig.cs @@ -9,8 +9,8 @@ namespace MQTTnet.Benchmarks.Configurations { public RuntimeCompareConfig() { - Add(Job.Default.With(Runtime.Clr)); - Add(Job.Default.With(Runtime.Core).With(CsProjCoreToolchain.NetCoreApp21)); + Add(Job.Default.With(ClrRuntime.Net472)); + Add(Job.Default.With(CoreRuntime.Core22).With(CsProjCoreToolchain.NetCoreApp22)); } } diff --git a/Tests/MQTTnet.Benchmarks/MQTTnet.Benchmarks.csproj b/Tests/MQTTnet.Benchmarks/MQTTnet.Benchmarks.csproj index 03aabc8..b34c5a3 100644 --- a/Tests/MQTTnet.Benchmarks/MQTTnet.Benchmarks.csproj +++ b/Tests/MQTTnet.Benchmarks/MQTTnet.Benchmarks.csproj @@ -9,7 +9,7 @@ - + diff --git a/Tests/MQTTnet.Core.Tests/MQTTnet.Tests.csproj b/Tests/MQTTnet.Core.Tests/MQTTnet.Tests.csproj index fb76bba..a4b6881 100644 --- a/Tests/MQTTnet.Core.Tests/MQTTnet.Tests.csproj +++ b/Tests/MQTTnet.Core.Tests/MQTTnet.Tests.csproj @@ -8,7 +8,7 @@ - + diff --git a/Tests/MQTTnet.TestApp.NetCore/MQTTnet.TestApp.NetCore.csproj b/Tests/MQTTnet.TestApp.NetCore/MQTTnet.TestApp.NetCore.csproj index d60256e..4fffc33 100644 --- a/Tests/MQTTnet.TestApp.NetCore/MQTTnet.TestApp.NetCore.csproj +++ b/Tests/MQTTnet.TestApp.NetCore/MQTTnet.TestApp.NetCore.csproj @@ -12,7 +12,7 @@ - + From f949d54aef6bbf8b3db99838d7122a6a43783925 Mon Sep 17 00:00:00 2001 From: Federico Di Gregorio Date: Tue, 17 Dec 2019 11:54:59 +0100 Subject: [PATCH 32/51] Added IMqttRetainedMessagesManager * Added interface IMqttRetainedMessagesManager to allow for different retained messages manager implementations. The interface copies almost exactly the current MqttRetainedMessagesManager implementation. * Added IMqttRetainedMessagesManager.Start() to configure the provided IMqttRetainedMessagesManager outside the constructor. This method returns Task because some implementations that use external storage may be slow on initialization (e.g., connect to a database). * Modified MqttRetainedMessagesManager to implement new interface. --- .../Server/IMqttRetainedMessagesManager.cs | 21 +++++++++++++++++++ Source/MQTTnet/Server/IMqttServerOptions.cs | 4 ++-- Source/MQTTnet/Server/MqttClientConnection.cs | 16 +++++++------- Source/MQTTnet/Server/MqttClientSession.cs | 2 +- .../Server/MqttClientSessionsManager.cs | 16 +++++++------- .../Server/MqttRetainedMessagesManager.cs | 11 +++++----- Source/MQTTnet/Server/MqttServer.cs | 13 ++++++------ Source/MQTTnet/Server/MqttServerOptions.cs | 4 +++- .../Server/MqttServerOptionsBuilder.cs | 10 +++++++-- 9 files changed, 64 insertions(+), 33 deletions(-) create mode 100644 Source/MQTTnet/Server/IMqttRetainedMessagesManager.cs diff --git a/Source/MQTTnet/Server/IMqttRetainedMessagesManager.cs b/Source/MQTTnet/Server/IMqttRetainedMessagesManager.cs new file mode 100644 index 0000000..36ace61 --- /dev/null +++ b/Source/MQTTnet/Server/IMqttRetainedMessagesManager.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using MQTTnet.Diagnostics; + +namespace MQTTnet.Server +{ + public interface IMqttRetainedMessagesManager + { + Task Start(IMqttServerOptions options, IMqttNetChildLogger logger); + + Task LoadMessagesAsync(); + + Task ClearMessagesAsync(); + + Task HandleMessageAsync(string clientId, MqttApplicationMessage applicationMessage); + + Task> GetMessagesAsync(); + + Task> GetSubscribedMessagesAsync(ICollection topicFilters); + } +} diff --git a/Source/MQTTnet/Server/IMqttServerOptions.cs b/Source/MQTTnet/Server/IMqttServerOptions.cs index 2145845..7df6f54 100644 --- a/Source/MQTTnet/Server/IMqttServerOptions.cs +++ b/Source/MQTTnet/Server/IMqttServerOptions.cs @@ -22,8 +22,8 @@ namespace MQTTnet.Server MqttServerTcpEndpointOptions DefaultEndpointOptions { get; } MqttServerTlsTcpEndpointOptions TlsEndpointOptions { get; } - IMqttServerStorage Storage { get; } - + IMqttServerStorage Storage { get; } + IMqttRetainedMessagesManager RetainedMessagesManager { get; } } } \ No newline at end of file diff --git a/Source/MQTTnet/Server/MqttClientConnection.cs b/Source/MQTTnet/Server/MqttClientConnection.cs index ed36b6a..c9e2553 100644 --- a/Source/MQTTnet/Server/MqttClientConnection.cs +++ b/Source/MQTTnet/Server/MqttClientConnection.cs @@ -21,7 +21,7 @@ namespace MQTTnet.Server private readonly MqttPacketDispatcher _packetDispatcher = new MqttPacketDispatcher(); private readonly CancellationTokenSource _cancellationToken = new CancellationTokenSource(); - private readonly MqttRetainedMessagesManager _retainedMessagesManager; + private readonly IMqttRetainedMessagesManager _retainedMessagesManager; private readonly MqttClientKeepAliveMonitor _keepAliveMonitor; private readonly MqttClientSessionsManager _sessionsManager; @@ -36,7 +36,7 @@ namespace MQTTnet.Server private Task _packageReceiverTask; private DateTime _lastPacketReceivedTimestamp; private DateTime _lastNonKeepAlivePacketReceivedTimestamp; - + private long _receivedPacketsCount; private long _sentPacketsCount = 1; // Start with 1 because the CONNECT packet is not counted anywhere. private long _receivedApplicationMessagesCount; @@ -48,14 +48,14 @@ namespace MQTTnet.Server MqttClientSession session, IMqttServerOptions serverOptions, MqttClientSessionsManager sessionsManager, - MqttRetainedMessagesManager retainedMessagesManager, + IMqttRetainedMessagesManager retainedMessagesManager, IMqttNetChildLogger logger) { Session = session ?? throw new ArgumentNullException(nameof(session)); _serverOptions = serverOptions ?? throw new ArgumentNullException(nameof(serverOptions)); _sessionsManager = sessionsManager ?? throw new ArgumentNullException(nameof(sessionsManager)); _retainedMessagesManager = retainedMessagesManager ?? throw new ArgumentNullException(nameof(retainedMessagesManager)); - + _channelAdapter = channelAdapter ?? throw new ArgumentNullException(nameof(channelAdapter)); _dataConverter = _channelAdapter.PacketFormatterAdapter.DataConverter; _endpoint = _channelAdapter.Endpoint; @@ -76,7 +76,7 @@ namespace MQTTnet.Server public string ClientId => ConnectPacket.ClientId; public MqttClientSession Session { get; } - + public async Task StopAsync() { StopInternal(); @@ -112,7 +112,7 @@ namespace MQTTnet.Server status.BytesSent = _channelAdapter.BytesSent; status.BytesReceived = _channelAdapter.BytesReceived; } - + public void Dispose() { _cancellationToken.Dispose(); @@ -130,7 +130,7 @@ namespace MQTTnet.Server try { _logger.Info("Client '{0}': Session started.", ClientId); - + _channelAdapter.ReadingPacketStartedCallback = OnAdapterReadingPacketStarted; _channelAdapter.ReadingPacketCompletedCallback = OnAdapterReadingPacketCompleted; @@ -244,7 +244,7 @@ namespace MQTTnet.Server _channelAdapter.ReadingPacketCompletedCallback = null; _logger.Info("Client '{0}': Session stopped.", ClientId); - + _packageReceiverTask = null; } diff --git a/Source/MQTTnet/Server/MqttClientSession.cs b/Source/MQTTnet/Server/MqttClientSession.cs index d165001..d097b9f 100644 --- a/Source/MQTTnet/Server/MqttClientSession.cs +++ b/Source/MQTTnet/Server/MqttClientSession.cs @@ -52,7 +52,7 @@ namespace MQTTnet.Server ApplicationMessagesQueue.Enqueue(applicationMessage, senderClientId, checkSubscriptionsResult.QualityOfServiceLevel, isRetainedApplicationMessage); } - public async Task SubscribeAsync(ICollection topicFilters, MqttRetainedMessagesManager retainedMessagesManager) + public async Task SubscribeAsync(ICollection topicFilters, IMqttRetainedMessagesManager retainedMessagesManager) { await SubscriptionsManager.SubscribeAsync(topicFilters).ConfigureAwait(false); diff --git a/Source/MQTTnet/Server/MqttClientSessionsManager.cs b/Source/MQTTnet/Server/MqttClientSessionsManager.cs index 2da3efc..7e366a6 100644 --- a/Source/MQTTnet/Server/MqttClientSessionsManager.cs +++ b/Source/MQTTnet/Server/MqttClientSessionsManager.cs @@ -25,13 +25,13 @@ namespace MQTTnet.Server private readonly CancellationToken _cancellationToken; private readonly MqttServerEventDispatcher _eventDispatcher; - private readonly MqttRetainedMessagesManager _retainedMessagesManager; + private readonly IMqttRetainedMessagesManager _retainedMessagesManager; private readonly IMqttServerOptions _options; private readonly IMqttNetChildLogger _logger; public MqttClientSessionsManager( IMqttServerOptions options, - MqttRetainedMessagesManager retainedMessagesManager, + IMqttRetainedMessagesManager retainedMessagesManager, CancellationToken cancellationToken, MqttServerEventDispatcher eventDispatcher, IMqttNetChildLogger logger) @@ -72,7 +72,7 @@ namespace MQTTnet.Server { var clientStatus = new MqttClientStatus(connection); connection.FillStatus(clientStatus); - + var sessionStatus = new MqttSessionStatus(connection.Session, this); connection.Session.FillStatus(sessionStatus); clientStatus.Session = sessionStatus; @@ -91,7 +91,7 @@ namespace MQTTnet.Server { var sessionStatus = new MqttSessionStatus(session, this); session.FillStatus(sessionStatus); - + result.Add(sessionStatus); } @@ -259,7 +259,7 @@ namespace MQTTnet.Server var connection = await CreateConnectionAsync(connectPacket, connectionValidatorContext, channelAdapter).ConfigureAwait(false); await _eventDispatcher.HandleClientConnectedAsync(clientId).ConfigureAwait(false); - + disconnectType = await connection.RunAsync(connectionValidatorContext).ConfigureAwait(false); } catch (OperationCanceledException) @@ -272,7 +272,7 @@ namespace MQTTnet.Server finally { if (clientWasConnected) - { + { if (clientId != null) { _connections.TryRemove(clientId, out _); @@ -333,13 +333,13 @@ namespace MQTTnet.Server { await existingConnection.StopAsync().ConfigureAwait(false); } - + if (isSessionPresent) { if (connectPacket.CleanSession) { session = null; - + _logger.Verbose("Deleting existing session of client '{0}'.", connectPacket.ClientId); } else diff --git a/Source/MQTTnet/Server/MqttRetainedMessagesManager.cs b/Source/MQTTnet/Server/MqttRetainedMessagesManager.cs index 2e6af16..e79bcef 100644 --- a/Source/MQTTnet/Server/MqttRetainedMessagesManager.cs +++ b/Source/MQTTnet/Server/MqttRetainedMessagesManager.cs @@ -7,20 +7,21 @@ using MQTTnet.Internal; namespace MQTTnet.Server { - public class MqttRetainedMessagesManager + public class MqttRetainedMessagesManager : IMqttRetainedMessagesManager { private readonly byte[] _emptyArray = new byte[0]; private readonly AsyncLock _messagesLock = new AsyncLock(); private readonly Dictionary _messages = new Dictionary(); - private readonly IMqttNetChildLogger _logger; - private readonly IMqttServerOptions _options; + private IMqttNetChildLogger _logger; + private IMqttServerOptions _options; - public MqttRetainedMessagesManager(IMqttServerOptions options, IMqttNetChildLogger logger) + public Task Start(IMqttServerOptions options, IMqttNetChildLogger logger) { if (logger == null) throw new ArgumentNullException(nameof(logger)); _logger = logger.CreateChildLogger(nameof(MqttRetainedMessagesManager)); _options = options ?? throw new ArgumentNullException(nameof(options)); + return Task.CompletedTask; } public async Task LoadMessagesAsync() @@ -128,7 +129,7 @@ namespace MQTTnet.Server break; } } - + return matchingRetainedMessages; } diff --git a/Source/MQTTnet/Server/MqttServer.cs b/Source/MQTTnet/Server/MqttServer.cs index f902fc0..dee2274 100644 --- a/Source/MQTTnet/Server/MqttServer.cs +++ b/Source/MQTTnet/Server/MqttServer.cs @@ -19,7 +19,7 @@ namespace MQTTnet.Server private readonly IMqttNetChildLogger _logger; private MqttClientSessionsManager _clientSessionsManager; - private MqttRetainedMessagesManager _retainedMessagesManager; + private IMqttRetainedMessagesManager _retainedMessagesManager; private CancellationTokenSource _cancellationTokenSource; public MqttServer(IEnumerable adapters, IMqttNetChildLogger logger) @@ -48,7 +48,7 @@ namespace MQTTnet.Server get => _eventDispatcher.ClientDisconnectedHandler; set => _eventDispatcher.ClientDisconnectedHandler = value; } - + public IMqttServerClientSubscribedTopicHandler ClientSubscribedTopicHandler { get => _eventDispatcher.ClientSubscribedTopicHandler; @@ -60,7 +60,7 @@ namespace MQTTnet.Server get => _eventDispatcher.ClientUnsubscribedTopicHandler; set => _eventDispatcher.ClientUnsubscribedTopicHandler = value; } - + public IMqttApplicationMessageReceivedHandler ApplicationMessageReceivedHandler { get => _eventDispatcher.ApplicationMessageReceivedHandler; @@ -121,7 +121,8 @@ namespace MQTTnet.Server _cancellationTokenSource = new CancellationTokenSource(); - _retainedMessagesManager = new MqttRetainedMessagesManager(Options, _logger); + _retainedMessagesManager = Options.RetainedMessagesManager ?? new MqttRetainedMessagesManager(); + await _retainedMessagesManager.Start(Options, _logger); await _retainedMessagesManager.LoadMessagesAsync().ConfigureAwait(false); _clientSessionsManager = new MqttClientSessionsManager(Options, _retainedMessagesManager, _cancellationTokenSource.Token, _eventDispatcher, _logger); @@ -150,9 +151,9 @@ namespace MQTTnet.Server { return; } - + await _clientSessionsManager.StopAsync().ConfigureAwait(false); - + _cancellationTokenSource.Cancel(false); foreach (var adapter in _adapters) diff --git a/Source/MQTTnet/Server/MqttServerOptions.cs b/Source/MQTTnet/Server/MqttServerOptions.cs index d5f6737..c6c7c4f 100644 --- a/Source/MQTTnet/Server/MqttServerOptions.cs +++ b/Source/MQTTnet/Server/MqttServerOptions.cs @@ -21,7 +21,7 @@ namespace MQTTnet.Server public IMqttServerConnectionValidator ConnectionValidator { get; set; } public IMqttServerApplicationMessageInterceptor ApplicationMessageInterceptor { get; set; } - + public IMqttServerClientMessageQueueInterceptor ClientMessageQueueInterceptor { get; set; } public IMqttServerSubscriptionInterceptor SubscriptionInterceptor { get; set; } @@ -29,5 +29,7 @@ namespace MQTTnet.Server public IMqttServerUnsubscriptionInterceptor UnsubscriptionInterceptor { get; set; } public IMqttServerStorage Storage { get; set; } + + public IMqttRetainedMessagesManager RetainedMessagesManager { get; set; } } } diff --git a/Source/MQTTnet/Server/MqttServerOptionsBuilder.cs b/Source/MQTTnet/Server/MqttServerOptionsBuilder.cs index 15126a1..2970fab 100644 --- a/Source/MQTTnet/Server/MqttServerOptionsBuilder.cs +++ b/Source/MQTTnet/Server/MqttServerOptionsBuilder.cs @@ -57,7 +57,7 @@ namespace MQTTnet.Server _options.DefaultEndpointOptions.IsEnabled = false; return this; } - + public MqttServerOptionsBuilder WithEncryptedEndpoint() { _options.TlsEndpointOptions.IsEnabled = true; @@ -118,13 +118,19 @@ namespace MQTTnet.Server return this; } #endif - + public MqttServerOptionsBuilder WithStorage(IMqttServerStorage value) { _options.Storage = value; return this; } + public MqttServerOptionsBuilder WithRetainedMessagesManager(IMqttRetainedMessagesManager value) + { + _options.RetainedMessagesManager = value; + return this; + } + public MqttServerOptionsBuilder WithConnectionValidator(IMqttServerConnectionValidator value) { _options.ConnectionValidator = value; From 55676965d93da80baccdb98fc10a225b0cd3403b Mon Sep 17 00:00:00 2001 From: Federico Di Gregorio Date: Tue, 17 Dec 2019 12:39:57 +0100 Subject: [PATCH 33/51] Use IList in IMqttRetainedMessagesManager return values --- Source/MQTTnet/Server/IMqttRetainedMessagesManager.cs | 2 +- Source/MQTTnet/Server/MqttRetainedMessagesManager.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/MQTTnet/Server/IMqttRetainedMessagesManager.cs b/Source/MQTTnet/Server/IMqttRetainedMessagesManager.cs index 36ace61..6ffdd2b 100644 --- a/Source/MQTTnet/Server/IMqttRetainedMessagesManager.cs +++ b/Source/MQTTnet/Server/IMqttRetainedMessagesManager.cs @@ -16,6 +16,6 @@ namespace MQTTnet.Server Task> GetMessagesAsync(); - Task> GetSubscribedMessagesAsync(ICollection topicFilters); + Task> GetSubscribedMessagesAsync(ICollection topicFilters); } } diff --git a/Source/MQTTnet/Server/MqttRetainedMessagesManager.cs b/Source/MQTTnet/Server/MqttRetainedMessagesManager.cs index e79bcef..c4e2f96 100644 --- a/Source/MQTTnet/Server/MqttRetainedMessagesManager.cs +++ b/Source/MQTTnet/Server/MqttRetainedMessagesManager.cs @@ -104,7 +104,7 @@ namespace MQTTnet.Server } } - public async Task> GetSubscribedMessagesAsync(ICollection topicFilters) + public async Task> GetSubscribedMessagesAsync(ICollection topicFilters) { if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); From 3600ebbc624333d3a92d93ace214c37ef14fa4b6 Mon Sep 17 00:00:00 2001 From: Federico Di Gregorio Date: Wed, 18 Dec 2019 10:25:24 +0100 Subject: [PATCH 34/51] Create default MqttRetainedMessagesManager in options --- .../Exceptions/MqttConfigurationException.cs | 21 +++++++++++++++++++ Source/MQTTnet/Server/MqttServer.cs | 5 ++++- Source/MQTTnet/Server/MqttServerOptions.cs | 2 +- 3 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 Source/MQTTnet/Exceptions/MqttConfigurationException.cs diff --git a/Source/MQTTnet/Exceptions/MqttConfigurationException.cs b/Source/MQTTnet/Exceptions/MqttConfigurationException.cs new file mode 100644 index 0000000..4d10faf --- /dev/null +++ b/Source/MQTTnet/Exceptions/MqttConfigurationException.cs @@ -0,0 +1,21 @@ +using System; + +namespace MQTTnet.Exceptions +{ + public class MqttConfigurationException : Exception + { + protected MqttConfigurationException() + { + } + + public MqttConfigurationException(Exception innerException) + : base(innerException.Message, innerException) + { + } + + public MqttConfigurationException(string message) + : base(message) + { + } + } +} diff --git a/Source/MQTTnet/Server/MqttServer.cs b/Source/MQTTnet/Server/MqttServer.cs index dee2274..4c5ab62 100644 --- a/Source/MQTTnet/Server/MqttServer.cs +++ b/Source/MQTTnet/Server/MqttServer.cs @@ -7,6 +7,7 @@ using MQTTnet.Adapter; using MQTTnet.Client.Publishing; using MQTTnet.Client.Receiving; using MQTTnet.Diagnostics; +using MQTTnet.Exceptions; using MQTTnet.Protocol; using MQTTnet.Server.Status; @@ -117,11 +118,13 @@ namespace MQTTnet.Server { Options = options ?? throw new ArgumentNullException(nameof(options)); + if (Options.RetainedMessagesManager == null) throw new MqttConfigurationException("options.RetainedMessagesManager should not be null."); + if (_cancellationTokenSource != null) throw new InvalidOperationException("The server is already started."); _cancellationTokenSource = new CancellationTokenSource(); - _retainedMessagesManager = Options.RetainedMessagesManager ?? new MqttRetainedMessagesManager(); + _retainedMessagesManager = Options.RetainedMessagesManager; await _retainedMessagesManager.Start(Options, _logger); await _retainedMessagesManager.LoadMessagesAsync().ConfigureAwait(false); diff --git a/Source/MQTTnet/Server/MqttServerOptions.cs b/Source/MQTTnet/Server/MqttServerOptions.cs index c6c7c4f..9773e72 100644 --- a/Source/MQTTnet/Server/MqttServerOptions.cs +++ b/Source/MQTTnet/Server/MqttServerOptions.cs @@ -30,6 +30,6 @@ namespace MQTTnet.Server public IMqttServerStorage Storage { get; set; } - public IMqttRetainedMessagesManager RetainedMessagesManager { get; set; } + public IMqttRetainedMessagesManager RetainedMessagesManager { get; set; } = new MqttRetainedMessagesManager(); } } From 14419e4d63f6d128fe13c99d05121fa28baa65a7 Mon Sep 17 00:00:00 2001 From: JanEggers Date: Fri, 20 Dec 2019 13:47:00 +0100 Subject: [PATCH 35/51] fixed clients overlapping from one test to another by: - adding client and server wrappers to verify the clientid starts with the testname - adding disposable baseclass - clear disconnect handler from client so it does not try to reconnect after it is disposed - moved code from dispose to cleanup in case it is called in a stop method as dispose may only be called once while start / stop may be called multiple times --- Source/MQTTnet/Adapter/MqttChannelAdapter.cs | 20 +--- Source/MQTTnet/Client/MqttClient.cs | 36 ++++-- .../Implementations/MqttTcpServerAdapter.cs | 16 ++- .../Implementations/MqttWebSocketChannel.cs | 33 ++--- Source/MQTTnet/Internal/BlockingQueue.cs | 10 +- Source/MQTTnet/Internal/Disposable.cs | 54 +++++++++ ...ttClientSessionApplicationMessagesQueue.cs | 11 +- .../Server/MqttClientSessionsManager.cs | 10 +- .../MQTTnet.Core.Tests/MQTTv5/Client_Tests.cs | 8 +- .../MQTTv5/Feature_Tests.cs | 4 +- .../MQTTnet.Core.Tests/MQTTv5/Server_Tests.cs | 4 +- .../ManagedMqttClient_Tests.cs | 16 +-- .../Mockups/TestClientWrapper.cs | 94 +++++++++++++++ .../Mockups/TestEnvironment.cs | 37 +++--- .../Mockups/TestServerWrapper.cs | 108 +++++++++++++++++ Tests/MQTTnet.Core.Tests/MqttClient_Tests.cs | 34 +++--- Tests/MQTTnet.Core.Tests/MqttFactory_Tests.cs | 8 +- Tests/MQTTnet.Core.Tests/RPC_Tests.cs | 8 +- .../MQTTnet.Core.Tests/RoundtripTime_Tests.cs | 4 +- .../MQTTnet.Core.Tests/Server_Status_Tests.cs | 18 +-- Tests/MQTTnet.Core.Tests/Server_Tests.cs | 113 ++++++++---------- Tests/MQTTnet.Core.Tests/Session_Tests.cs | 4 +- 22 files changed, 483 insertions(+), 167 deletions(-) create mode 100644 Source/MQTTnet/Internal/Disposable.cs create mode 100644 Tests/MQTTnet.Core.Tests/Mockups/TestClientWrapper.cs create mode 100644 Tests/MQTTnet.Core.Tests/Mockups/TestServerWrapper.cs diff --git a/Source/MQTTnet/Adapter/MqttChannelAdapter.cs b/Source/MQTTnet/Adapter/MqttChannelAdapter.cs index 4a0a85d..f364168 100644 --- a/Source/MQTTnet/Adapter/MqttChannelAdapter.cs +++ b/Source/MQTTnet/Adapter/MqttChannelAdapter.cs @@ -14,7 +14,7 @@ using MQTTnet.Packets; namespace MQTTnet.Adapter { - public class MqttChannelAdapter : IMqttChannelAdapter + public class MqttChannelAdapter : Disposable, IMqttChannelAdapter { private const uint ErrorOperationAborted = 0x800703E3; private const int ReadBufferSize = 4096; // TODO: Move buffer size to config @@ -26,9 +26,7 @@ namespace MQTTnet.Adapter private readonly MqttPacketReader _packetReader; private readonly byte[] _fixedHeaderBuffer = new byte[2]; - - private bool _isDisposed; - + private long _bytesReceived; private long _bytesSent; @@ -269,19 +267,13 @@ namespace MQTTnet.Adapter } } - public void Dispose() - { - _isDisposed = true; - - _channel?.Dispose(); - } - - private void ThrowIfDisposed() + protected override void Dispose(bool disposing) { - if (_isDisposed) + if (disposing) { - throw new ObjectDisposedException(nameof(MqttChannelAdapter)); + _channel?.Dispose(); } + base.Dispose(disposing); } private static bool IsWrappedException(Exception exception) diff --git a/Source/MQTTnet/Client/MqttClient.cs b/Source/MQTTnet/Client/MqttClient.cs index c8aa859..c9ef061 100644 --- a/Source/MQTTnet/Client/MqttClient.cs +++ b/Source/MQTTnet/Client/MqttClient.cs @@ -20,7 +20,7 @@ using MQTTnet.Protocol; namespace MQTTnet.Client { - public class MqttClient : IMqttClient + public class MqttClient : Disposable, IMqttClient { private readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider(); private readonly MqttPacketDispatcher _packetDispatcher = new MqttPacketDispatcher(); @@ -63,6 +63,8 @@ namespace MQTTnet.Client ThrowIfConnected("It is not allowed to connect with a server after the connection is established."); + ThrowIfDisposed(); + MqttClientAuthenticateResult authenticateResult = null; try @@ -79,13 +81,16 @@ namespace MQTTnet.Client var adapter = _adapterFactory.CreateClientAdapter(options, _logger); _adapter = adapter; - _logger.Verbose($"Trying to connect with server '{options.ChannelOptions}' (Timeout={options.CommunicationTimeout})."); - await _adapter.ConnectAsync(options.CommunicationTimeout, cancellationToken).ConfigureAwait(false); - _logger.Verbose("Connection with server established."); + using (var combined = CancellationTokenSource.CreateLinkedTokenSource(backgroundCancellationToken, cancellationToken)) + { + _logger.Verbose($"Trying to connect with server '{options.ChannelOptions}' (Timeout={options.CommunicationTimeout})."); + await _adapter.ConnectAsync(options.CommunicationTimeout, combined.Token).ConfigureAwait(false); + _logger.Verbose("Connection with server established."); - _packetReceiverTask = Task.Run(() => TryReceivePacketsAsync(backgroundCancellationToken), backgroundCancellationToken); + _packetReceiverTask = Task.Run(() => TryReceivePacketsAsync(backgroundCancellationToken), backgroundCancellationToken); - authenticateResult = await AuthenticateAsync(adapter, options.WillMessage, cancellationToken).ConfigureAwait(false); + authenticateResult = await AuthenticateAsync(adapter, options.WillMessage, combined.Token).ConfigureAwait(false); + } _sendTracker.Restart(); @@ -161,6 +166,7 @@ namespace MQTTnet.Client { if (options == null) throw new ArgumentNullException(nameof(options)); + ThrowIfDisposed(); ThrowIfNotConnected(); var subscribePacket = _adapter.PacketFormatterAdapter.DataConverter.CreateSubscribePacket(options); @@ -174,6 +180,7 @@ namespace MQTTnet.Client { if (options == null) throw new ArgumentNullException(nameof(options)); + ThrowIfDisposed(); ThrowIfNotConnected(); var unsubscribePacket = _adapter.PacketFormatterAdapter.DataConverter.CreateUnsubscribePacket(options); @@ -189,6 +196,7 @@ namespace MQTTnet.Client MqttTopicValidator.ThrowIfInvalid(applicationMessage.Topic); + ThrowIfDisposed(); ThrowIfNotConnected(); var publishPacket = _adapter.PacketFormatterAdapter.DataConverter.CreatePublishPacket(applicationMessage); @@ -214,7 +222,7 @@ namespace MQTTnet.Client } } - public void Dispose() + private void Cleanup() { _backgroundCancellationTokenSource?.Cancel(false); _backgroundCancellationTokenSource?.Dispose(); @@ -224,6 +232,18 @@ namespace MQTTnet.Client _adapter = null; } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + Cleanup(); + + DisconnectedHandler = null; + } + base.Dispose(disposing); + } + private async Task AuthenticateAsync(IMqttChannelAdapter channelAdapter, MqttApplicationMessage willApplicationMessage, CancellationToken cancellationToken) { var connectPacket = channelAdapter.PacketFormatterAdapter.DataConverter.CreateConnectPacket( @@ -288,7 +308,7 @@ namespace MQTTnet.Client } finally { - Dispose(); + Cleanup(); _cleanDisconnectInitiated = false; _logger.Info("Disconnected."); diff --git a/Source/MQTTnet/Implementations/MqttTcpServerAdapter.cs b/Source/MQTTnet/Implementations/MqttTcpServerAdapter.cs index d7f4e6f..501c4da 100644 --- a/Source/MQTTnet/Implementations/MqttTcpServerAdapter.cs +++ b/Source/MQTTnet/Implementations/MqttTcpServerAdapter.cs @@ -8,11 +8,12 @@ using System.Threading; using System.Threading.Tasks; using MQTTnet.Adapter; using MQTTnet.Diagnostics; +using MQTTnet.Internal; using MQTTnet.Server; namespace MQTTnet.Implementations { - public class MqttTcpServerAdapter : IMqttServerAdapter + public class MqttTcpServerAdapter : Disposable, IMqttServerAdapter { private readonly List _listeners = new List(); private readonly IMqttNetChildLogger _logger; @@ -72,11 +73,11 @@ namespace MQTTnet.Implementations public Task StopAsync() { - Dispose(); + Cleanup(); return Task.FromResult(0); } - public void Dispose() + private void Cleanup() { _cancellationTokenSource?.Cancel(false); _cancellationTokenSource?.Dispose(); @@ -90,6 +91,15 @@ namespace MQTTnet.Implementations _listeners.Clear(); } + protected override void Dispose(bool disposing) + { + if (disposing) + { + Cleanup(); + } + base.Dispose(disposing); + } + private void RegisterListeners(MqttServerTcpEndpointBaseOptions options, X509Certificate2 tlsCertificate, CancellationToken cancellationToken) { if (!options.BoundInterNetworkAddress.Equals(IPAddress.None)) diff --git a/Source/MQTTnet/Implementations/MqttWebSocketChannel.cs b/Source/MQTTnet/Implementations/MqttWebSocketChannel.cs index db53936..4a3dc70 100644 --- a/Source/MQTTnet/Implementations/MqttWebSocketChannel.cs +++ b/Source/MQTTnet/Implementations/MqttWebSocketChannel.cs @@ -6,10 +6,11 @@ using System.Threading; using System.Threading.Tasks; using MQTTnet.Channel; using MQTTnet.Client.Options; +using MQTTnet.Internal; namespace MQTTnet.Implementations { - public class MqttWebSocketChannel : IMqttChannel + public class MqttWebSocketChannel : Disposable, IMqttChannel { private readonly MqttClientWebSocketOptions _options; @@ -141,22 +142,26 @@ namespace MQTTnet.Implementations } } - public void Dispose() + protected override void Dispose(bool disposing) { - _sendLock?.Dispose(); - _sendLock = null; - - try - { - _webSocket?.Dispose(); - } - catch (ObjectDisposedException) - { - } - finally + if (disposing) { - _webSocket = null; + _sendLock?.Dispose(); + _sendLock = null; + + try + { + _webSocket?.Dispose(); + } + catch (ObjectDisposedException) + { + } + finally + { + _webSocket = null; + } } + base.Dispose(disposing); } private IWebProxy CreateProxy() diff --git a/Source/MQTTnet/Internal/BlockingQueue.cs b/Source/MQTTnet/Internal/BlockingQueue.cs index 6225105..2fa21be 100644 --- a/Source/MQTTnet/Internal/BlockingQueue.cs +++ b/Source/MQTTnet/Internal/BlockingQueue.cs @@ -4,7 +4,7 @@ using System.Threading; namespace MQTTnet.Internal { - public class BlockingQueue : IDisposable + public class BlockingQueue : Disposable { private readonly object _syncRoot = new object(); private readonly LinkedList _items = new LinkedList(); @@ -109,9 +109,13 @@ namespace MQTTnet.Internal } } - public void Dispose() + protected override void Dispose(bool disposing) { - _gate.Dispose(); + if (disposing) + { + _gate.Dispose(); + } + base.Dispose(disposing); } } } diff --git a/Source/MQTTnet/Internal/Disposable.cs b/Source/MQTTnet/Internal/Disposable.cs new file mode 100644 index 0000000..2074b49 --- /dev/null +++ b/Source/MQTTnet/Internal/Disposable.cs @@ -0,0 +1,54 @@ +using System; + +namespace MQTTnet.Internal +{ + public class Disposable : IDisposable + { + protected void ThrowIfDisposed() + { + if (_isDisposed) + { + throw new ObjectDisposedException(GetType().Name); + } + } + + + #region IDisposable Support + + private bool _isDisposed = false; // To detect redundant calls + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + // TODO: dispose managed state (managed objects). + } + + // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below. + // TODO: set large fields to null. + } + + // TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources. + // ~Disposable() + // { + // // Do not change this code. Put cleanup code in Dispose(bool disposing) above. + // Dispose(false); + // } + + // This code added to correctly implement the disposable pattern. + public void Dispose() + { + if (_isDisposed) + { + return; + } + // Do not change this code. Put cleanup code in Dispose(bool disposing) above. + Dispose(true); + // TODO: uncomment the following line if the finalizer is overridden above. + // GC.SuppressFinalize(this); + + _isDisposed = true; + } + #endregion + } +} diff --git a/Source/MQTTnet/Server/MqttClientSessionApplicationMessagesQueue.cs b/Source/MQTTnet/Server/MqttClientSessionApplicationMessagesQueue.cs index 901ac75..0cf19c8 100644 --- a/Source/MQTTnet/Server/MqttClientSessionApplicationMessagesQueue.cs +++ b/Source/MQTTnet/Server/MqttClientSessionApplicationMessagesQueue.cs @@ -6,7 +6,7 @@ using System.Threading.Tasks; namespace MQTTnet.Server { - public class MqttClientSessionApplicationMessagesQueue : IDisposable + public class MqttClientSessionApplicationMessagesQueue : Disposable { private readonly AsyncQueue _messageQueue = new AsyncQueue(); @@ -71,9 +71,14 @@ namespace MQTTnet.Server } } - public void Dispose() + protected override void Dispose(bool disposing) { - _messageQueue.Dispose(); + if (disposing) + { + _messageQueue.Dispose(); + } + + base.Dispose(disposing); } } } diff --git a/Source/MQTTnet/Server/MqttClientSessionsManager.cs b/Source/MQTTnet/Server/MqttClientSessionsManager.cs index 2da3efc..890cf6a 100644 --- a/Source/MQTTnet/Server/MqttClientSessionsManager.cs +++ b/Source/MQTTnet/Server/MqttClientSessionsManager.cs @@ -13,7 +13,7 @@ using MQTTnet.Server.Status; namespace MQTTnet.Server { - public class MqttClientSessionsManager : IDisposable + public class MqttClientSessionsManager : Disposable { private readonly AsyncQueue _messageQueue = new AsyncQueue(); @@ -145,9 +145,13 @@ namespace MQTTnet.Server _logger.Verbose("Session for client '{0}' deleted.", clientId); } - public void Dispose() + protected override void Dispose(bool disposing) { - _messageQueue?.Dispose(); + if (disposing) + { + _messageQueue?.Dispose(); + } + base.Dispose(disposing); } private async Task TryProcessQueuedApplicationMessagesAsync(CancellationToken cancellationToken) diff --git a/Tests/MQTTnet.Core.Tests/MQTTv5/Client_Tests.cs b/Tests/MQTTnet.Core.Tests/MQTTv5/Client_Tests.cs index 59d18fc..f033b5b 100644 --- a/Tests/MQTTnet.Core.Tests/MQTTv5/Client_Tests.cs +++ b/Tests/MQTTnet.Core.Tests/MQTTv5/Client_Tests.cs @@ -18,10 +18,12 @@ namespace MQTTnet.Tests.MQTTv5 [TestClass] public class Client_Tests { + public TestContext TestContext { get; set; } + [TestMethod] public async Task Connect_With_New_Mqtt_Features() { - using (var testEnvironment = new TestEnvironment()) + using (var testEnvironment = new TestEnvironment(TestContext)) { await testEnvironment.StartServerAsync(); @@ -61,7 +63,7 @@ namespace MQTTnet.Tests.MQTTv5 [TestMethod] public async Task Connect_With_AssignedClientId() { - using (var testEnvironment = new TestEnvironment()) + using (var testEnvironment = new TestEnvironment(TestContext)) { string serverConnectedClientId = null; string serverDisconnectedClientId = null; @@ -357,7 +359,7 @@ namespace MQTTnet.Tests.MQTTv5 [TestMethod] public async Task Publish_And_Receive_New_Properties() { - using (var testEnvironment = new TestEnvironment()) + using (var testEnvironment = new TestEnvironment(TestContext)) { await testEnvironment.StartServerAsync(); diff --git a/Tests/MQTTnet.Core.Tests/MQTTv5/Feature_Tests.cs b/Tests/MQTTnet.Core.Tests/MQTTv5/Feature_Tests.cs index 294ee84..6507fff 100644 --- a/Tests/MQTTnet.Core.Tests/MQTTv5/Feature_Tests.cs +++ b/Tests/MQTTnet.Core.Tests/MQTTv5/Feature_Tests.cs @@ -13,10 +13,12 @@ namespace MQTTnet.Tests.MQTTv5 [TestClass] public class Feature_Tests { + public TestContext TestContext { get; set; } + [TestMethod] public async Task Use_User_Properties() { - using (var testEnvironment = new TestEnvironment()) + using (var testEnvironment = new TestEnvironment(TestContext)) { await testEnvironment.StartServerAsync(); diff --git a/Tests/MQTTnet.Core.Tests/MQTTv5/Server_Tests.cs b/Tests/MQTTnet.Core.Tests/MQTTv5/Server_Tests.cs index e3020ce..014bd92 100644 --- a/Tests/MQTTnet.Core.Tests/MQTTv5/Server_Tests.cs +++ b/Tests/MQTTnet.Core.Tests/MQTTv5/Server_Tests.cs @@ -11,10 +11,12 @@ namespace MQTTnet.Tests.MQTTv5 [TestClass] public class Server_Tests { + public TestContext TestContext { get; set; } + [TestMethod] public async Task Will_Message_Send() { - using (var testEnvironment = new TestEnvironment()) + using (var testEnvironment = new TestEnvironment(TestContext)) { var receivedMessagesCount = 0; diff --git a/Tests/MQTTnet.Core.Tests/ManagedMqttClient_Tests.cs b/Tests/MQTTnet.Core.Tests/ManagedMqttClient_Tests.cs index 1cf9ab9..edfe058 100644 --- a/Tests/MQTTnet.Core.Tests/ManagedMqttClient_Tests.cs +++ b/Tests/MQTTnet.Core.Tests/ManagedMqttClient_Tests.cs @@ -18,6 +18,8 @@ namespace MQTTnet.Tests [TestClass] public class ManagedMqttClient_Tests { + public TestContext TestContext { get; set; } + [TestMethod] public async Task Drop_New_Messages_On_Full_Queue() { @@ -54,7 +56,7 @@ namespace MQTTnet.Tests [TestMethod] public async Task ManagedClients_Will_Message_Send() { - using (var testEnvironment = new TestEnvironment()) + using (var testEnvironment = new TestEnvironment(TestContext)) { var receivedMessagesCount = 0; @@ -88,7 +90,7 @@ namespace MQTTnet.Tests [TestMethod] public async Task Start_Stop() { - using (var testEnvironment = new TestEnvironment()) + using (var testEnvironment = new TestEnvironment(TestContext)) { var factory = new MqttFactory(); @@ -115,7 +117,7 @@ namespace MQTTnet.Tests [TestMethod] public async Task Storage_Queue_Drains() { - using (var testEnvironment = new TestEnvironment()) + using (var testEnvironment = new TestEnvironment(TestContext)) { testEnvironment.IgnoreClientLogErrors = true; testEnvironment.IgnoreServerLogErrors = true; @@ -167,7 +169,7 @@ namespace MQTTnet.Tests [TestMethod] public async Task Subscriptions_And_Unsubscriptions_Are_Made_And_Reestablished_At_Reconnect() { - using (var testEnvironment = new TestEnvironment()) + using (var testEnvironment = new TestEnvironment(TestContext)) { var unmanagedClient = testEnvironment.CreateClient(); var managedClient = await CreateManagedClientAsync(testEnvironment, unmanagedClient); @@ -232,7 +234,7 @@ namespace MQTTnet.Tests [TestMethod] public async Task Subscriptions_Subscribe_Only_New_Subscriptions() { - using (var testEnvironment = new TestEnvironment()) + using (var testEnvironment = new TestEnvironment(TestContext)) { var managedClient = await CreateManagedClientAsync(testEnvironment); @@ -265,7 +267,7 @@ namespace MQTTnet.Tests [TestMethod] public async Task Subscriptions_Are_Published_Immediately() { - using (var testEnvironment = new TestEnvironment()) + using (var testEnvironment = new TestEnvironment(TestContext)) { // Use a long connection check interval to verify that the subscriptions // do not depend on the connection check interval anymore @@ -289,7 +291,7 @@ namespace MQTTnet.Tests [TestMethod] public async Task Subscriptions_Are_Cleared_At_Logout() { - using (var testEnvironment = new TestEnvironment()) + using (var testEnvironment = new TestEnvironment(TestContext)) { var managedClient = await CreateManagedClientAsync(testEnvironment); diff --git a/Tests/MQTTnet.Core.Tests/Mockups/TestClientWrapper.cs b/Tests/MQTTnet.Core.Tests/Mockups/TestClientWrapper.cs new file mode 100644 index 0000000..dc0e3a9 --- /dev/null +++ b/Tests/MQTTnet.Core.Tests/Mockups/TestClientWrapper.cs @@ -0,0 +1,94 @@ +using System.Threading; +using System.Threading.Tasks; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using MQTTnet.Client; +using MQTTnet.Client.Connecting; +using MQTTnet.Client.Disconnecting; +using MQTTnet.Client.ExtendedAuthenticationExchange; +using MQTTnet.Client.Options; +using MQTTnet.Client.Publishing; +using MQTTnet.Client.Receiving; +using MQTTnet.Client.Subscribing; +using MQTTnet.Client.Unsubscribing; + +namespace MQTTnet.Tests.Mockups +{ + public class TestClientWrapper : IMqttClient + { + public TestClientWrapper(IMqttClient implementation, TestContext testContext) + { + Implementation = implementation; + TestContext = testContext; + } + + public IMqttClient Implementation { get; } + public TestContext TestContext { get; } + + public bool IsConnected => Implementation.IsConnected; + + public IMqttClientOptions Options => Implementation.Options; + + public IMqttClientConnectedHandler ConnectedHandler { get => Implementation.ConnectedHandler; set => Implementation.ConnectedHandler = value; } + public IMqttClientDisconnectedHandler DisconnectedHandler { get => Implementation.DisconnectedHandler; set => Implementation.DisconnectedHandler = value; } + public IMqttApplicationMessageReceivedHandler ApplicationMessageReceivedHandler { get => Implementation.ApplicationMessageReceivedHandler; set => Implementation.ApplicationMessageReceivedHandler = value; } + + public Task ConnectAsync(IMqttClientOptions options, CancellationToken cancellationToken) + { + switch (options) + { + case MqttClientOptionsBuilder builder: + { + var existingClientId = builder.Build().ClientId; + if (!existingClientId.StartsWith(TestContext.TestName)) + { + builder.WithClientId(TestContext.TestName + existingClientId); + } + } + break; + case MqttClientOptions op: + { + var existingClientId = op.ClientId; + if (!existingClientId.StartsWith(TestContext.TestName)) + { + op.ClientId = TestContext.TestName + existingClientId; + } + } + break; + default: + break; + } + + return Implementation.ConnectAsync(options, cancellationToken); + } + + public Task DisconnectAsync(MqttClientDisconnectOptions options, CancellationToken cancellationToken) + { + return Implementation.DisconnectAsync(options, cancellationToken); + } + + public void Dispose() + { + Implementation.Dispose(); + } + + public Task PublishAsync(MqttApplicationMessage applicationMessage, CancellationToken cancellationToken) + { + return Implementation.PublishAsync(applicationMessage, cancellationToken); + } + + public Task SendExtendedAuthenticationExchangeDataAsync(MqttExtendedAuthenticationExchangeData data, CancellationToken cancellationToken) + { + return Implementation.SendExtendedAuthenticationExchangeDataAsync(data, cancellationToken); + } + + public Task SubscribeAsync(MqttClientSubscribeOptions options, CancellationToken cancellationToken) + { + return Implementation.SubscribeAsync(options, cancellationToken); + } + + public Task UnsubscribeAsync(MqttClientUnsubscribeOptions options, CancellationToken cancellationToken) + { + return Implementation.UnsubscribeAsync(options, cancellationToken); + } + } +} \ No newline at end of file diff --git a/Tests/MQTTnet.Core.Tests/Mockups/TestEnvironment.cs b/Tests/MQTTnet.Core.Tests/Mockups/TestEnvironment.cs index 5cda537..7f2dcac 100644 --- a/Tests/MQTTnet.Core.Tests/Mockups/TestEnvironment.cs +++ b/Tests/MQTTnet.Core.Tests/Mockups/TestEnvironment.cs @@ -2,14 +2,16 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Microsoft.VisualStudio.TestTools.UnitTesting; using MQTTnet.Client; using MQTTnet.Client.Options; using MQTTnet.Diagnostics; +using MQTTnet.Internal; using MQTTnet.Server; namespace MQTTnet.Tests.Mockups { - public class TestEnvironment : IDisposable + public class TestEnvironment : Disposable { private readonly MqttFactory _mqttFactory = new MqttFactory(); private readonly List _clients = new List(); @@ -33,7 +35,9 @@ namespace MQTTnet.Tests.Mockups public IMqttNetLogger ClientLogger => _clientLogger; - public TestEnvironment() + public TestContext TestContext { get; } + + public TestEnvironment(TestContext testContext) { _serverLogger.LogMessagePublished += (s, e) => { @@ -56,13 +60,14 @@ namespace MQTTnet.Tests.Mockups } } }; + TestContext = testContext; } public IMqttClient CreateClient() { var client = _mqttFactory.CreateMqttClient(_clientLogger); _clients.Add(client); - return client; + return new TestClientWrapper(client, TestContext); } public Task StartServerAsync() @@ -77,7 +82,7 @@ namespace MQTTnet.Tests.Mockups throw new InvalidOperationException("Server already started."); } - Server = _mqttFactory.CreateMqttServer(_serverLogger); + Server = new TestServerWrapper(_mqttFactory.CreateMqttServer(_serverLogger), TestContext, this); await Server.StartAsync(options.WithDefaultEndpointPort(ServerPort).Build()); return Server; @@ -85,7 +90,7 @@ namespace MQTTnet.Tests.Mockups public Task ConnectClientAsync() { - return ConnectClientAsync(new MqttClientOptionsBuilder()); + return ConnectClientAsync(new MqttClientOptionsBuilder() ); } public async Task ConnectClientAsync(MqttClientOptionsBuilder options) @@ -127,21 +132,25 @@ namespace MQTTnet.Tests.Mockups } } - public void Dispose() + protected override void Dispose(bool disposing) { - foreach (var mqttClient in _clients) + if (disposing) { - mqttClient?.Dispose(); - } + foreach (var mqttClient in _clients) + { + mqttClient?.Dispose(); + } - Server?.StopAsync().GetAwaiter().GetResult(); + Server?.StopAsync().GetAwaiter().GetResult(); - ThrowIfLogErrors(); + ThrowIfLogErrors(); - if (_exceptions.Any()) - { - throw new Exception($"{_exceptions.Count} exceptions tracked.\r\n" + string.Join(Environment.NewLine, _exceptions)); + if (_exceptions.Any()) + { + throw new Exception($"{_exceptions.Count} exceptions tracked.\r\n" + string.Join(Environment.NewLine, _exceptions)); + } } + base.Dispose(disposing); } public void TrackException(Exception exception) diff --git a/Tests/MQTTnet.Core.Tests/Mockups/TestServerWrapper.cs b/Tests/MQTTnet.Core.Tests/Mockups/TestServerWrapper.cs new file mode 100644 index 0000000..f990197 --- /dev/null +++ b/Tests/MQTTnet.Core.Tests/Mockups/TestServerWrapper.cs @@ -0,0 +1,108 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using MQTTnet.Client.Publishing; +using MQTTnet.Client.Receiving; +using MQTTnet.Server; +using MQTTnet.Server.Status; + +namespace MQTTnet.Tests.Mockups +{ + public class TestServerWrapper : IMqttServer + { + public TestServerWrapper(IMqttServer implementation, TestContext testContext, TestEnvironment testEnvironment) + { + Implementation = implementation; + TestContext = testContext; + TestEnvironment = testEnvironment; + } + + public IMqttServer Implementation { get; } + public TestContext TestContext { get; } + public TestEnvironment TestEnvironment { get; } + public IMqttServerStartedHandler StartedHandler { get => Implementation.StartedHandler; set => Implementation.StartedHandler = value; } + public IMqttServerStoppedHandler StoppedHandler { get => Implementation.StoppedHandler; set => Implementation.StoppedHandler = value; } + public IMqttServerClientConnectedHandler ClientConnectedHandler { get => Implementation.ClientConnectedHandler; set => Implementation.ClientConnectedHandler = value; } + public IMqttServerClientDisconnectedHandler ClientDisconnectedHandler { get => Implementation.ClientDisconnectedHandler; set => Implementation.ClientDisconnectedHandler = value; } + public IMqttServerClientSubscribedTopicHandler ClientSubscribedTopicHandler { get => Implementation.ClientSubscribedTopicHandler; set => Implementation.ClientSubscribedTopicHandler = value; } + public IMqttServerClientUnsubscribedTopicHandler ClientUnsubscribedTopicHandler { get => Implementation.ClientUnsubscribedTopicHandler; set => Implementation.ClientUnsubscribedTopicHandler = value; } + + public IMqttServerOptions Options => Implementation.Options; + + public IMqttApplicationMessageReceivedHandler ApplicationMessageReceivedHandler { get => Implementation.ApplicationMessageReceivedHandler; set => Implementation.ApplicationMessageReceivedHandler = value; } + + public Task ClearRetainedApplicationMessagesAsync() + { + return Implementation.ClearRetainedApplicationMessagesAsync(); + } + + public Task> GetClientStatusAsync() + { + return Implementation.GetClientStatusAsync(); + } + + public Task> GetRetainedApplicationMessagesAsync() + { + return Implementation.GetRetainedApplicationMessagesAsync(); + } + + public Task> GetSessionStatusAsync() + { + return Implementation.GetSessionStatusAsync(); + } + + public Task PublishAsync(MqttApplicationMessage applicationMessage, CancellationToken cancellationToken) + { + return Implementation.PublishAsync(applicationMessage, cancellationToken); + } + + public Task StartAsync(IMqttServerOptions options) + { + switch (options) + { + case MqttServerOptionsBuilder builder: + if (builder.Build().ConnectionValidator == null) + { + builder.WithConnectionValidator(ConnectionValidator); + } + break; + case MqttServerOptions op: + if (op.ConnectionValidator == null) + { + op.ConnectionValidator = new MqttServerConnectionValidatorDelegate(ConnectionValidator); + } + break; + default: + break; + } + + return Implementation.StartAsync(options); + } + + public void ConnectionValidator(MqttConnectionValidatorContext ctx) + { + if (!ctx.ClientId.StartsWith(TestContext.TestName)) + { + TestEnvironment.TrackException(new InvalidOperationException($"invalid client connected '{ctx.ClientId}'")); + ctx.ReasonCode = Protocol.MqttConnectReasonCode.ClientIdentifierNotValid; + } + } + + public Task StopAsync() + { + return Implementation.StopAsync(); + } + + public Task SubscribeAsync(string clientId, ICollection topicFilters) + { + return Implementation.SubscribeAsync(clientId, topicFilters); + } + + public Task UnsubscribeAsync(string clientId, ICollection topicFilters) + { + return Implementation.UnsubscribeAsync(clientId, topicFilters); + } + } +} \ 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 51d1753..9cd3024 100644 --- a/Tests/MQTTnet.Core.Tests/MqttClient_Tests.cs +++ b/Tests/MQTTnet.Core.Tests/MqttClient_Tests.cs @@ -20,10 +20,12 @@ namespace MQTTnet.Tests [TestClass] public class Client_Tests { + public TestContext TestContext { get; set; } + [TestMethod] public async Task Send_Reply_In_Message_Handler_For_Same_Client() { - using (var testEnvironment = new TestEnvironment()) + using (var testEnvironment = new TestEnvironment(TestContext)) { await testEnvironment.StartServerAsync(); var client = await testEnvironment.ConnectClientAsync(); @@ -57,7 +59,7 @@ namespace MQTTnet.Tests [TestMethod] public async Task Send_Reply_In_Message_Handler() { - using (var testEnvironment = new TestEnvironment()) + using (var testEnvironment = new TestEnvironment(TestContext)) { await testEnvironment.StartServerAsync(); var client1 = await testEnvironment.ConnectClientAsync(); @@ -89,7 +91,7 @@ namespace MQTTnet.Tests [TestMethod] public async Task Reconnect() { - using (var testEnvironment = new TestEnvironment()) + using (var testEnvironment = new TestEnvironment(TestContext)) { var server = await testEnvironment.StartServerAsync(); var client = await testEnvironment.ConnectClientAsync(); @@ -112,7 +114,7 @@ namespace MQTTnet.Tests [TestMethod] public async Task Reconnect_While_Server_Offline() { - using (var testEnvironment = new TestEnvironment()) + using (var testEnvironment = new TestEnvironment(TestContext)) { testEnvironment.IgnoreClientLogErrors = true; @@ -149,7 +151,7 @@ namespace MQTTnet.Tests [TestMethod] public async Task Reconnect_From_Disconnected_Event() { - using (var testEnvironment = new TestEnvironment()) + using (var testEnvironment = new TestEnvironment(TestContext)) { testEnvironment.IgnoreClientLogErrors = true; @@ -189,7 +191,7 @@ namespace MQTTnet.Tests [TestMethod] public async Task PacketIdentifier_In_Publish_Result() { - using (var testEnvironment = new TestEnvironment()) + using (var testEnvironment = new TestEnvironment(TestContext)) { await testEnvironment.StartServerAsync(); var client = await testEnvironment.ConnectClientAsync(); @@ -238,7 +240,7 @@ namespace MQTTnet.Tests [TestMethod] public async Task Fire_Disconnected_Event_On_Server_Shutdown() { - using (var testEnvironment = new TestEnvironment()) + using (var testEnvironment = new TestEnvironment(TestContext)) { var server = await testEnvironment.StartServerAsync(); var client = await testEnvironment.ConnectClientAsync(); @@ -290,7 +292,7 @@ namespace MQTTnet.Tests // is an issue). const int MessagesCount = 50; - using (var testEnvironment = new TestEnvironment()) + using (var testEnvironment = new TestEnvironment(TestContext)) { await testEnvironment.StartServerAsync(); @@ -330,7 +332,7 @@ namespace MQTTnet.Tests [TestMethod] public async Task Send_Reply_For_Any_Received_Message() { - using (var testEnvironment = new TestEnvironment()) + using (var testEnvironment = new TestEnvironment(TestContext)) { await testEnvironment.StartServerAsync(); @@ -374,7 +376,7 @@ namespace MQTTnet.Tests [TestMethod] public async Task Publish_With_Correct_Retain_Flag() { - using (var testEnvironment = new TestEnvironment()) + using (var testEnvironment = new TestEnvironment(TestContext)) { await testEnvironment.StartServerAsync(); @@ -405,7 +407,7 @@ namespace MQTTnet.Tests [TestMethod] public async Task Subscribe_In_Callback_Events() { - using (var testEnvironment = new TestEnvironment()) + using (var testEnvironment = new TestEnvironment(TestContext)) { await testEnvironment.StartServerAsync(); @@ -444,7 +446,7 @@ namespace MQTTnet.Tests [TestMethod] public async Task Message_Send_Retry() { - using (var testEnvironment = new TestEnvironment()) + using (var testEnvironment = new TestEnvironment(TestContext)) { testEnvironment.IgnoreClientLogErrors = true; testEnvironment.IgnoreServerLogErrors = true; @@ -488,7 +490,7 @@ namespace MQTTnet.Tests [TestMethod] public async Task NoConnectedHandler_Connect_DoesNotThrowException() { - using (var testEnvironment = new TestEnvironment()) + using (var testEnvironment = new TestEnvironment(TestContext)) { await testEnvironment.StartServerAsync(); @@ -501,7 +503,7 @@ namespace MQTTnet.Tests [TestMethod] public async Task NoDisconnectedHandler_Disconnect_DoesNotThrowException() { - using (var testEnvironment = new TestEnvironment()) + using (var testEnvironment = new TestEnvironment(TestContext)) { await testEnvironment.StartServerAsync(); var client = await testEnvironment.ConnectClientAsync(); @@ -516,7 +518,7 @@ namespace MQTTnet.Tests [TestMethod] public async Task Frequent_Connects() { - using (var testEnvironment = new TestEnvironment()) + using (var testEnvironment = new TestEnvironment(TestContext)) { await testEnvironment.StartServerAsync(); @@ -560,7 +562,7 @@ namespace MQTTnet.Tests [TestMethod] public async Task No_Payload() { - using (var testEnvironment = new TestEnvironment()) + using (var testEnvironment = new TestEnvironment(TestContext)) { await testEnvironment.StartServerAsync(); diff --git a/Tests/MQTTnet.Core.Tests/MqttFactory_Tests.cs b/Tests/MQTTnet.Core.Tests/MqttFactory_Tests.cs index 75bda9b..eb7aba3 100644 --- a/Tests/MQTTnet.Core.Tests/MqttFactory_Tests.cs +++ b/Tests/MQTTnet.Core.Tests/MqttFactory_Tests.cs @@ -18,7 +18,7 @@ namespace MQTTnet.Tests //This test compares //1. correct logID string logId = "logId"; - bool invalidLogIdOccured = false; + string invalidLogId = null; //2. if the total log calls are the same for global and local int globalLogCount = 0; @@ -31,7 +31,7 @@ namespace MQTTnet.Tests { if (logId != e.TraceMessage.LogId) { - invalidLogIdOccured = true; + invalidLogId = e.TraceMessage.LogId; } Interlocked.Increment(ref globalLogCount); }); @@ -42,7 +42,7 @@ namespace MQTTnet.Tests { if (logId != e.TraceMessage.LogId) { - invalidLogIdOccured = true; + invalidLogId = e.TraceMessage.LogId; } Interlocked.Increment(ref localLogCount); }; @@ -69,7 +69,7 @@ namespace MQTTnet.Tests MqttNetGlobalLogger.LogMessagePublished -= globalLog; } - Assert.IsFalse(invalidLogIdOccured); + Assert.IsNull(invalidLogId); Assert.AreNotEqual(0, globalLogCount); Assert.AreEqual(globalLogCount, localLogCount); } diff --git a/Tests/MQTTnet.Core.Tests/RPC_Tests.cs b/Tests/MQTTnet.Core.Tests/RPC_Tests.cs index a420697..947c104 100644 --- a/Tests/MQTTnet.Core.Tests/RPC_Tests.cs +++ b/Tests/MQTTnet.Core.Tests/RPC_Tests.cs @@ -18,6 +18,8 @@ namespace MQTTnet.Tests [TestClass] public class RPC_Tests { + public TestContext TestContext { get; set; } + [TestMethod] public Task Execute_Success_With_QoS_0() { @@ -58,7 +60,7 @@ namespace MQTTnet.Tests [ExpectedException(typeof(MqttCommunicationTimedOutException))] public async Task Execute_Timeout() { - using (var testEnvironment = new TestEnvironment()) + using (var testEnvironment = new TestEnvironment(TestContext)) { await testEnvironment.StartServerAsync(); @@ -73,7 +75,7 @@ namespace MQTTnet.Tests [ExpectedException(typeof(MqttCommunicationTimedOutException))] public async Task Execute_With_Custom_Topic_Names() { - using (var testEnvironment = new TestEnvironment()) + using (var testEnvironment = new TestEnvironment(TestContext)) { await testEnvironment.StartServerAsync(); @@ -86,7 +88,7 @@ namespace MQTTnet.Tests private async Task Execute_Success(MqttQualityOfServiceLevel qosLevel, MqttProtocolVersion protocolVersion) { - using (var testEnvironment = new TestEnvironment()) + using (var testEnvironment = new TestEnvironment(TestContext)) { await testEnvironment.StartServerAsync(); var responseSender = await testEnvironment.ConnectClientAsync(new MqttClientOptionsBuilder().WithProtocolVersion(protocolVersion)); diff --git a/Tests/MQTTnet.Core.Tests/RoundtripTime_Tests.cs b/Tests/MQTTnet.Core.Tests/RoundtripTime_Tests.cs index 02055a8..377d142 100644 --- a/Tests/MQTTnet.Core.Tests/RoundtripTime_Tests.cs +++ b/Tests/MQTTnet.Core.Tests/RoundtripTime_Tests.cs @@ -11,10 +11,12 @@ namespace MQTTnet.Tests [TestClass] public class RoundtripTime_Tests { + public TestContext TestContext { get; set; } + [TestMethod] public async Task Round_Trip_Time() { - using (var testEnvironment = new TestEnvironment()) + using (var testEnvironment = new TestEnvironment(TestContext)) { await testEnvironment.StartServerAsync(); var receiverClient = await testEnvironment.ConnectClientAsync(); diff --git a/Tests/MQTTnet.Core.Tests/Server_Status_Tests.cs b/Tests/MQTTnet.Core.Tests/Server_Status_Tests.cs index c4a5d27..93c1d55 100644 --- a/Tests/MQTTnet.Core.Tests/Server_Status_Tests.cs +++ b/Tests/MQTTnet.Core.Tests/Server_Status_Tests.cs @@ -13,10 +13,12 @@ namespace MQTTnet.Tests [TestClass] public class Server_Status_Tests { + public TestContext TestContext { get; set; } + [TestMethod] public async Task Show_Client_And_Session_Statistics() { - using (var testEnvironment = new TestEnvironment()) + using (var testEnvironment = new TestEnvironment(TestContext)) { var server = await testEnvironment.StartServerAsync(); @@ -31,8 +33,8 @@ namespace MQTTnet.Tests Assert.AreEqual(2, clientStatus.Count); Assert.AreEqual(2, sessionStatus.Count); - Assert.IsTrue(clientStatus.Any(s => s.ClientId == "client1")); - Assert.IsTrue(clientStatus.Any(s => s.ClientId == "client2")); + Assert.IsTrue(clientStatus.Any(s => s.ClientId == c1.Options.ClientId)); + Assert.IsTrue(clientStatus.Any(s => s.ClientId == c2.Options.ClientId)); await c1.DisconnectAsync(); await c2.DisconnectAsync(); @@ -50,7 +52,7 @@ namespace MQTTnet.Tests [TestMethod] public async Task Disconnect_Client() { - using (var testEnvironment = new TestEnvironment()) + using (var testEnvironment = new TestEnvironment(TestContext)) { var server = await testEnvironment.StartServerAsync(); @@ -62,7 +64,7 @@ namespace MQTTnet.Tests Assert.AreEqual(1, clientStatus.Count); - Assert.IsTrue(clientStatus.Any(s => s.ClientId == "client1")); + Assert.IsTrue(clientStatus.Any(s => s.ClientId == c1.Options.ClientId)); await clientStatus.First().DisconnectAsync(); @@ -79,7 +81,7 @@ namespace MQTTnet.Tests [TestMethod] public async Task Keep_Persistent_Session() { - using (var testEnvironment = new TestEnvironment()) + using (var testEnvironment = new TestEnvironment(TestContext)) { var server = await testEnvironment.StartServerAsync(new MqttServerOptionsBuilder().WithPersistentSessions()); @@ -111,7 +113,7 @@ namespace MQTTnet.Tests [TestMethod] public async Task Track_Sent_Application_Messages() { - using (var testEnvironment = new TestEnvironment()) + using (var testEnvironment = new TestEnvironment(TestContext)) { var server = await testEnvironment.StartServerAsync(new MqttServerOptionsBuilder().WithPersistentSessions()); @@ -132,7 +134,7 @@ namespace MQTTnet.Tests [TestMethod] public async Task Track_Sent_Packets() { - using (var testEnvironment = new TestEnvironment()) + using (var testEnvironment = new TestEnvironment(TestContext)) { var server = await testEnvironment.StartServerAsync(new MqttServerOptionsBuilder().WithPersistentSessions()); diff --git a/Tests/MQTTnet.Core.Tests/Server_Tests.cs b/Tests/MQTTnet.Core.Tests/Server_Tests.cs index c3d6df1..cbbc1fc 100644 --- a/Tests/MQTTnet.Core.Tests/Server_Tests.cs +++ b/Tests/MQTTnet.Core.Tests/Server_Tests.cs @@ -22,10 +22,12 @@ namespace MQTTnet.Tests [TestClass] public class Server_Tests { + public TestContext TestContext { get; set; } + [TestMethod] public async Task Use_Empty_Client_ID() { - using (var testEnvironment = new TestEnvironment()) + using (var testEnvironment = new TestEnvironment(TestContext)) { await testEnvironment.StartServerAsync(); @@ -51,7 +53,8 @@ namespace MQTTnet.Tests MqttQualityOfServiceLevel.AtMostOnce, "A/B/C", MqttQualityOfServiceLevel.AtMostOnce, - 1); + 1, + TestContext); } [TestMethod] @@ -62,7 +65,8 @@ namespace MQTTnet.Tests MqttQualityOfServiceLevel.AtLeastOnce, "A/B/C", MqttQualityOfServiceLevel.AtLeastOnce, - 1); + 1, + TestContext); } [TestMethod] @@ -73,13 +77,14 @@ namespace MQTTnet.Tests MqttQualityOfServiceLevel.ExactlyOnce, "A/B/C", MqttQualityOfServiceLevel.ExactlyOnce, - 1); + 1, + TestContext); } [TestMethod] public async Task Use_Clean_Session() { - using (var testEnvironment = new TestEnvironment()) + using (var testEnvironment = new TestEnvironment(TestContext)) { await testEnvironment.StartServerAsync(); @@ -93,7 +98,7 @@ namespace MQTTnet.Tests [TestMethod] public async Task Will_Message_Do_Not_Send() { - using (var testEnvironment = new TestEnvironment()) + using (var testEnvironment = new TestEnvironment(TestContext)) { var receivedMessagesCount = 0; @@ -119,7 +124,7 @@ namespace MQTTnet.Tests [TestMethod] public async Task Will_Message_Send() { - using (var testEnvironment = new TestEnvironment()) + using (var testEnvironment = new TestEnvironment(TestContext)) { var receivedMessagesCount = 0; @@ -145,7 +150,7 @@ namespace MQTTnet.Tests [TestMethod] public async Task Intercept_Subscription() { - using (var testEnvironment = new TestEnvironment()) + using (var testEnvironment = new TestEnvironment(TestContext)) { await testEnvironment.StartServerAsync(new MqttServerOptionsBuilder().WithSubscriptionInterceptor( c => @@ -184,7 +189,7 @@ namespace MQTTnet.Tests [TestMethod] public async Task Subscribe_Unsubscribe() { - using (var testEnvironment = new TestEnvironment()) + using (var testEnvironment = new TestEnvironment(TestContext)) { var receivedMessagesCount = 0; @@ -204,7 +209,7 @@ namespace MQTTnet.Tests var subscribeEventCalled = false; server.ClientSubscribedTopicHandler = new MqttServerClientSubscribedHandlerDelegate(e => { - subscribeEventCalled = e.TopicFilter.Topic == "a" && e.ClientId == "c1"; + subscribeEventCalled = e.TopicFilter.Topic == "a" && e.ClientId == c1.Options.ClientId; }); await c1.SubscribeAsync(new TopicFilter { Topic = "a", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce }); @@ -218,7 +223,7 @@ namespace MQTTnet.Tests var unsubscribeEventCalled = false; server.ClientUnsubscribedTopicHandler = new MqttServerClientUnsubscribedTopicHandlerDelegate(e => { - unsubscribeEventCalled = e.TopicFilter == "a" && e.ClientId == "c1"; + unsubscribeEventCalled = e.TopicFilter == "a" && e.ClientId == c1.Options.ClientId; }); await c1.UnsubscribeAsync("a"); @@ -238,7 +243,7 @@ namespace MQTTnet.Tests [TestMethod] public async Task Subscribe_Multiple_In_Single_Request() { - using (var testEnvironment = new TestEnvironment()) + using (var testEnvironment = new TestEnvironment(TestContext)) { var receivedMessagesCount = 0; @@ -271,7 +276,7 @@ namespace MQTTnet.Tests [TestMethod] public async Task Subscribe_Multiple_In_Multiple_Request() { - using (var testEnvironment = new TestEnvironment()) + using (var testEnvironment = new TestEnvironment(TestContext)) { var receivedMessagesCount = 0; @@ -310,7 +315,7 @@ namespace MQTTnet.Tests [TestMethod] public async Task Publish_From_Server() { - using (var testEnvironment = new TestEnvironment()) + using (var testEnvironment = new TestEnvironment(TestContext)) { var server = await testEnvironment.StartServerAsync(); @@ -336,7 +341,7 @@ namespace MQTTnet.Tests var receivedMessagesCount = 0; var locked = new object(); - using (var testEnvironment = new TestEnvironment()) + using (var testEnvironment = new TestEnvironment(TestContext)) { await testEnvironment.StartServerAsync(); @@ -378,7 +383,7 @@ namespace MQTTnet.Tests [TestMethod] public async Task Session_Takeover() { - using (var testEnvironment = new TestEnvironment()) + using (var testEnvironment = new TestEnvironment(TestContext)) { await testEnvironment.StartServerAsync(); @@ -400,7 +405,7 @@ namespace MQTTnet.Tests [TestMethod] public async Task No_Messages_If_No_Subscription() { - using (var testEnvironment = new TestEnvironment()) + using (var testEnvironment = new TestEnvironment(TestContext)) { await testEnvironment.StartServerAsync(); @@ -433,7 +438,7 @@ namespace MQTTnet.Tests [TestMethod] public async Task Set_Subscription_At_Server() { - using (var testEnvironment = new TestEnvironment()) + using (var testEnvironment = new TestEnvironment(TestContext)) { var server = await testEnvironment.StartServerAsync(); server.ClientConnectedHandler = new MqttServerClientConnectedHandlerDelegate(e => server.SubscribeAsync(e.ClientId, "topic1")); @@ -464,7 +469,7 @@ namespace MQTTnet.Tests [TestMethod] public async Task Shutdown_Disconnects_Clients_Gracefully() { - using (var testEnvironment = new TestEnvironment()) + using (var testEnvironment = new TestEnvironment(TestContext)) { var server = await testEnvironment.StartServerAsync(new MqttServerOptionsBuilder()); @@ -486,7 +491,7 @@ namespace MQTTnet.Tests [TestMethod] public async Task Handle_Clean_Disconnect() { - using (var testEnvironment = new TestEnvironment()) + using (var testEnvironment = new TestEnvironment(TestContext)) { var server = await testEnvironment.StartServerAsync(new MqttServerOptionsBuilder()); @@ -515,7 +520,7 @@ namespace MQTTnet.Tests [TestMethod] public async Task Client_Disconnect_Without_Errors() { - using (var testEnvironment = new TestEnvironment()) + using (var testEnvironment = new TestEnvironment(TestContext)) { bool clientWasConnected; @@ -546,7 +551,7 @@ namespace MQTTnet.Tests { const int ClientCount = 50; - using (var testEnvironment = new TestEnvironment()) + using (var testEnvironment = new TestEnvironment(TestContext)) { var server = await testEnvironment.StartServerAsync(); @@ -598,7 +603,7 @@ namespace MQTTnet.Tests [TestMethod] public async Task Retained_Messages_Flow() { - using (var testEnvironment = new TestEnvironment()) + using (var testEnvironment = new TestEnvironment(TestContext)) { var retainedMessage = new MqttApplicationMessageBuilder().WithTopic("r").WithPayload("r").WithRetainFlag().Build(); @@ -635,7 +640,7 @@ namespace MQTTnet.Tests [TestMethod] public async Task Receive_No_Retained_Message_After_Subscribe() { - using (var testEnvironment = new TestEnvironment()) + using (var testEnvironment = new TestEnvironment(TestContext)) { await testEnvironment.StartServerAsync(); @@ -658,7 +663,7 @@ namespace MQTTnet.Tests [TestMethod] public async Task Receive_Retained_Message_After_Subscribe() { - using (var testEnvironment = new TestEnvironment()) + using (var testEnvironment = new TestEnvironment(TestContext)) { await testEnvironment.StartServerAsync(); @@ -689,7 +694,7 @@ namespace MQTTnet.Tests [TestMethod] public async Task Clear_Retained_Message_With_Empty_Payload() { - using (var testEnvironment = new TestEnvironment()) + using (var testEnvironment = new TestEnvironment(TestContext)) { var receivedMessagesCount = 0; @@ -717,7 +722,7 @@ namespace MQTTnet.Tests [TestMethod] public async Task Clear_Retained_Message_With_Null_Payload() { - using (var testEnvironment = new TestEnvironment()) + using (var testEnvironment = new TestEnvironment(TestContext)) { var receivedMessagesCount = 0; @@ -745,7 +750,7 @@ namespace MQTTnet.Tests [TestMethod] public async Task Intercept_Application_Message() { - using (var testEnvironment = new TestEnvironment()) + using (var testEnvironment = new TestEnvironment(TestContext)) { await testEnvironment.StartServerAsync( new MqttServerOptionsBuilder().WithApplicationMessageInterceptor( @@ -768,7 +773,7 @@ namespace MQTTnet.Tests { var serverStorage = new TestServerStorage(); - using (var testEnvironment = new TestEnvironment()) + using (var testEnvironment = new TestEnvironment(TestContext)) { await testEnvironment.StartServerAsync(new MqttServerOptionsBuilder().WithStorage(serverStorage)); @@ -785,7 +790,7 @@ namespace MQTTnet.Tests [TestMethod] public async Task Publish_After_Client_Connects() { - using (var testEnvironment = new TestEnvironment()) + using (var testEnvironment = new TestEnvironment(TestContext)) { var server = await testEnvironment.StartServerAsync(); server.UseClientConnectedHandler(async e => @@ -818,7 +823,7 @@ namespace MQTTnet.Tests context.ApplicationMessage.Payload = Encoding.ASCII.GetBytes("extended"); } - using (var testEnvironment = new TestEnvironment()) + using (var testEnvironment = new TestEnvironment(TestContext)) { await testEnvironment.StartServerAsync(new MqttServerOptionsBuilder().WithApplicationMessageInterceptor(Interceptor)); @@ -844,7 +849,7 @@ namespace MQTTnet.Tests [TestMethod] public async Task Send_Long_Body() { - using (var testEnvironment = new TestEnvironment()) + using (var testEnvironment = new TestEnvironment(TestContext)) { const int PayloadSizeInMB = 30; const int CharCount = PayloadSizeInMB * 1024 * 1024; @@ -892,28 +897,15 @@ namespace MQTTnet.Tests context.ReturnCode = MqttConnectReturnCode.ConnectionRefusedNotAuthorized; }); - using (var testEnvironment = new TestEnvironment()) + using (var testEnvironment = new TestEnvironment(TestContext)) { testEnvironment.IgnoreClientLogErrors = true; await testEnvironment.StartServerAsync(serverOptions); - try - { - await testEnvironment.ConnectClientAsync(); - Assert.Fail("An exception should be raised."); - } - catch (Exception exception) - { - if (exception is MqttConnectingFailedException connectingFailedException) - { - Assert.AreEqual(MqttClientConnectResultCode.NotAuthorized, connectingFailedException.ResultCode); - } - else - { - Assert.Fail("Wrong exception."); - } - } + + var connectingFailedException = await Assert.ThrowsExceptionAsync(() => testEnvironment.ConnectClientAsync()); + Assert.AreEqual(MqttClientConnectResultCode.NotAuthorized, connectingFailedException.ResultCode); } } @@ -934,7 +926,7 @@ namespace MQTTnet.Tests [TestMethod] public async Task Same_Client_Id_Refuse_Connection() { - using (var testEnvironment = new TestEnvironment()) + using (var testEnvironment = new TestEnvironment(TestContext)) { testEnvironment.IgnoreClientLogErrors = true; @@ -1023,7 +1015,7 @@ namespace MQTTnet.Tests [TestMethod] public async Task Same_Client_Id_Connect_Disconnect_Event_Order() { - using (var testEnvironment = new TestEnvironment()) + using (var testEnvironment = new TestEnvironment(TestContext)) { var server = await testEnvironment.StartServerAsync(new MqttServerOptionsBuilder()); @@ -1107,7 +1099,7 @@ namespace MQTTnet.Tests [TestMethod] public async Task Remove_Session() { - using (var testEnvironment = new TestEnvironment()) + using (var testEnvironment = new TestEnvironment(TestContext)) { var server = await testEnvironment.StartServerAsync(new MqttServerOptionsBuilder()); @@ -1126,7 +1118,7 @@ namespace MQTTnet.Tests [TestMethod] public async Task Stop_And_Restart() { - using (var testEnvironment = new TestEnvironment()) + using (var testEnvironment = new TestEnvironment(TestContext)) { testEnvironment.IgnoreClientLogErrors = true; @@ -1152,7 +1144,7 @@ namespace MQTTnet.Tests [TestMethod] public async Task Close_Idle_Connection() { - using (var testEnvironment = new TestEnvironment()) + using (var testEnvironment = new TestEnvironment(TestContext)) { await testEnvironment.StartServerAsync(new MqttServerOptionsBuilder().WithDefaultCommunicationTimeout(TimeSpan.FromSeconds(1))); @@ -1181,7 +1173,7 @@ namespace MQTTnet.Tests [TestMethod] public async Task Send_Garbage() { - using (var testEnvironment = new TestEnvironment()) + using (var testEnvironment = new TestEnvironment(TestContext)) { await testEnvironment.StartServerAsync(new MqttServerOptionsBuilder().WithDefaultCommunicationTimeout(TimeSpan.FromSeconds(1))); @@ -1214,7 +1206,7 @@ namespace MQTTnet.Tests [TestMethod] public async Task Do_Not_Send_Retained_Messages_For_Denied_Subscription() { - using (var testEnvironment = new TestEnvironment()) + using (var testEnvironment = new TestEnvironment(TestContext)) { await testEnvironment.StartServerAsync(new MqttServerOptionsBuilder().WithSubscriptionInterceptor(c => { @@ -1258,7 +1250,7 @@ namespace MQTTnet.Tests [TestMethod] public async Task Collect_Messages_In_Disconnected_Session() { - using (var testEnvironment = new TestEnvironment()) + using (var testEnvironment = new TestEnvironment(TestContext)) { var server = await testEnvironment.StartServerAsync(new MqttServerOptionsBuilder().WithPersistentSessions()); @@ -1285,7 +1277,7 @@ namespace MQTTnet.Tests Assert.AreEqual(0, clientStatus.Count); Assert.AreEqual(2, sessionStatus.Count); - Assert.AreEqual(3, sessionStatus.First(s => s.ClientId == "a").PendingApplicationMessagesCount); + Assert.AreEqual(3, sessionStatus.First(s => s.ClientId == client1.Options.ClientId).PendingApplicationMessagesCount); } } @@ -1294,9 +1286,10 @@ namespace MQTTnet.Tests MqttQualityOfServiceLevel qualityOfServiceLevel, string topicFilter, MqttQualityOfServiceLevel filterQualityOfServiceLevel, - int expectedReceivedMessagesCount) + int expectedReceivedMessagesCount, + TestContext testContext) { - using (var testEnvironment = new TestEnvironment()) + using (var testEnvironment = new TestEnvironment(testContext)) { var receivedMessagesCount = 0; diff --git a/Tests/MQTTnet.Core.Tests/Session_Tests.cs b/Tests/MQTTnet.Core.Tests/Session_Tests.cs index d06bd4e..073f272 100644 --- a/Tests/MQTTnet.Core.Tests/Session_Tests.cs +++ b/Tests/MQTTnet.Core.Tests/Session_Tests.cs @@ -11,10 +11,12 @@ namespace MQTTnet.Tests [TestClass] public class Session_Tests { + public TestContext TestContext { get; set; } + [TestMethod] public async Task Set_Session_Item() { - using (var testEnvironment = new TestEnvironment()) + using (var testEnvironment = new TestEnvironment(TestContext)) { var serverOptions = new MqttServerOptionsBuilder() .WithConnectionValidator(delegate (MqttConnectionValidatorContext context) From 698f0b66d2ab97b39d298ab34bdef08943598944 Mon Sep 17 00:00:00 2001 From: JanEggers Date: Fri, 20 Dec 2019 15:36:03 +0100 Subject: [PATCH 36/51] fixed object disposed exception on netfx fixing #803 #733 #728 #486 #158 --- .../MQTTnet/Implementations/MqttTcpChannel.cs | 14 ++- .../Implementations/MqttTcpServerListener.cs | 7 +- .../PlatformAbstractionLayer.cs | 92 +++++++++++++++++++ Tests/MQTTnet.Core.Tests/MQTTnet.Tests.csproj | 2 +- .../Mockups/TestClientWrapper.cs | 4 +- Tests/MQTTnet.Core.Tests/MqttClient_Tests.cs | 30 ++++++ .../MqttTcpChannel_Tests.cs | 6 +- Tests/MQTTnet.Core.Tests/Server_Tests.cs | 11 ++- 8 files changed, 144 insertions(+), 22 deletions(-) create mode 100644 Source/MQTTnet/Implementations/PlatformAbstractionLayer.cs diff --git a/Source/MQTTnet/Implementations/MqttTcpChannel.cs b/Source/MQTTnet/Implementations/MqttTcpChannel.cs index 8f012cb..d500813 100644 --- a/Source/MQTTnet/Implementations/MqttTcpChannel.cs +++ b/Source/MQTTnet/Implementations/MqttTcpChannel.cs @@ -72,11 +72,7 @@ namespace MQTTnet.Implementations // Workaround for: workaround for https://github.com/dotnet/corefx/issues/24430 using (cancellationToken.Register(() => socket.Dispose())) { -#if NET452 || NET461 - await Task.Factory.FromAsync(socket.BeginConnect, socket.EndConnect, _options.Server, _options.GetPort(), null).ConfigureAwait(false); -#else - await socket.ConnectAsync(_options.Server, _options.GetPort()).ConfigureAwait(false); -#endif + await PlatformAbstractionLayer.ConnectAsync(socket, _options.Server, _options.GetPort()).ConfigureAwait(false); } var networkStream = new NetworkStream(socket, true); @@ -117,6 +113,10 @@ namespace MQTTnet.Implementations return await _stream.ReadAsync(buffer, offset, count, cancellationToken).ConfigureAwait(false); } } + catch (ObjectDisposedException) + { + return 0; + } catch (IOException exception) { if (exception.InnerException is SocketException socketException) @@ -143,6 +143,10 @@ namespace MQTTnet.Implementations await _stream.WriteAsync(buffer, offset, count, cancellationToken).ConfigureAwait(false); } } + catch (ObjectDisposedException) + { + return; + } catch (IOException exception) { if (exception.InnerException is SocketException socketException) diff --git a/Source/MQTTnet/Implementations/MqttTcpServerListener.cs b/Source/MQTTnet/Implementations/MqttTcpServerListener.cs index d57888e..f2f439e 100644 --- a/Source/MQTTnet/Implementations/MqttTcpServerListener.cs +++ b/Source/MQTTnet/Implementations/MqttTcpServerListener.cs @@ -107,12 +107,7 @@ namespace MQTTnet.Implementations { try { -#if NET452 || NET461 - var clientSocket = await Task.Factory.FromAsync(_socket.BeginAccept, _socket.EndAccept, null).ConfigureAwait(false); -#else - var clientSocket = await _socket.AcceptAsync().ConfigureAwait(false); -#endif - + var clientSocket = await PlatformAbstractionLayer.AcceptAsync(_socket).ConfigureAwait(false); if (clientSocket == null) { continue; diff --git a/Source/MQTTnet/Implementations/PlatformAbstractionLayer.cs b/Source/MQTTnet/Implementations/PlatformAbstractionLayer.cs new file mode 100644 index 0000000..a940eac --- /dev/null +++ b/Source/MQTTnet/Implementations/PlatformAbstractionLayer.cs @@ -0,0 +1,92 @@ +using System; +using System.Net; +using System.Net.Sockets; +using System.Threading.Tasks; + +namespace MQTTnet.Implementations +{ + public static class PlatformAbstractionLayer + { + public static async Task AcceptAsync(Socket socket) + { +#if NET452 || NET461 + try + { + return await Task.Factory.FromAsync(socket.BeginAccept, socket.EndAccept, null).ConfigureAwait(false); + } + catch (ObjectDisposedException) + { + return null; + } +#else + return await socket.AcceptAsync().ConfigureAwait(false); +#endif + } + + + public static Task ConnectAsync(Socket socket, IPAddress ip, int port) + { +#if NET452 || NET461 + return Task.Factory.FromAsync(socket.BeginConnect, socket.EndConnect, ip, port, null); +#else + return socket.ConnectAsync(ip, port); +#endif + } + + public static Task ConnectAsync(Socket socket, string host, int port) + { +#if NET452 || NET461 + return Task.Factory.FromAsync(socket.BeginConnect, socket.EndConnect, host, port, null); +#else + return socket.ConnectAsync(host, port); +#endif + } + +#if NET452 || NET461 + public class SocketWrapper + { + private readonly Socket _socket; + private readonly ArraySegment _buffer; + private readonly SocketFlags _socketFlags; + + public SocketWrapper(Socket socket, ArraySegment buffer, SocketFlags socketFlags) + { + _socket = socket; + _buffer = buffer; + _socketFlags = socketFlags; + } + + public static IAsyncResult BeginSend(AsyncCallback callback, object state) + { + var real = (SocketWrapper)state; + return real._socket.BeginSend(real._buffer.Array, real._buffer.Offset, real._buffer.Count, real._socketFlags, callback, state); + } + + public static IAsyncResult BeginReceive(AsyncCallback callback, object state) + { + var real = (SocketWrapper)state; + return real._socket.BeginReceive(real._buffer.Array, real._buffer.Offset, real._buffer.Count, real._socketFlags, callback, state); + } + } +#endif + + public static Task SendAsync(Socket socket, ArraySegment buffer, SocketFlags socketFlags) + { +#if NET452 || NET461 + return Task.Factory.FromAsync(SocketWrapper.BeginSend, socket.EndSend, new SocketWrapper(socket, buffer, socketFlags)); +#else + return socket.SendAsync(buffer, socketFlags); +#endif + } + + public static Task ReceiveAsync(Socket socket, ArraySegment buffer, SocketFlags socketFlags) + { +#if NET452 || NET461 + return Task.Factory.FromAsync(SocketWrapper.BeginReceive, socket.EndReceive, new SocketWrapper(socket, buffer, socketFlags)); +#else + return socket.ReceiveAsync(buffer, socketFlags); +#endif + } + + } +} diff --git a/Tests/MQTTnet.Core.Tests/MQTTnet.Tests.csproj b/Tests/MQTTnet.Core.Tests/MQTTnet.Tests.csproj index a4b6881..7bf14cb 100644 --- a/Tests/MQTTnet.Core.Tests/MQTTnet.Tests.csproj +++ b/Tests/MQTTnet.Core.Tests/MQTTnet.Tests.csproj @@ -1,7 +1,7 @@  - netcoreapp2.2 + netcoreapp2.2;net461 false diff --git a/Tests/MQTTnet.Core.Tests/Mockups/TestClientWrapper.cs b/Tests/MQTTnet.Core.Tests/Mockups/TestClientWrapper.cs index dc0e3a9..2500a6f 100644 --- a/Tests/MQTTnet.Core.Tests/Mockups/TestClientWrapper.cs +++ b/Tests/MQTTnet.Core.Tests/Mockups/TestClientWrapper.cs @@ -39,7 +39,7 @@ namespace MQTTnet.Tests.Mockups case MqttClientOptionsBuilder builder: { var existingClientId = builder.Build().ClientId; - if (!existingClientId.StartsWith(TestContext.TestName)) + if (existingClientId != null && !existingClientId.StartsWith(TestContext.TestName)) { builder.WithClientId(TestContext.TestName + existingClientId); } @@ -48,7 +48,7 @@ namespace MQTTnet.Tests.Mockups case MqttClientOptions op: { var existingClientId = op.ClientId; - if (!existingClientId.StartsWith(TestContext.TestName)) + if (existingClientId != null && !existingClientId.StartsWith(TestContext.TestName)) { op.ClientId = TestContext.TestName + existingClientId; } diff --git a/Tests/MQTTnet.Core.Tests/MqttClient_Tests.cs b/Tests/MQTTnet.Core.Tests/MqttClient_Tests.cs index 9cd3024..b8f4fbc 100644 --- a/Tests/MQTTnet.Core.Tests/MqttClient_Tests.cs +++ b/Tests/MQTTnet.Core.Tests/MqttClient_Tests.cs @@ -237,6 +237,36 @@ namespace MQTTnet.Tests } } + [TestMethod] + public async Task ConnectTimeout_Throws_Exception() + { + var factory = new MqttFactory(); + using (var client = factory.CreateMqttClient()) + { + bool disconnectHandlerCalled = false; + try + { + client.DisconnectedHandler = new MqttClientDisconnectedHandlerDelegate(args => + { + disconnectHandlerCalled = true; + }); + + await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("1.2.3.4").Build()); + + Assert.Fail("Must fail!"); + } + catch (Exception exception) + { + Assert.IsNotNull(exception); + Assert.IsInstanceOfType(exception, typeof(MqttCommunicationException)); + //Assert.IsInstanceOfType(exception.InnerException, typeof(SocketException)); + } + + await Task.Delay(100); // disconnected handler is called async + Assert.IsTrue(disconnectHandlerCalled); + } + } + [TestMethod] public async Task Fire_Disconnected_Event_On_Server_Shutdown() { diff --git a/Tests/MQTTnet.Core.Tests/MqttTcpChannel_Tests.cs b/Tests/MQTTnet.Core.Tests/MqttTcpChannel_Tests.cs index 436d2d1..a4b0ca7 100644 --- a/Tests/MQTTnet.Core.Tests/MqttTcpChannel_Tests.cs +++ b/Tests/MQTTnet.Core.Tests/MqttTcpChannel_Tests.cs @@ -28,14 +28,14 @@ namespace MQTTnet.Tests { while (!ct.IsCancellationRequested) { - var client = await serverSocket.AcceptAsync(); + var client = await PlatformAbstractionLayer.AcceptAsync(serverSocket); var data = new byte[] { 128 }; - await client.SendAsync(new ArraySegment(data), SocketFlags.None); + await PlatformAbstractionLayer.SendAsync(client, new ArraySegment(data), SocketFlags.None); } }, ct.Token); var clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - await clientSocket.ConnectAsync(IPAddress.Loopback, 50001); + await PlatformAbstractionLayer.ConnectAsync(clientSocket, IPAddress.Loopback, 50001); await Task.Delay(100, ct.Token); diff --git a/Tests/MQTTnet.Core.Tests/Server_Tests.cs b/Tests/MQTTnet.Core.Tests/Server_Tests.cs index cbbc1fc..755dafe 100644 --- a/Tests/MQTTnet.Core.Tests/Server_Tests.cs +++ b/Tests/MQTTnet.Core.Tests/Server_Tests.cs @@ -13,6 +13,7 @@ using MQTTnet.Client.Disconnecting; using MQTTnet.Client.Options; using MQTTnet.Client.Receiving; using MQTTnet.Client.Subscribing; +using MQTTnet.Implementations; using MQTTnet.Protocol; using MQTTnet.Server; using MQTTnet.Tests.Mockups; @@ -1140,7 +1141,7 @@ namespace MQTTnet.Tests await testEnvironment.ConnectClientAsync(); } } - + [TestMethod] public async Task Close_Idle_Connection() { @@ -1149,14 +1150,14 @@ namespace MQTTnet.Tests await testEnvironment.StartServerAsync(new MqttServerOptionsBuilder().WithDefaultCommunicationTimeout(TimeSpan.FromSeconds(1))); var client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - await client.ConnectAsync("localhost", testEnvironment.ServerPort); + await PlatformAbstractionLayer.ConnectAsync(client, "localhost", testEnvironment.ServerPort); // Don't send anything. The server should close the connection. await Task.Delay(TimeSpan.FromSeconds(3)); try { - var receivedBytes = await client.ReceiveAsync(new ArraySegment(new byte[10]), SocketFlags.Partial); + var receivedBytes = await PlatformAbstractionLayer.ReceiveAsync(client, new ArraySegment(new byte[10]), SocketFlags.Partial); if (receivedBytes == 0) { return; @@ -1180,7 +1181,7 @@ namespace MQTTnet.Tests // Send an invalid packet and ensure that the server will close the connection and stay in a waiting state // forever. This is security related. var client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - await client.ConnectAsync("localhost", testEnvironment.ServerPort); + await PlatformAbstractionLayer.ConnectAsync(client, "localhost", testEnvironment.ServerPort); var buffer = Encoding.UTF8.GetBytes("Garbage"); client.Send(buffer, buffer.Length, SocketFlags.None); @@ -1189,7 +1190,7 @@ namespace MQTTnet.Tests try { - var receivedBytes = await client.ReceiveAsync(new ArraySegment(new byte[10]), SocketFlags.Partial); + var receivedBytes = await PlatformAbstractionLayer.ReceiveAsync(client, new ArraySegment(new byte[10]), SocketFlags.Partial); if (receivedBytes == 0) { return; From a77e0b8dde664377b89560109a51af7cedc68a08 Mon Sep 17 00:00:00 2001 From: JanEggers Date: Sat, 21 Dec 2019 09:09:03 +0100 Subject: [PATCH 37/51] spread dispose pattern --- .../ManagedMqttClient.cs | 49 +++++++------------ .../MQTTnet/Implementations/MqttTcpChannel.cs | 16 ++++-- .../Implementations/MqttWebSocketChannel.cs | 35 +++++++------ Source/MQTTnet/Internal/Disposable.cs | 2 + .../PacketDispatcher/MqttPacketAwaiter.cs | 11 +++-- 5 files changed, 62 insertions(+), 51 deletions(-) diff --git a/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs b/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs index 89efe6b..9bb1e54 100644 --- a/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs +++ b/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs @@ -16,7 +16,7 @@ using MQTTnet.Server; namespace MQTTnet.Extensions.ManagedClient { - public class ManagedMqttClient : IManagedMqttClient + public class ManagedMqttClient : Disposable, IManagedMqttClient { private readonly BlockingQueue _messageQueue = new BlockingQueue(); @@ -42,15 +42,15 @@ namespace MQTTnet.Extensions.ManagedClient private Task _maintainConnectionTask; private ManagedMqttClientStorageManager _storageManager; - - private bool _disposed; - + public ManagedMqttClient(IMqttClient mqttClient, IMqttNetChildLogger logger) { _mqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient)); if (logger == null) throw new ArgumentNullException(nameof(logger)); _logger = logger.CreateChildLogger(nameof(ManagedMqttClient)); + + Options = new ManagedMqttClientOptions(); } public bool IsConnected => _mqttClient.IsConnected; @@ -242,36 +242,25 @@ namespace MQTTnet.Extensions.ManagedClient return Task.FromResult(0); } - public void Dispose() + protected override void Dispose(bool disposing) { - if (_disposed) + if (disposing) { - return; - } - - _disposed = true; - - StopPublishing(); - StopMaintainingConnection(); - - if (_maintainConnectionTask != null) - { - Task.WaitAny(_maintainConnectionTask); - _maintainConnectionTask = null; - } + StopPublishing(); + StopMaintainingConnection(); - _messageQueue.Dispose(); - _messageQueueLock.Dispose(); - _mqttClient.Dispose(); - _subscriptionsQueuedSignal.Dispose(); - } + if (_maintainConnectionTask != null) + { + _maintainConnectionTask.GetAwaiter().GetResult(); + _maintainConnectionTask = null; + } - private void ThrowIfDisposed() - { - if (_disposed) - { - throw new ObjectDisposedException(nameof(ManagedMqttClient)); + _messageQueue.Dispose(); + _messageQueueLock.Dispose(); + _mqttClient.Dispose(); + _subscriptionsQueuedSignal.Dispose(); } + base.Dispose(disposing); } private async Task MaintainConnectionAsync(CancellationToken cancellationToken) @@ -292,7 +281,7 @@ namespace MQTTnet.Extensions.ManagedClient } finally { - if (!_disposed) + if (!IsDisposed) { try { diff --git a/Source/MQTTnet/Implementations/MqttTcpChannel.cs b/Source/MQTTnet/Implementations/MqttTcpChannel.cs index d500813..9b2ba56 100644 --- a/Source/MQTTnet/Implementations/MqttTcpChannel.cs +++ b/Source/MQTTnet/Implementations/MqttTcpChannel.cs @@ -10,10 +10,11 @@ using System.Runtime.ExceptionServices; using System.Threading; using MQTTnet.Channel; using MQTTnet.Client.Options; +using MQTTnet.Internal; namespace MQTTnet.Implementations { - public class MqttTcpChannel : IMqttChannel + public class MqttTcpChannel : Disposable, IMqttChannel { private readonly IMqttClientOptions _clientOptions; private readonly MqttClientTcpOptions _options; @@ -94,7 +95,7 @@ namespace MQTTnet.Implementations public Task DisconnectAsync(CancellationToken cancellationToken) { - Dispose(); + Cleanup(); return Task.FromResult(0); } @@ -158,7 +159,7 @@ namespace MQTTnet.Implementations } } - public void Dispose() + private void Cleanup() { // When the stream is disposed it will also close the socket and this will also dispose it. // So there is no need to dispose the socket again. @@ -177,6 +178,15 @@ namespace MQTTnet.Implementations _stream = null; } + protected override void Dispose(bool disposing) + { + if (disposing) + { + Cleanup(); + } + base.Dispose(disposing); + } + private bool InternalUserCertificateValidationCallback(object sender, X509Certificate x509Certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { if (_options.TlsOptions.CertificateValidationCallback != null) diff --git a/Source/MQTTnet/Implementations/MqttWebSocketChannel.cs b/Source/MQTTnet/Implementations/MqttWebSocketChannel.cs index 4a3dc70..c159b91 100644 --- a/Source/MQTTnet/Implementations/MqttWebSocketChannel.cs +++ b/Source/MQTTnet/Implementations/MqttWebSocketChannel.cs @@ -112,7 +112,7 @@ namespace MQTTnet.Implementations await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, cancellationToken).ConfigureAwait(false); } - Dispose(); + Cleanup(); } public async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) @@ -146,24 +146,29 @@ namespace MQTTnet.Implementations { if (disposing) { - _sendLock?.Dispose(); - _sendLock = null; - - try - { - _webSocket?.Dispose(); - } - catch (ObjectDisposedException) - { - } - finally - { - _webSocket = null; - } + Cleanup(); } base.Dispose(disposing); } + private void Cleanup() + { + _sendLock?.Dispose(); + _sendLock = null; + + try + { + _webSocket?.Dispose(); + } + catch (ObjectDisposedException) + { + } + finally + { + _webSocket = null; + } + } + private IWebProxy CreateProxy() { if (string.IsNullOrEmpty(_options.ProxyOptions?.Address)) diff --git a/Source/MQTTnet/Internal/Disposable.cs b/Source/MQTTnet/Internal/Disposable.cs index 2074b49..f8a72b5 100644 --- a/Source/MQTTnet/Internal/Disposable.cs +++ b/Source/MQTTnet/Internal/Disposable.cs @@ -4,6 +4,8 @@ namespace MQTTnet.Internal { public class Disposable : IDisposable { + protected bool IsDisposed => _isDisposed; + protected void ThrowIfDisposed() { if (_isDisposed) diff --git a/Source/MQTTnet/PacketDispatcher/MqttPacketAwaiter.cs b/Source/MQTTnet/PacketDispatcher/MqttPacketAwaiter.cs index 19df6d4..8f906f6 100644 --- a/Source/MQTTnet/PacketDispatcher/MqttPacketAwaiter.cs +++ b/Source/MQTTnet/PacketDispatcher/MqttPacketAwaiter.cs @@ -2,11 +2,12 @@ using System.Threading; using System.Threading.Tasks; using MQTTnet.Exceptions; +using MQTTnet.Internal; using MQTTnet.Packets; namespace MQTTnet.PacketDispatcher { - public sealed class MqttPacketAwaiter : IMqttPacketAwaiter where TPacket : MqttBasePacket + public sealed class MqttPacketAwaiter : Disposable, IMqttPacketAwaiter where TPacket : MqttBasePacket { private readonly TaskCompletionSource _taskCompletionSource = new TaskCompletionSource(); private readonly ushort? _packetIdentifier; @@ -52,9 +53,13 @@ namespace MQTTnet.PacketDispatcher Task.Run(() => _taskCompletionSource.TrySetCanceled()); } - public void Dispose() + protected override void Dispose(bool disposing) { - _owningPacketDispatcher.RemovePacketAwaiter(_packetIdentifier); + if (disposing) + { + _owningPacketDispatcher.RemovePacketAwaiter(_packetIdentifier); + } + base.Dispose(disposing); } } } \ No newline at end of file From ac06a001e2cc6552a30f96531c59c395c1802da5 Mon Sep 17 00:00:00 2001 From: JanEggers Date: Sat, 21 Dec 2019 10:42:18 +0100 Subject: [PATCH 38/51] fixed some warnings --- Tests/MQTTnet.Benchmarks/LoggerBenchmark.cs | 3 ++- Tests/MQTTnet.Benchmarks/MessageProcessingBenchmark.cs | 3 ++- Tests/MQTTnet.Benchmarks/SerializerBenchmark.cs | 3 ++- Tests/MQTTnet.Benchmarks/TopicFilterComparerBenchmark.cs | 3 ++- Tests/MQTTnet.Core.Tests/Server_Tests.cs | 2 +- .../MQTTnet.TestApp.AspNetCore2.csproj | 5 +---- Tests/MQTTnet.TestApp.NetCore/ServerTest.cs | 2 +- 7 files changed, 11 insertions(+), 10 deletions(-) diff --git a/Tests/MQTTnet.Benchmarks/LoggerBenchmark.cs b/Tests/MQTTnet.Benchmarks/LoggerBenchmark.cs index 0039434..cfc88d4 100644 --- a/Tests/MQTTnet.Benchmarks/LoggerBenchmark.cs +++ b/Tests/MQTTnet.Benchmarks/LoggerBenchmark.cs @@ -1,9 +1,10 @@ using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Jobs; using MQTTnet.Diagnostics; namespace MQTTnet.Benchmarks { - [ClrJob] + [SimpleJob(RuntimeMoniker.Net461)] [RPlotExporter] [MemoryDiagnoser] public class LoggerBenchmark diff --git a/Tests/MQTTnet.Benchmarks/MessageProcessingBenchmark.cs b/Tests/MQTTnet.Benchmarks/MessageProcessingBenchmark.cs index c821ee0..99fe030 100644 --- a/Tests/MQTTnet.Benchmarks/MessageProcessingBenchmark.cs +++ b/Tests/MQTTnet.Benchmarks/MessageProcessingBenchmark.cs @@ -1,11 +1,12 @@ using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Jobs; using MQTTnet.Client; using MQTTnet.Client.Options; using MQTTnet.Server; namespace MQTTnet.Benchmarks { - [ClrJob] + [SimpleJob(RuntimeMoniker.Net461)] [RPlotExporter, RankColumn] [MemoryDiagnoser] public class MessageProcessingBenchmark diff --git a/Tests/MQTTnet.Benchmarks/SerializerBenchmark.cs b/Tests/MQTTnet.Benchmarks/SerializerBenchmark.cs index 51e7ecb..00433cf 100644 --- a/Tests/MQTTnet.Benchmarks/SerializerBenchmark.cs +++ b/Tests/MQTTnet.Benchmarks/SerializerBenchmark.cs @@ -8,10 +8,11 @@ using MQTTnet.Adapter; using MQTTnet.Channel; using MQTTnet.Formatter; using MQTTnet.Formatter.V3; +using BenchmarkDotNet.Jobs; namespace MQTTnet.Benchmarks { - [ClrJob] + [SimpleJob(RuntimeMoniker.Net461)] [RPlotExporter] [MemoryDiagnoser] public class SerializerBenchmark diff --git a/Tests/MQTTnet.Benchmarks/TopicFilterComparerBenchmark.cs b/Tests/MQTTnet.Benchmarks/TopicFilterComparerBenchmark.cs index bbce630..2df92ad 100644 --- a/Tests/MQTTnet.Benchmarks/TopicFilterComparerBenchmark.cs +++ b/Tests/MQTTnet.Benchmarks/TopicFilterComparerBenchmark.cs @@ -1,10 +1,11 @@ using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Jobs; using MQTTnet.Server; using System; namespace MQTTnet.Benchmarks { - [ClrJob] + [SimpleJob(RuntimeMoniker.Net461)] [RPlotExporter] [MemoryDiagnoser] public class TopicFilterComparerBenchmark diff --git a/Tests/MQTTnet.Core.Tests/Server_Tests.cs b/Tests/MQTTnet.Core.Tests/Server_Tests.cs index 755dafe..3ebdaa6 100644 --- a/Tests/MQTTnet.Core.Tests/Server_Tests.cs +++ b/Tests/MQTTnet.Core.Tests/Server_Tests.cs @@ -895,7 +895,7 @@ namespace MQTTnet.Tests { var serverOptions = new MqttServerOptionsBuilder().WithConnectionValidator(context => { - context.ReturnCode = MqttConnectReturnCode.ConnectionRefusedNotAuthorized; + context.ReasonCode = MqttConnectReasonCode.NotAuthorized; }); using (var testEnvironment = new TestEnvironment(TestContext)) diff --git a/Tests/MQTTnet.TestApp.AspNetCore2/MQTTnet.TestApp.AspNetCore2.csproj b/Tests/MQTTnet.TestApp.AspNetCore2/MQTTnet.TestApp.AspNetCore2.csproj index b8a9307..391a044 100644 --- a/Tests/MQTTnet.TestApp.AspNetCore2/MQTTnet.TestApp.AspNetCore2.csproj +++ b/Tests/MQTTnet.TestApp.AspNetCore2/MQTTnet.TestApp.AspNetCore2.csproj @@ -11,14 +11,11 @@ - - - + - diff --git a/Tests/MQTTnet.TestApp.NetCore/ServerTest.cs b/Tests/MQTTnet.TestApp.NetCore/ServerTest.cs index bd62671..b0d6534 100644 --- a/Tests/MQTTnet.TestApp.NetCore/ServerTest.cs +++ b/Tests/MQTTnet.TestApp.NetCore/ServerTest.cs @@ -30,7 +30,7 @@ namespace MQTTnet.TestApp.NetCore { if (p.Username != "USER" || p.Password != "PASS") { - p.ReturnCode = MqttConnectReturnCode.ConnectionRefusedBadUsernameOrPassword; + p.ReasonCode = MqttConnectReasonCode.BadUserNameOrPassword; } } }), From 069c19aab94a82cc6a844805622085465b741ed8 Mon Sep 17 00:00:00 2001 From: PMExtra Date: Mon, 23 Dec 2019 20:38:29 +0800 Subject: [PATCH 39/51] Support existing session with ManagedMqttClient. --- .../ManagedMqttClient.cs | 14 ++++++++------ .../ReconnectionResult.cs | 1 + 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs b/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs index 89efe6b..4767f4a 100644 --- a/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs +++ b/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs @@ -91,10 +91,6 @@ namespace MQTTnet.Extensions.ManagedClient if (options == null) throw new ArgumentNullException(nameof(options)); if (options.ClientOptions == null) throw new ArgumentException("The client options are not set.", nameof(options)); - if (!options.ClientOptions.CleanSession) - { - throw new NotSupportedException("The managed client does not support existing sessions."); - } if (!_maintainConnectionTask?.IsCompleted ?? false) throw new InvalidOperationException("The managed client is already started."); @@ -333,6 +329,12 @@ namespace MQTTnet.Extensions.ManagedClient return; } + if (connectionState == ReconnectionResult.Recovered) + { + StartPublishing(); + return; + } + if (connectionState == ReconnectionResult.StillConnected) { await PublishSubscriptionsAsync(Options.ConnectionCheckInterval, cancellationToken).ConfigureAwait(false); @@ -544,8 +546,8 @@ namespace MQTTnet.Extensions.ManagedClient try { - await _mqttClient.ConnectAsync(Options.ClientOptions).ConfigureAwait(false); - return ReconnectionResult.Reconnected; + var result = await _mqttClient.ConnectAsync(Options.ClientOptions).ConfigureAwait(false); + return result.IsSessionPresent ? ReconnectionResult.Recovered : ReconnectionResult.Reconnected; } catch (Exception exception) { diff --git a/Source/MQTTnet.Extensions.ManagedClient/ReconnectionResult.cs b/Source/MQTTnet.Extensions.ManagedClient/ReconnectionResult.cs index fa876c3..092662f 100644 --- a/Source/MQTTnet.Extensions.ManagedClient/ReconnectionResult.cs +++ b/Source/MQTTnet.Extensions.ManagedClient/ReconnectionResult.cs @@ -4,6 +4,7 @@ { StillConnected, Reconnected, + Recovered, NotConnected } } From 328ffc734fe1d6b6fcdd257444682c47c32bd586 Mon Sep 17 00:00:00 2001 From: JanEggers Date: Sat, 28 Dec 2019 15:55:35 +0100 Subject: [PATCH 40/51] dont allow publish before the client is started and adjusted testclient accordingly --- .../MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs | 3 +-- Tests/MQTTnet.TestApp.NetCore/ManagedClientTest.cs | 6 +++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs b/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs index 9bb1e54..d12a6a3 100644 --- a/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs +++ b/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs @@ -49,8 +49,6 @@ namespace MQTTnet.Extensions.ManagedClient if (logger == null) throw new ArgumentNullException(nameof(logger)); _logger = logger.CreateChildLogger(nameof(ManagedMqttClient)); - - Options = new ManagedMqttClientOptions(); } public bool IsConnected => _mqttClient.IsConnected; @@ -150,6 +148,7 @@ namespace MQTTnet.Extensions.ManagedClient ThrowIfDisposed(); if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage)); + if (Options == null) throw new InvalidOperationException("call StartAsync before publishing messages"); MqttTopicValidator.ThrowIfInvalid(applicationMessage.ApplicationMessage.Topic); diff --git a/Tests/MQTTnet.TestApp.NetCore/ManagedClientTest.cs b/Tests/MQTTnet.TestApp.NetCore/ManagedClientTest.cs index dc7d925..c4fd68f 100644 --- a/Tests/MQTTnet.TestApp.NetCore/ManagedClientTest.cs +++ b/Tests/MQTTnet.TestApp.NetCore/ManagedClientTest.cs @@ -40,11 +40,11 @@ namespace MQTTnet.TestApp.NetCore Console.WriteLine(">> RECEIVED: " + e.ApplicationMessage.Topic); }); - await managedClient.PublishAsync(builder => builder.WithTopic("Step").WithPayload("1")); - await managedClient.PublishAsync(builder => builder.WithTopic("Step").WithPayload("2").WithAtLeastOnceQoS()); - await managedClient.StartAsync(options); + await managedClient.PublishAsync(builder => builder.WithTopic("Step").WithPayload("1")); + await managedClient.PublishAsync(builder => builder.WithTopic("Step").WithPayload("2").WithAtLeastOnceQoS()); + await managedClient.SubscribeAsync(new TopicFilter { Topic = "xyz", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce }); await managedClient.SubscribeAsync(new TopicFilter { Topic = "abc", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce }); From d4088420cf88382b4486be8ce8516ee34931c8b2 Mon Sep 17 00:00:00 2001 From: Jim Schaad Date: Tue, 31 Dec 2019 14:33:31 -0800 Subject: [PATCH 41/51] Return correct set of user properties Return the response not the request set of user properties --- Source/MQTTnet/Formatter/V5/MqttV500DataConverter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/MQTTnet/Formatter/V5/MqttV500DataConverter.cs b/Source/MQTTnet/Formatter/V5/MqttV500DataConverter.cs index 42d3241..886b937 100644 --- a/Source/MQTTnet/Formatter/V5/MqttV500DataConverter.cs +++ b/Source/MQTTnet/Formatter/V5/MqttV500DataConverter.cs @@ -139,7 +139,7 @@ namespace MQTTnet.Formatter.V5 ReasonCode = connectionValidatorContext.ReasonCode, Properties = new MqttConnAckPacketProperties { - UserProperties = connectionValidatorContext.UserProperties, + UserProperties = connectionValidatorContext.ResponseUserProperties, AuthenticationMethod = connectionValidatorContext.AuthenticationMethod, AuthenticationData = connectionValidatorContext.ResponseAuthenticationData, AssignedClientIdentifier = connectionValidatorContext.AssignedClientIdentifier, From 9c378fb08859ddf05bbdfc06a39983ae0c352cb5 Mon Sep 17 00:00:00 2001 From: Jim Schaad Date: Tue, 31 Dec 2019 14:44:25 -0800 Subject: [PATCH 42/51] Provide the full ACK on a connection failure There are some systems that return information on a failed exception and thus the client wants access to the full set of ACK information. Specifically the user properties. --- Source/MQTTnet/Adapter/MqttConnectingFailedException.cs | 7 ++++--- Source/MQTTnet/Client/MqttClient.cs | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Source/MQTTnet/Adapter/MqttConnectingFailedException.cs b/Source/MQTTnet/Adapter/MqttConnectingFailedException.cs index 44d50ec..158cc9a 100644 --- a/Source/MQTTnet/Adapter/MqttConnectingFailedException.cs +++ b/Source/MQTTnet/Adapter/MqttConnectingFailedException.cs @@ -5,12 +5,13 @@ namespace MQTTnet.Adapter { public class MqttConnectingFailedException : MqttCommunicationException { - public MqttConnectingFailedException(MqttClientConnectResultCode resultCode) + public MqttConnectingFailedException(MqttClientAuthenticateResult resultCode) : base($"Connecting with MQTT server failed ({resultCode.ToString()}).") { - ResultCode = resultCode; + Result = resultCode; } - public MqttClientConnectResultCode ResultCode { get; } + public MqttClientAuthenticateResult Result { get; } + public MqttClientConnectResultCode ResultCode => Result.ResultCode; } } diff --git a/Source/MQTTnet/Client/MqttClient.cs b/Source/MQTTnet/Client/MqttClient.cs index c9ef061..4d8269d 100644 --- a/Source/MQTTnet/Client/MqttClient.cs +++ b/Source/MQTTnet/Client/MqttClient.cs @@ -255,7 +255,7 @@ namespace MQTTnet.Client if (result.ResultCode != MqttClientConnectResultCode.Success) { - throw new MqttConnectingFailedException(result.ResultCode); + throw new MqttConnectingFailedException(result); } _logger.Verbose("Authenticated MQTT connection with server established."); From 27a61819d3e8272f5dabee7c065d8c3b4804efec Mon Sep 17 00:00:00 2001 From: JanEggers Date: Sat, 4 Jan 2020 11:52:43 +0100 Subject: [PATCH 43/51] removed unused code --- .../MQTTnet/Internal/AsyncAutoResetEvent.cs | 131 ---------- .../AsyncAutoResentEvent_Tests.cs | 237 ------------------ 2 files changed, 368 deletions(-) delete mode 100644 Source/MQTTnet/Internal/AsyncAutoResetEvent.cs delete mode 100644 Tests/MQTTnet.Core.Tests/AsyncAutoResentEvent_Tests.cs diff --git a/Source/MQTTnet/Internal/AsyncAutoResetEvent.cs b/Source/MQTTnet/Internal/AsyncAutoResetEvent.cs deleted file mode 100644 index cd62f07..0000000 --- a/Source/MQTTnet/Internal/AsyncAutoResetEvent.cs +++ /dev/null @@ -1,131 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; - -namespace MQTTnet.Internal -{ - // Inspired from Stephen Toub (https://blogs.msdn.microsoft.com/pfxteam/2012/02/11/building-async-coordination-primitives-part-2-asyncautoresetevent/) and Chris Gillum (https://stackoverflow.com/a/43012490) - public class AsyncAutoResetEvent - { - private readonly LinkedList> _waiters = new LinkedList>(); - - private bool _isSignaled; - - public AsyncAutoResetEvent() - : this(false) - { - } - - public AsyncAutoResetEvent(bool signaled) - { - _isSignaled = signaled; - } - - public int WaitersCount - { - get - { - lock (_waiters) - { - return _waiters.Count; - } - } - } - - public Task WaitOneAsync() - { - return WaitOneAsync(CancellationToken.None); - } - - public Task WaitOneAsync(TimeSpan timeout) - { - return WaitOneAsync(timeout, CancellationToken.None); - } - - public Task WaitOneAsync(CancellationToken cancellationToken) - { - return WaitOneAsync(Timeout.InfiniteTimeSpan, cancellationToken); - } - - public async Task WaitOneAsync(TimeSpan timeout, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - TaskCompletionSource tcs; - - lock (_waiters) - { - if (_isSignaled) - { - _isSignaled = false; - return true; - } - - if (timeout == TimeSpan.Zero) - { - return _isSignaled; - } - - tcs = new TaskCompletionSource(); - _waiters.AddLast(tcs); - } - - Task winner; - if (timeout == Timeout.InfiniteTimeSpan) - { - using (cancellationToken.Register(() => { tcs.TrySetCanceled(); })) - { - await tcs.Task.ConfigureAwait(false); - winner = tcs.Task; - } - } - else - { - winner = await Task.WhenAny(tcs.Task, Task.Delay(timeout, cancellationToken)).ConfigureAwait(false); - } - - var taskWasSignaled = winner == tcs.Task; - if (taskWasSignaled) - { - return true; - } - - // We timed-out; remove our reference to the task. - // This is an O(n) operation since waiters is a LinkedList. - lock (_waiters) - { - _waiters.Remove(tcs); - - if (winner.Status == TaskStatus.Canceled) - { - throw new OperationCanceledException(cancellationToken); - } - - throw new TimeoutException(); - } - } - - public void Set() - { - TaskCompletionSource toRelease = null; - - lock (_waiters) - { - if (_waiters.Count > 0) - { - // Signal the first task in the waiters list. - toRelease = _waiters.First.Value; - _waiters.RemoveFirst(); - } - else if (!_isSignaled) - { - // No tasks are pending - _isSignaled = true; - } - } - - toRelease?.TrySetResult(true); - } - } -} diff --git a/Tests/MQTTnet.Core.Tests/AsyncAutoResentEvent_Tests.cs b/Tests/MQTTnet.Core.Tests/AsyncAutoResentEvent_Tests.cs deleted file mode 100644 index d72712d..0000000 --- a/Tests/MQTTnet.Core.Tests/AsyncAutoResentEvent_Tests.cs +++ /dev/null @@ -1,237 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using MQTTnet.Internal; - -namespace MQTTnet.Tests -{ - [TestClass] - // Inspired from the vs-threading tests (https://github.com/Microsoft/vs-threading/blob/master/src/Microsoft.VisualStudio.Threading.Tests/AsyncAutoResetEventTests.cs) - public class AsyncAutoResetEvent_Tests - { - private readonly AsyncAutoResetEvent _aare; - - public AsyncAutoResetEvent_Tests() - { - _aare = new AsyncAutoResetEvent(); - } - - [TestMethod] - public async Task Cleanup_Waiters() - { - var @lock = new AsyncAutoResetEvent(); - - var waitOnePassed = false; - -#pragma warning disable 4014 - Task.Run(async () => -#pragma warning restore 4014 - { - await @lock.WaitOneAsync(TimeSpan.FromSeconds(2)); - waitOnePassed = true; - }); - - await Task.Delay(500); - - Assert.AreEqual(1, @lock.WaitersCount); - - @lock.Set(); - - await Task.Delay(1000); - - Assert.IsTrue(waitOnePassed); - Assert.AreEqual(0, @lock.WaitersCount); - } - - [TestMethod] - public async Task SingleThreadedPulse() - { - for (int i = 0; i < 5; i++) - { - var t = _aare.WaitOneAsync(); - Assert.IsFalse(t.IsCompleted); - _aare.Set(); - await t; - Assert.IsTrue(t.IsCompleted); - } - } - - [TestMethod] - public async Task MultipleSetOnlySignalsOnce() - { - _aare.Set(); - _aare.Set(); - await _aare.WaitOneAsync(); - var t = _aare.WaitOneAsync(); - Assert.IsFalse(t.IsCompleted); - await Task.Delay(500); - Assert.IsFalse(t.IsCompleted); - _aare.Set(); - await t; - Assert.IsTrue(t.IsCompleted); - } - - [TestMethod] - public async Task OrderPreservingQueue() - { - var waiters = new Task[5]; - for (int i = 0; i < waiters.Length; i++) - { - waiters[i] = _aare.WaitOneAsync(); - } - - for (int i = 0; i < waiters.Length; i++) - { - _aare.Set(); - await waiters[i].ConfigureAwait(false); - } - } - - // This test does not work in appveyor but on local machine it does!? - /////// - /////// Verifies that inlining continuations do not have to complete execution before Set() returns. - /////// - ////[TestMethod] - ////public async Task SetReturnsBeforeInlinedContinuations() - ////{ - //// var setReturned = new ManualResetEventSlim(); - //// var inlinedContinuation = _aare.WaitOneAsync() - //// .ContinueWith(delegate - //// { - //// // Arrange to synchronously block the continuation until Set() has returned, - //// // which would deadlock if Set does not return until inlined continuations complete. - //// Assert.IsTrue(setReturned.Wait(500)); - //// }); - //// await Task.Delay(100); - //// _aare.Set(); - //// setReturned.Set(); - //// Assert.IsTrue(inlinedContinuation.Wait(500)); - ////} - - [TestMethod] - public void WaitAsync_WithCancellationToken() - { - var cts = new CancellationTokenSource(); - Task waitTask = _aare.WaitOneAsync(cts.Token); - Assert.IsFalse(waitTask.IsCompleted); - - // Cancel the request and ensure that it propagates to the task. - cts.Cancel(); - try - { - waitTask.GetAwaiter().GetResult(); - Assert.IsTrue(false, "Task was expected to transition to a canceled state."); - } - catch (OperationCanceledException) - { - } - - // Now set the event and verify that a future waiter gets the signal immediately. - _aare.Set(); - waitTask = _aare.WaitOneAsync(); - Assert.AreEqual(TaskStatus.WaitingForActivation, waitTask.Status); - } - - [TestMethod] - public void WaitAsync_WithCancellationToken_Precanceled() - { - // We construct our own pre-canceled token so that we can do - // a meaningful identity check later. - var tokenSource = new CancellationTokenSource(); - tokenSource.Cancel(); - var token = tokenSource.Token; - - // Verify that a pre-set signal is not reset by a canceled wait request. - _aare.Set(); - try - { - _aare.WaitOneAsync(token).GetAwaiter().GetResult(); - Assert.IsTrue(false, "Task was expected to transition to a canceled state."); - } - catch (OperationCanceledException ex) - { - Assert.AreEqual(token, ex.CancellationToken); - } - - // Verify that the signal was not acquired. - Task waitTask = _aare.WaitOneAsync(); - Assert.AreEqual(TaskStatus.RanToCompletion, waitTask.Status); - } - - [TestMethod] - public async Task WaitAsync_WithTimeout() - { - Task waitTask = _aare.WaitOneAsync(TimeSpan.FromMilliseconds(500)); - Assert.IsFalse(waitTask.IsCompleted); - - // Cancel the request and ensure that it propagates to the task. - await Task.Delay(1000).ConfigureAwait(false); - try - { - waitTask.GetAwaiter().GetResult(); - Assert.IsTrue(false, "Task was expected to transition to a timeout state."); - } - catch (TimeoutException) - { - Assert.IsTrue(true); - } - - // Now set the event and verify that a future waiter gets the signal immediately. - _aare.Set(); - waitTask = _aare.WaitOneAsync(TimeSpan.FromMilliseconds(500)); - Assert.AreEqual(TaskStatus.RanToCompletion, waitTask.Status); - } - - [TestMethod] - public void WaitAsync_Canceled_DoesNotInlineContinuations() - { - var cts = new CancellationTokenSource(); - var task = _aare.WaitOneAsync(cts.Token); - - var completingActionFinished = new ManualResetEventSlim(); - var continuation = task.ContinueWith( - _ => Assert.IsTrue(completingActionFinished.Wait(500)), - CancellationToken.None, - TaskContinuationOptions.None, - TaskScheduler.Default); - - cts.Cancel(); - completingActionFinished.Set(); - - // Rethrow the exception if it turned out it deadlocked. - continuation.GetAwaiter().GetResult(); - } - - [TestMethod] - public async Task AsyncAutoResetEvent() - { - var aare = new AsyncAutoResetEvent(); - - var globalI = 0; -#pragma warning disable 4014 - Task.Run(async () => -#pragma warning restore 4014 - { - await aare.WaitOneAsync(CancellationToken.None); - globalI += 1; - }); - -#pragma warning disable 4014 - Task.Run(async () => -#pragma warning restore 4014 - { - await aare.WaitOneAsync(CancellationToken.None); - globalI += 2; - }); - - await Task.Delay(500); - aare.Set(); - await Task.Delay(500); - aare.Set(); - await Task.Delay(100); - - Assert.AreEqual(3, globalI); - } - } -} \ No newline at end of file From 138cd16c8dbeb4b5a3163bb44fadff5ef3240336 Mon Sep 17 00:00:00 2001 From: JanEggers Date: Sat, 4 Jan 2020 11:53:15 +0100 Subject: [PATCH 44/51] fixed compilation --- .../Implementations/PlatformAbstractionLayer.cs | 12 ++++++++++++ Source/MQTTnet/Server/MqttRetainedMessagesManager.cs | 3 ++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/Source/MQTTnet/Implementations/PlatformAbstractionLayer.cs b/Source/MQTTnet/Implementations/PlatformAbstractionLayer.cs index a940eac..ee9057a 100644 --- a/Source/MQTTnet/Implementations/PlatformAbstractionLayer.cs +++ b/Source/MQTTnet/Implementations/PlatformAbstractionLayer.cs @@ -88,5 +88,17 @@ namespace MQTTnet.Implementations #endif } + public static Task CompletedTask + { + get + { +#if NET452 + return Task.FromResult(0); +#else + return Task.CompletedTask; +#endif + } + } + } } diff --git a/Source/MQTTnet/Server/MqttRetainedMessagesManager.cs b/Source/MQTTnet/Server/MqttRetainedMessagesManager.cs index c4e2f96..f4ebe48 100644 --- a/Source/MQTTnet/Server/MqttRetainedMessagesManager.cs +++ b/Source/MQTTnet/Server/MqttRetainedMessagesManager.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using MQTTnet.Diagnostics; +using MQTTnet.Implementations; using MQTTnet.Internal; namespace MQTTnet.Server @@ -21,7 +22,7 @@ namespace MQTTnet.Server if (logger == null) throw new ArgumentNullException(nameof(logger)); _logger = logger.CreateChildLogger(nameof(MqttRetainedMessagesManager)); _options = options ?? throw new ArgumentNullException(nameof(options)); - return Task.CompletedTask; + return PlatformAbstractionLayer.CompletedTask; } public async Task LoadMessagesAsync() From a152066ed34bd5d8114cbbecf82d183316d84bfd Mon Sep 17 00:00:00 2001 From: JanEggers Date: Sat, 4 Jan 2020 11:54:41 +0100 Subject: [PATCH 45/51] fixed managed client so it does not send disconnect packet when disposed --- Source/MQTTnet/Internal/Disposable.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Source/MQTTnet/Internal/Disposable.cs b/Source/MQTTnet/Internal/Disposable.cs index f8a72b5..2ce3423 100644 --- a/Source/MQTTnet/Internal/Disposable.cs +++ b/Source/MQTTnet/Internal/Disposable.cs @@ -44,12 +44,13 @@ namespace MQTTnet.Internal { return; } + + _isDisposed = true; + // Do not change this code. Put cleanup code in Dispose(bool disposing) above. Dispose(true); // TODO: uncomment the following line if the finalizer is overridden above. // GC.SuppressFinalize(this); - - _isDisposed = true; } #endregion } From 630400da53267e83ad1f6129137b23e3e300afc4 Mon Sep 17 00:00:00 2001 From: JanEggers Date: Sat, 4 Jan 2020 11:55:12 +0100 Subject: [PATCH 46/51] avoid task.run on newer platforms --- .../PacketDispatcher/MqttPacketAwaiter.cs | 36 ++++++++++++++++--- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/Source/MQTTnet/PacketDispatcher/MqttPacketAwaiter.cs b/Source/MQTTnet/PacketDispatcher/MqttPacketAwaiter.cs index 8f906f6..b172290 100644 --- a/Source/MQTTnet/PacketDispatcher/MqttPacketAwaiter.cs +++ b/Source/MQTTnet/PacketDispatcher/MqttPacketAwaiter.cs @@ -9,7 +9,7 @@ namespace MQTTnet.PacketDispatcher { public sealed class MqttPacketAwaiter : Disposable, IMqttPacketAwaiter where TPacket : MqttBasePacket { - private readonly TaskCompletionSource _taskCompletionSource = new TaskCompletionSource(); + private readonly TaskCompletionSource _taskCompletionSource; private readonly ushort? _packetIdentifier; private readonly MqttPacketDispatcher _owningPacketDispatcher; @@ -17,13 +17,18 @@ namespace MQTTnet.PacketDispatcher { _packetIdentifier = packetIdentifier; _owningPacketDispatcher = owningPacketDispatcher ?? throw new ArgumentNullException(nameof(owningPacketDispatcher)); +#if NET452 + _taskCompletionSource = new TaskCompletionSource(); +#else + _taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); +#endif } public async Task WaitOneAsync(TimeSpan timeout) { using (var timeoutToken = new CancellationTokenSource(timeout)) { - timeoutToken.Token.Register(() => _taskCompletionSource.TrySetException(new MqttCommunicationTimedOutException())); + timeoutToken.Token.Register(() => Fail(new MqttCommunicationTimedOutException())); var packet = await _taskCompletionSource.Task.ConfigureAwait(false); return (TPacket)packet; @@ -33,24 +38,47 @@ namespace MQTTnet.PacketDispatcher public void Complete(MqttBasePacket packet) { if (packet == null) throw new ArgumentNullException(nameof(packet)); - + + +#if NET452 // To prevent deadlocks it is required to call the _TrySetResult_ method // from a new thread because the awaiting code will not(!) be executed in // a new thread automatically (due to await). Furthermore _this_ thread will // do it. But _this_ thread is also reading incoming packets -> deadlock. + // NET452 does not support RunContinuationsAsynchronously Task.Run(() => _taskCompletionSource.TrySetResult(packet)); +#else + _taskCompletionSource.TrySetResult(packet); +#endif } public void Fail(Exception exception) { if (exception == null) throw new ArgumentNullException(nameof(exception)); - +#if NET452 + // To prevent deadlocks it is required to call the _TrySetResult_ method + // from a new thread because the awaiting code will not(!) be executed in + // a new thread automatically (due to await). Furthermore _this_ thread will + // do it. But _this_ thread is also reading incoming packets -> deadlock. + // NET452 does not support RunContinuationsAsynchronously Task.Run(() => _taskCompletionSource.TrySetException(exception)); +#else + _taskCompletionSource.TrySetException(exception); +#endif } public void Cancel() { +#if NET452 + // To prevent deadlocks it is required to call the _TrySetResult_ method + // from a new thread because the awaiting code will not(!) be executed in + // a new thread automatically (due to await). Furthermore _this_ thread will + // do it. But _this_ thread is also reading incoming packets -> deadlock. + // NET452 does not support RunContinuationsAsynchronously Task.Run(() => _taskCompletionSource.TrySetCanceled()); +#else + _taskCompletionSource.TrySetCanceled(); +#endif } protected override void Dispose(bool disposing) From b1559a8fa55aea86d4704a23961c143f10b4a77d Mon Sep 17 00:00:00 2001 From: Jim Schaad Date: Wed, 8 Jan 2020 11:11:41 -0800 Subject: [PATCH 47/51] Correct to print right result Lost a change while moving from my developement branch to here. --- Source/MQTTnet/Adapter/MqttConnectingFailedException.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/MQTTnet/Adapter/MqttConnectingFailedException.cs b/Source/MQTTnet/Adapter/MqttConnectingFailedException.cs index 158cc9a..9a14999 100644 --- a/Source/MQTTnet/Adapter/MqttConnectingFailedException.cs +++ b/Source/MQTTnet/Adapter/MqttConnectingFailedException.cs @@ -6,7 +6,7 @@ namespace MQTTnet.Adapter public class MqttConnectingFailedException : MqttCommunicationException { public MqttConnectingFailedException(MqttClientAuthenticateResult resultCode) - : base($"Connecting with MQTT server failed ({resultCode.ToString()}).") + : base($"Connecting with MQTT server failed ({resultCode.ResultCode.ToString()}).") { Result = resultCode; } From 7dde2f1f4c8e5f2b06d67a362752bb529a816b68 Mon Sep 17 00:00:00 2001 From: Jim Schaad Date: Sun, 12 Jan 2020 12:31:09 -0800 Subject: [PATCH 48/51] Change variable name per review request Change the name of the variable from requestCode to request --- Source/MQTTnet/Adapter/MqttConnectingFailedException.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/MQTTnet/Adapter/MqttConnectingFailedException.cs b/Source/MQTTnet/Adapter/MqttConnectingFailedException.cs index 9a14999..ab49d93 100644 --- a/Source/MQTTnet/Adapter/MqttConnectingFailedException.cs +++ b/Source/MQTTnet/Adapter/MqttConnectingFailedException.cs @@ -5,10 +5,10 @@ namespace MQTTnet.Adapter { public class MqttConnectingFailedException : MqttCommunicationException { - public MqttConnectingFailedException(MqttClientAuthenticateResult resultCode) - : base($"Connecting with MQTT server failed ({resultCode.ResultCode.ToString()}).") + public MqttConnectingFailedException(MqttClientAuthenticateResult result) + : base($"Connecting with MQTT server failed ({result.ResultCode.ToString()}).") { - Result = resultCode; + Result = result; } public MqttClientAuthenticateResult Result { get; } From 313f90a526ca95619727947ee8bce45a2f3cc0e2 Mon Sep 17 00:00:00 2001 From: Christoph Date: Tue, 28 Jan 2020 14:36:19 +0100 Subject: [PATCH 49/51] Added receiveTracker to detect whether messages are received from broker. --- Source/MQTTnet/Client/MqttClient.cs | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/Source/MQTTnet/Client/MqttClient.cs b/Source/MQTTnet/Client/MqttClient.cs index 27b56ff..ea78843 100644 --- a/Source/MQTTnet/Client/MqttClient.cs +++ b/Source/MQTTnet/Client/MqttClient.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Diagnostics; using System.Threading; using System.Threading.Tasks; @@ -25,6 +25,7 @@ namespace MQTTnet.Client private readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider(); private readonly MqttPacketDispatcher _packetDispatcher = new MqttPacketDispatcher(); private readonly Stopwatch _sendTracker = new Stopwatch(); + private readonly Stopwatch _receiveTracker = new Stopwatch(); private readonly object _disconnectLock = new object(); private readonly IMqttClientAdapterFactory _adapterFactory; @@ -88,6 +89,7 @@ namespace MQTTnet.Client authenticateResult = await AuthenticateAsync(adapter, options.WillMessage, cancellationToken).ConfigureAwait(false); _sendTracker.Restart(); + _receiveTracker.Restart(); if (Options.KeepAlivePeriod != TimeSpan.Zero) { @@ -344,7 +346,9 @@ namespace MQTTnet.Client try { await _adapter.SendPacketAsync(requestPacket, Options.CommunicationTimeout, cancellationToken).ConfigureAwait(false); - return await packetAwaiter.WaitOneAsync(Options.CommunicationTimeout).ConfigureAwait(false); + var response = await packetAwaiter.WaitOneAsync(Options.CommunicationTimeout).ConfigureAwait(false); + _receiveTracker.Restart(); + return response; } catch (MqttCommunicationTimedOutException) { @@ -369,14 +373,14 @@ namespace MQTTnet.Client keepAliveSendInterval = Options.KeepAliveSendInterval.Value; } - var waitTime = keepAliveSendInterval - _sendTracker.Elapsed; - if (waitTime <= TimeSpan.Zero) + var waitTimeSend = keepAliveSendInterval - _sendTracker.Elapsed; + var waitTimeReceive = keepAliveSendInterval - _receiveTracker.Elapsed; + if (waitTimeSend <= TimeSpan.Zero || waitTimeReceive <= TimeSpan.Zero) { await SendAndReceiveAsync(new MqttPingReqPacket(), cancellationToken).ConfigureAwait(false); - waitTime = keepAliveSendInterval; } - await Task.Delay(waitTime, cancellationToken).ConfigureAwait(false); + await Task.Delay(keepAliveSendInterval, cancellationToken).ConfigureAwait(false); } } catch (Exception exception) @@ -473,6 +477,8 @@ namespace MQTTnet.Client { try { + _receiveTracker.Restart(); + if (packet is MqttPublishPacket publishPacket) { await TryProcessReceivedPublishPacketAsync(publishPacket, cancellationToken).ConfigureAwait(false); @@ -652,4 +658,4 @@ namespace MQTTnet.Client return Interlocked.CompareExchange(ref _disconnectGate, 1, 0) != 0; } } -} \ No newline at end of file +} From 5f59aca62c46210efb68e8afdbe71b20aa4268da Mon Sep 17 00:00:00 2001 From: Dominik Viererbe Date: Tue, 25 Feb 2020 16:30:08 +0100 Subject: [PATCH 50/51] Added MqttClientUnsubscribeOptionsBuilder analogous to MqttClientSubscribeOptionsBuilder --- .../MqttClientUnsubscribeOptionsBuilder.cs | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 Source/MQTTnet/Client/Unsubscribing/MqttClientUnsubscribeOptionsBuilder.cs diff --git a/Source/MQTTnet/Client/Unsubscribing/MqttClientUnsubscribeOptionsBuilder.cs b/Source/MQTTnet/Client/Unsubscribing/MqttClientUnsubscribeOptionsBuilder.cs new file mode 100644 index 0000000..96c178f --- /dev/null +++ b/Source/MQTTnet/Client/Unsubscribing/MqttClientUnsubscribeOptionsBuilder.cs @@ -0,0 +1,60 @@ +using MQTTnet.Packets; +using System; +using System.Collections.Generic; +using System.Text; + +namespace MQTTnet.Client.Unsubscribing +{ + public class MqttClientUnsubscribeOptionsBuilder + { + private readonly MqttClientUnsubscribeOptions _unsubscribeOptions = new MqttClientUnsubscribeOptions(); + + public MqttClientUnsubscribeOptionsBuilder WithUserProperty(string name, string value) + { + if (name is null) throw new ArgumentNullException(nameof(name)); + if (value is null) throw new ArgumentNullException(nameof(value)); + + return WithUserProperty(new MqttUserProperty(name, value)); + } + + public MqttClientUnsubscribeOptionsBuilder WithUserProperty(MqttUserProperty userProperty) + { + if (userProperty is null) throw new ArgumentNullException(nameof(userProperty)); + + if (_unsubscribeOptions.UserProperties is null) + { + _unsubscribeOptions.UserProperties = new List(); + } + + _unsubscribeOptions.UserProperties.Add(userProperty); + + return this; + } + + public MqttClientUnsubscribeOptionsBuilder WithTopicFilter(string topic) + { + if (topic is null) throw new ArgumentNullException(nameof(topic)); + + if (_unsubscribeOptions.TopicFilters is null) + { + _unsubscribeOptions.TopicFilters = new List(); + } + + _unsubscribeOptions.TopicFilters.Add(topic); + + return this; + } + + public MqttClientUnsubscribeOptionsBuilder WithTopicFilter(TopicFilter topicFilter) + { + if (topicFilter is null) throw new ArgumentNullException(nameof(topicFilter)); + + return WithTopic(topicFilter.Topic); + } + + public MqttClientUnsubscribeOptions Build() + { + return _unsubscribeOptions; + } + } +} From a20c845a5ac52a3c090adef0e494ef25fa0b1ae4 Mon Sep 17 00:00:00 2001 From: Dominik Viererbe Date: Mon, 9 Mar 2020 18:27:35 +0100 Subject: [PATCH 51/51] Update MQTTnet.nuspec --- Build/MQTTnet.nuspec | 1 + 1 file changed, 1 insertion(+) diff --git a/Build/MQTTnet.nuspec b/Build/MQTTnet.nuspec index 963ee06..ba32a42 100644 --- a/Build/MQTTnet.nuspec +++ b/Build/MQTTnet.nuspec @@ -11,6 +11,7 @@ false MQTTnet is a high performance .NET library for MQTT based communication. It provides a MQTT client and a MQTT server (broker) and supports v3.1.0, v3.1.1 and v5.0.0 of the MQTT protocol. +* [ManagedClient] Added builder class for MqttClientUnsubscribeOptions (thanks to @dominikviererbe). * [ManagedClient] Added support for persisted sessions (thansk to @PMExtra). * [Server] Added support for assigned client IDs (MQTTv5 only) (thanks to @bcrosnier).