diff --git a/Source/MQTTnet.Tests/MQTTv5/Server_Tests.cs b/Source/MQTTnet.Tests/MQTTv5/Server_Tests.cs index 7326c94a..180117b0 100644 --- a/Source/MQTTnet.Tests/MQTTv5/Server_Tests.cs +++ b/Source/MQTTnet.Tests/MQTTv5/Server_Tests.cs @@ -175,6 +175,49 @@ namespace MQTTnet.Tests.MQTTv5 } } + [TestMethod] + public async Task Disconnect_with_Reason() + { + using (var testEnvironment = CreateTestEnvironment()) + { + var disconnectReason = MQTTnet.Client.MqttClientDisconnectReason.UnspecifiedError; + + string testClientId = null; + + await testEnvironment.StartServer(); + + testEnvironment.Server.ClientConnectedAsync += e => + { + testClientId = e.ClientId; + return PlatformAbstractionLayer.CompletedTask; + }; + + var client = await testEnvironment.ConnectClient(new MQTTnet.Client.MqttClientOptionsBuilder().WithProtocolVersion(MQTTnet.Formatter.MqttProtocolVersion.V500)); + + client.DisconnectedAsync += e => + { + disconnectReason = e.Reason; + return PlatformAbstractionLayer.CompletedTask; + }; + + await LongTestDelay(); + + // Test client should be connected now + + Assert.IsTrue(testClientId != null); + + // Have the server disconnect the client with AdministrativeAction reason + + await testEnvironment.Server.DisconnectClientAsync(testClientId, Protocol.MqttDisconnectReasonCode.AdministrativeAction); + + await LongTestDelay(); + + // The reason should be returned to the client in the DISCONNECT packet + + Assert.AreEqual(MQTTnet.Client.MqttClientDisconnectReason.AdministrativeAction, disconnectReason); + } + } + MqttClientOptions CreateClientOptions(TestEnvironment testEnvironment, string clientId, bool cleanSession, uint sessionExpiryInterval) { return testEnvironment.Factory.CreateClientOptionsBuilder() diff --git a/Source/MQTTnet.Tests/Server/General.cs b/Source/MQTTnet.Tests/Server/General.cs index 2d9cf418..148bf98c 100644 --- a/Source/MQTTnet.Tests/Server/General.cs +++ b/Source/MQTTnet.Tests/Server/General.cs @@ -935,7 +935,62 @@ namespace MQTTnet.Tests.Server Assert.IsTrue(client.IsConnected); } } - + + [TestMethod] + public async Task Disconnect_Client_with_Reason() + { + using (var testEnvironment = CreateTestEnvironment()) + { + var disconnectPacketReceived = false; + + string testClientId = null; + + await testEnvironment.StartServer(); + + testEnvironment.Server.ClientConnectedAsync += e => + { + testClientId = e.ClientId; + return PlatformAbstractionLayer.CompletedTask; + }; + + var client = testEnvironment.CreateClient(); + + client.InspectPackage += e => + { + if (e.Buffer.Length > 0) + { + if (e.Buffer[0] == (byte)Protocol.MqttControlPacketType.Disconnect << 4) + { + disconnectPacketReceived = true; + } + } + return PlatformAbstractionLayer.CompletedTask; + }; + + await client.ConnectAsync( + new MqttClientOptionsBuilder().WithTcpServer("localhost", testEnvironment.ServerPort) + .WithProtocolVersion(MQTTnet.Formatter.MqttProtocolVersion.V311) + .Build() + ); + + await LongTestDelay(); + + // Test client should be connected now + + Assert.IsTrue(testClientId != null); + + // Have the server disconnect the client with SessionTakenOver reason; no DISCONNECT should be sent to MQTT 3.1.1 clients + + await testEnvironment.Server.DisconnectClientAsync(testClientId, Protocol.MqttDisconnectReasonCode.SessionTakenOver); + + await LongTestDelay(); + + // MQTT 3.1.1 client should not receive a disconnect packet + + Assert.IsFalse(disconnectPacketReceived, "Disconnect packet received when none is expected"); + } + } + void ConnectionValidationHandler(ValidatingConnectionEventArgs eventArgs) { if (_connected.ContainsKey(eventArgs.ClientId)) diff --git a/Source/MQTTnet/Server/Internal/MqttClient.cs b/Source/MQTTnet/Server/Internal/MqttClient.cs index 087473c5..c79447e9 100644 --- a/Source/MQTTnet/Server/Internal/MqttClient.cs +++ b/Source/MQTTnet/Server/Internal/MqttClient.cs @@ -147,7 +147,9 @@ namespace MQTTnet.Server { IsRunning = false; - if (reason == MqttDisconnectReasonCode.SessionTakenOver || reason == MqttDisconnectReasonCode.KeepAliveTimeout) + // Potentially send disconnect reason to MQTT 5 clients + + if (ChannelAdapter.PacketFormatterAdapter.ProtocolVersion == MqttProtocolVersion.V500 && reason != MqttDisconnectReasonCode.NormalDisconnection) { // Is is very important to send the DISCONNECT packet here BEFORE cancelling the // token because the entire connection is closed (disposed) as soon as the cancellation